fix(auth): longer IDs and invite codes, register limits, single-use invites (FLAW-32, FLAW-39, FLAW-40)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m2s

- NewID uses 96 random bits; invite codes get their own 100 bits in Base32.
- Registration and forgot-password are limited per address via loginGuard.
- The invite is claimed atomically before the account is created and
  released again when the account cannot be created.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-29 19:28:09 +01:00
1 parent 1af3a6e90a
commit 3709282a4f
11 files changed
+220 -15

No files matched your search

+17 -2
View File
@@ -168,6 +168,10 @@ func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusBadRequest, "Ungültige Anfrage")
return
}
if wait := s.loginGuard.throttle("register", clientIPFrom(r), limitRegisterPerIP); wait > 0 {
writeThrottled(w, wait)
return
}
if strings.TrimSpace(req.Email) == "" {
writeError(w, http.StatusBadRequest, "E-Mail-Adresse erforderlich")
return
@@ -212,15 +216,26 @@ func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
return
}
// The invite is claimed before the account exists, so it cannot be redeemed twice at once
var claim string
if validInvite != nil {
if claim, err = s.store.ClaimUserInvite(validInvite.ID); err != nil {
writeError(w, http.StatusForbidden, err.Error())
return
}
}
user, err := s.store.CreateUser(req.Email, hash, req.DisplayName, role)
if err != nil {
if validInvite != nil {
s.store.ReleaseUserInvite(validInvite.ID, claim)
}
writeError(w, http.StatusConflict, "E-Mail-Adresse existiert bereits")
return
}
if validInvite != nil {
// Otherwise the code could be used again
if err := s.store.MarkUserInviteUsed(validInvite.ID, user.ID); err != nil {
if err := s.store.FinishUserInvite(validInvite.ID, claim, user.ID); err != nil {
slog.Error("Einladungscode konnte nicht als benutzt markiert werden", "invite", validInvite.ID, "error", err)
}
if validInvite.OrganizationID != "" {