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

+40 -26
View File
@@ -26,9 +26,11 @@ type Config struct {
// ALLOW_REGISTRATION=false (the template default) leaves the choice to the admin settings.
RegistrationMode string
RegistrationDomains string
LogLevel string
Debug bool
DemoMode bool
// RegistrationVerifyEmail: REGISTRATION_VERIFY_EMAIL ("true"/"false", "" = admin settings decide)
RegistrationVerifyEmail string
LogLevel string
Debug bool
DemoMode bool
// DemoResetMinutes: in demo mode the database is reset to the sample data this often (0 = never)
DemoResetMinutes int
// TrustedProxies: whose X-Forwarded-For/X-Real-IP is believed ("private", "all", "none" or CIDRs)
@@ -233,29 +235,30 @@ func Load() *Config {
smtpSkipVerify := strings.ToLower(getEnv("SMTP_SKIP_VERIFY", "false")) == "true"
return &Config{
Port: port,
DataDir: dataDir,
DatabasePath: dbPath,
JWTSecret: jwtSecret,
BaseURL: baseURL,
BaseURLSet: strings.TrimSpace(os.Getenv("BASE_URL")) != "",
AllowRegistration: allowReg,
RegistrationMode: registrationMode(allowReg),
RegistrationDomains: strings.TrimSpace(os.Getenv("REGISTRATION_DOMAINS")),
LogLevel: logLevel,
Debug: debug,
DemoMode: demoMode,
DemoResetMinutes: demoResetMinutes(),
TrustedProxies: getEnv("TRUSTED_PROXIES", "private"),
DisableLoginProtection: strings.ToLower(getEnv("LOGIN_PROTECTION", "true")) == "false",
SMTPHost: smtpHost,
SMTPPort: smtpPort,
SMTPUser: smtpUser,
SMTPPass: smtpPass,
SMTPFrom: smtpFrom,
SMTPFromName: getEnv("SMTP_FROM_NAME", "MindSift"),
SMTPTLS: smtpTLS,
SMTPSkipVerify: smtpSkipVerify,
Port: port,
DataDir: dataDir,
DatabasePath: dbPath,
JWTSecret: jwtSecret,
BaseURL: baseURL,
BaseURLSet: strings.TrimSpace(os.Getenv("BASE_URL")) != "",
AllowRegistration: allowReg,
RegistrationMode: registrationMode(allowReg),
RegistrationDomains: strings.TrimSpace(os.Getenv("REGISTRATION_DOMAINS")),
RegistrationVerifyEmail: boolEnvOrEmpty("REGISTRATION_VERIFY_EMAIL"),
LogLevel: logLevel,
Debug: debug,
DemoMode: demoMode,
DemoResetMinutes: demoResetMinutes(),
TrustedProxies: getEnv("TRUSTED_PROXIES", "private"),
DisableLoginProtection: strings.ToLower(getEnv("LOGIN_PROTECTION", "true")) == "false",
SMTPHost: smtpHost,
SMTPPort: smtpPort,
SMTPUser: smtpUser,
SMTPPass: smtpPass,
SMTPFrom: smtpFrom,
SMTPFromName: getEnv("SMTP_FROM_NAME", "MindSift"),
SMTPTLS: smtpTLS,
SMTPSkipVerify: smtpSkipVerify,
Speech: SpeechConfig{
URL: strings.TrimRight(getEnv("STT_URL", ""), "/"),
Model: getEnv("STT_MODEL", "deepdml/faster-whisper-large-v3-turbo-ct2"),
@@ -349,3 +352,14 @@ func registrationMode(allowReg bool) string {
}
return ""
}
// boolEnvOrEmpty: "true" or "false" when the variable is set to a boolean, "" when unset or unknown.
func boolEnvOrEmpty(key string) string {
switch strings.ToLower(strings.TrimSpace(os.Getenv(key))) {
case "true", "1", "yes", "on":
return "true"
case "false", "0", "no", "off":
return "false"
}
return ""
}