Verifier Dim B (homogeneity + code↔docs confrontation) flagged 5 ADRs stuck at "Partially Implemented" while the corresponding code is live. Audit + status update: - ADR-0009 (Hybrid testing) → Implemented; SDK gen explicitly out of scope - ADR-0013 (OpenAPI toolchain) → Implemented; SDK gen explicitly out of scope, cross-refs ADR-0009 - ADR-0018 (User auth) → Implemented; user model, JWT auth, password reset, admin endpoints, greet personalization, BDD coverage all live (verified in pkg/user/, pkg/auth/, features/auth/) - ADR-0019 (Postgres) → Implemented (core); per-item next-steps audit: CI integration ✅, performance tuning + monitoring tracked separately - ADR-0024 (BDD test org) → Implemented Phase 1+2+3; PR #35 closed Phase 3 parallel testing with 2.85x speedup, strategy in ADR-0025 No code changes — pure status reconciliation. The Status field is now the single source of truth for what's done vs deferred, removing the "forever Partial" doc drift the verifier flagged.
17 KiB
18. User Management and Authentication System
Date: 2026-04-06 Status: Implemented (user model, JWT auth, password-reset workflow, admin endpoints, greet personalization, BDD coverage all live; future enhancements like 2FA / email verification belong in separate ADRs) Authors: Product Owner Decision Drivers: Security, User Personalization, Admin Functionality
Context
The dance-lessons-coach application currently lacks user management and authentication capabilities. To provide personalized experiences and administrative functions, we need to implement a secure user authentication system with PostgreSQL persistence.
Decision
We will implement a user management and authentication system with the following characteristics:
Core Features
-
User Model
- Username-based identification (no email/personal info)
- Password authentication with secure hashing
- User profile fields: description and current goal
- Admin flag for privileged users
-
Authentication System
- JWT-based authentication
- Secure password hashing (bcrypt)
- Session management
- Admin master password for non-persisted admin access
-
Password Reset Workflow
- Admin-assisted password reset (no email/phone required)
- Allow password reset flag for users
- Unauthenticated password reset endpoint for flagged users
-
Integration Points
- Greet service personalization based on authenticated user
- Admin-only endpoints for user management
- BDD test coverage for all authentication flows
- CI/CD pipeline updates for new dependencies
Technical Implementation
Database Schema (PostgreSQL)
CREATE TABLE users (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE,
deleted_at TIMESTAMP WITH TIME ZONE,
username VARCHAR(50) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
description TEXT,
current_goal TEXT,
is_admin BOOLEAN DEFAULT FALSE,
allow_password_reset BOOLEAN DEFAULT FALSE,
last_login TIMESTAMP WITH TIME ZONE
);
Technology Stack
- ORM: GORM (for PostgreSQL integration) - aligns with existing interface-based design
- Authentication: JWT with HS256 signing - stateless, scalable
- Password Hashing: bcrypt - industry standard for password storage
- Validation: go-playground/validator - consistent with existing validation approach
- Database: PostgreSQL (containerized) - production-ready database
- Configuration: Viper integration - consistent with existing config management
- Logging: Zerolog integration - consistent with existing logging approach
- Telemetry: OpenTelemetry support - consistent with existing observability
Architecture Alignment
The user management system follows the established dance-lessons-coach patterns:
-
Interface-based Design:
type UserRepository interface { CreateUser(user *User) error GetUserByUsername(username string) (*User, error) // ... other methods } type AuthService interface { Authenticate(username, password string) (*User, error) GenerateJWT(user *User) (string, error) ValidateJWT(token string) (*User, error) } -
Context-aware Services:
func (s *AuthService) Authenticate(ctx context.Context, username, password string) (*User, error) { log.Trace().Ctx(ctx).Str("username", username).Msg("Authenticating user") // ... authentication logic } -
Configuration Integration:
type AuthConfig struct { JWTSecret string `mapstructure:"jwt_secret"` JWTExpiration time.Duration `mapstructure:"jwt_expiration"` AdminUsername string `mapstructure:"admin_username"` AdminMasterPassword string `mapstructure:"admin_master_password"` } -
Chi Router Integration:
func (h *AuthHandler) RegisterRoutes(router chi.Router) { router.Post("/register", h.handleRegister) router.Post("/login", h.handleLogin) // ... other routes }
Security Considerations
- Password Storage: bcrypt with work factor 12
- JWT Security:
- 30-minute expiration for access tokens
- Secure random signing key
- HTTPS-only cookies
- Secret Rotation: Multiple valid secrets with retention policy (see Issue #8)
- Admin Access:
- Master password from environment variable
- Non-persisted admin user
- Full access to user management endpoints
- Password Reset Control: Only admins can flag users for reset
- Password Reset Security:
- Admin-Only Flagging: Only authenticated admins can set
allow_password_resetflag - Flag-Based Access: Unauthenticated reset only works for admin-flagged users
- Automatic Flag Clearing: Flag is cleared after successful password reset
- No Self-Service: Users cannot flag themselves or others for reset
- Admin-Only Flagging: Only authenticated admins can set
- Rate Limiting: On authentication endpoints (3 attempts/hour for password reset)
- Input Validation: Strict username validation (alphanumeric, 3-50 chars)
API Endpoints
Public Endpoints
POST /api/v1/auth/register- User registrationPOST /api/v1/auth/login- User loginPOST /api/v1/auth/reset-password- Password reset (for flagged users)
Authenticated Endpoints
GET /api/v1/users/me- Get current user profilePUT /api/v1/users/me- Update current user profilePUT /api/v1/users/me/password- Change own password
Admin-Only Endpoints
GET /api/v1/admin/users- List all usersPOST /api/v1/admin/users/{username}/allow-reset- Allow password resetDELETE /api/v1/admin/users/{username}- Delete user
Integration with Existing Services
Greet Service Enhancement
The authentication system integrates seamlessly with the existing greet service by leveraging Go's context package:
// Updated greet service with authentication support
func (s *Service) Greet(ctx context.Context, name string) string {
log.Trace().Ctx(ctx).Str("name", name).Msg("Greet function called")
// Extract authenticated username from context using existing auth package
if username := auth.GetUsernameFromContext(ctx); username != "" {
log.Trace().Ctx(ctx).Str("authenticated_user", username).Msg("Using authenticated username")
return "Hello " + username + "!"
}
// Fallback to original behavior for backward compatibility
if name == "" {
return "Hello world!"
}
return "Hello " + name + "!"
}
Authentication Middleware
The system includes Chi middleware for JWT validation:
func (s *AuthService) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Extract and validate JWT token
token := auth.ExtractTokenFromRequest(r)
if token == "" {
next.ServeHTTP(w, r)
return // Continue without authentication
}
// Validate token and extract user
if user, err := s.ValidateJWT(ctx, token); err == nil {
// Add user to context for downstream services
ctx = auth.SetUserInContext(ctx, user)
r = r.WithContext(ctx)
}
next.ServeHTTP(w, r.WithContext(ctx))
})
}
Server Integration
The authentication middleware is added to the Chi router stack:
// In server.go
func (s *Server) getAllMiddlewares() []func(http.Handler) http.Handler {
middlewares := []func(http.Handler) http.Handler{
middleware.StripSlashes,
middleware.Recoverer,
s.authService.Middleware, // Add auth middleware
}
if s.withOTEL {
middlewares = append(middlewares, func(next http.Handler) http.Handler {
return otelhttp.NewHandler(next, "")
})
}
return middlewares
}
Configuration Extension
The existing Config struct is extended with authentication settings:
// In config.go
type AuthConfig struct {
JWTSecret string `mapstructure:"jwt_secret"`
JWTExpiration time.Duration `mapstructure:"jwt_expiration"`
AdminUsername string `mapstructure:"admin_username"`
AdminMasterPassword string `mapstructure:"admin_master_password"`
}
type DatabaseConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
Name string `mapstructure:"name"`
SSLMode string `mapstructure:"ssl_mode"`
}
// Extended Config struct
type Config struct {
Server ServerConfig `mapstructure:"server"`
Shutdown ShutdownConfig `mapstructure:"shutdown"`
Logging LoggingConfig `mapstructure:"logging"`
Telemetry TelemetryConfig `mapstructure:"telemetry"`
API APIConfig `mapstructure:"api"`
Auth AuthConfig `mapstructure:"auth"`
Database DatabaseConfig `mapstructure:"database"`
}
Consequences
Positive
- Personalized Experience: Users see their username in greetings
- Admin Capabilities: Secure administrative functions
- Password Recovery: Admin-assisted workflow without contact info
- Security: Proper authentication and authorization
- Extensibility: Foundation for future user-based features
Negative
- Increased Complexity: Additional dependencies and configuration
- Database Requirement: PostgreSQL container needed for development
- Migration Complexity: Existing tests need updates
- CI/CD Changes: Pipeline needs adjustment for new dependencies
- Performance Impact: Authentication middleware adds overhead
Neutral
- Learning Curve: Team needs to learn GORM and JWT
- Testing Overhead: Additional BDD scenarios required
- Documentation Needs: Comprehensive API documentation required
Alternatives Considered
Alternative 1: No Authentication
- Pros: Simpler, no database needed
- Cons: No personalization, no admin functions
- Rejected: Doesn't meet product requirements
Alternative 2: Session-based Auth
- Pros: Traditional approach, well-understood
- Cons: State management complexity, scaling issues
- Rejected: JWT provides better scalability
Alternative 3: OAuth/OIDC
- Pros: Industry standard, delegation to identity providers
- Cons: Complex setup, external dependencies
- Rejected: Overkill for current requirements
Alternative 4: Pure SQL (no ORM)
- Pros: No ORM overhead, direct control
- Cons: More boilerplate, manual query building
- Rejected: GORM provides good balance of control and convenience
Implementation Plan
This implementation builds upon the completed phases and follows the established dance-lessons-coach patterns.
Phase 10: User Management Foundation (Next Phase)
Objective: Establish database models and basic user management
-
Add Dependencies:
github.com/golang-jwt/jwt/v5for JWT authenticationgolang.org/x/cryptofor bcrypt password hashinggorm.io/gormandgorm.io/driver/postgresfor ORM
-
Create User Package:
pkg/user/models.go- User model and GORM repositorypkg/user/repository.go- Interface-based repositorypkg/user/service.go- User management service
-
Database Setup:
- Add PostgreSQL container to
docker-compose.yml - Create database migration scripts
- Implement GORM database connection
- Add PostgreSQL container to
-
Configuration Extension:
- Extend
Configstruct withAuthConfigandDatabaseConfig - Add environment variable bindings with
DLC_prefix - Update
LoadConfig()function
- Extend
Phase 11: Authentication System
Objective: Implement secure authentication with JWT
-
Auth Service:
pkg/auth/service.go- JWT generation/validationpkg/auth/middleware.go- Chi authentication middlewarepkg/auth/context.go- Context utilities for user data
-
Authentication Endpoints:
POST /api/v1/auth/register- User registrationPOST /api/v1/auth/login- User loginPOST /api/v1/auth/refresh- Token refresh
-
Password Security:
- Implement bcrypt password hashing (work factor 12)
- Add password validation rules
- Implement secure password comparison
-
Admin Authentication:
- Master password configuration
- Non-persisted admin user
- Admin middleware for privileged endpoints
Phase 12: Integration & Personalization
Objective: Integrate authentication with existing services
-
Greet Service Enhancement:
- Update
pkg/greet/greet.goto check authenticated username - Maintain backward compatibility
- Add context-based username extraction
- Update
-
User Profile Endpoints:
GET /api/v1/users/me- Get current user profilePUT /api/v1/users/me- Update profile (description, goal)PUT /api/v1/users/me/password- Change password
-
Admin Endpoints:
GET /api/v1/admin/users- List all usersPOST /api/v1/admin/users/{username}/allow-reset- Allow password resetDELETE /api/v1/admin/users/{username}- Delete user
Phase 13: Password Reset & Testing
Objective: Implement password reset workflow and comprehensive testing
-
Password Reset Workflow:
POST /api/v1/auth/reset-password- Unauthenticated reset for flagged users- Admin flag management
- Rate limiting for reset endpoints
-
BDD Test Scenarios:
- User registration and login
- Personalized greetings for authenticated users
- Admin password reset workflow
- Error handling and validation
-
Unit & Integration Tests:
- Password hashing/verification
- JWT token generation/validation
- Repository methods
- Authentication middleware
Phase 14: CI/CD & Documentation
Objective: Update pipeline and create comprehensive documentation
-
CI/CD Updates:
- Add PostgreSQL service to CI environment
- Update dependency management
- Add authentication test suite
- Implement security scanning
-
Documentation:
- Update
AGENTS.mdwith new architecture - Create user management wiki page
- Update API documentation with Swagger
- Add configuration examples
- Update
-
Deployment Preparation:
- Create database migration guide
- Update Docker configuration
- Add environment variable documentation
- Create rollback plan
Alignment with Existing Phases
This implementation builds upon the completed phases:
- Phase 1-3: Uses existing Go 1.26.1, Chi router, Zerolog, and interface-based design
- Phase 5: Extends Viper configuration management
- Phase 6: Integrates with graceful shutdown patterns
- Phase 7: Maintains OpenTelemetry compatibility
- Phase 8: Follows existing build system patterns
- Phase 9: Preserves trace-level logging approach
Backward Compatibility
The implementation maintains full backward compatibility:
- Greet Service: Falls back to original behavior when no authentication
- API Endpoints: Existing
/api/v1/greet/*endpoints unchanged - Configuration: All existing config options preserved
- Logging: Maintains existing Zerolog integration
- Telemetry: OpenTelemetry continues to work unchanged
Success Metrics
- Security: No authentication bypass vulnerabilities
- Performance: <50ms auth middleware overhead
- Reliability: 99.9% uptime for auth endpoints
- Adoption: 80% of API calls authenticated within 3 months
- Satisfaction: User feedback on personalization features
Open Questions
- Should we implement refresh tokens for longer sessions?
- What should be the maximum session duration?
- Should we add IP-based rate limiting for auth endpoints?
- What username characters should be allowed beyond alphanumeric?
- Should we implement account lockout after failed attempts?
Future Considerations
- Multi-factor Authentication: For enhanced security
- OAuth Integration: For third-party identity providers
- User Activity Logging: For audit trails
- Password Strength Meter: For better user experience
- Account Recovery: Email/phone-based recovery options
- JWT Secret Rotation: Implement secret persistence and rotation mechanism (Issue #8)
References
Approved by: [Product Owner] Approval Date: [To be determined] Implementation Target: Q2 2024