# ADR-010: Authentication Module Design

## Status
Accepted

## Context

With the core utilities and notes management complete, StarPunk needs an authentication system for the admin interface. ADR-005 already decided on using IndieLogin.com as the authentication provider. This ADR defines the specific module architecture and implementation approach for the authentication system.

The authentication module must:
1. Integrate with IndieLogin.com for identity verification
2. Manage secure sessions for authenticated users
3. Protect admin routes from unauthorized access
4. Handle CSRF protection for the authentication flow
5. Support single-user authorization (V1 requirement)

## Decision

### Module Architecture

**Single Module Approach**: Implement all authentication logic in a single `starpunk/auth.py` module.

**Rationale**:
- Authentication is ~200-300 lines of focused code
- Single responsibility: manage authentication and sessions
- No need to split into multiple files for V1
- Easier to understand and maintain

### Session Storage Strategy

**Database-Backed Sessions** with these characteristics:
- Session tokens stored in SQLite (not Redis/Memcached)
- Cryptographically secure token generation (32 bytes)
- 30-day expiry with activity-based refresh
- Automatic cleanup of expired sessions

**Schema**:
```sql
CREATE TABLE sessions (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    session_token TEXT UNIQUE NOT NULL,
    me TEXT NOT NULL,
    created_at TIMESTAMP NOT NULL,
    expires_at TIMESTAMP NOT NULL,
    last_used_at TIMESTAMP,
    user_agent TEXT,
    ip_address TEXT
);

CREATE TABLE auth_state (
    state TEXT PRIMARY KEY,
    created_at TIMESTAMP NOT NULL,
    expires_at TIMESTAMP NOT NULL,
    redirect_uri TEXT
);
```

### Security Architecture

**Token Security**:
- Use `secrets.token_urlsafe(32)` for session tokens
- Store token hash in database (SHA-256), not plaintext
- HttpOnly, Secure, SameSite=Lax cookies
- No JavaScript access to session tokens

**CSRF Protection**:
- Generate state token for each auth request
- Store in `auth_state` table with 5-minute expiry
- Verify state on callback before code exchange
- Single-use tokens (delete after verification)

**Authorization Model**:
- Single admin user (configured via ADMIN_ME environment variable)
- Strict equality check (no substring matching)
- Reject any user whose `me` URL doesn't exactly match ADMIN_ME
- Log all authentication attempts for security audit

### Function Architecture

**Core Functions** (6 total):
1. `initiate_login(me_url: str) -> str` - Start IndieLogin flow
2. `handle_callback(code: str, state: str) -> Optional[str]` - Process callback
3. `create_session(me: str) -> str` - Create new session
4. `verify_session(token: str) -> Optional[Dict]` - Validate session
5. `destroy_session(token: str) -> None` - Logout
6. `require_auth(f)` - Decorator for protected routes

**Helper Functions** (4 total):
1. `_generate_state_token() -> str` - CSRF token generation
2. `_verify_state_token(state: str) -> bool` - CSRF validation
3. `_cleanup_expired_sessions() -> None` - Maintenance
4. `_hash_token(token: str) -> str` - Security hashing

### Error Handling Strategy

**Custom Exceptions**:
```python
class AuthError(Exception): pass
class InvalidStateError(AuthError): pass
class UnauthorizedError(AuthError): pass
class IndieLoginError(AuthError): pass
```

**Error Responses**:
- Invalid state: 400 Bad Request with clear error
- Unauthorized user: 403 Forbidden
- IndieLogin failure: 502 Bad Gateway
- Session expired: Redirect to login

### Integration Points

**Flask Integration**:
- Use Flask's `g` object for request-scoped user data
- Integrate with Flask's session for flash messages
- Use Flask's `before_request` for session refresh
- Leverage Flask's error handlers for auth errors

**Database Integration**:
- Use existing `get_db()` connection management
- Transactions for session creation/deletion
- Prepared statements to prevent SQL injection

## Rationale

### Why Database Sessions Over Flask-Session?

**Database sessions chosen**:
- Already have SQLite, no new dependencies
- Persistent across server restarts
- Can query active sessions
- Supports session invalidation
- Better security (server-side storage)

**Flask-Session rejected**:
- Adds Redis/Memcached dependency
- Overkill for single-user system
- More complex deployment

### Why Token Hashing?

Even though we're single-user, proper security practices:
- Defense in depth principle
- Prevents token leakage if database exposed
- Industry best practice
- No performance impact for our scale

### Why 30-Day Sessions?

Balance between security and usability:
- Long enough to avoid frequent re-authentication
- Short enough to limit exposure window
- Activity-based refresh keeps active sessions alive
- Matches common web application patterns

### Why Single Module?

**Simplicity principle**:
- Authentication is cohesive functionality
- ~300 lines doesn't justify splitting
- Easier to audit security in one file
- Reduces import complexity

## Consequences

### Positive
1. **Simple implementation** - Single file, clear responsibilities
2. **Secure by default** - Industry best practices applied
3. **Zero dependencies** - Uses existing stack (SQLite, httpx)
4. **Maintainable** - All auth logic in one place
5. **Testable** - Clear function boundaries, mockable
6. **Production-ready** - Proper session management, security
7. **IndieWeb compliant** - Full IndieAuth specification support

### Negative
1. **Manual session cleanup** - Need periodic expired session removal
2. **No rate limiting** - Could add in V2 if needed
3. **Single admin limitation** - Architectural constraint for V1
4. **No 2FA** - Relies entirely on IndieLogin's security

### Mitigations

**For session cleanup**:
- Run cleanup on each login attempt
- Add admin command for manual cleanup
- Document in operations guide

**For rate limiting**:
- Deploy behind reverse proxy (nginx/Caddy)
- Add to V2 if abuse detected
- Log attempts for monitoring

## Implementation Checklist

- [ ] Create `starpunk/auth.py` module
- [ ] Add session tables to database schema
- [ ] Implement core authentication functions
- [ ] Add custom exception classes
- [ ] Create require_auth decorator
- [ ] Write comprehensive tests (target: 90% coverage)
- [ ] Add security logging
- [ ] Document configuration requirements
- [ ] Create integration tests with IndieLogin
- [ ] Security audit checklist

## References

- [ADR-005: IndieLogin Authentication](/home/phil/Projects/starpunk/docs/decisions/ADR-005-indielogin-authentication.md)
- [IndieAuth Specification](https://www.w3.org/TR/indieauth/)
- [OWASP Session Management](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)
- [Flask Security Best Practices](https://flask.palletsprojects.com/en/3.0.x/security/)

---

**ADR**: 010
**Date**: 2025-11-18
**Status**: Accepted
**Decision**: Single-module database-backed session authentication with IndieLogin
**Supersedes**: None