Auth System Architecture
Current state of the DocView authentication system — magic link auth, device code flow for CLI, and anonymous publish with account claiming.
Tech Stack
- Better Auth — self-hosted auth library (no third-party auth services)
- PostgreSQL — session & user storage
- Resend — magic link email delivery
- Express.js — API server
Database Schema
erDiagram
user {
text id PK
text name
text email UK
bool emailVerified
timestamp createdAt
}
session {
text id PK
text userId FK
text token UK
timestamp expiresAt
text ipAddress
text userAgent
}
account {
text id PK
text userId FK
text providerId
text accessToken
}
verification {
text id PK
text identifier
text value
timestamp expiresAt
}
device_codes {
varchar code PK
text session_token
text user_id
varchar status
timestamp expires_at
}
documents {
uuid id PK
varchar slug UK
text author_id FK
varchar claim_token
varchar visibility
varchar private_key
}
user ||--o{ session : "has"
user ||--o{ account : "has"
user ||--o{ documents : "owns"
device_codes }o--|| user : "approved by"Magic Link Flow (Web)
The primary auth method. No passwords — email-only.
sequenceDiagram
participant U as User
participant B as Browser
participant S as Server
participant BA as Better Auth
participant R as Resend API
participant DB as PostgreSQL
U->>B: Enter email
B->>S: POST /api/auth/sign-in/magic-link
S->>BA: Handle magic link request
BA->>DB: Store verification token (15m TTL)
BA->>R: Send email with magic link
R-->>U: Email arrives
U->>B: Click magic link
B->>S: GET /api/auth/magic-link/verify?token=...
S->>BA: Verify token
BA->>DB: Delete verification token
BA->>DB: Create session (30d TTL)
BA-->>B: Set cookie: better-auth.session_token
B-->>U: Logged inDevice Code Flow (CLI)
Allows CLI tools and MCP clients to authenticate without a browser session.
sequenceDiagram
participant CLI as CLI / MCP
participant S as Server
participant DB as PostgreSQL
participant B as Browser
participant U as User
CLI->>S: POST /api/device-code
S->>DB: Insert code (8-char hex, 30m TTL)
S-->>CLI: { code, verification_url }
CLI->>U: "Open this URL and approve"
U->>B: Open /auth/device?code=ABCD1234
Note over B,S: User signs in via magic link if needed
U->>B: Click "Approve"
B->>S: POST /api/device-code/approve (with session cookie)
S->>DB: UPDATE status='approved', session_token=cookie
loop Poll every 3s
CLI->>S: GET /api/device-code/:code
S-->>CLI: { status, session_token }
end
CLI->>CLI: Save token to ~/.docview/token.jsonAnonymous Publish + Account Claim
Users can publish without signing in, then claim ownership later.
sequenceDiagram
participant U as Anonymous User
participant S as Server
participant DB as PostgreSQL
U->>S: POST /api/docs (no auth)
S->>DB: INSERT doc (author_id=NULL, claim_token=random 32 bytes)
S-->>U: { url, claim_token }
Note over U: User sees "Sign in to own this doc" banner
U->>S: Magic link sign-in flow
S-->>U: Authenticated, redirect with ?claim=token&auto_claim=1
U->>S: POST /api/docs/:slug/claim { claim_token }
S->>DB: UPDATE SET author_id=$userId, claim_token=NULL
S-->>U: Document now ownedSession Resolution
How every request determines the authenticated user.
flowchart TD
A[Request arrives] --> B{Cookie present?}
B -->|Yes| C[Better Auth: getSession]
C --> D{Valid session?}
D -->|Yes| E[Set req.userId + req.userEmail]
D -->|No| F{Authorization header?}
B -->|No| F
F -->|Bearer token| G[Query session table]
G --> H{Token valid + not expired?}
H -->|Yes| E
H -->|No| I[Continue unauthenticated]
F -->|None| I
E --> J[Route handler]
I --> J
style E fill:#e8f5e9
style I fill:#fff3e0Route Auth Middleware
flowchart LR
subgraph Public["No Auth Required"]
A1[GET /d/:slug]
A2[POST /api/device-code]
A3[GET /api/device-code/:code]
end
subgraph Optional["optionalAuth"]
B1[POST /api/docs — publish]
B2[GET /api/docs/:slug/comments]
end
subgraph Required["requireAuth"]
C1[GET /api/docs — list mine]
C2[DELETE /api/docs/:slug]
C3[POST /api/docs/:slug/comments]
C4[POST /api/docs/:slug/claim]
C5[POST /api/device-code/approve]
end
style Public fill:#e1f5fe
style Optional fill:#fff9c4
style Required fill:#fce4ecKey Files
| File | Purpose |
|---|---|
src/auth.js |
Better Auth config, magic link plugin |
src/server.js |
Session middleware, Bearer token fallback |
src/routes/api.js |
Auth middleware, all API endpoints |
src/routes/viewer.js |
Device approval page, claim token in viewer |
client/scripts/doc.js |
Web UI sign-in, claim banner, auto-claim |
mcp/index.js |
CLI tools: publish, login, list, delete |
mcp/auth.js |
Session file persistence (~/.docview/token.json) |
Security Notes
No passwords are stored. Authentication is email-based only via magic links.
- Session tokens: JWTs stored in PostgreSQL with expiration
- Magic links: 15-minute TTL, single-use
- Private doc keys: 32-byte random hex
- Claim tokens: 32-byte random hex, single-use
- Cookies:
__Secure-prefix,httpOnly, HTTPS-only - Queries: Parameterized via
pglibrary (no SQL injection)