hore: enable more linters and fix findings

This commit is contained in:
Ingmar Stein
2025-12-11 00:01:53 +01:00
parent b0188a4321
commit 0bf1e1c2ad
25 changed files with 122 additions and 85 deletions

25
.golangci.yml Normal file
View File

@@ -0,0 +1,25 @@
version: "2"
linters:
default: fast
enable:
- errcheck
- govet
- ineffassign
- modernize
- staticcheck
- unused
disable:
- cyclop
- depguard
- funcorder
- funlen
- gocognit
- gocyclo
- lll
- maintidx
- mnd
- nestif
- nlreturn
- testpackage
- wsl

View File

@@ -63,7 +63,7 @@ The core functionality involves serving WebP images to devices, generated by ren
## Development Conventions
* **Formatting:** Use `go fmt ./...` (or `goimports`).
* **Linting:** Use `go vet`, `golangci-lint`.
* **Linting:** Use `golangci-lint`.
* **Logging:** Use `log/slog` for structured logging.
* **Templates:** Use `{{ t .Localizer "MessageID" }}` for translated strings.
* **Database:** Use GORM for DB interactions. Ensure migrations are updated in `cmd/migrate` or auto-migrated in `server.go` if appropriate.

View File

@@ -46,6 +46,7 @@ func main() {
if err != nil {
return nil
}
return os.Chown(path, uid, gid)
})
if err != nil {

View File

@@ -40,6 +40,7 @@ func runHealthCheck(url string) error {
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("status code: %d", resp.StatusCode)
}
return nil
}
@@ -80,6 +81,7 @@ func openDB(dsn, logLevel string) (*gorm.DB, error) {
}
}
}
return db, err
}

View File

@@ -153,6 +153,7 @@ func ListSystemApps(dataDir string) ([]AppMetadata, error) {
apps[i].Supports2x = true
}
found = true
break
}
}
@@ -199,6 +200,7 @@ func scanSystemApps(dataDir string) ([]AppMetadata, error) {
apps = append(apps, app)
}
}
return apps, nil
}
@@ -271,5 +273,6 @@ func ListUserApps(dataDir, username string) ([]AppMetadata, error) {
}
}
}
return apps, nil
}

View File

@@ -27,12 +27,14 @@ func VerifyPassword(hashStr, password string) (bool, bool, error) {
// 1. Check for Argon2id (Standard)
if strings.HasPrefix(hashStr, "$argon2id") {
valid, err := verifyArgon2id(hashStr, password)
return valid, false, err // Valid and current
}
// 2. Check for Scrypt (Legacy)
if strings.HasPrefix(hashStr, "scrypt:") {
valid, err := verifyScrypt(hashStr, password)
return valid, true, err // Valid but legacy
}
@@ -42,11 +44,13 @@ func VerifyPassword(hashStr, password string) (bool, bool, error) {
if err == nil {
return true, true, nil // Valid but legacy
}
return false, false, err
}
if strings.HasPrefix(hashStr, "pbkdf2:sha256:") {
valid, err := verifyPbkdf2(hashStr, password)
return valid, true, err // Valid but legacy
}

View File

@@ -9,24 +9,24 @@ import (
)
type Settings struct {
DBDSN string `env:"DB_DSN" envDefault:"data/tronbyt.db"`
DataDir string `env:"DATA_DIR" envDefault:"data"`
Production string `env:"PRODUCTION" envDefault:"1"`
DBDSN string `env:"DB_DSN" envDefault:"data/tronbyt.db"`
DataDir string `env:"DATA_DIR" envDefault:"data"`
Production string `env:"PRODUCTION" envDefault:"1"`
EnableUserRegistration string `env:"ENABLE_USER_REGISTRATION" envDefault:"1"`
MaxUsers int `env:"MAX_USERS" envDefault:"0"`
SingleUserAutoLogin string `env:"SINGLE_USER_AUTO_LOGIN" envDefault:"0"`
SystemAppsRepo string `env:"SYSTEM_APPS_REPO" envDefault:"https://github.com/tronbyt/apps.git"`
MaxUsers int `env:"MAX_USERS" envDefault:"0"`
SingleUserAutoLogin string `env:"SINGLE_USER_AUTO_LOGIN" envDefault:"0"`
SystemAppsRepo string `env:"SYSTEM_APPS_REPO" envDefault:"https://github.com/tronbyt/apps.git"`
RedisURL string `env:"REDIS_URL"`
Host string `env:"TRONBYT_HOST" envDefault:""`
Port string `env:"TRONBYT_PORT" envDefault:"8000"`
Host string `env:"TRONBYT_HOST" envDefault:""`
Port string `env:"TRONBYT_PORT" envDefault:"8000"`
UnixSocket string `env:"TRONBYT_UNIX_SOCKET"`
SSLKeyFile string `env:"TRONBYT_SSL_KEYFILE"`
SSLCertFile string `env:"TRONBYT_SSL_CERTFILE"`
TrustedProxies string `env:"TRONBYT_TRUSTED_PROXIES" envDefault:"*"`
LogLevel string `env:"LOG_LEVEL" envDefault:"INFO"`
TrustedProxies string `env:"TRONBYT_TRUSTED_PROXIES" envDefault:"*"`
LogLevel string `env:"LOG_LEVEL" envDefault:"INFO"`
}
// TemplateConfig holds configuration values needed in templates
// TemplateConfig holds configuration values needed in templates.
type TemplateConfig struct {
EnableUserRegistration string
SingleUserAutoLogin string

View File

@@ -8,41 +8,41 @@ import (
"gorm.io/gorm/logger"
)
// GORMSlogLogger wraps slog for GORM logging
// GORMSlogLogger wraps slog for GORM logging.
type GORMSlogLogger struct {
LogLevel logger.LogLevel
SlowThreshold time.Duration
}
// LogMode log mode
// LogMode log mode.
func (l *GORMSlogLogger) LogMode(level logger.LogLevel) logger.Interface {
newLogger := *l
newLogger.LogLevel = level
return &newLogger
}
// Info prints info
// Info prints info.
func (l *GORMSlogLogger) Info(ctx context.Context, msg string, data ...any) {
if l.LogLevel >= logger.Info {
slog.InfoContext(ctx, msg, data...)
}
}
// Warn prints warn messages
// Warn prints warn messages.
func (l *GORMSlogLogger) Warn(ctx context.Context, msg string, data ...any) {
if l.LogLevel >= logger.Warn {
slog.WarnContext(ctx, msg, data...)
}
}
// Error prints error messages
// Error prints error messages.
func (l *GORMSlogLogger) Error(ctx context.Context, msg string, data ...any) {
if l.LogLevel >= logger.Error {
slog.ErrorContext(ctx, msg, data...)
}
}
// Trace prints trace messages
// Trace prints trace messages.
func (l *GORMSlogLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
if l.LogLevel <= logger.Silent {
return
@@ -65,7 +65,7 @@ func (l *GORMSlogLogger) Trace(ctx context.Context, begin time.Time, fc func() (
}
}
// NewGORMSlogLogger creates a new GORM logger that uses slog
// NewGORMSlogLogger creates a new GORM logger that uses slog.
func NewGORMSlogLogger(slogLevel logger.LogLevel, slowThreshold time.Duration) logger.Interface {
return &GORMSlogLogger{
LogLevel: slogLevel,

View File

@@ -84,6 +84,7 @@ func (p *ProtocolType) UnmarshalJSON(b []byte) error {
return err
}
*p = ProtocolType(strings.ToUpper(s))
return nil
}
@@ -98,19 +99,22 @@ const (
type Brightness int
// Value implements the driver.Valuer interface for database storage.
func (b Brightness) Value() (driver.Value, error) {
return int64(b), nil
}
func (b *Brightness) Scan(value interface{}) error {
func (b *Brightness) Scan(value any) error {
if val, ok := value.(int64); ok {
*b = Brightness(val)
return nil
}
return errors.New("failed to scan Brightness")
}
// Percent returns brightness as 0.0-1.0
// Percent returns brightness as 0.0-1.0.
func (b Brightness) Percent() float64 {
if b < 0 {
return 0.0
@@ -121,12 +125,12 @@ func (b Brightness) Percent() float64 {
return float64(b) / 100.0
}
// Uint8 returns brightness as 0-255
// Uint8 returns brightness as 0-255.
func (b Brightness) Uint8() uint8 {
return uint8(b.Percent() * 255.0)
}
// UIScale returns 0-5
// UIScale returns 0-5.
func (b Brightness) UIScale(customScale map[int]int) int {
v := int(b)
if customScale != nil {
@@ -190,7 +194,7 @@ func (b Brightness) UIScale(customScale map[int]int) int {
return 5
}
// BrightnessFromUIScale converts a UI scale value (0-5) to Brightness percentage
// BrightnessFromUIScale converts a UI scale value (0-5) to Brightness percentage.
func BrightnessFromUIScale(uiValue int, customScale map[int]int) Brightness {
if customScale != nil {
if val, ok := customScale[uiValue]; ok {
@@ -213,7 +217,7 @@ func BrightnessFromUIScale(uiValue int, customScale map[int]int) Brightness {
return Brightness(20) // Default
}
// ParseCustomBrightnessScale parses a comma-separated string into a map
// ParseCustomBrightnessScale parses a comma-separated string into a map.
func ParseCustomBrightnessScale(scaleStr string) map[int]int {
if scaleStr == "" {
return nil
@@ -268,7 +272,7 @@ func (l DeviceLocation) Value() (driver.Value, error) {
return json.Marshal(l)
}
func (l *DeviceLocation) Scan(value interface{}) error {
func (l *DeviceLocation) Scan(value any) error {
if value == nil {
return nil
}
@@ -296,7 +300,7 @@ func (i DeviceInfo) Value() (driver.Value, error) {
return json.Marshal(i)
}
func (i *DeviceInfo) Scan(value interface{}) error {
func (i *DeviceInfo) Scan(value any) error {
if value == nil {
return nil
}
@@ -311,15 +315,14 @@ func (i *DeviceInfo) Scan(value interface{}) error {
return json.Unmarshal(bytes, i)
}
// JSONMap is a helper for storing arbitrary JSON in the DB
// JSONMap is a helper for storing arbitrary JSON in the DB.
type JSONMap map[string]any
func (j JSONMap) Value() (driver.Value, error) {
return json.Marshal(j)
}
func (j *JSONMap) Scan(value interface{}) error {
func (j *JSONMap) Scan(value any) error {
bytes, ok := value.([]byte)
if !ok {
return errors.New("type assertion to []byte failed")
@@ -327,14 +330,14 @@ func (j *JSONMap) Scan(value interface{}) error {
return json.Unmarshal(bytes, j)
}
// StringSlice is a helper for storing []string as JSON
// StringSlice is a helper for storing []string as JSON.
type StringSlice []string
func (s StringSlice) Value() (driver.Value, error) {
return json.Marshal(s)
}
func (s *StringSlice) Scan(value interface{}) error {
func (s *StringSlice) Scan(value any) error {
bytes, ok := value.([]byte)
if !ok {
return errors.New("type assertion to []byte failed")

View File

@@ -76,6 +76,7 @@ func getFirmwareFilename(deviceType string, swapColors bool) string {
if swapColors {
return "tidbyt-gen1_swap.bin"
}
return "tidbyt-gen1.bin"
}
}
@@ -92,7 +93,7 @@ func updateFirmwareData(data []byte) ([]byte, error) {
// 1. Calculate Checksum (XOR sum of data + 0xEF)
checksum := byte(0xEF)
for i := 0; i < dataLen; i++ {
for i := range dataLen {
checksum ^= data[i]
}

View File

@@ -46,6 +46,7 @@ func GetRepoInfo(path string, remoteURL string) (*RepoInfo, error) {
branchName = ref.Name().Short()
return fmt.Errorf("found") // Hack to exit ForEach
}
return nil
})
if err != nil && err.Error() != "found" {
@@ -84,6 +85,7 @@ func EnsureRepo(path string, url string, update bool) error {
Progress: os.Stdout,
Depth: 1,
})
return err
}
@@ -105,6 +107,7 @@ func EnsureRepo(path string, url string, update bool) error {
if err := os.RemoveAll(path); err != nil {
return fmt.Errorf("failed to remove old repo: %w", err)
}
return EnsureRepo(path, url, update)
}
}

View File

@@ -103,12 +103,12 @@ type LegacyApp struct {
ColorFilter *string `json:"color_filter"`
}
// Wrapper for the JSON blob structure in DB
// UserDataBlob is a wrapper for the JSON blob structure in DB.
type UserDataBlob struct {
Users map[string]LegacyUser `json:"users"`
}
// ParseBool handles boolean polymorphism (bool or int 0/1)
// ParseBool handles boolean polymorphism (bool or int 0/1).
func ParseBool(val any) bool {
if v, ok := val.(bool); ok {
return v
@@ -119,10 +119,11 @@ func ParseBool(val any) bool {
if v, ok := val.(float64); ok {
return int(v) != 0
}
return false
}
// Helper to handle Brightness polymorphism (int or object with value)
// ParseBrightness is a helper to handle Brightness polymorphism (int or object with value).
func ParseBrightness(val any) int {
if v, ok := val.(float64); ok {
return int(v)
@@ -135,6 +136,7 @@ func ParseBrightness(val any) int {
return int(v)
}
}
return 0
}
@@ -148,7 +150,7 @@ func ParseTimeStr(val any) string {
return ""
}
// ParseDuration parses ISO8601 duration (PT1.5S) or numeric seconds into int64 nanoseconds
// ParseDuration parses ISO8601 duration (PT1.5S) or numeric seconds into int64 nanoseconds.
func ParseDuration(val any) int64 {
if val == nil {
return 0

View File

@@ -71,6 +71,7 @@ func MigrateLegacyDB(oldDBPath, newDBLocation, dataDir string) error {
}
slog.Info("Migration complete.")
return nil
}
@@ -109,6 +110,7 @@ func readLegacyUsers(dbPath string) ([]legacy.LegacyUser, error) {
}
users = append(users, user)
}
return users, nil
}
@@ -344,6 +346,7 @@ func migrateDirectories(oldDBPath, newDataDir string) error {
return fmt.Errorf("failed to move %s: %w", srcPath, err)
}
}
return nil
}

View File

@@ -579,7 +579,7 @@ func (s *Server) handleDots(w http.ResponseWriter, r *http.Request) {
}
}
// Helper to get device webp directory (from server.go)
// getDeviceWebpDir is a helper to get device webp directory (from server.go).
func (s *Server) getDeviceWebPDir(deviceID string) string {
path := filepath.Join(s.DataDir, "webp", deviceID)
if err := os.MkdirAll(path, 0755); err != nil {

View File

@@ -69,7 +69,7 @@ func newTestServerAPI(t *testing.T) *Server {
return s
}
// Helper to create a request with API key
// Helper to create a request with API key.
func newAPIRequest(method, path, apiKey string, body []byte) *http.Request {
req := httptest.NewRequest(method, path, bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
@@ -80,7 +80,7 @@ func newAPIRequest(method, path, apiKey string, body []byte) *http.Request {
func TestHandleDots(t *testing.T) {
s := newTestServerAPI(t)
req, _ := http.NewRequest("GET", "/dots?w=2&h=1&r=0.75", nil)
req, _ := http.NewRequest(http.MethodGet, "/dots?w=2&h=1&r=0.75", nil)
rr := httptest.NewRecorder()
s.ServeHTTP(rr, req)

View File

@@ -10,7 +10,7 @@ import (
"github.com/gorilla/websocket"
)
// handleNextApp is the handler for GET /{id}/next
// handleNextApp is the handler for GET /{id}/next.
func (s *Server) handleNextApp(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")

View File

@@ -29,7 +29,7 @@ func TestHandleNextApp(t *testing.T) {
t.Fatalf("Failed to save pushed image: %v", err)
}
req := httptest.NewRequest("GET", "/testdevice/next", nil)
req := httptest.NewRequest(http.MethodGet, "/testdevice/next", nil)
req.SetPathValue("id", "testdevice")
rr := httptest.NewRecorder()

View File

@@ -34,7 +34,7 @@ func (s *Server) UpdateFirmwareBinaries() error {
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo)
req, err := http.NewRequest("GET", url, nil)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return err
}
@@ -107,7 +107,7 @@ func (s *Server) UpdateFirmwareBinaries() error {
slog.Info("Downloading firmware", "asset", asset.Name)
// Use API URL with Accept header to get binary
dReq, _ := http.NewRequest("GET", asset.URL, nil)
dReq, _ := http.NewRequest(http.MethodGet, asset.URL, nil)
dReq.Header.Set("Accept", "application/octet-stream")
if token != "" {
dReq.Header.Set("Authorization", "Bearer "+token)

View File

@@ -22,7 +22,7 @@ func TestHandleFirmwareGenerateGet(t *testing.T) {
var device data.Device
s.DB.First(&device, "id = ?", "testdevice")
req := httptest.NewRequest("GET", "/devices/testdevice/firmware", nil)
req := httptest.NewRequest(http.MethodGet, "/devices/testdevice/firmware", nil)
ctx := context.WithValue(req.Context(), userContextKey, &user)
ctx = context.WithValue(ctx, deviceContextKey, &device)
req = req.WithContext(ctx)
@@ -69,7 +69,7 @@ func TestHandleFirmwareGeneratePost(t *testing.T) {
form.Add("wifi_password", "TestPass")
form.Add("img_url", "http://example.com/image")
req := httptest.NewRequest("POST", "/devices/testdevice/firmware", strings.NewReader(form.Encode()))
req := httptest.NewRequest(http.MethodPost, "/devices/testdevice/firmware", strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
ctx := context.WithValue(req.Context(), userContextKey, &user)

View File

@@ -170,7 +170,7 @@ func (s *Server) RequireApp(next http.HandlerFunc) http.HandlerFunc {
}
}
// UserFromContext retrieves the User from the context
// UserFromContext retrieves the User from the context.
func UserFromContext(ctx context.Context) (*data.User, error) {
u, ok := ctx.Value(userContextKey).(*data.User)
if !ok {
@@ -179,7 +179,7 @@ func UserFromContext(ctx context.Context) (*data.User, error) {
return u, nil
}
// DeviceFromContext retrieves the Device from the context
// DeviceFromContext retrieves the Device from the context.
func DeviceFromContext(ctx context.Context) (*data.Device, error) {
d, ok := ctx.Value(deviceContextKey).(*data.Device)
if !ok {
@@ -188,7 +188,7 @@ func DeviceFromContext(ctx context.Context) (*data.Device, error) {
return d, nil
}
// AppFromContext retrieves the App from the context
// AppFromContext retrieves the App from the context.
func AppFromContext(ctx context.Context) (*data.App, error) {
a, ok := ctx.Value(appContextKey).(*data.App)
if !ok {
@@ -197,7 +197,7 @@ func AppFromContext(ctx context.Context) (*data.App, error) {
return a, nil
}
// Helper to get Context objects without error checking (panics if missing, use only within middleware)
// GetUser is a helper to get Context objects without error checking (panics if missing, use only within middleware).
func GetUser(r *http.Request) *data.User {
u, _ := UserFromContext(r.Context())
if u == nil {
@@ -222,7 +222,7 @@ func GetApp(r *http.Request) *data.App {
return a
}
// Helper to wrap APIAuthMiddleware for ServeMux which expects generic handler
// APIAuth is a helper to wrap APIAuthMiddleware for ServeMux which expects generic handler.
func (s *Server) APIAuth(next http.HandlerFunc) http.HandlerFunc {
return s.APIAuthMiddleware(next).ServeHTTP
}

View File

@@ -212,7 +212,7 @@ func createExpandedAppsList(device *data.Device, apps []data.App) []data.App {
return apps
}
var expanded []data.App
expanded := make([]data.App, 0, len(device.Apps)*10) // Pre-allocate to a reasonable size
for i, app := range apps {
expanded = append(expanded, app)
// Add interstitial after each regular app, except the last one

View File

@@ -13,12 +13,14 @@ import (
"io"
"io/fs"
"log/slog"
"maps"
"math/big"
"net"
"net/http"
"os"
"path/filepath"
"runtime/debug"
"slices"
"sort"
"strconv"
"strings"
@@ -48,7 +50,7 @@ import (
"gopkg.in/yaml.v3"
)
// AppManifest reflects a subset of manifest.yaml for internal updates
// AppManifest reflects a subset of manifest.yaml for internal updates.
type AppManifest struct {
Broken *bool `yaml:"broken,omitempty"`
BrokenReason *string `yaml:"brokenReason,omitempty"`
@@ -145,7 +147,7 @@ type CreateDeviceFormData struct {
LocationJSON string
}
// Map template names to their file paths relative to web/templates
// Map template names to their file paths relative to web/templates.
var templateFiles = map[string]string{
"index": "manager/index.html",
"adminindex": "manager/adminindex.html",
@@ -403,10 +405,7 @@ func NewServer(db *gorm.DB, cfg *config.Settings) *Server {
if length < 0 {
length = 0
}
end := start + length
if end > len(s) {
end = len(s)
}
end := min(start+length, len(s))
if start > len(s) {
return ""
}
@@ -418,12 +417,7 @@ func NewServer(db *gorm.DB, cfg *config.Settings) *Server {
return args
},
"contains": func(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
return slices.Contains(slice, item)
},
}
@@ -673,7 +667,7 @@ func (s *Server) renderTemplate(w http.ResponseWriter, r *http.Request, name str
}
}
// localizeOrID helper to safely localize or return ID
// localizeOrID helper to safely localize or return ID.
func (s *Server) localizeOrID(localizer *i18n.Localizer, messageID string) string {
config := &i18n.LocalizeConfig{
MessageID: messageID,
@@ -694,7 +688,7 @@ func (s *Server) getLocalizer(r *http.Request) *i18n.Localizer {
return i18n.NewLocalizer(s.Bundle, accept, language.English.String())
}
// getDeviceTypeChoices returns a map of device type values to display names
// getDeviceTypeChoices returns a map of device type values to display names.
func (s *Server) getDeviceTypeChoices(localizer *i18n.Localizer) map[string]string {
choices := make(map[string]string)
@@ -723,7 +717,7 @@ func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
slog.Debug("handleIndex called")
user := GetUser(r)
var devicesWithUI []DeviceWithUIScale
devicesWithUI := make([]DeviceWithUIScale, 0, len(user.Devices)) // Pre-allocate
for i := range user.Devices {
device := &user.Devices[i]
@@ -841,8 +835,8 @@ func (s *Server) getRealIP(r *http.Request) string {
isTrusted = true
} else {
// Simple check for comma-separated list of IPs
proxies := strings.Split(trustedProxies, ",")
for _, proxy := range proxies {
proxies := strings.SplitSeq(trustedProxies, ",")
for proxy := range proxies {
if strings.TrimSpace(proxy) == remoteIP {
isTrusted = true
break
@@ -1081,7 +1075,7 @@ func (s *Server) duplicateAppToDeviceLogic(r *http.Request, user *data.User, sou
// Generate a unique iname for the duplicate on the target device
var newIname string
maxAttempts := 900 // Max 900 attempts for 3-digit numbers
for i := 0; i < maxAttempts; i++ {
for range maxAttempts {
n, err := rand.Int(rand.Reader, big.NewInt(900)) // 0-899
if err != nil {
return fmt.Errorf("failed to generate random number for iname: %w", err)
@@ -1117,9 +1111,7 @@ func (s *Server) duplicateAppToDeviceLogic(r *http.Request, user *data.User, sou
// Deep copy Config map if it exists
if originalApp.Config != nil {
newConfig := make(data.JSONMap)
for k, v := range originalApp.Config {
newConfig[k] = v
}
maps.Copy(newConfig, originalApp.Config)
duplicatedApp.Config = newConfig
}
@@ -1362,7 +1354,7 @@ func (s *Server) handleAddAppPost(w http.ResponseWriter, r *http.Request) {
// Generate iname (random 3-digit string, matching Python version)
var iname string
for i := 0; i < 100; i++ {
for i := range 100 {
// Random integer between 100 and 999
n, err := rand.Int(rand.Reader, big.NewInt(900))
if err != nil {
@@ -1906,6 +1898,7 @@ func (s *Server) handleMoveApp(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func (s *Server) handleDuplicateApp(w http.ResponseWriter, r *http.Request) {
user := GetUser(r)
device := GetDevice(r)
@@ -1913,7 +1906,7 @@ func (s *Server) handleDuplicateApp(w http.ResponseWriter, r *http.Request) {
// Generate new iname (random 3-digit string)
var newIname string
for i := 0; i < 100; i++ {
for i := range 100 {
// Random integer between 100 and 999
n, err := rand.Int(rand.Reader, big.NewInt(900))
if err != nil {
@@ -2683,7 +2676,7 @@ func (s *Server) checkForUpdates() {
func (s *Server) doUpdateCheck() {
url := "https://api.github.com/repos/tronbyt/server/releases/latest"
req, err := http.NewRequest("GET", url, nil)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
slog.Debug("Failed to create HTTP request for update check", "error", err)
return

View File

@@ -38,7 +38,7 @@ func newTestServer(t *testing.T) *Server {
func TestHealthCheck(t *testing.T) {
s := newTestServer(t)
req, _ := http.NewRequest("GET", "/health", nil)
req, _ := http.NewRequest(http.MethodGet, "/health", nil)
rr := httptest.NewRecorder()
s.ServeHTTP(rr, req)
@@ -65,7 +65,7 @@ func TestLoginRedirectToRegisterIfNoUsers(t *testing.T) {
t.Fatalf("Expected 0 users, got %d", count)
}
req, _ := http.NewRequest("GET", "/auth/login", nil)
req, _ := http.NewRequest(http.MethodGet, "/auth/login", nil)
rr := httptest.NewRecorder()
s.ServeHTTP(rr, req)

View File

@@ -17,7 +17,7 @@ import (
"github.com/go-webauthn/webauthn/webauthn"
)
// Wrapper for data.User to satisfy webauthn.User interface
// WebAuthnUser is a wrapper for data.User to satisfy webauthn.User interface.
type WebAuthnUser struct {
User *data.User
Credentials []webauthn.Credential
@@ -93,7 +93,7 @@ func (s *Server) handleWebAuthnRegisterBegin(w http.ResponseWriter, r *http.Requ
return
}
var credentials []webauthn.Credential
credentials := make([]webauthn.Credential, 0, len(user.Credentials))
for _, cred := range user.Credentials {
idBytes, err := base64.URLEncoding.DecodeString(cred.ID)
if err != nil {
@@ -313,7 +313,7 @@ func (s *Server) handleWebAuthnLoginFinish(w http.ResponseWriter, r *http.Reques
s.DB.Where("user_id = ?", cred.UserID).Find(&userCreds)
cred.User.Credentials = userCreds // Attach credentials to user object
var waCredentials []webauthn.Credential
waCredentials := make([]webauthn.Credential, 0, 1) // Expecting 1 or 0
for _, c := range userCreds {
idBytes, _ := base64.URLEncoding.DecodeString(c.ID)
aaguid, _ := hex.DecodeString(c.Authenticator)

View File

@@ -70,10 +70,7 @@ func (s *Server) wsWriteLoop(ctx context.Context, conn *websocket.Conn, initialD
var timeoutSec int
if device.Info.ProtocolVersion != nil {
// New firmware: wait longer to allow buffering/ack
timeoutSec = dwell * 2
if timeoutSec < 25 {
timeoutSec = 25
}
timeoutSec = max(dwell*2, 25)
} else {
// Old firmware: wait exactly dwell time
timeoutSec = dwell