Files
dance-lessons-coach/pkg/user/user.go
Gabriel Radureau f71495b6fc
Some checks failed
CI/CD Pipeline / Build Docker Cache (push) Successful in 57s
CI/CD Pipeline / Trigger Docker Push (push) Has been cancelled
CI/CD Pipeline / CI Pipeline (push) Has been cancelled
feat(admin): GET /api/v1/admin/jwt/secrets — metadata-only introspection (#51)
Co-authored-by: Gabriel Radureau <arcodange@gmail.com>
Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
2026-05-05 09:51:54 +02:00

101 lines
4.5 KiB
Go

package user
import (
"context"
"time"
)
// User represents a user in the system
type User struct {
ID uint `json:"id" gorm:"primaryKey"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
DeletedAt *time.Time `json:"deleted_at,omitempty" gorm:"index"`
Username string `json:"username" gorm:"unique;not null" validate:"required,min=3,max=50"`
PasswordHash string `json:"-" gorm:"not null"`
Description *string `json:"description,omitempty"`
CurrentGoal *string `json:"current_goal,omitempty"`
IsAdmin bool `json:"is_admin" gorm:"default:false"`
AllowPasswordReset bool `json:"allow_password_reset" gorm:"default:false"`
LastLogin *time.Time `json:"last_login,omitempty"`
}
// UserRepository defines the interface for user persistence
type UserRepository interface {
CreateUser(ctx context.Context, user *User) error
GetUserByUsername(ctx context.Context, username string) (*User, error)
GetUserByID(ctx context.Context, id uint) (*User, error)
UpdateUser(ctx context.Context, user *User) error
DeleteUser(ctx context.Context, id uint) error
AllowPasswordReset(ctx context.Context, username string) error
CompletePasswordReset(ctx context.Context, username, newPassword string) error
UserExists(ctx context.Context, username string) (bool, error)
CheckDatabaseHealth(ctx context.Context) error
}
// AuthService defines interface for authentication operations
type AuthService interface {
Authenticate(ctx context.Context, username, password string) (*User, error)
GenerateJWT(ctx context.Context, user *User) (string, error)
ValidateJWT(ctx context.Context, token string) (*User, error)
AdminAuthenticate(ctx context.Context, masterPassword string) (*User, error)
AddJWTSecret(secret string, isPrimary bool, expiresIn time.Duration)
RotateJWTSecret(newSecret string)
GetJWTSecretByIndex(index int) (string, bool)
ResetJWTSecrets() // Reset JWT secrets to initial state for test cleanup
// StartJWTSecretCleanupLoop starts a goroutine that periodically calls
// RemoveExpiredJWTSecrets at the given interval, stopping when ctx is
// cancelled. Implements the cleanup half of ADR-0021. interval <= 0
// disables the loop.
StartJWTSecretCleanupLoop(ctx context.Context, interval time.Duration)
// RemoveExpiredJWTSecrets triggers an immediate cleanup pass and returns
// the count of removed non-primary expired secrets. Useful for tests
// driving cleanup synchronously.
RemoveExpiredJWTSecrets() int
// ListJWTSecretsInfo returns metadata about every currently-tracked JWT
// secret WITHOUT exposing the secret values. Used by the admin
// introspection endpoint and BDD tests verifying cleanup behavior.
// Order is preserved from internal storage (insertion order).
ListJWTSecretsInfo() []JWTSecretInfo
}
// JWTSecretInfo is a non-sensitive metadata view of a JWT secret.
// The secret VALUE is intentionally NOT included — exposing it via an
// API endpoint, even an admin one, would defeat the point of the
// retention/rotation infrastructure.
type JWTSecretInfo struct {
IsPrimary bool `json:"is_primary"`
CreatedAtUnix int64 `json:"created_at_unix"`
ExpiresAtUnix *int64 `json:"expires_at_unix,omitempty"`
AgeSeconds int64 `json:"age_seconds"`
IsExpired bool `json:"is_expired"`
SecretSHA256 string `json:"secret_sha256"` // first 16 hex chars of sha256 — fingerprint, not the secret
}
// UserManager defines interface for user management operations
type UserManager interface {
UserExists(ctx context.Context, username string) (bool, error)
CreateUser(ctx context.Context, user *User) error
}
// PasswordService defines interface for password operations
type PasswordService interface {
HashPassword(ctx context.Context, password string) (string, error)
RequestPasswordReset(ctx context.Context, username string) error
CompletePasswordReset(ctx context.Context, username, newPassword string) error
}
// UserService composes all user-related interfaces using Go's interface composition
// This is cleaner than aggregation and better for testing
type UserService interface {
AuthService
UserManager
PasswordService
}
// PasswordResetService defines the interface for password reset workflow
type PasswordResetService interface {
RequestPasswordReset(ctx context.Context, username string) error
CompletePasswordReset(ctx context.Context, username, newPassword string) error
}