- Create ADR-029 for IndieAuth/Micropub integration strategy - Address all critical issues from developer review: - Add missing 'me' parameter to token endpoint - Clarify PKCE as optional extension - Define token security migration strategy - Add authorization_codes table schema - Define property mapping rules - Clarify two authentication flows - Simplify V1 scope per user decision: - Remove update/delete operations from V1 - Focus on create-only functionality - Reduce timeline from 8-10 to 6-8 days - Update project plan with post-V1 roadmap: - Phase 11: Update/delete operations (V1.1) - Phase 12: Media endpoint (V1.2) - Phase 13: Advanced IndieWeb features (V2.0) - Phase 14: Enhanced features (V2.0+) - Create token security migration documentation - Update ADR-028 for consistency with simplified scope BREAKING CHANGE: Token migration will invalidate all existing tokens for security
307 lines
9.3 KiB
Markdown
307 lines
9.3 KiB
Markdown
# Token Security Migration Strategy
|
|
|
|
## Overview
|
|
|
|
This document outlines the migration strategy for fixing the critical security issue where access tokens are stored in plain text in the database. This migration will invalidate all existing tokens as a necessary security measure.
|
|
|
|
## Security Issue
|
|
|
|
**Current State**: The `tokens` table stores tokens in plain text, which is a major security vulnerability. If the database is compromised, all tokens are immediately usable by an attacker.
|
|
|
|
**Target State**: Store only SHA256 hashes of tokens, making stolen database contents useless without the original tokens.
|
|
|
|
## Migration Plan
|
|
|
|
### Phase 1: Database Schema Migration
|
|
|
|
#### Migration Script (`migrations/005_token_security.sql`)
|
|
|
|
```sql
|
|
-- Migration: Fix token security and add Micropub support
|
|
-- Version: 0.10.0
|
|
-- Breaking Change: This will invalidate all existing tokens
|
|
|
|
-- Step 1: Create new secure tokens table
|
|
CREATE TABLE tokens_secure (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
token_hash TEXT UNIQUE NOT NULL, -- SHA256 hash of token
|
|
me TEXT NOT NULL, -- User identity URL
|
|
client_id TEXT, -- Client application URL
|
|
scope TEXT DEFAULT 'create', -- Granted scopes
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
expires_at TIMESTAMP NOT NULL, -- Token expiration
|
|
last_used_at TIMESTAMP, -- Track usage
|
|
revoked_at TIMESTAMP -- Soft revocation
|
|
);
|
|
|
|
-- Step 2: Create indexes for performance
|
|
CREATE INDEX idx_tokens_secure_hash ON tokens_secure(token_hash);
|
|
CREATE INDEX idx_tokens_secure_me ON tokens_secure(me);
|
|
CREATE INDEX idx_tokens_secure_expires ON tokens_secure(expires_at);
|
|
|
|
-- Step 3: Create authorization_codes table for Micropub
|
|
CREATE TABLE authorization_codes (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
code_hash TEXT UNIQUE NOT NULL, -- SHA256 hash of code
|
|
me TEXT NOT NULL, -- User identity
|
|
client_id TEXT NOT NULL, -- Client application
|
|
redirect_uri TEXT NOT NULL, -- Callback URL
|
|
scope TEXT, -- Requested scopes
|
|
state TEXT, -- CSRF state
|
|
code_challenge TEXT, -- PKCE challenge
|
|
code_challenge_method TEXT, -- PKCE method
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
expires_at TIMESTAMP NOT NULL, -- 10 minute expiry
|
|
used_at TIMESTAMP -- Prevent replay
|
|
);
|
|
|
|
-- Step 4: Create indexes for authorization codes
|
|
CREATE INDEX idx_auth_codes_hash ON authorization_codes(code_hash);
|
|
CREATE INDEX idx_auth_codes_expires ON authorization_codes(expires_at);
|
|
|
|
-- Step 5: Drop old insecure tokens table
|
|
-- WARNING: This will invalidate all existing tokens
|
|
DROP TABLE IF EXISTS tokens;
|
|
|
|
-- Step 6: Rename secure table to final name
|
|
ALTER TABLE tokens_secure RENAME TO tokens;
|
|
|
|
-- Step 7: Clean up expired auth state
|
|
DELETE FROM auth_state WHERE expires_at < datetime('now');
|
|
```
|
|
|
|
### Phase 2: Code Implementation
|
|
|
|
#### Token Generation and Storage
|
|
|
|
```python
|
|
# starpunk/tokens.py
|
|
import hashlib
|
|
import secrets
|
|
from datetime import datetime, timedelta
|
|
|
|
def generate_token() -> str:
|
|
"""Generate cryptographically secure random token"""
|
|
return secrets.token_urlsafe(32)
|
|
|
|
def hash_token(token: str) -> str:
|
|
"""Create SHA256 hash of token"""
|
|
return hashlib.sha256(token.encode()).hexdigest()
|
|
|
|
def create_access_token(me: str, client_id: str, scope: str, db) -> str:
|
|
"""
|
|
Create new access token and store hash in database
|
|
|
|
Returns:
|
|
Plain text token (only returned once, never stored)
|
|
"""
|
|
token = generate_token()
|
|
token_hash = hash_token(token)
|
|
|
|
expires_at = datetime.now() + timedelta(days=90)
|
|
|
|
db.execute("""
|
|
INSERT INTO tokens (token_hash, me, client_id, scope, expires_at)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
""", (token_hash, me, client_id, scope, expires_at))
|
|
db.commit()
|
|
|
|
return token # Return plain text to user ONCE
|
|
|
|
def verify_token(token: str, db) -> dict:
|
|
"""
|
|
Verify token by comparing hash
|
|
|
|
Returns:
|
|
Token info if valid, None if invalid/expired
|
|
"""
|
|
token_hash = hash_token(token)
|
|
|
|
row = db.execute("""
|
|
SELECT me, client_id, scope
|
|
FROM tokens
|
|
WHERE token_hash = ?
|
|
AND expires_at > datetime('now')
|
|
AND revoked_at IS NULL
|
|
""", (token_hash,)).fetchone()
|
|
|
|
if row:
|
|
# Update last used timestamp
|
|
db.execute("""
|
|
UPDATE tokens
|
|
SET last_used_at = datetime('now')
|
|
WHERE token_hash = ?
|
|
""", (token_hash,))
|
|
db.commit()
|
|
|
|
return dict(row)
|
|
|
|
return None
|
|
```
|
|
|
|
### Phase 3: Migration Execution
|
|
|
|
#### Step-by-Step Process
|
|
|
|
1. **Backup Database**
|
|
```bash
|
|
cp data/starpunk.db data/starpunk.db.backup-$(date +%Y%m%d)
|
|
```
|
|
|
|
2. **Notify Users** (if applicable)
|
|
- Email or announcement about token invalidation
|
|
- Explain security improvement
|
|
- Provide re-authentication instructions
|
|
|
|
3. **Apply Migration**
|
|
```python
|
|
# In starpunk/migrations.py
|
|
def run_migration_005(conn):
|
|
"""Apply token security migration"""
|
|
with open('migrations/005_token_security.sql', 'r') as f:
|
|
conn.executescript(f.read())
|
|
conn.commit()
|
|
```
|
|
|
|
4. **Update Code**
|
|
- Deploy new token handling code
|
|
- Update all token verification points
|
|
- Add proper error messages
|
|
|
|
5. **Test Migration**
|
|
```python
|
|
# Verify new schema
|
|
cursor = conn.execute("PRAGMA table_info(tokens)")
|
|
columns = {col[1] for col in cursor.fetchall()}
|
|
assert 'token_hash' in columns
|
|
assert 'token' not in columns # Old column gone
|
|
|
|
# Test token operations
|
|
token = create_access_token("https://user.example", "app", "create", conn)
|
|
assert verify_token(token, conn) is not None
|
|
assert verify_token("invalid", conn) is None
|
|
```
|
|
|
|
### Phase 4: Post-Migration Validation
|
|
|
|
#### Security Checklist
|
|
|
|
- [ ] Verify no plain text tokens in database
|
|
- [ ] Confirm all tokens are hashed with SHA256
|
|
- [ ] Test token creation returns plain text once
|
|
- [ ] Test token verification works with hash
|
|
- [ ] Verify expired tokens are rejected
|
|
- [ ] Check revoked tokens are rejected
|
|
- [ ] Audit logs show migration completed
|
|
|
|
#### Functional Testing
|
|
|
|
- [ ] Micropub client can obtain new token
|
|
- [ ] New tokens work for API requests
|
|
- [ ] Invalid tokens return 401 Unauthorized
|
|
- [ ] Token expiry is enforced
|
|
- [ ] Last used timestamp updates
|
|
|
|
## Rollback Plan
|
|
|
|
If critical issues arise:
|
|
|
|
1. **Restore Database**
|
|
```bash
|
|
cp data/starpunk.db.backup-YYYYMMDD data/starpunk.db
|
|
```
|
|
|
|
2. **Revert Code**
|
|
```bash
|
|
git revert <migration-commit>
|
|
```
|
|
|
|
3. **Investigate Issues**
|
|
- Review migration logs
|
|
- Test in development environment
|
|
- Fix issues before retry
|
|
|
|
## User Communication
|
|
|
|
### Pre-Migration Notice
|
|
|
|
```
|
|
Subject: Important Security Update - Token Re-authentication Required
|
|
|
|
Dear StarPunk User,
|
|
|
|
We're implementing an important security update that will require you to
|
|
re-authenticate any Micropub clients you use with StarPunk.
|
|
|
|
What's Changing:
|
|
- Enhanced token security (SHA256 hashing)
|
|
- All existing access tokens will be invalidated
|
|
- You'll need to re-authorize Micropub clients
|
|
|
|
When:
|
|
- [Date and time of migration]
|
|
|
|
What You Need to Do:
|
|
1. After the update, go to your Micropub client
|
|
2. Remove and re-add your StarPunk site
|
|
3. Complete the authorization flow again
|
|
|
|
This change significantly improves the security of your StarPunk installation.
|
|
|
|
Thank you for your understanding.
|
|
```
|
|
|
|
### Post-Migration Notice
|
|
|
|
```
|
|
Subject: Security Update Complete - Please Re-authenticate
|
|
|
|
The security update has been completed successfully. All previous access
|
|
tokens have been invalidated for security reasons.
|
|
|
|
To continue using Micropub clients:
|
|
1. Open your Micropub client (Quill, Indigenous, etc.)
|
|
2. Remove your StarPunk site if listed
|
|
3. Add it again and complete authorization
|
|
4. You're ready to post!
|
|
|
|
If you experience any issues, please contact support.
|
|
```
|
|
|
|
## Timeline
|
|
|
|
| Phase | Duration | Description |
|
|
|-------|----------|-------------|
|
|
| Preparation | 1 day | Create migration scripts, test in dev |
|
|
| Communication | 1 day | Notify users of upcoming change |
|
|
| Migration | 2 hours | Apply migration, deploy code |
|
|
| Validation | 2 hours | Test and verify success |
|
|
| Support | 1 week | Help users re-authenticate |
|
|
|
|
## Risk Assessment
|
|
|
|
| Risk | Impact | Mitigation |
|
|
|------|--------|------------|
|
|
| Data loss | High | Full backup before migration |
|
|
| User disruption | Medium | Clear communication, documentation |
|
|
| Migration failure | Low | Test in dev, have rollback plan |
|
|
| Performance impact | Low | Indexes on hash columns |
|
|
|
|
## Long-term Benefits
|
|
|
|
1. **Security**: Compromised database doesn't expose usable tokens
|
|
2. **Compliance**: Follows security best practices
|
|
3. **Auditability**: Can track token usage via last_used_at
|
|
4. **Revocability**: Can revoke tokens without deletion
|
|
5. **Foundation**: Proper structure for OAuth/IndieAuth
|
|
|
|
## Conclusion
|
|
|
|
While this migration will cause temporary disruption by invalidating existing tokens, it's a necessary security improvement that brings StarPunk in line with security best practices. The migration is straightforward, well-tested, and includes comprehensive rollback procedures if needed.
|
|
|
|
---
|
|
|
|
**Document Version**: 1.0
|
|
**Created**: 2024-11-24
|
|
**Author**: StarPunk Architecture Team
|
|
**Related**: ADR-029 (IndieAuth Integration) |