Invalid redirect_uri. Cannot redirect safely.
``` **Rate Limiting**: None at endpoint level (handled by verification service) **Authentication**: None initially (domain verification IS the authentication) --- ### POST /authorize/verify-code **Purpose**: Verify code during authorization flow. **Form Data**: - `email` (required): Email address from rel="me" - `code` (required): 6-digit verification code - `me` (required): User's profile URL - `client_id` (required): Client application URL - `redirect_uri` (required): Redirect URI - `state` (required): State parameter **Success Response**: HTML page (consent screen) **Error Response**: HTML page (code entry form with error message) --- ### POST /authorize/consent **Purpose**: Handle user consent decision. **Form Data**: - `action` (required): "approve" or "deny" - `me` (required): User's profile URL - `client_id` (required): Client application URL - `redirect_uri` (required): Redirect URI - `state` (required): State parameter **Success Response (Approve)** (302 Found): ``` {redirect_uri}?code={authorization_code}&state={state} ``` **Success Response (Deny)** (302 Found): ``` {redirect_uri}?error=access_denied&error_description=User+denied+authorization&state={state} ``` ## Data Models ### Verified Domain (Database Table) **Table**: `domains` **Schema** (from Phase 1): ```sql CREATE TABLE domains ( domain TEXT PRIMARY KEY, verification_method TEXT NOT NULL, -- 'two_factor' for v1.0.0 verified BOOLEAN NOT NULL DEFAULT FALSE, verified_at TIMESTAMP, last_dns_check TIMESTAMP, last_email_check TIMESTAMP ); ``` **Updated in Phase 2**: Change `verification_method` values from `'email'` / `'txt_record'` to `'two_factor'`. **Migration**: `002_update_verification_method.sql`: ```sql -- Update verification_method values to reflect two-factor requirement UPDATE domains SET verification_method = 'two_factor' WHERE verification_method IN ('email', 'txt_record'); ``` **Indexes** (from Phase 1): ```sql CREATE INDEX idx_domains_domain ON domains(domain); CREATE INDEX idx_domains_verified ON domains(verified); ``` --- ### Authorization Code (In-Memory) **Storage**: Phase 1 CodeStorage with metadata **Structure**: ```python { "code": "abc123...", # 43-char base64url (32 bytes) "me": "https://example.com", "client_id": "https://client.example.com", "redirect_uri": "https://client.example.com/callback", "state": "client-provided-state", "created_at": datetime, "expires_at": datetime, # created_at + 10 minutes "used": False # For Phase 3 token endpoint } ``` **TTL**: 10 minutes (per W3C spec: "shortly after") **Storage Location**: Phase 1 CodeStorage service --- ### Verification Code Metadata (In-Memory) **Storage**: Additional metadata alongside verification codes **Structure**: ```python { "email": "user@example.com", "domain": "example.com", "attempts": 0, # Increment on each failed attempt "created_at": datetime } ``` **Purpose**: Track attempts and associate email with domain for rate limiting. **TTL**: Same as verification code (15 minutes) ## Security Requirements ### Input Validation **Domain Parameter**: ```python def validate_domain(domain: str) -> Tuple[bool, Optional[str], Optional[str]]: """ Validate domain parameter. Returns: (is_valid, normalized_domain, error_message) """ # Remove protocol if present if domain.startswith('http://') or domain.startswith('https://'): domain = domain.split('://', 1)[1] # Remove path if present if '/' in domain: domain = domain.split('/')[0] # Lowercase domain = domain.lower().strip() # Must contain at least one dot if '.' not in domain: return False, None, "Domain must contain at least one dot (e.g., example.com)" # Must not be empty if not domain: return False, None, "Domain cannot be empty" # Must not contain invalid characters if any(c in domain for c in [' ', '@', ':', '?', '#']): return False, None, "Domain contains invalid characters" # Length check if len(domain) > 253: return False, None, "Domain too long (max 253 characters)" return True, domain, None ``` **Email Parameter**: ```python def validate_email(email: str) -> bool: """ Validate email format (RFC 5322 simplified). Used by rel="me" discovery service. """ email_regex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' if not re.match(email_regex, email): return False if len(email) > 254: # RFC 5321 maximum return False if email.count('@') != 1: return False local, domain = email.split('@') if '.' not in domain: return False return True ``` **URL Parameters** (me, client_id, redirect_uri): ```python def validate_url(url: str, param_name: str) -> Tuple[bool, Optional[str]]: """ Validate URL parameter. Returns: (is_valid, error_message) """ from urllib.parse import urlparse try: parsed = urlparse(url) except Exception: return False, f"{param_name} must be a valid URL" # Must have scheme and netloc if not parsed.scheme or not parsed.netloc: return False, f"{param_name} must be a complete URL (e.g., https://example.com)" # Must be http or https if parsed.scheme not in ['http', 'https']: return False, f"{param_name} must use http or https" # No fragments for 'me' parameter if param_name == "me" and parsed.fragment: return False, "me parameter must not contain fragment" # No credentials if parsed.username or parsed.password: return False, f"{param_name} must not contain credentials" return True, None ``` --- ### HTTPS Enforcement **Configuration**: ```python # In production config if not DEBUG: # Enforce HTTPS app.add_middleware(HTTPSRedirectMiddleware) # Reject HTTP redirect_uri (except localhost) if redirect_uri.startswith('http://'): parsed = urlparse(redirect_uri) if parsed.hostname not in ['localhost', '127.0.0.1']: return error_response("redirect_uri must use HTTPS in production") ``` **HTML Fetching**: - HTTPS only (hardcoded `https://` in URL) - SSL certificate verification enforced (`verify=True`, no option to disable) - Reject sites with invalid certificates --- ### HTML Parsing Security **BeautifulSoup Configuration**: ```python # Use html.parser (Python standard library, safe for untrusted HTML) soup = BeautifulSoup(html_content, 'html.parser') ``` **Why html.parser**: - Part of Python standard library (no external C dependencies) - Designed for untrusted HTML - No script execution - No external resource loading - Handles malformed HTML gracefully **Size Limits**: - Maximum response size: 5MB (configurable) - Checked both in Content-Length header and actual content **Timeout**: - HTTP request timeout: 10 seconds (configurable) - Prevents hanging on slow sites --- ### Protection Against Open Redirects **redirect_uri Validation**: ```python def validate_redirect_uri(redirect_uri: str, client_id: str) -> Tuple[bool, Optional[str]]: """ Validate redirect_uri against client_id. Returns: (is_valid, warning_message) """ from urllib.parse import urlparse redirect_parsed = urlparse(redirect_uri) client_parsed = urlparse(client_id) # Must be HTTPS (except localhost) if redirect_parsed.scheme != 'https': if redirect_parsed.hostname not in ['localhost', '127.0.0.1']: return False, "redirect_uri must use HTTPS" # Must have valid hostname if not redirect_parsed.hostname: return False, "redirect_uri must have valid hostname" redirect_domain = redirect_parsed.hostname.lower() client_domain = client_parsed.hostname.lower() # Exact match: OK if redirect_domain == client_domain: return True, None # Subdomain of client: OK if redirect_domain.endswith('.' + client_domain): return True, None # Different domain: WARNING (display to user, but allow) warning = ( f"Warning: Redirect to different domain ({redirect_domain}) " f"than client ({client_domain}). Ensure you trust this application." ) return True, warning ``` **Display Warning to User**: - If redirect_uri domain differs from client_id domain, show warning on consent screen - User must explicitly approve redirect to different domain - Prevents phishing via redirect URI manipulation --- ### CSRF Protection **State Parameter**: - Required in authorization request - Stored with authorization code - Passed through verification and consent steps - Returned unchanged in redirect - Client validates state matches original (client responsibility per OAuth 2.0) **Gondulf does NOT validate state** - This is intentional per OAuth 2.0: - State is opaque to authorization server - Client generates state, client validates state - Gondulf only passes it through unchanged --- ### Code Replay Prevention **Authorization Code**: - Single-use enforcement (Phase 3 token endpoint marks as used) - 10-minute expiration - Bound to client_id, redirect_uri, me - Stored in memory (Phase 1 CodeStorage) **Verification Code**: - Single-use: Deleted after successful verification - 15-minute expiration - Max 3 attempts before invalidation - Constant-time comparison (prevent timing attacks) ## Testing Requirements ### Unit Tests **HTML Fetcher Service** (9 tests): - ✅ Successful HTTPS fetch returns content - ✅ SSL verification failure returns None - ✅ Timeout returns None - ✅ HTTP error codes (404, 500) return None - ✅ Redirects followed (up to max) - ✅ Too many redirects returns None - ✅ Content-Length exceeds limit returns None - ✅ Actual content exceeds limit returns None - ✅ Custom User-Agent sent **rel="me" Discovery Service** (12 tests): - ✅ Discovery from `` tag - ✅ Discovery from `` tag - ✅ Multiple rel="me" links: first mailto selected - ✅ Malformed HTML handled - ✅ Missing rel="me" returns None - ✅ Invalid email in link returns None - ✅ Empty href returns None - ✅ Non-mailto links ignored - ✅ mailto with query params strips params - ✅ Email validation: valid formats - ✅ Email validation: invalid formats - ✅ Exception during parsing returns None **Domain Verification Service** (15 tests): - ✅ Full flow: DNS → rel="me" → email → code - ✅ DNS failure blocks flow - ✅ Site fetch failure blocks flow - ✅ rel="me" failure blocks flow - ✅ Email send failure cleans up code - ✅ Code verification success stores domain - ✅ Code verification failure decrements attempts - ✅ Too many attempts invalidates code - ✅ Invalid code returns error - ✅ Code expiration handled - ✅ Rate limiting works - ✅ Already verified domain check - ✅ Email masking correct - ✅ Constant-time comparison used - ✅ Metadata tracking works **Estimated Unit Test Count**: ~36 tests --- ### Integration Tests **Verification Endpoints** (10 tests): - ✅ POST /api/verify/start success case - ✅ POST /api/verify/start with invalid domain - ✅ POST /api/verify/start with DNS failure - ✅ POST /api/verify/start with rel="me" failure - ✅ POST /api/verify/start with email send failure - ✅ POST /api/verify/code success case - ✅ POST /api/verify/code with invalid code - ✅ POST /api/verify/code with expired code - ✅ POST /api/verify/code with missing code - ✅ POST /api/verify/code with too many attempts **Authorization Endpoint** (15 tests): - ✅ GET /authorize with valid params (already verified domain) - ✅ GET /authorize with valid params (new domain) - ✅ GET /authorize with invalid response_type - ✅ GET /authorize with invalid me parameter - ✅ GET /authorize with invalid client_id - ✅ GET /authorize with invalid redirect_uri - ✅ GET /authorize with missing state - ✅ POST /authorize/verify-code with valid code - ✅ POST /authorize/verify-code with invalid code - ✅ POST /authorize/consent with action=approve - ✅ POST /authorize/consent with action=deny - ✅ Authorization code stored with metadata - ✅ Authorization code expires after 10 min - ✅ State parameter passed through - ✅ redirect_uri domain mismatch shows warning **Estimated Integration Test Count**: ~25 tests --- ### End-to-End Tests **Complete Flows** (5 tests): - ✅ Full auth flow: /authorize → verify → consent → redirect with code - ✅ Full auth flow with cached domain (skip verification) - ✅ User denies consent → redirect with access_denied - ✅ DNS verification failure → error page → retry → success - ✅ Invalid code × 3 → error "too many attempts" **Estimated E2E Test Count**: ~5 tests --- ### Security Tests **Input Validation** (8 tests): - ✅ Malformed domain rejected - ✅ Malformed email rejected (during validation) - ✅ Malformed URL (me, client_id, redirect_uri) rejected - ✅ URL with credentials rejected - ✅ URL with fragment rejected (me parameter) - ✅ Oversized HTML (>5MB) rejected - ✅ Invalid email in rel="me" logged and skipped - ✅ SQL injection attempts in domain parameter (should be parameterized) **Authentication Security** (5 tests): - ✅ Expired code rejected - ✅ Used code rejected (Phase 3) - ✅ Invalid code rejected - ✅ Brute force prevented (max 3 attempts) - ✅ Constant-time comparison used (verify via timing analysis - difficult to test) **TLS/HTTPS** (4 tests): - ✅ HTTP redirect_uri rejected in production - ✅ Invalid SSL certificate rejected - ✅ Site fetch over HTTPS only - ✅ HTTP allowed for localhost only **Open Redirect** (3 tests): - ✅ redirect_uri domain mismatch shows warning - ✅ Invalid redirect_uri shows error page (no redirect) - ✅ redirect_uri without hostname rejected **Estimated Security Test Count**: ~20 tests --- ### Coverage Target **Phase 2 Overall**: 80%+ coverage (same as Phase 1) **Critical Code** (95%+ coverage): - Domain verification service (orchestration logic) - rel="me" discovery (email extraction) - Authorization endpoint (parameter validation) - Security functions (validation, constant-time comparison) **Total Estimated Test Count**: ~86 tests ## Error Handling ### DNS Verification Failure **Error Message**: ``` DNS Verification Failed The DNS TXT record was not found for your domain. Please add the following TXT record to your DNS: Type: TXT Name: _gondulf.example.com Value: verified DNS changes may take up to 24 hours to propagate. [Retry] ``` **HTTP Response**: 200 OK (HTML error page) **Logging**: WARNING level with domain --- ### rel="me" Discovery Failure **Error Message**: ``` Email Discovery Failed No rel="me" email link was found on your homepage. Please add the following to https://example.com: This allows us to discover your email address automatically. Learn more: https://indieweb.org/rel-me [Retry] ``` **HTTP Response**: 200 OK (HTML error page) **Logging**: WARNING level with domain --- ### Site Unreachable **Error Message**: ``` Site Fetch Failed Could not fetch your site at https://example.com Please check: • Site is accessible via HTTPS • SSL certificate is valid • No firewall blocking requests [Retry] ``` **HTTP Response**: 200 OK (HTML error page) **Logging**: ERROR level with domain and error details --- ### Email Send Failure **Error Message**: ``` Email Delivery Failed Failed to send verification code to u***@example.com Please check: • Email address is correct in your rel="me" link • Email server is accepting mail • Check spam/junk folder [Retry] ``` **HTTP Response**: 200 OK (HTML error page) **Logging**: ERROR level with masked email --- ### Invalid Code **Error Message**: ``` Invalid code. 2 attempts remaining. ``` **HTTP Response**: 200 OK (code entry form with error) **Logging**: WARNING level with masked email --- ### Too Many Attempts **Error Message**: ``` Too Many Attempts You have exceeded the maximum number of attempts. Please request a new verification code. [Request New Code] ``` **HTTP Response**: 200 OK (error page with retry link) **Logging**: WARNING level with masked email --- ### Rate Limit Exceeded **Error Message**: ``` Rate Limit Exceeded Too many verification requests for this domain. Please wait 1 hour before requesting another code. ``` **HTTP Response**: 200 OK (error page) **Logging**: WARNING level with domain --- ### OAuth 2.0 Errors (Authorization Endpoint) **Error Redirect Format**: ``` {redirect_uri}?error={error_code}&error_description={description}&state={state} ``` **Error Codes**: - `invalid_request`: Missing or invalid parameter - `unauthorized_client`: Client not authorized - `access_denied`: User denied authorization - `unsupported_response_type`: response_type not "code" - `server_error`: Internal server error **Example**: ``` https://client.example.com/callback?error=invalid_request&error_description=Missing+state+parameter&state=abc123 ``` **Logging**: WARNING or ERROR level depending on error type --- ### Error Logging Standards **Log Levels**: - **DEBUG**: Normal operations, detailed flow - **INFO**: Successful operations (code sent, domain verified) - **WARNING**: Expected errors (invalid code, DNS not found) - **ERROR**: Unexpected errors (SMTP failure, site unreachable) - **CRITICAL**: System failures (should not occur in Phase 2) **What to Log**: - ✅ Domain (public information) - ✅ Email (partial mask: first 3 chars) - ✅ Error details (for debugging) - ✅ Request IDs (for correlation) **What NOT to Log**: - ❌ Full email addresses - ❌ Verification codes - ❌ Authorization codes - ❌ User-Agent (GDPR) - ❌ IP addresses (GDPR) ## Dependencies ### New Python Packages **Add to pyproject.toml**: ```toml [project] dependencies = [ # ... existing dependencies from Phase 1 "beautifulsoup4>=4.12.0", # HTML parsing for rel="me" discovery ] ``` **Why beautifulsoup4**: - Robust HTML parsing (handles malformed HTML) - Safe for untrusted content (no script execution) - Standard in Python ecosystem - Pure Python (no C dependencies with html.parser) ### Phase 1 Dependencies Used - `requests` (HTTP fetching - already in pyproject.toml) - `dnspython` (DNS queries - Phase 1) - `smtplib` (Email sending - Python stdlib, used by Phase 1) - `sqlalchemy` (Database - Phase 1) - `fastapi` (Web framework - Phase 1) - `pydantic` (Data validation - Phase 1) ### Configuration Additions **Optional new environment variables**: ```bash # HTML Fetching (optional - has defaults) GONDULF_HTML_FETCH_TIMEOUT=10 # seconds GONDULF_HTML_MAX_SIZE=5242880 # bytes (5MB) GONDULF_HTML_MAX_REDIRECTS=5 # Rate Limiting (optional - has defaults) GONDULF_VERIFICATION_RATE_LIMIT=3 # codes per domain per hour ``` **Add to .env.example**: ```bash # HTML Fetching Configuration (optional) GONDULF_HTML_FETCH_TIMEOUT=10 GONDULF_HTML_MAX_SIZE=5242880 GONDULF_HTML_MAX_REDIRECTS=5 # Rate Limiting (optional) GONDULF_VERIFICATION_RATE_LIMIT=3 ``` ## Implementation Notes ### Suggested Implementation Order 1. **HTML Fetcher Service** (0.5 days) - Straightforward HTTP fetching - Few dependencies - Easy to test in isolation 2. **rel="me" Discovery Service** (0.5 days) - Pure parsing logic - No external dependencies (besides HTML input) - Easy to test with mock HTML 3. **Domain Verification Service** (1 day) - Orchestrates all services - More complex logic - Needs all previous services complete 4. **Database Migration** (0.5 days) - Simple UPDATE query - Apply before verification endpoints 5. **Verification Endpoints** (0.5 days) - Thin API layer over service - FastAPI makes this straightforward 6. **Authorization Endpoint** (3-4 days) - Most complex component - HTML templates needed - Multiple sub-endpoints - Needs comprehensive testing 7. **Integration Testing** (1 day) - Test all components together - End-to-end flow verification **Total**: ~7-8 days (matches estimate in phase-1-impact-assessment.md) --- ### Risks and Mitigations **Risk 1: HTML Parsing Edge Cases** - **Mitigation**: BeautifulSoup handles malformed HTML gracefully - **Testing**: Include malformed HTML in test cases - **Fallback**: Clear error messages guide users to fix HTML **Risk 2: Email Delivery Failures** - **Mitigation**: Comprehensive SMTP error handling - **Testing**: Mock SMTP failures in tests - **Fallback**: Clear troubleshooting instructions in error messages **Risk 3: DNS TXT Record Setup Complexity** - **Mitigation**: Clear setup instructions with examples - **User Education**: Document common DNS providers - **Support**: Provide example DNS configurations **Risk 4: Authorization Endpoint Complexity** - **Mitigation**: Break into smaller sub-endpoints (verify-code, consent) - **Testing**: Comprehensive integration tests - **Design**: Keep state management simple (use forms, avoid complex sessions) **Risk 5: Rate Limiting Implementation** - **Mitigation**: Start with simple in-memory tracking (Phase 2) - **Future**: Migrate to Redis for distributed rate limiting (Phase 3+) - **Placeholder**: Implement rate limit check, return False for now --- ### Performance Considerations **HTML Fetching**: - Timeout: 10 seconds (prevent hanging) - Size limit: 5MB (prevent memory exhaustion) - Concurrent requests: Not needed in Phase 2 (one request per auth flow) **Database Queries**: - Index on domains.domain ensures fast lookups - Simple SELECT queries (no joins in Phase 2) - Consider adding index on domains.verified if needed **In-Memory Storage**: - Verification codes: ~100 bytes each - Authorization codes: ~200 bytes each - Expected load: 10s of users, <100 concurrent verifications - Memory impact: Negligible (<10KB) **rel="me" Parsing**: - BeautifulSoup is pure Python (not fastest, but sufficient) - HTML size limited to 5MB (parse time <1 second) - No performance issues expected for typical homepages --- ### Future Extensibility **Redis Integration** (Phase 3+): - Replace in-memory CodeStorage with Redis - Enables distributed deployment (multiple Gondulf instances) - No code changes needed (CodeStorage interface unchanged) **Client Metadata Caching** (Phase 3): - Cache client_id fetch results - Reduces HTTP requests during authorization - Store in database or Redis **PKCE Support** (v1.1.0): - Add code_challenge validation in authorization endpoint - Add code_verifier validation in token endpoint (Phase 3) - No breaking changes to v1.0.0 clients **Additional Authentication Methods** (v1.2.0+): - GitHub/GitLab OAuth providers - WebAuthn support - All additive (user chooses method) ## Acceptance Criteria Phase 2 is complete when ALL of the following criteria are met: ### Functionality - [ ] HTML fetcher service fetches user homepages successfully - [ ] rel="me" discovery service discovers email from HTML - [ ] Domain verification service orchestrates two-factor verification - [ ] DNS TXT verification required and working - [ ] Email verification via rel="me" required and working - [ ] Verification endpoints (/api/verify/start, /api/verify/code) working - [ ] Authorization endpoint (/authorize) validates all parameters - [ ] Authorization endpoint checks domain verification status - [ ] Authorization endpoint shows verification form for unverified domains - [ ] Authorization endpoint shows consent screen after verification - [ ] Authorization code generated and stored on approval - [ ] User can deny consent (redirects with access_denied) - [ ] State parameter passed through all steps ### Testing - [ ] All unit tests passing (estimated ~36 tests) - [ ] All integration tests passing (estimated ~25 tests) - [ ] All end-to-end tests passing (estimated ~5 tests) - [ ] All security tests passing (estimated ~20 tests) - [ ] Test coverage ≥80% overall - [ ] Test coverage ≥95% for domain verification service - [ ] Test coverage ≥95% for authorization endpoint - [ ] No known bugs or failing tests ### Security - [ ] HTTPS enforcement working (production) - [ ] SSL certificate validation enforced (HTML fetching) - [ ] HTML parsing secure (BeautifulSoup with html.parser) - [ ] Input validation comprehensive (domain, email, URLs) - [ ] Open redirect protection working (redirect_uri validation) - [ ] Constant-time code comparison used - [ ] Rate limiting implemented (basic in-memory) - [ ] Attempt limiting working (max 3 per code) - [ ] No PII in logs (email masked, no full addresses) - [ ] Authorization codes single-use (marked for Phase 3) ### Error Handling - [ ] DNS verification failure shows clear instructions - [ ] rel="me" discovery failure shows HTML example - [ ] Site unreachable shows troubleshooting steps - [ ] Email send failure shows error with retry - [ ] Invalid code shows attempts remaining - [ ] Too many attempts invalidates code - [ ] Rate limit exceeded shows wait time - [ ] OAuth 2.0 errors formatted correctly - [ ] All errors logged appropriately ### Documentation - [ ] All new services have docstrings - [ ] All public methods have type hints - [ ] API endpoints documented (this design doc) - [ ] Error messages user-friendly - [ ] Setup instructions clear (DNS + rel="me") - [ ] Database migration documented ### Dependencies - [ ] beautifulsoup4 added to pyproject.toml - [ ] No new system dependencies (all Python) - [ ] Configuration updated (.env.example) ### Database - [ ] Migration 002 applied successfully - [ ] domains.verification_method updated to 'two_factor' - [ ] No schema changes needed (existing schema works) ### Integration - [ ] All Phase 1 services integrated successfully - [ ] DNS service used for TXT verification - [ ] Email service used for code sending - [ ] Database service used for storing verified domains - [ ] In-memory storage used for codes - [ ] Logging used throughout ### Performance - [ ] HTML fetching completes within 10 seconds - [ ] rel="me" parsing completes within 1 second - [ ] Full verification flow completes within 30 seconds - [ ] Authorization endpoint responds within 2 seconds - [ ] No memory leaks (codes expire and clean up) ## Timeline Estimate **Phase 2 Implementation**: 7-9 days **Breakdown**: - HTML Fetcher Service: 0.5 days - rel="me" Discovery Service: 0.5 days - Domain Verification Service: 1 day - Database Migration: 0.5 days - Verification Endpoints: 0.5 days - Authorization Endpoint: 3-4 days - Integration Testing: 1 day - Documentation: 0.5 days (included in parallel) **Dependencies**: Phase 1 complete and approved **Risk Buffer**: +2 days (for unforeseen issues with HTML parsing or authorization flow complexity) ## Sign-off **Design Status**: Complete and ready for implementation **Architect**: Claude (Architect Agent) **Date**: 2025-11-20 **Next Steps**: 1. Developer reviews design document 2. Developer asks clarification questions if needed 3. Architect updates design based on feedback 4. Developer begins implementation following design 5. Developer creates implementation report upon completion 6. Architect reviews implementation report **Related Documents**: - `/docs/architecture/overview.md` - System architecture - `/docs/architecture/indieauth-protocol.md` - IndieAuth protocol implementation - `/docs/architecture/security.md` - Security architecture - `/docs/architecture/phase-1-impact-assessment.md` - Phase 2 requirements - `/docs/decisions/ADR-005-email-based-authentication-v1-0-0.md` - Two-factor verification decision - `/docs/decisions/ADR-008-rel-me-email-discovery.md` - rel="me" pattern decision - `/docs/reports/2025-11-20-phase-1-foundation.md` - Phase 1 implementation - `/docs/roadmap/v1.0.0.md` - Version plan --- **DESIGN READY: Phase 2 Domain Verification - Please review /docs/designs/phase-2-domain-verification.md**