✨ feat(user): magic_link_tokens table + repository (ADR-0028 Phase A.3)
Adds the persistence layer for the passwordless-auth flow. The token VALUE is never stored — only its sha256 hex digest, mirroring ADR-0021 secret retention via fingerprint. Plaintext is generated server-side, emailed once, and rehashed on consume. Repository methods : Create / GetByHash / MarkConsumed / DeleteExpired. AutoMigrate wired in both PostgresRepository init paths (DSN-built + cfg-built). Tests : - 5 unit tests : token generation shape, URL-safety, uniqueness, hash stability - 5 integration tests (build tag `integration`) : end-to-end against real Postgres, cover the happy path, missing-hash, consume idempotency, expired-cleanup, and the unique-index defensive check
This commit is contained in:
@@ -160,7 +160,7 @@ func NewPostgresRepositoryFromDSN(cfg *config.Config, dsn string) (*PostgresRepo
|
||||
sqlDB.SetMaxIdleConns(cfg.GetDatabaseMaxIdleConns())
|
||||
sqlDB.SetConnMaxLifetime(cfg.GetDatabaseConnMaxLifetime())
|
||||
|
||||
if err := db.AutoMigrate(&User{}); err != nil {
|
||||
if err := db.AutoMigrate(&User{}, &MagicLinkToken{}); err != nil {
|
||||
return nil, fmt.Errorf("failed to auto-migrate via custom DSN: %w", err)
|
||||
}
|
||||
|
||||
@@ -264,8 +264,8 @@ func (r *PostgresRepository) initializeDatabase() error {
|
||||
sqlDB.SetMaxIdleConns(r.config.GetDatabaseMaxIdleConns())
|
||||
sqlDB.SetConnMaxLifetime(r.config.GetDatabaseConnMaxLifetime())
|
||||
|
||||
// Auto-migrate the User model
|
||||
if err := r.db.AutoMigrate(&User{}); err != nil {
|
||||
// Auto-migrate the User model + MagicLinkToken (ADR-0028 Phase A)
|
||||
if err := r.db.AutoMigrate(&User{}, &MagicLinkToken{}); err != nil {
|
||||
return fmt.Errorf("failed to auto-migrate: %w", err)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user