fix(auth): require email authentication every login
CRITICAL SECURITY FIX: - Email code required EVERY login (authentication, not verification) - DNS TXT check cached separately (domain verification) - New auth_sessions table for per-login state - Codes hashed with SHA-256, constant-time comparison - Max 3 attempts, 10-minute session expiry - OAuth params stored server-side (security improvement) New files: - services/auth_session.py - migrations 004, 005 - ADR-010: domain verification vs user authentication 312 tests passing, 86.21% coverage 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
# ADR-010: Domain Verification vs User Authentication Separation
|
||||
|
||||
Date: 2025-01-22
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
The initial implementation conflated two fundamentally different security concepts:
|
||||
|
||||
1. **Domain Verification**: Proving that a domain has been configured to use this IndieAuth server
|
||||
2. **User Authentication**: Proving that the current user has the right to authenticate as the claimed identity
|
||||
|
||||
This conflation resulted in the email verification code (intended for user authentication) being cached after first use, effectively bypassing authentication for all subsequent users of the same domain.
|
||||
|
||||
This is a critical security vulnerability.
|
||||
|
||||
## Decision
|
||||
|
||||
We will strictly separate these two concepts:
|
||||
|
||||
### Domain Verification (One-time, Cached)
|
||||
|
||||
**Purpose**: Establish that a domain owner has configured their domain to use Gondulf as their IndieAuth server.
|
||||
|
||||
**Method**: DNS TXT record at `_indieauth.{domain}` containing a server-specific verification string.
|
||||
|
||||
**Storage**: Persistent in `domains` table with verification timestamp.
|
||||
|
||||
**Frequency**: Checked once, then cached. Re-validated periodically (every 24 hours) to detect configuration changes.
|
||||
|
||||
**Security Model**: This is a configuration check, not authentication. It answers: "Is this domain set up to use Gondulf?"
|
||||
|
||||
### User Authentication (Per-Login, Never Cached)
|
||||
|
||||
**Purpose**: Prove that the person attempting to log in has access to the identity they claim.
|
||||
|
||||
**Method**: 6-digit code sent to the rel="me" email discovered from the user's homepage.
|
||||
|
||||
**Storage**: Temporary in `auth_sessions` table, expires after 5-10 minutes.
|
||||
|
||||
**Frequency**: Required for EVERY authorization attempt, never cached.
|
||||
|
||||
**Security Model**: This is actual authentication. It answers: "Is this person who they claim to be right now?"
|
||||
|
||||
### Data Flow
|
||||
|
||||
1. Authorization request received
|
||||
2. Domain verification check (cached, one-time per domain)
|
||||
3. Profile discovery (fetch rel="me" email)
|
||||
4. User authentication (email code, every login)
|
||||
5. Consent
|
||||
6. Authorization code issued
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- **Security**: Users are actually authenticated on every login
|
||||
- **Correctness**: Matches the purpose of IndieAUTH - to authenticate users
|
||||
- **Multi-user**: Multiple people can manage the same domain independently
|
||||
- **Isolation**: One user's authentication does not affect another's
|
||||
|
||||
### Negative
|
||||
|
||||
- **User Experience**: Users must check email on every login (this is correct behavior, not a bug)
|
||||
- **Migration**: Existing implementation needs significant refactoring
|
||||
- **Complexity**: Two separate systems to maintain (verification and authentication)
|
||||
|
||||
### Technical Debt Resolved
|
||||
|
||||
This ADR addresses a fundamental architectural error. The email verification system was incorrectly designed as part of domain setup rather than per-login authentication.
|
||||
|
||||
## Notes
|
||||
|
||||
The name "IndieAuth" contains "Auth" which means Authentication. The core purpose is to authenticate users, not just verify domain configurations. This distinction is fundamental and non-negotiable.
|
||||
|
||||
Any future features that seem like they could be "cached once" must be carefully evaluated:
|
||||
- Domain configuration (DNS, endpoints) = can be cached
|
||||
- User authentication state = NEVER cached
|
||||
Reference in New Issue
Block a user