Add implementation workflow documentation

2026-04-06 20:44:55 +02:00
parent b67eda6d22
commit 41d69df422

@@ -0,0 +1,648 @@
# 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
```mermaid
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:**
```sql
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 password
- `description`: User's personal description
- `current_goal`: User's current dance learning goal
- `is_admin`: Administrative privileges flag
- `allow_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:**
```bash
# 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:**
```json
{
"username": "john_doe",
"password": "securePassword123!"
}
```
**Response (201 Created):**
```json
{
"id": 1,
"username": "john_doe",
"created_at": "2024-04-06T10:00:00Z",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```
**Validation Rules:**
- `username`: Required, 3-50 chars, alphanumeric only
- `password`: Required, min 8 chars
#### POST `/api/v1/auth/login`
**Request:**
```json
{
"username": "john_doe",
"password": "securePassword123!"
}
```
**Response (200 OK):**
```json
{
"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):**
```json
{
"username": "john_doe",
"new_password": "newSecurePassword456!"
}
```
**Response (200 OK):**
```json
{
"message": "Password reset successfully"
}
```
### User Profile Endpoints
#### GET `/api/v1/users/me`
**Headers:**
```
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```
**Response (200 OK):**
```json
{
"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:**
```json
{
"description": "Passionate dancer learning multiple styles",
"current_goal": "Prepare for salsa competition"
}
```
**Response (200 OK):**
```json
{
"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:**
```json
{
"current_password": "securePassword123!",
"new_password": "evenMoreSecurePassword456!"
}
```
**Response (200 OK):**
```json
{
"message": "Password updated successfully"
}
```
### Admin Endpoints
#### GET `/api/v1/admin/users`
**Headers:**
```
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Admin-Key: master-admin-key
```
**Response (200 OK):**
```json
{
"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):**
```json
{
"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):**
```json
{
"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:**
```go
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
```mermaid
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
```json
{
"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/password
- `auth_token_expired`: JWT token expired
- `auth_token_invalid`: Invalid JWT token
- `auth_unauthorized`: Missing or invalid authorization
- `validation_failed`: Input validation failed
- `user_not_found`: User does not exist
- `user_exists`: Username already taken
- `password_reset_not_allowed`: User not flagged for reset
- `admin_required`: Admin privileges required
## Database Setup
### Docker Compose
```yaml
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
```bash
# 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
```gherkin
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
```bash
# 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
1. Add PostgreSQL service to CI environment
2. Run database migrations before tests
3. Include authentication tests in test suite
4. Add security scanning for dependencies
## Deployment Considerations
### Configuration
```yaml
# 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
```bash
# 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 logins
- `auth_login_failure_total`: Failed login attempts
- `auth_register_total`: User registrations
- `auth_token_issued_total`: JWT tokens issued
- `user_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)
1. **Refresh Tokens:** Long-lived refresh tokens with rotation
2. **Rate Limiting:** IP-based rate limiting for auth endpoints
3. **Password Strength:** Enforce stronger password requirements
4. **Account Lockout:** Temporary lockout after failed attempts
### Medium-term (Next 6 Months)
1. **Multi-factor Authentication:** TOTP or backup codes
2. **User Activity Logging:** Comprehensive audit trails
3. **Session Management:** View and revoke active sessions
4. **Password Expiration:** Enforce periodic password changes
### Long-term (Future)
1. **OAuth Integration:** Google, Facebook, Apple sign-in
2. **Social Features:** User profiles, followers, messaging
3. **Role-Based Access:** Fine-grained permissions
4. **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_reset` flag 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
1. **Add Dependencies:**
```bash
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
```
2. **Update Configuration:**
- Add database configuration
- Add JWT configuration
- Add admin configuration
3. **Update Server:**
- Add authentication middleware
- Add user repository initialization
- Add auth routes
4. **Update Greet Service:**
- Modify to check for authenticated username
- Maintain backward compatibility
5. **Update Tests:**
- Add authentication scenarios
- Update existing tests for new behavior
- Add BDD tests for user management
6. **Update CI/CD:**
- Add PostgreSQL to test environment
- Update test scripts
- Add security scanning
## References
- [GORM Documentation](https://gorm.io/)
- [JWT RFC 7519](https://tools.ietf.org/html/rfc7519)
- [bcrypt Documentation](https://pkg.go.dev/golang.org/x/crypto/bcrypt)
- [OWASP Authentication Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html)
- [Chi Router Middleware](https://github.com/go-chi/chi)
## Appendix
### Username Validation Regex
```go
var usernameRegex = regexp.MustCompile(`^[a-zA-Z0-9]{3,50}$`)
```
### Password Hashing Example
```go
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
```go
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))
}
```