Address 21 previously undefined behaviors across specs: - Authentication: Replace email/password with OIDC (Pocket-ID) - Cycle tracking: Add fixed-luteal phase scaling formula with examples - Calendar: Document period logging behavior (preserve predictions) - Garmin: Clarify connection is required (no phase-only mode) - Dashboard: Add UI states, dark mode, onboarding, accessibility - Notifications: Document timezone batching approach - New specs: observability.md (health, metrics, logging) - New specs: testing.md (unit + integration strategy) - Main spec: Add backup/recovery, known limitations, API updates Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
182 lines
4.2 KiB
Markdown
182 lines
4.2 KiB
Markdown
# Authentication Specification
|
|
|
|
## Job to Be Done
|
|
|
|
When I access PhaseFlow, I want to securely log in with my identity provider, so that my personal health data remains private.
|
|
|
|
## Auth Provider
|
|
|
|
Using PocketBase for authentication and data storage, with OIDC (Pocket-ID) as the primary identity provider.
|
|
|
|
**Connection:**
|
|
- `POCKETBASE_URL` environment variable
|
|
- `src/lib/pocketbase.ts` initializes client
|
|
|
|
## Login Flow
|
|
|
|
### OIDC Authentication (Pocket-ID)
|
|
|
|
1. User clicks "Sign In" on `/login`
|
|
2. App redirects to Pocket-ID authorization endpoint
|
|
3. User authenticates with Pocket-ID (handles MFA if configured)
|
|
4. Pocket-ID redirects back with authorization code
|
|
5. PocketBase exchanges code for tokens
|
|
6. User redirected to dashboard
|
|
|
|
### OIDC Configuration
|
|
|
|
Configure in PocketBase Admin UI: **Settings → Auth providers → OpenID Connect**
|
|
|
|
**Required Settings:**
|
|
- `Client ID` - From Pocket-ID application
|
|
- `Client Secret` - From Pocket-ID application
|
|
- `Issuer URL` - Your Pocket-ID instance URL (e.g., `https://id.yourdomain.com`)
|
|
|
|
**Environment Variables:**
|
|
```env
|
|
POCKETBASE_OIDC_CLIENT_ID=phaseflow
|
|
POCKETBASE_OIDC_CLIENT_SECRET=xxx
|
|
POCKETBASE_OIDC_ISSUER_URL=https://id.yourdomain.com
|
|
```
|
|
|
|
### Session Management
|
|
|
|
- PocketBase manages session tokens automatically
|
|
- Auth state persisted in browser (cookie/localStorage)
|
|
- Session expires after 14 days of inactivity
|
|
|
|
## Pages
|
|
|
|
### `/login`
|
|
|
|
**Elements:**
|
|
- "Sign In with Pocket-ID" button
|
|
- Error message display
|
|
|
|
**Behavior:**
|
|
- Redirect to `/` on successful login
|
|
- Show error message on failed attempt
|
|
- Rate limit: 5 attempts per minute
|
|
|
|
### Protected Routes
|
|
|
|
All routes except `/login` require authentication.
|
|
|
|
**Middleware Check:**
|
|
1. Check for valid PocketBase auth token
|
|
2. If invalid/missing, redirect to `/login`
|
|
3. If valid, proceed to requested page
|
|
|
|
## API Authentication
|
|
|
|
### User Context
|
|
|
|
API routes access current user via:
|
|
```typescript
|
|
const pb = new PocketBase(process.env.POCKETBASE_URL);
|
|
// Auth token from request cookies
|
|
const user = pb.authStore.model;
|
|
```
|
|
|
|
### Protected Endpoints
|
|
|
|
All `/api/*` routes except:
|
|
- `/api/calendar/[userId]/[token].ics` (token-based auth)
|
|
- `/api/cron/*` (CRON_SECRET auth)
|
|
|
|
## User API
|
|
|
|
### GET `/api/user`
|
|
|
|
Returns current authenticated user profile.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": "user123",
|
|
"email": "user@example.com",
|
|
"garminConnected": true,
|
|
"cycleLength": 31,
|
|
"lastPeriodDate": "2024-01-01",
|
|
"notificationTime": "07:00",
|
|
"timezone": "America/New_York"
|
|
}
|
|
```
|
|
|
|
### PATCH `/api/user`
|
|
|
|
Updates user profile fields.
|
|
|
|
**Request Body (partial update):**
|
|
```json
|
|
{
|
|
"cycleLength": 28,
|
|
"notificationTime": "06:30"
|
|
}
|
|
```
|
|
|
|
## User Schema
|
|
|
|
See `src/types/index.ts` for full `User` interface.
|
|
|
|
**Auth-related fields:**
|
|
- `id` - PocketBase record ID
|
|
- `email` - Login email
|
|
|
|
**Profile fields:**
|
|
- `cycleLength` - Personal cycle length (days)
|
|
- `notificationTime` - Preferred notification hour
|
|
- `timezone` - User's timezone
|
|
|
|
## PocketBase Client (`src/lib/pocketbase.ts`)
|
|
|
|
**Exports:**
|
|
- `pb` - Initialized PocketBase client
|
|
- `getCurrentUser()` - Get authenticated user
|
|
- `isAuthenticated()` - Check auth status
|
|
|
|
## Settings Page (`/settings`)
|
|
|
|
User profile management:
|
|
- View/edit cycle length
|
|
- View/edit notification time
|
|
- View/edit timezone
|
|
- Link to Garmin settings
|
|
|
|
## Success Criteria
|
|
|
|
1. OIDC login flow completes in under 5 seconds (including redirect)
|
|
2. Session persists across browser refreshes
|
|
3. Unauthorized access redirects to login
|
|
4. User data isolated by authentication
|
|
|
|
## Acceptance Tests
|
|
|
|
- [ ] OIDC redirect initiates correctly
|
|
- [ ] Successful OIDC callback creates/updates user
|
|
- [ ] Session persists after page refresh
|
|
- [ ] Protected routes redirect when not authenticated
|
|
- [ ] GET `/api/user` returns current user data
|
|
- [ ] PATCH `/api/user` updates user record
|
|
- [ ] Logout clears session completely
|
|
- [ ] Auth cookie is HttpOnly and Secure
|
|
|
|
## Future Enhancements
|
|
|
|
### Open Registration
|
|
|
|
When open registration is enabled:
|
|
- Add `/signup` page with OIDC provider selection
|
|
- New users created automatically on first OIDC login
|
|
- Admin approval workflow (optional)
|
|
|
|
### Additional OAuth2 Providers
|
|
|
|
PocketBase supports multiple OAuth2 providers. Future options:
|
|
- Google
|
|
- GitHub
|
|
- Apple
|
|
- Other OIDC-compliant providers
|
|
|
|
Each provider configured separately in PocketBase Admin UI.
|