feat: authentication with local accounts, sessions, CSRF, RBAC, and OIDC PKCE (phase 3)
- argon2id (t=3, m=64MiB, p=2) PHC hashing honoring embedded params - token-bucket rate limiting (10/15min per IP+email) on login/register - opaque 32B session tokens in Mongo, 30-day sliding expiry, logout-all - CSRF double-submit cookie/header on authenticated mutations - bootstrap admin from env with forced first-login password change - requireAuth/requireRole middleware with disabled-account enforcement - OIDC code flow with PKCE: lazy discovery, account linking only on verified email, auto-created developer accounts - unit tests (RBAC matrix, CSRF, password, rate limiter) + integration suite covering the full auth matrix incl. an in-test fake IdP Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"bountyboard/internal/config"
|
||||
"bountyboard/internal/domain"
|
||||
"bountyboard/internal/store"
|
||||
"bountyboard/internal/ulid"
|
||||
)
|
||||
|
||||
// EnsureBootstrapAdmin creates the initial admin from ADMIN_EMAIL /
|
||||
// ADMIN_INITIAL_PASSWORD when the users collection is empty (§3). The
|
||||
// password change is forced at first login.
|
||||
func EnsureBootstrapAdmin(ctx context.Context, st *store.Store, cfg *config.Config, log *slog.Logger) error {
|
||||
n, err := st.CountUsers(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n > 0 {
|
||||
return nil
|
||||
}
|
||||
if cfg.AdminEmail == "" || cfg.AdminInitialPassword == "" {
|
||||
log.Warn("users collection is empty but ADMIN_EMAIL/ADMIN_INITIAL_PASSWORD are not set; no admin bootstrapped")
|
||||
return nil
|
||||
}
|
||||
hash, err := HashPassword(cfg.AdminInitialPassword)
|
||||
if err != nil {
|
||||
return fmt.Errorf("hash bootstrap password: %w", err)
|
||||
}
|
||||
u := &domain.User{
|
||||
ID: ulid.New(),
|
||||
Email: cfg.AdminEmail,
|
||||
Name: "Administrator",
|
||||
Roles: domain.Roles{Admin: true},
|
||||
Auth: domain.Auth{
|
||||
Local: &domain.LocalAuth{PasswordHash: hash, MustChange: true},
|
||||
},
|
||||
Settings: domain.UserSettings{
|
||||
Theme: "light",
|
||||
Notifications: domain.NotificationPrefs{Email: true, InApp: true},
|
||||
},
|
||||
}
|
||||
if err := st.CreateUser(ctx, u); err != nil {
|
||||
return fmt.Errorf("bootstrap admin: %w", err)
|
||||
}
|
||||
log.Info("bootstrap admin created; password change is forced at first login", "email", u.Email)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user