feat: Implement Phase 4 Web Interface with bugfixes (v0.5.2)

## Phase 4: Web Interface Implementation

Implemented complete web interface with public and admin routes,
templates, CSS, and development authentication.

### Core Features

**Public Routes**:
- Homepage with recent published notes
- Note permalinks with microformats2
- Server-side rendering (Jinja2)

**Admin Routes**:
- Login via IndieLogin
- Dashboard with note management
- Create, edit, delete notes
- Protected with @require_auth decorator

**Development Authentication**:
- Dev login bypass for local testing (DEV_MODE only)
- Security safeguards per ADR-011
- Returns 404 when disabled

**Templates & Frontend**:
- Base layouts (public + admin)
- 8 HTML templates with microformats2
- Custom responsive CSS (114 lines)
- Error pages (404, 500)

### Bugfixes (v0.5.1 → v0.5.2)

1. **Cookie collision fix (v0.5.1)**:
   - Renamed auth cookie from "session" to "starpunk_session"
   - Fixed redirect loop between dev login and admin dashboard
   - Flask's session cookie no longer conflicts with auth

2. **HTTP 404 error handling (v0.5.1)**:
   - Update route now returns 404 for nonexistent notes
   - Delete route now returns 404 for nonexistent notes
   - Follows ADR-012 HTTP Error Handling Policy
   - Pattern consistency across all admin routes

3. **Note model enhancement (v0.5.2)**:
   - Exposed deleted_at field from database schema
   - Enables soft deletion verification in tests
   - Follows ADR-013 transparency principle

### Architecture

**New ADRs**:
- ADR-011: Development Authentication Mechanism
- ADR-012: HTTP Error Handling Policy
- ADR-013: Expose deleted_at Field in Note Model

**Standards Compliance**:
- Uses uv for Python environment
- Black formatted, Flake8 clean
- Follows git branching strategy
- Version incremented per versioning strategy

### Test Results

- 405/406 tests passing (99.75%)
- 87% code coverage
- All security tests passing
- Manual testing confirmed working

### Documentation

- Complete implementation reports in docs/reports/
- Architecture reviews in docs/reviews/
- Design documents in docs/design/
- CHANGELOG updated for v0.5.2

### Files Changed

**New Modules**:
- starpunk/dev_auth.py
- starpunk/routes/ (public, admin, auth, dev_auth)

**Templates**: 10 files (base, pages, admin, errors)
**Static**: CSS and optional JavaScript
**Tests**: 4 test files for routes and templates
**Docs**: 20+ architectural and implementation documents

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-18 23:01:53 -07:00
parent 575a02186b
commit 0cca8169ce
56 changed files with 13151 additions and 304 deletions

View File

@@ -0,0 +1,227 @@
# Cookie Naming Convention
**Status**: ACTIVE
**Date**: 2025-11-18
**Version**: 1.0
## Purpose
This document establishes the naming convention for HTTP cookies in StarPunk to prevent conflicts with web framework reserved names and ensure clear ownership of cookie data.
## Standard
All StarPunk application cookies **MUST** use the `starpunk_` prefix to avoid conflicts with framework-reserved names.
## Rationale
**Problem**: Cookie name collision between application cookies and framework cookies can cause unexpected behavior. In Phase 4, we discovered that using a cookie named `session` conflicted with Flask's server-side session mechanism, causing an authentication redirect loop.
**Solution**: Namespace all application cookies with an application-specific prefix.
## Reserved Names (DO NOT USE)
The following cookie names are reserved by frameworks and libraries. StarPunk MUST NOT use these names:
### Flask Framework
- `session` - Reserved for Flask's server-side session (used by flash messages, session storage)
### Common Auth Frameworks
- `csrf_token` - Common CSRF protection cookie name
- `remember_token` - Common "remember me" authentication
- `auth_token` - Generic authentication token
### Generic Reserved Names
Avoid any single-word generic names that might conflict with frameworks or browsers:
- `token`
- `user`
- `id`
- `data`
- `state`
## StarPunk Cookie Names
All StarPunk cookies use the `starpunk_` prefix for clear ownership.
### Current Cookies
| Cookie Name | Purpose | Security Attributes | Max Age |
|-------------|---------|---------------------|---------|
| `starpunk_session` | Authentication session token | HttpOnly, Secure (prod), SameSite=Lax | 30 days |
### Future Cookies
All future cookies must:
1. Use `starpunk_` prefix
2. Be documented in this table
3. Have explicit security attributes defined
4. Be reviewed for conflicts with framework conventions
**Example future cookies**:
- `starpunk_preferences` - User preferences (if added)
- `starpunk_analytics` - Analytics consent (if added)
- `starpunk_theme` - Theme selection (if added)
## Security Attributes
All StarPunk cookies MUST specify these security attributes:
### Required Attributes
**HttpOnly**:
- Use for authentication and sensitive cookies
- Prevents JavaScript access
- Mitigates XSS attacks
**Secure**:
- Use in production (HTTPS)
- Can be `False` in development (HTTP)
- Prevents transmission over unencrypted connections
**SameSite**:
- Use `Lax` or `Strict`
- Prevents CSRF attacks
- `Lax` allows top-level navigation with cookie
- `Strict` never sends cookie cross-site
**Max-Age**:
- Always set explicit expiry
- Don't rely on session cookies (cleared on browser close)
- Choose appropriate lifetime for use case
### Example
```python
response.set_cookie(
"starpunk_session", # Name with prefix
session_token, # Value
httponly=True, # Prevent JS access
secure=is_production, # HTTPS only in prod
samesite="Lax", # CSRF protection
max_age=30 * 24 * 60 * 60, # 30 days
)
```
## Implementation Checklist
When adding a new cookie:
- [ ] Name uses `starpunk_` prefix
- [ ] Name doesn't conflict with framework/library cookies
- [ ] Purpose is documented in this file
- [ ] Security attributes are explicitly set
- [ ] HttpOnly is used for sensitive data
- [ ] Secure is conditional on production vs development
- [ ] SameSite is set (Lax or Strict)
- [ ] Max-Age is appropriate for use case
- [ ] Cookie is reviewed by architect
- [ ] Tests verify cookie behavior
- [ ] Cookie is documented in API contracts
## Reading Cookies
When reading cookies, always use the full prefixed name:
```python
# Correct
session_token = request.cookies.get("starpunk_session")
# Incorrect - missing prefix
session_token = request.cookies.get("session")
```
## Deletion
When deleting cookies, use the same name that was used to set them:
```python
# Correct
response.delete_cookie("starpunk_session")
# Incorrect - missing prefix
response.delete_cookie("session")
```
## Framework Cookie Coexistence
StarPunk and Flask cookies can coexist without conflict:
**StarPunk cookies**:
- `starpunk_session` - Application authentication
**Flask cookies** (framework-managed):
- `session` - Flask server-side session (for flash messages)
Both are necessary and serve different purposes. Do not interfere with Flask's `session` cookie.
## Migration Notes
### Version 0.5.1 Migration
**Breaking Change**: Authentication cookie renamed from `session` to `starpunk_session`.
**Impact**: All existing authenticated users were logged out and needed to re-authenticate.
**Reason**: Fix critical authentication redirect loop caused by cookie name collision.
**Future Migrations**: If cookie names need to change, consider:
1. Dual-cookie period (read from both old and new)
2. Client-side cookie migration via JavaScript
3. Clear documentation of breaking change
4. User communication about re-authentication
## Validation
### During Development
- Review cookie names in code review
- Check for `starpunk_` prefix
- Verify security attributes are set
- Test with browser DevTools → Application → Cookies
### During Testing
- Automated tests should verify cookie names
- Integration tests should check cookie attributes
- Browser tests should verify cookie behavior
### Example Test
```python
def test_auth_cookie_name(client):
"""Test authentication uses correct cookie name"""
response = client.post("/dev/login")
# Verify correct cookie name
assert "starpunk_session" in response.headers.getlist("Set-Cookie")
# Verify does not use reserved name
cookies_str = str(response.headers.getlist("Set-Cookie"))
assert "session=" not in cookies_str or "starpunk_session=" in cookies_str
```
## References
### Internal Documentation
- **Auth Redirect Loop Fix**: `/docs/reports/2025-11-18-auth-redirect-loop-fix.md`
- **ADR-011**: Development Authentication Mechanism
- **ADR-005**: IndieLogin Authentication
### External Standards
- [RFC 6265 - HTTP State Management Mechanism (Cookies)](https://tools.ietf.org/html/rfc6265)
- [Flask Session Documentation](https://flask.palletsprojects.com/en/latest/api/#flask.session)
- [OWASP Session Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)
## History
### Version 1.0 (2025-11-18)
- Initial version
- Established `starpunk_` prefix convention
- Documented reserved names
- Added security attribute requirements
- Created as part of auth redirect loop fix (v0.5.1)
---
**Document Owner**: StarPunk Architecture Team
**Last Updated**: 2025-11-18
**Status**: Active Standard