Deadcode-verified removals (RTA reachability from main, cross-checked with -test and textual xrefs): Go server: - whole files: middleware/transaction.go, middleware/error_handler.go, middleware/password_validator.go (live registration machinery kept), handlers/sync.go, sync/offline.go - unrouted endpoints: auth UpdateUsername/UpdateEmail, timezone settings handler, OPDS RegisterOPDS, Kobo LibrarySync handler, ReaderHandler.ShowReader, JSON-data twins on collections/library - duplicate book-matching handlers (book_matching.go Handler twins, matching.go MatchingHandler twins), superseded helpers (router.parseUUID, handlers.ValidateDeviceToken, normalizePath, userExists, generateLockID) Templ/client: - never-routed pages: api_explorer.templ, queue.templ and their client modules api-explorer.ts, api-explorer-docs.ts, queue.ts - fully orphaned wrapper modules events.ts, dom.ts - broken clearSyncQueue (called a nonexistent endpoint) and the hidden, never-populated sync-queue section on the devices page - 166+ unused exports demoted to private (knip-verified); ~85 truly dead declarations removed via tsc noUnusedLocals fixpoint - stale 'interactive API Explorer' claims dropped from docs The docs drift-gate package (internal/docs), templ-bound modules (dashboard, stats, header, index, login, profile*, bookshelf, collections, toast-error), sqlc-generated code, and web/static are intentionally untouched.
89 lines
3.0 KiB
Go
89 lines
3.0 KiB
Go
package middleware
|
|
|
|
import (
|
|
"bookhoard/internal/database"
|
|
"regexp"
|
|
"sync"
|
|
|
|
"github.com/go-playground/validator/v10"
|
|
)
|
|
|
|
// specialCharRegex matches the historical "special character" set used by the
|
|
// password complexity rules.
|
|
const specialCharRegex = `[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]`
|
|
|
|
// PasswordValidator validates password complexity against the configured rules.
|
|
// When a database.SettingsRegistry is wired via SetSettings, rules are read live and the
|
|
// regex set is recompiled under a mutex on each validation. Without a registry
|
|
// the historical hardcoded defaults (8+ chars, upper/lower/number/special) apply.
|
|
type PasswordValidator struct {
|
|
settings *database.SettingsRegistry
|
|
}
|
|
|
|
// SetSettings wires the tunable settings registry.
|
|
func (v *PasswordValidator) SetSettings(s *database.SettingsRegistry) { v.settings = s }
|
|
|
|
// compileSpecialRegex isolates the regexp compile (which is safe to call
|
|
// concurrently, but we keep it behind a cached var for the no-registry path).
|
|
var (
|
|
specialOnce sync.Once
|
|
specialRe *regexp.Regexp
|
|
)
|
|
|
|
func specialRegex() *regexp.Regexp {
|
|
specialOnce.Do(func() {
|
|
specialRe = regexp.MustCompile(specialCharRegex)
|
|
})
|
|
return specialRe
|
|
}
|
|
|
|
func (v *PasswordValidator) rules() database.PasswordRules {
|
|
if v.settings != nil {
|
|
return v.settings.PasswordRules()
|
|
}
|
|
return database.PasswordRules{MinLength: 8, Upper: true, Lower: true, Number: true, Special: true}
|
|
}
|
|
|
|
// CheckPassword applies the active rules to a single password.
|
|
func (v *PasswordValidator) CheckPassword(password string) bool {
|
|
r := v.rules()
|
|
if len(password) < r.MinLength {
|
|
return false
|
|
}
|
|
if r.Upper && !regexp.MustCompile(`[A-Z]`).MatchString(password) {
|
|
return false
|
|
}
|
|
if r.Lower && !regexp.MustCompile(`[a-z]`).MatchString(password) {
|
|
return false
|
|
}
|
|
if r.Number && !regexp.MustCompile(`[0-9]`).MatchString(password) {
|
|
return false
|
|
}
|
|
if r.Special && !specialRegex().MatchString(password) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// defaultPasswordValidator backs the passwordcomplex struct tag registered by
|
|
// RegisterPasswordValidation. Callers that want live rule updates should
|
|
// construct their own PasswordValidator and call SetSettings.
|
|
var defaultPasswordValidator = &PasswordValidator{}
|
|
|
|
// RegisterPasswordValidation registers the password validator with the
|
|
// validator instance. The registered func re-evaluates rules on every call, so
|
|
// changes to the wired registry take effect immediately.
|
|
func RegisterPasswordValidation(v *validator.Validate) error {
|
|
return v.RegisterValidation("passwordcomplex", func(fl validator.FieldLevel) bool {
|
|
return defaultPasswordValidator.CheckPassword(fl.Field().String())
|
|
})
|
|
}
|
|
|
|
// SetDefaultPasswordSettings wires the settings registry into the package-level
|
|
// default validator so that the passwordcomplex struct-tag validator (used by
|
|
// echo's CustomValidator) follows live configuration. Intended to be called
|
|
// once at startup.
|
|
func SetDefaultPasswordSettings(s *database.SettingsRegistry) {
|
|
defaultPasswordValidator.SetSettings(s)
|
|
}
|