diff --git a/frontend/src/lib/pages/WelcomeAssistant.svelte b/frontend/src/lib/pages/WelcomeAssistant.svelte index d183c8752..1fcd9f8e7 100644 --- a/frontend/src/lib/pages/WelcomeAssistant.svelte +++ b/frontend/src/lib/pages/WelcomeAssistant.svelte @@ -144,7 +144,9 @@ window.location.reload(); } catch (err) { console.error('Setup error:', err); - error = t('setup.setupError'); + // Show the server's message when it sends one, for example a password over + // bcrypt's 72 byte input limit. The generic string stays as the fallback. + error = err?.message || t('setup.setupError'); submitting = false; } } diff --git a/internal/handlers/setup.go b/internal/handlers/setup.go index 14e112bde..8c5e5ee7c 100644 --- a/internal/handlers/setup.go +++ b/internal/handlers/setup.go @@ -17,6 +17,10 @@ import ( "golang.org/x/crypto/bcrypt" ) +// bcrypt's input limit. x/crypto/bcrypt refuses inputs longer than this instead +// of truncating them, so a password over 72 bytes cannot be hashed at all. +const maxPasswordBytes = 72 + // AuthMiddleware interface to avoid circular imports type AuthMiddleware interface { MarkSetupCompleted() @@ -502,6 +506,13 @@ func (h *SetupHandler) validateSetupRequest(req models.SetupRequest) error { if req.AdminUser.Password == "" { return fmt.Errorf("admin password is required") } + // bcrypt's input limit is 72 bytes, and x/crypto/bcrypt errors instead of + // truncating. An over-long password therefore surfaces as a 500 here and the + // UI shows a generic setup failure. Validate before hashing so the client gets + // an actionable message. len(string) counts bytes, which is what matters. + if len(req.AdminUser.Password) > maxPasswordBytes { + return fmt.Errorf("admin password must be %d bytes or fewer (bcrypt input limit)", maxPasswordBytes) + } // Basic email validation if !strings.Contains(req.AdminUser.Email, "@") {