Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion frontend/src/lib/pages/WelcomeAssistant.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
11 changes: 11 additions & 0 deletions internal/handlers/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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, "@") {
Expand Down
Loading