feat(auth): confirm the e-mail address of new accounts by link
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m21s

- Setting / REGISTRATION_VERIFY_EMAIL; only with SMTP and BASE_URL; invites skip it
- Hashed one-time tokens (48 h), resend with cooldown, unconfirmed accounts purged
- Approval modes notify admins only after the address is confirmed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-31 14:01:41 +01:00
1 parent 1406650b7c
commit ab97638361
25 files changed
+621 -56

No files matched your search

+15 -1
View File
@@ -95,8 +95,10 @@ func (s *Server) handleAdminGetUsers(w http.ResponseWriter, r *http.Request) {
return
}
pending := s.store.PendingRegistrationIDs()
unverified := s.store.UnverifiedEmailIDs()
for i := range users {
users[i].PendingApproval = pending[users[i].ID]
users[i].EmailUnverified = unverified[users[i].ID]
}
writeJSON(w, http.StatusOK, users)
}
@@ -197,7 +199,9 @@ func (s *Server) handleAdminUpdateUser(w http.ResponseWriter, r *http.Request) {
s.wsHub.DisconnectUser(targetUserID)
}
if req.IsActive != nil && *req.IsActive {
s.store.ClearPendingRegistration(targetUserID) // activating by hand counts as approval
// Activating by hand counts as approval and vouches for the address
s.store.ClearPendingRegistration(targetUserID)
s.store.ClearEmailVerification(targetUserID)
}
writeJSON(w, http.StatusOK, u)
}
@@ -332,6 +336,11 @@ func (s *Server) settingsForBrowser(settings *domain.SystemSettings) *domain.Sys
out.RegistrationDomains = strings.Join(storage.ParseEmailDomains(s.cfg.RegistrationDomains), ", ")
out.RegistrationConfiguredViaEnv = true
}
if s.cfg.RegistrationVerifyEmail != "" {
out.RegistrationVerifyEmail = s.cfg.RegistrationVerifyEmail == "true"
out.RegistrationVerifyViaEnv = true
}
out.EmailLinksAvailable = s.emailLinksAvailable()
return &out
}
@@ -358,6 +367,11 @@ func (s *Server) handleAdminUpdateSettings(w http.ResponseWriter, r *http.Reques
}
}
}
if s.cfg.RegistrationVerifyEmail != "" {
if cur, err := s.store.GetSystemSettings(); err == nil {
settings.RegistrationVerifyEmail = cur.RegistrationVerifyEmail
}
}
previousMode := s.store.SharingMode()
if err := s.store.UpdateSystemSettings(&settings); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())