Files
Gondulf/docs/decisions/ADR-010-domain-verification-vs-user-authentication.md
Phil Skentelbery 9135edfe84 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>
2025-11-22 15:16:26 -07:00

83 lines
3.2 KiB
Markdown

# 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