Add comprehensive Phase 2 documentation: - Complete design document for two-factor domain verification - Implementation guide with code examples - ADR for implementation decisions (ADR-0004) - ADR for rel="me" email discovery (ADR-008) - Phase 1 impact assessment - All 23 clarification questions answered - Updated architecture docs (indieauth-protocol, security) - Updated ADR-005 with rel="me" approach - Updated backlog with technical debt items Design ready for Phase 2 implementation. Generated with Claude Code https://claude.com/claude-code Co-Authored-By: Claude <noreply@anthropic.com>
701 lines
22 KiB
Markdown
701 lines
22 KiB
Markdown
# ADR-005: Two-Factor Domain Verification for v1.0.0 (DNS + Email via rel="me")
|
|
|
|
Date: 2025-11-20
|
|
Last Updated: 2025-11-20
|
|
|
|
## Status
|
|
Accepted (Updated)
|
|
|
|
## Context
|
|
|
|
Gondulf requires users to prove domain ownership to authenticate. Multiple authentication methods exist for proving domain control.
|
|
|
|
### Authentication Methods Evaluated
|
|
|
|
**1. Email Verification**
|
|
- User provides email at their domain
|
|
- Server sends verification code to email
|
|
- User enters code to prove email access
|
|
- Assumes: Email access = domain control
|
|
|
|
**2. DNS TXT Record**
|
|
- Admin adds TXT record to DNS: `_gondulf.example.com` = `verified`
|
|
- Server queries DNS to verify record
|
|
- Assumes: DNS control = domain control
|
|
|
|
**3. External Identity Providers** (GitHub, GitLab, etc.)
|
|
- User links domain to GitHub/GitLab profile
|
|
- Server verifies profile contains domain
|
|
- User authenticates via OAuth to provider
|
|
- Assumes: Provider verification = domain control
|
|
|
|
**4. WebAuthn / FIDO2**
|
|
- User registers hardware security key
|
|
- Authentication via cryptographic challenge
|
|
- Assumes: Physical key possession = domain control (after initial registration)
|
|
|
|
**5. IndieAuth Delegation**
|
|
- User's domain delegates to another IndieAuth server
|
|
- Server follows delegation chain
|
|
- Assumes: Delegated server = domain control
|
|
|
|
### User Requirements
|
|
|
|
From project brief:
|
|
- **v1.0.0**: Email-based ONLY (no other identity providers)
|
|
- **Simplicity**: Keep MVP simple and focused
|
|
- **Scale**: 10s of users initially
|
|
- **No client registration**: Simplify client onboarding
|
|
|
|
### Technical Constraints
|
|
|
|
**SMTP Dependency**:
|
|
- Requires email server configuration
|
|
- Potential delivery failures (spam filters, configuration errors)
|
|
- Dependency on external service (email provider)
|
|
|
|
**Security Considerations**:
|
|
- Email interception risk (transit security)
|
|
- Email account compromise risk (user responsibility)
|
|
- Code brute-force risk (limited entropy)
|
|
|
|
**User Experience**:
|
|
- Familiar pattern (like password reset)
|
|
- Requires email access during authentication
|
|
- Additional step vs. provider OAuth (GitHub, etc.)
|
|
|
|
## Decision
|
|
|
|
**Gondulf v1.0.0 will require BOTH DNS TXT record verification AND email verification using the IndieWeb rel="me" pattern. Both verifications must succeed for authentication to complete.**
|
|
|
|
### Implementation Approach
|
|
|
|
**Two-Factor Verification (Both Required)**:
|
|
|
|
1. **DNS TXT Record Verification (Required)**:
|
|
- Check for `_gondulf.{domain}` TXT record = `verified`
|
|
- If found: Proceed to email verification
|
|
- If not found: Authentication fails with instructions to add TXT record
|
|
- Proves: User controls DNS for the domain
|
|
|
|
2. **Email Discovery via rel="me" (Required)**:
|
|
- Fetch user's domain homepage (e.g., https://example.com)
|
|
- Parse HTML for `<link rel="me" href="mailto:user@example.com">`
|
|
- Extract email address from rel="me" link
|
|
- If not found: Authentication fails with instructions to add rel="me" link
|
|
- Proves: User has published email relationship on their site
|
|
|
|
3. **Email Verification Code (Required)**:
|
|
- Server generates 6-digit verification code
|
|
- Server sends code to discovered email address via SMTP
|
|
- User enters code (15-minute expiration)
|
|
- Verification code must be correct to complete authentication
|
|
- Proves: User controls the email account
|
|
|
|
**Why All Three?**:
|
|
- **DNS TXT**: Proves domain DNS control (strong ownership signal)
|
|
- **rel="me"**: Follows IndieWeb standard for identity claims
|
|
- **Email Code**: Proves active control of the email account (not just DNS/HTML)
|
|
- **Combined**: Two-factor verification provides stronger security than either alone
|
|
|
|
### Rationale
|
|
|
|
**Enhanced Security Model**:
|
|
- Two-factor verification: DNS control + Email control
|
|
- Prevents attacks where only one factor is compromised
|
|
- DNS TXT proves domain ownership
|
|
- Email code proves active account control
|
|
- rel="me" follows IndieWeb standards for identity
|
|
|
|
**Follows IndieWeb Standards**:
|
|
- rel="me" is standard practice for identity claims (see: https://thesatelliteoflove.com)
|
|
- Aligns with IndieAuth ecosystem expectations
|
|
- Users likely already have rel="me" links for other purposes
|
|
- Email discovery is self-documenting (user's site declares their email)
|
|
|
|
**No User-Provided Email Input**:
|
|
- Server discovers email from user's site (no manual entry)
|
|
- Prevents typos and social engineering
|
|
- Email is self-attested by user on their own domain
|
|
- Reduces attack surface (can't claim arbitrary email)
|
|
|
|
**Stronger Than Single-Factor**:
|
|
- Attacker needs DNS control AND email access
|
|
- Compromised DNS alone: insufficient
|
|
- Compromised email alone: insufficient
|
|
- Requires control of both infrastructure and communication
|
|
|
|
**Simplicity Maintained**:
|
|
- Two verification checks, but both straightforward
|
|
- DNS TXT: standard practice
|
|
- rel="me": standard HTML link
|
|
- Email code: familiar pattern
|
|
- Total setup time: < 5 minutes for technical users
|
|
|
|
## Consequences
|
|
|
|
### Positive Consequences
|
|
|
|
1. **Enhanced Security**:
|
|
- Two-factor verification (DNS + Email)
|
|
- Stronger ownership proof than single factor
|
|
- Prevents single-point-of-compromise attacks
|
|
- Aligns with security best practices
|
|
|
|
2. **IndieWeb Standard Compliance**:
|
|
- Follows rel="me" pattern from IndieWeb community
|
|
- Interoperability with other IndieWeb tools
|
|
- Users may already have rel="me" configured
|
|
- Self-documenting identity claims
|
|
|
|
3. **Reduced Attack Surface**:
|
|
- No user-provided email input (prevents typos/social engineering)
|
|
- Email discovered from user's own site
|
|
- Can't claim arbitrary email addresses
|
|
- User controls all verification requirements
|
|
|
|
4. **Implementation Simplicity**:
|
|
- HTML parsing for rel="me" (standard libraries)
|
|
- DNS queries (dnspython)
|
|
- SMTP email sending (smtplib)
|
|
- No external API dependencies
|
|
|
|
5. **Privacy**:
|
|
- Email addresses NOT stored after verification
|
|
- No data shared with third parties
|
|
- No tracking by external providers
|
|
- Minimal data collection
|
|
|
|
6. **Transparency**:
|
|
- User explicitly declares email on their site
|
|
- No hidden verification methods
|
|
- User controls both DNS and HTML
|
|
- Clear requirements for setup
|
|
|
|
### Negative Consequences
|
|
|
|
1. **Higher Setup Complexity**:
|
|
- Users must configure TWO things (DNS TXT + rel="me" link)
|
|
- More steps than single-factor approaches
|
|
- Requires basic HTML editing skills
|
|
- May deter non-technical users
|
|
|
|
2. **Email Dependency**:
|
|
- Requires functioning SMTP configuration
|
|
- Email delivery not guaranteed (spam filters)
|
|
- Users must have email access during authentication
|
|
- Email account compromise still a risk (mitigated by DNS requirement)
|
|
|
|
3. **User Experience**:
|
|
- More setup steps vs. simpler alternatives
|
|
- Requires checking email inbox during login
|
|
- Potential delay (email delivery time)
|
|
- Code expiration can frustrate users
|
|
- Both verifications must succeed (no fallback)
|
|
|
|
4. **HTML Parsing Complexity**:
|
|
- Must parse potentially malformed HTML
|
|
- Multiple possible HTML formats for rel="me"
|
|
- Case sensitivity issues
|
|
- Must handle various link formats (mailto: vs https://)
|
|
|
|
5. **Failure Points**:
|
|
- DNS lookup failure blocks authentication
|
|
- Site unavailable blocks authentication
|
|
- Email send failure blocks authentication
|
|
- No fallback mechanism (both required)
|
|
|
|
### Mitigation Strategies
|
|
|
|
**Clear Setup Instructions**:
|
|
```markdown
|
|
## Domain Verification Setup
|
|
|
|
Gondulf requires two verifications to prove domain ownership:
|
|
|
|
### Step 1: Add DNS TXT Record
|
|
Add this DNS record to your domain:
|
|
- Type: TXT
|
|
- Name: _gondulf.example.com
|
|
- Value: verified
|
|
|
|
This proves you control DNS for your domain.
|
|
|
|
### Step 2: Add rel="me" Link to Your Homepage
|
|
Add this HTML to your homepage (e.g., https://example.com/index.html):
|
|
<link rel="me" href="mailto:your-email@example.com">
|
|
|
|
This declares your email address publicly on your site.
|
|
|
|
### Step 3: Verify Email Access
|
|
During login:
|
|
- We'll discover your email from the rel="me" link
|
|
- We'll send a verification code to that email
|
|
- Enter the code to complete authentication
|
|
|
|
Setup time: ~5 minutes
|
|
```
|
|
|
|
**Robust HTML Parsing**:
|
|
```python
|
|
from bs4 import BeautifulSoup
|
|
from urllib.parse import urlparse
|
|
|
|
def discover_email_from_site(domain_url: str) -> Optional[str]:
|
|
"""
|
|
Fetch site and discover email from rel="me" link.
|
|
|
|
Returns: email address or None if not found
|
|
"""
|
|
try:
|
|
# Fetch homepage
|
|
response = requests.get(domain_url, timeout=10, allow_redirects=True)
|
|
response.raise_for_status()
|
|
|
|
# Parse HTML (handle malformed HTML gracefully)
|
|
soup = BeautifulSoup(response.content, 'html.parser')
|
|
|
|
# Find all rel="me" links
|
|
me_links = soup.find_all('link', rel='me') + soup.find_all('a', rel='me')
|
|
|
|
# Look for mailto: links
|
|
for link in me_links:
|
|
href = link.get('href', '')
|
|
if href.startswith('mailto:'):
|
|
email = href.replace('mailto:', '').strip()
|
|
# Validate email format
|
|
if validate_email_format(email):
|
|
logger.info(f"Discovered email via rel='me' for {domain_url}")
|
|
return email
|
|
|
|
logger.warning(f"No rel='me' mailto: link found for {domain_url}")
|
|
return None
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to discover email for {domain_url}: {e}")
|
|
return None
|
|
```
|
|
|
|
**DNS Verification**:
|
|
```python
|
|
def verify_dns_txt(domain: str) -> bool:
|
|
"""
|
|
Verify _gondulf.{domain} TXT record exists.
|
|
|
|
Returns: True if verified, False otherwise
|
|
"""
|
|
try:
|
|
import dns.resolver
|
|
|
|
# Query multiple resolvers for redundancy
|
|
resolvers = ['8.8.8.8', '1.1.1.1']
|
|
verified_count = 0
|
|
|
|
for resolver_ip in resolvers:
|
|
resolver = dns.resolver.Resolver()
|
|
resolver.nameservers = [resolver_ip]
|
|
resolver.timeout = 5
|
|
|
|
answers = resolver.resolve(f'_gondulf.{domain}', 'TXT')
|
|
for rdata in answers:
|
|
if rdata.to_text().strip('"') == 'verified':
|
|
verified_count += 1
|
|
break
|
|
|
|
# Require consensus from multiple resolvers
|
|
return verified_count >= 2
|
|
|
|
except Exception as e:
|
|
logger.warning(f"DNS verification failed for {domain}: {e}")
|
|
return False
|
|
```
|
|
|
|
**Helpful Error Messages**:
|
|
```python
|
|
# DNS TXT not found
|
|
if not dns_verified:
|
|
return ErrorResponse("""
|
|
DNS verification failed.
|
|
|
|
Please add this TXT record to your domain:
|
|
- Type: TXT
|
|
- Name: _gondulf.{domain}
|
|
- Value: verified
|
|
|
|
DNS changes may take up to 24 hours to propagate.
|
|
""")
|
|
|
|
# rel="me" not found
|
|
if not email_discovered:
|
|
return ErrorResponse("""
|
|
Could not find rel="me" link on your site.
|
|
|
|
Please add this to your homepage:
|
|
<link rel="me" href="mailto:your-email@example.com">
|
|
|
|
See: https://indieweb.org/rel-me for more information.
|
|
""")
|
|
|
|
# Email send failure
|
|
if not email_sent:
|
|
return ErrorResponse("""
|
|
Failed to send verification code to {email}.
|
|
|
|
Please check:
|
|
- Email address is correct in your rel="me" link
|
|
- Email server is accepting mail
|
|
- Check spam/junk folder
|
|
""")
|
|
```
|
|
|
|
**Code Security** (unchanged):
|
|
```python
|
|
# Sufficient entropy
|
|
code = ''.join(secrets.choice('0123456789') for _ in range(6))
|
|
# 1,000,000 possible codes
|
|
|
|
# Rate limiting
|
|
MAX_ATTEMPTS = 3 # Per email
|
|
MAX_CODES = 3 # Per hour per domain
|
|
|
|
# Expiration
|
|
CODE_LIFETIME = timedelta(minutes=15)
|
|
|
|
# Single-use enforcement
|
|
code_storage.mark_used(code_id)
|
|
```
|
|
|
|
## Implementation
|
|
|
|
### Complete Authentication Flow (v1.0.0)
|
|
|
|
```python
|
|
from datetime import datetime, timedelta
|
|
import secrets
|
|
import smtplib
|
|
import requests
|
|
import dns.resolver
|
|
from email.message import EmailMessage
|
|
from bs4 import BeautifulSoup
|
|
from typing import Optional, Tuple
|
|
|
|
class DomainVerificationService:
|
|
"""
|
|
Two-factor domain verification: DNS TXT + Email via rel="me"
|
|
"""
|
|
def __init__(self, smtp_config: dict):
|
|
self.smtp = smtp_config
|
|
self.codes = {} # In-memory storage for verification codes
|
|
|
|
def verify_domain_ownership(self, domain: str) -> Tuple[bool, Optional[str], Optional[str]]:
|
|
"""
|
|
Perform two-factor domain verification.
|
|
|
|
Returns: (success, email_discovered, error_message)
|
|
|
|
Steps:
|
|
1. Verify DNS TXT record
|
|
2. Discover email from rel="me" link
|
|
3. Send verification code to email
|
|
4. User enters code (handled separately)
|
|
"""
|
|
# Step 1: Verify DNS TXT record
|
|
dns_verified = self._verify_dns_txt(domain)
|
|
if not dns_verified:
|
|
return False, None, "DNS TXT record not found. Please add _gondulf.{domain} = verified"
|
|
|
|
# Step 2: Discover email from site's rel="me" link
|
|
email = self._discover_email_from_site(f"https://{domain}")
|
|
if not email:
|
|
return False, None, 'No rel="me" mailto: link found on homepage. Please add <link rel="me" href="mailto:you@example.com">'
|
|
|
|
# Step 3: Generate and send verification code
|
|
code_sent = self._send_verification_code(email, domain)
|
|
if not code_sent:
|
|
return False, email, f"Failed to send verification code to {email}"
|
|
|
|
# Return success with discovered email
|
|
return True, email, None
|
|
|
|
def verify_code(self, email: str, submitted_code: str) -> Tuple[bool, str]:
|
|
"""
|
|
Verify submitted code.
|
|
|
|
Returns: (success, domain or error_message)
|
|
"""
|
|
code_data = self.codes.get(email)
|
|
|
|
if not code_data:
|
|
return False, "No verification code found. Please request a new code."
|
|
|
|
# Check expiration
|
|
if datetime.utcnow() > code_data['expires_at']:
|
|
del self.codes[email]
|
|
return False, "Code expired. Please request a new code."
|
|
|
|
# Check attempts
|
|
code_data['attempts'] += 1
|
|
if code_data['attempts'] > 3:
|
|
del self.codes[email]
|
|
return False, "Too many attempts. Please restart authentication."
|
|
|
|
# Verify code (constant-time comparison)
|
|
if not secrets.compare_digest(submitted_code, code_data['code']):
|
|
return False, "Invalid code. Please try again."
|
|
|
|
# Success: Clean up and return domain
|
|
domain = code_data['domain']
|
|
del self.codes[email] # Single-use code
|
|
|
|
logger.info(f"Domain verified: {domain} (DNS + Email)")
|
|
return True, domain
|
|
|
|
def _verify_dns_txt(self, domain: str) -> bool:
|
|
"""
|
|
Verify _gondulf.{domain} TXT record exists with value 'verified'.
|
|
|
|
Returns: True if verified, False otherwise
|
|
"""
|
|
record_name = f'_gondulf.{domain}'
|
|
|
|
# Use multiple resolvers for redundancy
|
|
resolvers = ['8.8.8.8', '1.1.1.1']
|
|
verified_count = 0
|
|
|
|
for resolver_ip in resolvers:
|
|
try:
|
|
resolver = dns.resolver.Resolver()
|
|
resolver.nameservers = [resolver_ip]
|
|
resolver.timeout = 5
|
|
|
|
answers = resolver.resolve(record_name, 'TXT')
|
|
|
|
for rdata in answers:
|
|
if rdata.to_text().strip('"') == 'verified':
|
|
verified_count += 1
|
|
break
|
|
|
|
except Exception as e:
|
|
logger.debug(f"DNS query failed (resolver {resolver_ip}): {e}")
|
|
continue
|
|
|
|
# Require consensus from at least 2 resolvers
|
|
if verified_count >= 2:
|
|
logger.info(f"DNS TXT verified: {domain}")
|
|
return True
|
|
|
|
logger.warning(f"DNS TXT verification failed: {domain}")
|
|
return False
|
|
|
|
def _discover_email_from_site(self, domain_url: str) -> Optional[str]:
|
|
"""
|
|
Fetch domain homepage and discover email from rel="me" link.
|
|
|
|
Returns: email address or None if not found
|
|
"""
|
|
try:
|
|
# Fetch homepage
|
|
response = requests.get(domain_url, timeout=10, allow_redirects=True)
|
|
response.raise_for_status()
|
|
|
|
# Parse HTML (BeautifulSoup handles malformed HTML)
|
|
soup = BeautifulSoup(response.content, 'html.parser')
|
|
|
|
# Find all rel="me" links (both <link> and <a>)
|
|
me_links = soup.find_all('link', rel='me') + soup.find_all('a', rel='me')
|
|
|
|
# Look for mailto: links
|
|
for link in me_links:
|
|
href = link.get('href', '')
|
|
if href.startswith('mailto:'):
|
|
email = href.replace('mailto:', '').strip()
|
|
|
|
# Basic email validation
|
|
if '@' in email and '.' in email.split('@')[1]:
|
|
logger.info(f"Discovered email via rel='me': {domain_url}")
|
|
return email
|
|
|
|
logger.warning(f"No rel='me' mailto: link found: {domain_url}")
|
|
return None
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to discover email for {domain_url}: {e}")
|
|
return None
|
|
|
|
def _send_verification_code(self, email: str, domain: str) -> bool:
|
|
"""
|
|
Generate and send verification code to email.
|
|
|
|
Returns: True if sent successfully, False otherwise
|
|
"""
|
|
# Check rate limit
|
|
if self._is_rate_limited(domain):
|
|
logger.warning(f"Rate limit exceeded for domain: {domain}")
|
|
return False
|
|
|
|
# Generate 6-digit code
|
|
code = ''.join(secrets.choice('0123456789') for _ in range(6))
|
|
|
|
# Store code with expiration
|
|
self.codes[email] = {
|
|
'code': code,
|
|
'domain': domain,
|
|
'created_at': datetime.utcnow(),
|
|
'expires_at': datetime.utcnow() + timedelta(minutes=15),
|
|
'attempts': 0,
|
|
}
|
|
|
|
# Send email via SMTP
|
|
try:
|
|
msg = EmailMessage()
|
|
msg['From'] = self.smtp['from_email']
|
|
msg['To'] = email
|
|
msg['Subject'] = 'Gondulf Verification Code'
|
|
|
|
msg.set_content(f"""
|
|
Your Gondulf verification code is:
|
|
|
|
{code}
|
|
|
|
This code expires in 15 minutes.
|
|
|
|
Only enter this code if you initiated this login.
|
|
If you did not request this code, ignore this email.
|
|
""")
|
|
|
|
with smtplib.SMTP(self.smtp['host'], self.smtp['port'], timeout=10) as smtp:
|
|
smtp.starttls()
|
|
smtp.login(self.smtp['username'], self.smtp['password'])
|
|
smtp.send_message(msg)
|
|
|
|
logger.info(f"Verification code sent to {email[:3]}***@{email.split('@')[1]}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to send email to {email}: {e}")
|
|
return False
|
|
|
|
def _is_rate_limited(self, domain: str) -> bool:
|
|
"""
|
|
Check if domain is rate limited (max 3 codes per hour).
|
|
|
|
Returns: True if rate limited, False otherwise
|
|
"""
|
|
recent_codes = [
|
|
code for code in self.codes.values()
|
|
if code.get('domain') == domain
|
|
and datetime.utcnow() - code['created_at'] < timedelta(hours=1)
|
|
]
|
|
return len(recent_codes) >= 3
|
|
```
|
|
|
|
## Future Enhancements
|
|
|
|
### v1.1.0+: Additional Authentication Methods
|
|
|
|
**GitHub/GitLab Providers**:
|
|
- OAuth 2.0 flow with provider
|
|
- Verify domain in profile URL
|
|
- Link GitHub username to domain
|
|
|
|
**WebAuthn / FIDO2**:
|
|
- Register hardware security key
|
|
- Challenge/response authentication
|
|
- Strongest security option
|
|
|
|
**IndieAuth Delegation**:
|
|
- Follow rel="authorization_endpoint" link
|
|
- Delegate to another IndieAuth server
|
|
- Support federated authentication
|
|
|
|
These will be additive (user chooses method), not replacing email.
|
|
|
|
## Alternatives Considered
|
|
|
|
### Alternative 1: External Providers Only (GitHub, GitLab)
|
|
|
|
**Pros**:
|
|
- No email infrastructure needed
|
|
- Established OAuth 2.0 flows
|
|
- Users already have accounts
|
|
|
|
**Cons**:
|
|
- Contradicts user requirement (email-only in v1.0.0)
|
|
- Requires external API integration
|
|
- Users locked to specific providers
|
|
- Privacy concerns (data sharing)
|
|
|
|
**Rejected**: Violates user requirements for v1.0.0.
|
|
|
|
---
|
|
|
|
### Alternative 2: WebAuthn as Primary Method
|
|
|
|
**Pros**:
|
|
- Strongest security (hardware keys)
|
|
- Phishing-resistant
|
|
- No password/email needed
|
|
|
|
**Cons**:
|
|
- Requires hardware key (barrier to entry)
|
|
- Complex implementation (WebAuthn API)
|
|
- Browser compatibility issues
|
|
- Not suitable for MVP
|
|
|
|
**Rejected**: Too complex for MVP, hardware requirement.
|
|
|
|
---
|
|
|
|
### Alternative 3: SMS Verification
|
|
|
|
**Pros**:
|
|
- Familiar pattern
|
|
- Fast delivery
|
|
|
|
**Cons**:
|
|
- Requires phone number (PII collection)
|
|
- SMS delivery costs
|
|
- Phone number != domain ownership
|
|
- SIM swapping attacks
|
|
|
|
**Rejected**: Doesn't prove domain ownership, adds PII collection.
|
|
|
|
---
|
|
|
|
### Alternative 4: DNS Only (No Email Fallback)
|
|
|
|
**Pros**:
|
|
- Strongest proof of domain control
|
|
- No email infrastructure
|
|
- Simple implementation
|
|
|
|
**Cons**:
|
|
- Requires DNS knowledge
|
|
- Barrier to entry for non-technical users
|
|
- DNS propagation delays
|
|
- No fallback if DNS inaccessible
|
|
|
|
**Rejected**: Too restrictive, not accessible enough.
|
|
|
|
## References
|
|
|
|
- IndieWeb rel="me": https://indieweb.org/rel-me
|
|
- Example Implementation: https://thesatelliteoflove.com (Phil Skents' identity page)
|
|
- SMTP Protocol (RFC 5321): https://datatracker.ietf.org/doc/html/rfc5321
|
|
- Email Security (STARTTLS): https://datatracker.ietf.org/doc/html/rfc3207
|
|
- DNS TXT Records (RFC 1035): https://datatracker.ietf.org/doc/html/rfc1035
|
|
- HTML Link Relations: https://www.w3.org/TR/html5/links.html#linkTypes
|
|
- BeautifulSoup (HTML parsing): https://www.crummy.com/software/BeautifulSoup/
|
|
- WebAuthn (W3C): https://www.w3.org/TR/webauthn/ (future)
|
|
|
|
## Decision History
|
|
|
|
- 2025-11-20: Proposed (Architect) - Email primary, DNS optional
|
|
- 2025-11-20: Accepted (Architect) - Email primary, DNS optional
|
|
- 2025-11-20: **UPDATED** (Architect) - BOTH required (DNS + Email via rel="me")
|
|
- Changed from single-factor (email OR DNS) to two-factor (email AND DNS)
|
|
- Added rel="me" email discovery (IndieWeb standard)
|
|
- Removed user-provided email input (security improvement)
|
|
- Enhanced security model with dual verification
|
|
- TBD: Review after v1.0.0 deployment (gather user feedback)
|