15 KiB
User Management and Authentication System
Overview
The DanceLessonsCoach user management and authentication system provides secure user authentication, personalized experiences, and administrative capabilities. This document describes the system architecture, API endpoints, and integration points.
Architecture
graph TD
A[Client] -->|HTTP Request| B[Authentication Middleware]
B -->|Valid Token| C[Authorized Endpoints]
B -->|Invalid Token| D[401 Unauthorized]
C --> E[Greet Service]
C --> F[User Profile Service]
C --> G[Admin Service]
E -->|Personalized Response| A
F -->|User Data| H[PostgreSQL]
G -->|Admin Operations| H
Core Components
1. User Model
Database Schema:
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
);
Fields:
username: Unique identifier (3-50 alphanumeric characters)password_hash: bcrypt-hashed passworddescription: User's personal descriptioncurrent_goal: User's current dance learning goalis_admin: Administrative privileges flagallow_password_reset: Flag for password reset eligibility
2. Authentication Service
Features:
- JWT token generation and validation
- bcrypt password hashing (work factor 12)
- 30-minute token expiration
- Secure cookie-based token storage
- Admin master password authentication
Environment Variables:
# JWT Configuration
DLC_JWT_SECRET="your-secure-random-secret-key"
DLC_JWT_EXPIRATION="30m"
# Admin Configuration
DLC_ADMIN_USERNAME="admin"
DLC_ADMIN_MASTER_PASSWORD="secure-master-password"
# Database Configuration
DLC_DB_HOST="localhost"
DLC_DB_PORT="5432"
DLC_DB_USER="dancecoach"
DLC_DB_PASSWORD="secure-password"
DLC_DB_NAME="dance_lessons_coach"
DLC_DB_SSL_MODE="disable"
API Endpoints
Authentication Endpoints
POST /api/v1/auth/register
Request:
{
"username": "john_doe",
"password": "securePassword123!"
}
Response (201 Created):
{
"id": 1,
"username": "john_doe",
"created_at": "2024-04-06T10:00:00Z",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Validation Rules:
username: Required, 3-50 chars, alphanumeric onlypassword: Required, min 8 chars
POST /api/v1/auth/login
Request:
{
"username": "john_doe",
"password": "securePassword123!"
}
Response (200 OK):
{
"id": 1,
"username": "john_doe",
"is_admin": false,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2024-04-06T10:30:00Z"
}
POST /api/v1/auth/reset-password
Request (for flagged users only):
{
"username": "john_doe",
"new_password": "newSecurePassword456!"
}
Response (200 OK):
{
"message": "Password reset successfully"
}
User Profile Endpoints
GET /api/v1/users/me
Headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Response (200 OK):
{
"id": 1,
"username": "john_doe",
"description": "Dance enthusiast learning salsa",
"current_goal": "Master basic salsa steps",
"created_at": "2024-04-06T10:00:00Z",
"last_login": "2024-04-06T10:15:00Z"
}
PUT /api/v1/users/me
Request:
{
"description": "Passionate dancer learning multiple styles",
"current_goal": "Prepare for salsa competition"
}
Response (200 OK):
{
"id": 1,
"username": "john_doe",
"description": "Passionate dancer learning multiple styles",
"current_goal": "Prepare for salsa competition",
"updated_at": "2024-04-06T10:30:00Z"
}
PUT /api/v1/users/me/password
Request:
{
"current_password": "securePassword123!",
"new_password": "evenMoreSecurePassword456!"
}
Response (200 OK):
{
"message": "Password updated successfully"
}
Admin Endpoints
GET /api/v1/admin/users
Headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Admin-Key: master-admin-key
Response (200 OK):
{
"users": [
{
"id": 1,
"username": "john_doe",
"is_admin": false,
"allow_password_reset": false,
"created_at": "2024-04-06T10:00:00Z"
},
{
"id": 2,
"username": "jane_smith",
"is_admin": true,
"allow_password_reset": true,
"created_at": "2024-04-05T15:30:00Z"
}
],
"total": 2
}
POST /api/v1/admin/users/{username}/allow-reset
Headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Admin-Key: master-admin-key
Response (200 OK):
{
"message": "Password reset allowed for user john_doe"
}
DELETE /api/v1/admin/users/{username}
Headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Admin-Key: master-admin-key
Response (200 OK):
{
"message": "User john_doe deleted successfully"
}
Integration with Greet Service
Current Behavior
GET /api/v1/greet/John
Response: {"message": "Hello John!"}
New Behavior with Authentication
GET /api/v1/greet
Headers: Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Response: {"message": "Hello john_doe!"}
Implementation:
func (s *Service) Greet(ctx context.Context, name string) string {
// Extract authenticated username from context
username := auth.GetUsernameFromContext(ctx)
if username != "" {
return "Hello " + username + "!"
}
// Fallback to original behavior
if name == "" {
return "Hello world!"
}
return "Hello " + name + "!"
}
Password Reset Workflow
sequenceDiagram
participant User
participant Admin
participant System
participant Database
User->>System: Forgot password (no auth)
System-->>User: 403 Forbidden
User->>Admin: Request password reset
Admin->>System: POST /api/v1/admin/users/john_doe/allow-reset
System->>Database: Set allow_password_reset = true
Database-->>System: Success
System-->>Admin: 200 OK
User->>System: POST /api/v1/auth/reset-password
System->>Database: Check allow_password_reset flag
Database-->>System: Flag is true
System->>Database: Update password_hash
Database-->>System: Success
System->>Database: Set allow_password_reset = false
System-->>User: 200 OK
Security Considerations
Password Storage
- Algorithm: bcrypt with work factor 12
- Implementation:
golang.org/x/crypto/bcrypt - Salt: Automatic per-password salt
JWT Security
- Algorithm: HS256 with secure random key
- Expiration: 30 minutes (configurable)
- Storage: HTTP-only, Secure cookies
- Claims: User ID, username, admin flag, expiration
Rate Limiting
- Authentication Endpoints: 5 requests per minute per IP
- Password Reset: 3 attempts per hour per user
- Implementation: Chi middleware
Input Validation
- Username: 3-50 alphanumeric characters
- Password: Minimum 8 characters
- Description/Goal: Maximum 500 characters
Error Handling
Standard Error Format
{
"error": "error_code",
"message": "Human-readable message",
"details": [
{
"field": "username",
"message": "Username must be at least 3 characters"
}
]
}
Common Error Codes
auth_invalid_credentials: Invalid username/passwordauth_token_expired: JWT token expiredauth_token_invalid: Invalid JWT tokenauth_unauthorized: Missing or invalid authorizationvalidation_failed: Input validation faileduser_not_found: User does not existuser_exists: Username already takenpassword_reset_not_allowed: User not flagged for resetadmin_required: Admin privileges required
Database Setup
Docker Compose
version: '3.8'
services:
postgres:
image: postgres:15-alpine
container_name: dance-lessons-coach-db
environment:
POSTGRES_USER: dancecoach
POSTGRES_PASSWORD: secure-password
POSTGRES_DB: dance_lessons_coach
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dancecoach"]
interval: 5s
timeout: 5s
retries: 5
volumes:
postgres_data:
Database Migration
# Initialize database
go run cmd/server/main.go migrate
# Run migrations
goose -dir migrations/postgres up
Testing Strategy
Unit Tests
- Password hashing/verification
- JWT token generation/validation
- User model validation
- Repository methods
Integration Tests
- Authentication flow
- Authorization middleware
- Database operations
- Password reset workflow
BDD Tests
Feature: User Authentication
Scenario: Successful user registration
Given I am not authenticated
When I register with valid credentials
Then I should receive a JWT token
And my user account should be created
Scenario: Successful login
Given I have a registered account
When I login with correct credentials
Then I should receive a JWT token
And the token should expire in 30 minutes
Scenario: Personalized greeting for authenticated user
Given I am authenticated as "john_doe"
When I request the default greeting
Then the response should be "{"message":"Hello john_doe!"}"
CI/CD Integration
New Dependencies
# Add to go.mod
require (
github.com/golang-jwt/jwt/v5 v5.0.0
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
gorm.io/gorm v1.25.0
gorm.io/driver/postgres v1.5.0
)
Pipeline Changes
- Add PostgreSQL service to CI environment
- Run database migrations before tests
- Include authentication tests in test suite
- Add security scanning for dependencies
Deployment Considerations
Configuration
# config.yaml
database:
host: localhost
port: 5432
user: dancecoach
password: secure-password
name: dance_lessons_coach
ssl_mode: disable
auth:
jwt_secret: your-secure-random-secret-key
jwt_expiration: 30m
admin_username: admin
admin_master_password: secure-master-password
Environment Variables
# Database
export DLC_DB_HOST="localhost"
export DLC_DB_PORT="5432"
export DLC_DB_USER="dancecoach"
export DLC_DB_PASSWORD="secure-password"
export DLC_DB_NAME="dance_lessons_coach"
export DLC_DB_SSL_MODE="disable"
# Authentication
export DLC_JWT_SECRET="your-secure-random-secret-key"
export DLC_JWT_EXPIRATION="30m"
export DLC_ADMIN_USERNAME="admin"
export DLC_ADMIN_MASTER_PASSWORD="secure-master-password"
Monitoring and Observability
Metrics
auth_login_success_total: Successful loginsauth_login_failure_total: Failed login attemptsauth_register_total: User registrationsauth_token_issued_total: JWT tokens issueduser_active_total: Active users
Logging
- Authentication attempts (success/failure)
- Password changes
- Admin operations
- Rate limiting events
Alerts
- Multiple failed login attempts
- Admin account modifications
- Unusual password reset activity
Future Enhancements
Short-term (Next 3 Months)
- Refresh Tokens: Long-lived refresh tokens with rotation
- Rate Limiting: IP-based rate limiting for auth endpoints
- Password Strength: Enforce stronger password requirements
- Account Lockout: Temporary lockout after failed attempts
Medium-term (Next 6 Months)
- Multi-factor Authentication: TOTP or backup codes
- User Activity Logging: Comprehensive audit trails
- Session Management: View and revoke active sessions
- Password Expiration: Enforce periodic password changes
Long-term (Future)
- OAuth Integration: Google, Facebook, Apple sign-in
- Social Features: User profiles, followers, messaging
- Role-Based Access: Fine-grained permissions
- User Preferences: Theme, language, notifications
Troubleshooting
Common Issues
Issue: Authentication fails with valid credentials
- Check: Password hash comparison logic
- Check: JWT secret key configuration
- Check: Database connection
Issue: Password reset not working
- Check: User has
allow_password_resetflag set - Check: Admin has set the flag correctly
- Check: Rate limiting not blocking requests
Issue: Personalized greeting not showing username
- Check: Authentication middleware is properly configured
- Check: JWT token is valid and not expired
- Check: Context contains username after authentication
Migration Guide
From No Authentication to User System
-
Add Dependencies:
go get github.com/golang-jwt/jwt/v5 go get golang.org/x/crypto go get gorm.io/gorm go get gorm.io/driver/postgres -
Update Configuration:
- Add database configuration
- Add JWT configuration
- Add admin configuration
-
Update Server:
- Add authentication middleware
- Add user repository initialization
- Add auth routes
-
Update Greet Service:
- Modify to check for authenticated username
- Maintain backward compatibility
-
Update Tests:
- Add authentication scenarios
- Update existing tests for new behavior
- Add BDD tests for user management
-
Update CI/CD:
- Add PostgreSQL to test environment
- Update test scripts
- Add security scanning
References
- GORM Documentation
- JWT RFC 7519
- bcrypt Documentation
- OWASP Authentication Cheat Sheet
- Chi Router Middleware
Appendix
Username Validation Regex
var usernameRegex = regexp.MustCompile(`^[a-zA-Z0-9]{3,50}$`)
Password Hashing Example
import "golang.org/x/crypto/bcrypt"
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 12)
return string(bytes), err
}
func CheckPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
JWT Token Example
import (
"github.com/golang-jwt/jwt/v5"
"time"
)
type Claims struct {
UserID uint `json:"user_id"`
Username string `json:"username"`
IsAdmin bool `json:"is_admin"`
jwt.RegisteredClaims
}
func GenerateJWT(user *User, secret string, expiration time.Duration) (string, error) {
claims := &Claims{
UserID: user.ID,
Username: user.Username,
IsAdmin: user.IsAdmin,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expiration)),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(secret))
}