@prmichaelsen/acp-visualizer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -0
- package/agent/commands/acp.clarification-address.md +417 -0
- package/agent/commands/acp.clarification-capture.md +386 -0
- package/agent/commands/acp.clarification-create.md +437 -0
- package/agent/commands/acp.clarifications-research.md +326 -0
- package/agent/commands/acp.command-create.md +432 -0
- package/agent/commands/acp.design-create.md +286 -0
- package/agent/commands/acp.design-reference.md +355 -0
- package/agent/commands/acp.handoff.md +270 -0
- package/agent/commands/acp.index.md +423 -0
- package/agent/commands/acp.init.md +546 -0
- package/agent/commands/acp.package-create.md +895 -0
- package/agent/commands/acp.package-info.md +212 -0
- package/agent/commands/acp.package-install.md +539 -0
- package/agent/commands/acp.package-list.md +280 -0
- package/agent/commands/acp.package-publish.md +541 -0
- package/agent/commands/acp.package-remove.md +293 -0
- package/agent/commands/acp.package-search.md +307 -0
- package/agent/commands/acp.package-update.md +361 -0
- package/agent/commands/acp.package-validate.md +540 -0
- package/agent/commands/acp.pattern-create.md +386 -0
- package/agent/commands/acp.plan.md +587 -0
- package/agent/commands/acp.proceed.md +882 -0
- package/agent/commands/acp.project-create.md +675 -0
- package/agent/commands/acp.project-info.md +312 -0
- package/agent/commands/acp.project-list.md +226 -0
- package/agent/commands/acp.project-remove.md +379 -0
- package/agent/commands/acp.project-set.md +227 -0
- package/agent/commands/acp.project-update.md +307 -0
- package/agent/commands/acp.projects-restore.md +228 -0
- package/agent/commands/acp.projects-sync.md +347 -0
- package/agent/commands/acp.report.md +407 -0
- package/agent/commands/acp.resume.md +239 -0
- package/agent/commands/acp.sessions.md +301 -0
- package/agent/commands/acp.status.md +293 -0
- package/agent/commands/acp.sync.md +364 -0
- package/agent/commands/acp.task-create.md +500 -0
- package/agent/commands/acp.update.md +302 -0
- package/agent/commands/acp.validate.md +466 -0
- package/agent/commands/acp.version-check-for-updates.md +276 -0
- package/agent/commands/acp.version-check.md +191 -0
- package/agent/commands/acp.version-update.md +289 -0
- package/agent/commands/command.template.md +339 -0
- package/agent/commands/git.commit.md +526 -0
- package/agent/commands/git.init.md +514 -0
- package/agent/commands/tanstack-cloudflare.deploy.md +272 -0
- package/agent/commands/tanstack-cloudflare.tail.md +275 -0
- package/agent/design/.gitkeep +0 -0
- package/agent/design/design.template.md +154 -0
- package/agent/design/local.dashboard-layout-routing.md +288 -0
- package/agent/design/local.data-model-yaml-parsing.md +310 -0
- package/agent/design/local.search-filtering.md +331 -0
- package/agent/design/local.server-api-auto-refresh.md +235 -0
- package/agent/design/local.table-tree-views.md +299 -0
- package/agent/design/local.visualizer-requirements.md +349 -0
- package/agent/design/requirements.template.md +387 -0
- package/agent/index/.gitkeep +0 -0
- package/agent/index/acp.core.yaml +137 -0
- package/agent/index/local.main.template.yaml +37 -0
- package/agent/manifest.template.yaml +13 -0
- package/agent/manifest.yaml +302 -0
- package/agent/milestones/.gitkeep +0 -0
- package/agent/milestones/milestone-1-project-scaffold-data-pipeline.md +67 -0
- package/agent/milestones/milestone-1-{title}.template.md +206 -0
- package/agent/milestones/milestone-2-dashboard-views-interaction.md +79 -0
- package/agent/package.template.yaml +86 -0
- package/agent/patterns/.gitkeep +0 -0
- package/agent/patterns/bootstrap.template.md +1237 -0
- package/agent/patterns/pattern.template.md +382 -0
- package/agent/patterns/tanstack-cloudflare.acl-permissions.md +332 -0
- package/agent/patterns/tanstack-cloudflare.action-bar-item.md +416 -0
- package/agent/patterns/tanstack-cloudflare.api-route-handlers.md +401 -0
- package/agent/patterns/tanstack-cloudflare.auth-session-management.md +387 -0
- package/agent/patterns/tanstack-cloudflare.card-and-list.md +271 -0
- package/agent/patterns/tanstack-cloudflare.chat-engine.md +353 -0
- package/agent/patterns/tanstack-cloudflare.confirmation-tokens.md +346 -0
- package/agent/patterns/tanstack-cloudflare.durable-objects-websocket.md +516 -0
- package/agent/patterns/tanstack-cloudflare.email-service.md +431 -0
- package/agent/patterns/tanstack-cloudflare.expander.md +98 -0
- package/agent/patterns/tanstack-cloudflare.fcm-push.md +115 -0
- package/agent/patterns/tanstack-cloudflare.firebase-anonymous-sessions.md +441 -0
- package/agent/patterns/tanstack-cloudflare.firebase-auth.md +348 -0
- package/agent/patterns/tanstack-cloudflare.firebase-firestore.md +550 -0
- package/agent/patterns/tanstack-cloudflare.firebase-storage.md +369 -0
- package/agent/patterns/tanstack-cloudflare.form-controls.md +145 -0
- package/agent/patterns/tanstack-cloudflare.global-search-context.md +93 -0
- package/agent/patterns/tanstack-cloudflare.image-carousel.md +126 -0
- package/agent/patterns/tanstack-cloudflare.library-services.md +553 -0
- package/agent/patterns/tanstack-cloudflare.lightbox.md +169 -0
- package/agent/patterns/tanstack-cloudflare.markdown-content.md +115 -0
- package/agent/patterns/tanstack-cloudflare.mention-suggestions.md +98 -0
- package/agent/patterns/tanstack-cloudflare.modal.md +156 -0
- package/agent/patterns/tanstack-cloudflare.nextjs-to-tanstack-routing.md +461 -0
- package/agent/patterns/tanstack-cloudflare.notifications-engine.md +151 -0
- package/agent/patterns/tanstack-cloudflare.oauth-token-refresh.md +90 -0
- package/agent/patterns/tanstack-cloudflare.og-metadata.md +296 -0
- package/agent/patterns/tanstack-cloudflare.pagination.md +442 -0
- package/agent/patterns/tanstack-cloudflare.pill-input.md +220 -0
- package/agent/patterns/tanstack-cloudflare.provider-adapter.md +401 -0
- package/agent/patterns/tanstack-cloudflare.rate-limiting.md +323 -0
- package/agent/patterns/tanstack-cloudflare.scheduled-tasks.md +338 -0
- package/agent/patterns/tanstack-cloudflare.searchable-settings.md +375 -0
- package/agent/patterns/tanstack-cloudflare.slide-over.md +129 -0
- package/agent/patterns/tanstack-cloudflare.ssr-preload.md +571 -0
- package/agent/patterns/tanstack-cloudflare.third-party-api-integration.md +508 -0
- package/agent/patterns/tanstack-cloudflare.toast-system.md +142 -0
- package/agent/patterns/tanstack-cloudflare.unified-header.md +280 -0
- package/agent/patterns/tanstack-cloudflare.user-scoped-collections.md +628 -0
- package/agent/patterns/tanstack-cloudflare.websocket-manager.md +237 -0
- package/agent/patterns/tanstack-cloudflare.wrangler-configuration.md +358 -0
- package/agent/patterns/tanstack-cloudflare.zod-schema-validation.md +336 -0
- package/agent/progress.template.yaml +161 -0
- package/agent/progress.yaml +145 -0
- package/agent/schemas/package.schema.yaml +276 -0
- package/agent/scripts/acp.common.sh +1781 -0
- package/agent/scripts/acp.install.sh +333 -0
- package/agent/scripts/acp.package-create.sh +924 -0
- package/agent/scripts/acp.package-info.sh +288 -0
- package/agent/scripts/acp.package-install.sh +893 -0
- package/agent/scripts/acp.package-list.sh +311 -0
- package/agent/scripts/acp.package-publish.sh +420 -0
- package/agent/scripts/acp.package-remove.sh +348 -0
- package/agent/scripts/acp.package-search.sh +156 -0
- package/agent/scripts/acp.package-update.sh +517 -0
- package/agent/scripts/acp.package-validate.sh +1018 -0
- package/agent/scripts/acp.uninstall.sh +85 -0
- package/agent/scripts/acp.version-check-for-updates.sh +98 -0
- package/agent/scripts/acp.version-check.sh +47 -0
- package/agent/scripts/acp.version-update.sh +176 -0
- package/agent/scripts/acp.yaml-parser.sh +985 -0
- package/agent/scripts/acp.yaml-validate.sh +205 -0
- package/agent/tasks/.gitkeep +0 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-1-initialize-tanstack-start-project.md +210 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-2-implement-data-model-yaml-parser.md +294 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-3-build-server-api-data-loading.md +193 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-4-add-auto-refresh-sse.md +262 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-10-polish-integration-testing.md +156 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-5-build-dashboard-layout-routing.md +178 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-6-build-overview-page.md +141 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-7-implement-milestone-table-view.md +153 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-8-implement-milestone-tree-view.md +174 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-9-implement-search-filtering.md +233 -0
- package/agent/tasks/task-1-{title}.template.md +244 -0
- package/bin/visualize.mjs +84 -0
- package/package.json +48 -0
- package/src/components/ExtraFieldsBadge.tsx +15 -0
- package/src/components/FilterBar.tsx +33 -0
- package/src/components/Header.tsx +23 -0
- package/src/components/MilestoneTable.tsx +167 -0
- package/src/components/MilestoneTree.tsx +84 -0
- package/src/components/ProgressBar.tsx +20 -0
- package/src/components/SearchInput.tsx +22 -0
- package/src/components/Sidebar.tsx +54 -0
- package/src/components/StatusBadge.tsx +23 -0
- package/src/components/StatusDot.tsx +12 -0
- package/src/components/TaskList.tsx +36 -0
- package/src/components/ViewToggle.tsx +31 -0
- package/src/lib/config.ts +8 -0
- package/src/lib/file-watcher.ts +43 -0
- package/src/lib/search.ts +48 -0
- package/src/lib/types.ts +73 -0
- package/src/lib/useAutoRefresh.ts +31 -0
- package/src/lib/useCollapse.ts +31 -0
- package/src/lib/useFilteredData.ts +55 -0
- package/src/lib/yaml-loader-real.spec.ts +47 -0
- package/src/lib/yaml-loader.spec.ts +201 -0
- package/src/lib/yaml-loader.ts +265 -0
- package/src/routeTree.gen.ts +140 -0
- package/src/router.tsx +10 -0
- package/src/routes/__root.tsx +75 -0
- package/src/routes/api/watch.ts +29 -0
- package/src/routes/index.tsx +115 -0
- package/src/routes/milestones.tsx +50 -0
- package/src/routes/search.tsx +84 -0
- package/src/routes/tasks.tsx +63 -0
- package/src/services/progress-database.service.ts +46 -0
- package/src/styles.css +25 -0
- package/tsconfig.json +24 -0
- package/vite.config.ts +16 -0
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# Firebase Authentication
|
|
2
|
+
|
|
3
|
+
**Category**: Code
|
|
4
|
+
**Applicable To**: All server-side auth verification, session management, API route protection, and SSR auth checks
|
|
5
|
+
**Status**: Stable
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
This pattern covers how Firebase Admin SDK authentication is used throughout the project: session cookie management, token verification, route guards, SSR auth, and the client-to-server auth handshake. The project uses a dual-layer system: Firebase Client SDK for client-side auth (sign-in/sign-up) and Firebase Admin SDK (`@prmichaelsen/firebase-admin-sdk-v8`) for server-side session management via long-lived session cookies.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## When to Use This Pattern
|
|
16
|
+
|
|
17
|
+
**Use this pattern when:**
|
|
18
|
+
- Adding a new API route that requires authentication
|
|
19
|
+
- Adding a new SSR `beforeLoad` that needs user context
|
|
20
|
+
- Building a new server function (`createServerFn`) that accesses user data
|
|
21
|
+
- Implementing admin-only routes or features
|
|
22
|
+
|
|
23
|
+
**Don't use this pattern when:**
|
|
24
|
+
- Working on purely client-side components with no server interaction
|
|
25
|
+
- Building public/unauthenticated endpoints (use no auth check)
|
|
26
|
+
- Implementing MCP server auth (use `mcp-jwt.ts` JWT tokens instead)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Core Principles
|
|
31
|
+
|
|
32
|
+
1. **Session Cookies Over ID Tokens**: Server-side auth uses 14-day session cookies, not short-lived ID tokens
|
|
33
|
+
2. **Dual Verification Fallback**: `verifySessionCookie()` first, then `verifyIdToken()` for migration compatibility
|
|
34
|
+
3. **Null on Failure**: Auth functions return `null` on error, never throw — callers decide how to respond
|
|
35
|
+
4. **Always Initialize First**: Call `initFirebaseAdmin()` before any auth operation
|
|
36
|
+
5. **Anonymous Users Are Valid**: Anonymous sessions are real auth sessions — check `isAnonymous` when restricting features
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Implementation
|
|
41
|
+
|
|
42
|
+
### Auth Flow Overview
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Client Server
|
|
46
|
+
│ │
|
|
47
|
+
├─ Firebase signIn/signUp ──────► │
|
|
48
|
+
│ (gets ID token) │
|
|
49
|
+
│ │
|
|
50
|
+
├─ POST /api/auth/login ──────────► │
|
|
51
|
+
│ { idToken, turnstileToken? } │
|
|
52
|
+
│ ├─ verifyIdToken(idToken)
|
|
53
|
+
│ ├─ createSessionCookie(idToken, 14d)
|
|
54
|
+
│ ├─ Set-Cookie: session=...
|
|
55
|
+
│ ◄──────────────────────────────── │
|
|
56
|
+
│ │
|
|
57
|
+
├─ GET /api/some-endpoint ─────────► │
|
|
58
|
+
│ Cookie: session=... ├─ getServerSession(request)
|
|
59
|
+
│ │ └─ verifySessionCookie(cookie)
|
|
60
|
+
│ │ └─ fallback: verifyIdToken(cookie)
|
|
61
|
+
│ ◄─── { data } ────────────────── │
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Key Functions
|
|
65
|
+
|
|
66
|
+
#### `initFirebaseAdmin()` — SDK Initialization
|
|
67
|
+
|
|
68
|
+
**File**: `src/lib/firebase-admin.ts`
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { initializeApp as _initializeApp } from '@prmichaelsen/firebase-admin-sdk-v8'
|
|
72
|
+
|
|
73
|
+
export function initFirebaseAdmin() {
|
|
74
|
+
_initializeApp({
|
|
75
|
+
serviceAccount: process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY,
|
|
76
|
+
projectId: process.env.FIREBASE_PROJECT_ID,
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Called at the start of every API route handler and SSR `beforeLoad`. Idempotent — safe to call multiple times.
|
|
82
|
+
|
|
83
|
+
#### `getServerSession(request)` — Session Verification
|
|
84
|
+
|
|
85
|
+
**File**: `src/lib/auth/session.ts`
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { verifyIdToken, verifySessionCookie } from '@prmichaelsen/firebase-admin-sdk-v8'
|
|
89
|
+
|
|
90
|
+
export async function getServerSession(request: Request): Promise<ServerSession | null> {
|
|
91
|
+
const sessionCookie = getSessionCookie(request)
|
|
92
|
+
if (!sessionCookie) return null
|
|
93
|
+
|
|
94
|
+
let decodedToken
|
|
95
|
+
try {
|
|
96
|
+
decodedToken = await verifySessionCookie(sessionCookie)
|
|
97
|
+
} catch {
|
|
98
|
+
// Migration fallback — old ID tokens in cookies
|
|
99
|
+
decodedToken = await verifyIdToken(sessionCookie)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const isAnonymous = decodedToken.firebase?.sign_in_provider === 'anonymous' || !decodedToken.email
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
user: {
|
|
106
|
+
uid: decodedToken.sub,
|
|
107
|
+
email: decodedToken.email || null,
|
|
108
|
+
displayName: decodedToken.name || null,
|
|
109
|
+
photoURL: decodedToken.picture || null,
|
|
110
|
+
emailVerified: decodedToken.email_verified || false,
|
|
111
|
+
isAnonymous,
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### `getAuthSession()` — TanStack Server Function
|
|
118
|
+
|
|
119
|
+
**File**: `src/lib/auth/server-fn.ts`
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
export const getAuthSession = createServerFn({ method: 'GET' }).handler(async () => {
|
|
123
|
+
initFirebaseAdmin()
|
|
124
|
+
const session = await getServerSession(getRequest())
|
|
125
|
+
return session?.user || null
|
|
126
|
+
})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Uses `getRequest()` from `@tanstack/react-start/server` to access the Request object.
|
|
130
|
+
|
|
131
|
+
#### `createSessionCookie(idToken)` — Cookie Creation
|
|
132
|
+
|
|
133
|
+
**File**: `src/lib/auth/session.ts`
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
export async function createSessionCookie(idToken: string): Promise<string> {
|
|
137
|
+
const sessionCookie = await createFirebaseSessionCookie(idToken, {
|
|
138
|
+
expiresIn: 60 * 60 * 24 * 14 * 1000 // 14 days
|
|
139
|
+
})
|
|
140
|
+
return sessionCookie
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### Route Guards
|
|
145
|
+
|
|
146
|
+
**File**: `src/lib/auth/guards.ts`
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
export async function requireAuth(request: Request): Promise<Response | null> {
|
|
150
|
+
const session = await getServerSession(request)
|
|
151
|
+
if (!session?.user) {
|
|
152
|
+
return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 })
|
|
153
|
+
}
|
|
154
|
+
return null // null = authorized
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function requireAdmin(request: Request): Promise<Response | null> {
|
|
158
|
+
const session = await getServerSession(request)
|
|
159
|
+
if (!session?.user) return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 })
|
|
160
|
+
|
|
161
|
+
const ownerEmails = (process.env.OWNER_EMAILS || '').split(',').map(e => e.trim())
|
|
162
|
+
if (!session.user.email || !ownerEmails.includes(session.user.email)) {
|
|
163
|
+
return new Response(JSON.stringify({ error: 'Forbidden' }), { status: 403 })
|
|
164
|
+
}
|
|
165
|
+
return null
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Examples
|
|
172
|
+
|
|
173
|
+
### Example 1: API Route with Auth
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// src/routes/api/some-endpoint.tsx
|
|
177
|
+
GET: async () => {
|
|
178
|
+
initFirebaseAdmin()
|
|
179
|
+
|
|
180
|
+
const user = await getAuthSession()
|
|
181
|
+
if (!user || user.isAnonymous) {
|
|
182
|
+
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
|
183
|
+
status: 401,
|
|
184
|
+
headers: { 'Content-Type': 'application/json' },
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const data = await SomeDatabaseService.getData(user.uid)
|
|
189
|
+
return new Response(JSON.stringify({ data }), {
|
|
190
|
+
status: 200,
|
|
191
|
+
headers: { 'Content-Type': 'application/json' },
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Example 2: SSR beforeLoad with Auth Redirect
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// src/routes/settings/index.tsx
|
|
200
|
+
export const Route = createFileRoute('/settings/')({
|
|
201
|
+
beforeLoad: (async ({ context }: any) => {
|
|
202
|
+
const user = context.initialUser // From root beforeLoad
|
|
203
|
+
if (!user || user.isAnonymous) {
|
|
204
|
+
throw redirect({
|
|
205
|
+
to: '/auth',
|
|
206
|
+
search: { redirect_url: '/settings' },
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
return { initialUser: user }
|
|
210
|
+
}) as any,
|
|
211
|
+
component: SettingsPage,
|
|
212
|
+
})
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Example 3: Server Function with Auth
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
const updateProfile = createServerFn({ method: 'POST' })
|
|
219
|
+
.inputValidator((data: UpdateProfileInput) => data)
|
|
220
|
+
.handler(async ({ data }) => {
|
|
221
|
+
initFirebaseAdmin()
|
|
222
|
+
const session = await getServerSession(getRequest())
|
|
223
|
+
if (!session?.user) throw new Error('Unauthorized')
|
|
224
|
+
|
|
225
|
+
return await ProfileDatabaseService.updateProfile(session.user.uid, data)
|
|
226
|
+
})
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Example 4: Root Route — Global Auth Preloading
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
// src/routes/__root.tsx
|
|
233
|
+
beforeLoad: async () => {
|
|
234
|
+
const user = await getAuthSession()
|
|
235
|
+
|
|
236
|
+
let initialAIConsent, initialTosAccepted
|
|
237
|
+
if (typeof window === 'undefined' && user && !user.isAnonymous) {
|
|
238
|
+
initFirebaseAdmin()
|
|
239
|
+
const [consent, tos] = await Promise.all([
|
|
240
|
+
ConsentDatabaseService.getAIConsent(user.uid),
|
|
241
|
+
TosConsentDatabaseService.hasAcceptedCurrentTos(user.uid),
|
|
242
|
+
])
|
|
243
|
+
initialAIConsent = consent?.ai_data_sharing ?? null
|
|
244
|
+
initialTosAccepted = tos
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return { initialUser: user, initialAIConsent, initialTosAccepted }
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Anti-Patterns
|
|
254
|
+
|
|
255
|
+
### Using `getAuthSession()` Where You Have `context.initialUser`
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
// Bad: Redundant server function call when root already provides user
|
|
259
|
+
beforeLoad: async () => {
|
|
260
|
+
const user = await getAuthSession() // Unnecessary extra call
|
|
261
|
+
if (!user) throw redirect({ to: '/auth' })
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Good: Use context from root beforeLoad
|
|
265
|
+
beforeLoad: async ({ context }: any) => {
|
|
266
|
+
const user = context.initialUser // Already fetched by root
|
|
267
|
+
if (!user) throw redirect({ to: '/auth' })
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Throwing on Auth Failure in Session Functions
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
// Bad: Throws — callers can't distinguish auth failure from server error
|
|
275
|
+
export async function getServerSession(request: Request) {
|
|
276
|
+
const cookie = getSessionCookie(request)
|
|
277
|
+
if (!cookie) throw new Error('No session') // Don't throw
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Good: Returns null — caller decides the response
|
|
281
|
+
export async function getServerSession(request: Request): Promise<ServerSession | null> {
|
|
282
|
+
const cookie = getSessionCookie(request)
|
|
283
|
+
if (!cookie) return null
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Forgetting `initFirebaseAdmin()` in API Routes
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
// Bad: Will fail on first request
|
|
291
|
+
GET: async () => {
|
|
292
|
+
const user = await getAuthSession() // Firebase not initialized!
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Good: Always initialize
|
|
296
|
+
GET: async () => {
|
|
297
|
+
initFirebaseAdmin()
|
|
298
|
+
const user = await getAuthSession()
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Key Design Decisions
|
|
305
|
+
|
|
306
|
+
### Session Management
|
|
307
|
+
|
|
308
|
+
| Decision | Choice | Rationale |
|
|
309
|
+
|---|---|---|
|
|
310
|
+
| Session mechanism | 14-day session cookie | Longer-lived than ID tokens (1 hour), reduces re-auth |
|
|
311
|
+
| Cookie flags | HttpOnly, SameSite=Lax, Secure (prod) | Prevents XSS token theft; Secure disabled on localhost |
|
|
312
|
+
| Token fallback | verifySessionCookie → verifyIdToken | Migration compatibility for old ID token cookies |
|
|
313
|
+
| Anonymous users | Auto-created on first visit | Enables chat in The Void without signup |
|
|
314
|
+
|
|
315
|
+
### Auth Architecture
|
|
316
|
+
|
|
317
|
+
| Decision | Choice | Rationale |
|
|
318
|
+
|---|---|---|
|
|
319
|
+
| Admin detection | Email match against OWNER_EMAILS env | Simple, no separate admin role system needed |
|
|
320
|
+
| MCP auth | Separate JWT system (mcp-jwt.ts) | MCP servers need stateless tokens, not session cookies |
|
|
321
|
+
| Rate limiting | 5/min login, 3/5min signup | Prevent brute force and spam signups |
|
|
322
|
+
| CAPTCHA | Turnstile for signups, fail-open | Block bots but don't break auth if Turnstile API is down |
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Checklist for Implementation
|
|
327
|
+
|
|
328
|
+
- [ ] Call `initFirebaseAdmin()` before any auth operation
|
|
329
|
+
- [ ] Use `getAuthSession()` for server functions, `getServerSession(request)` for API routes
|
|
330
|
+
- [ ] Check `isAnonymous` when the feature requires a real account
|
|
331
|
+
- [ ] Return `null` on auth failure in utility functions (don't throw)
|
|
332
|
+
- [ ] Return 401 for unauthenticated, 403 for forbidden in API routes
|
|
333
|
+
- [ ] Use `context.initialUser` in `beforeLoad` instead of re-calling `getAuthSession()`
|
|
334
|
+
- [ ] Redirect to `/auth?redirect_url=...` for protected pages, not just `/auth`
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Related Patterns
|
|
339
|
+
|
|
340
|
+
- **[Database Service Conventions](./database-service-conventions.md)**: Auth-verified userId flows into all database service calls
|
|
341
|
+
- **[SSR Preload](./ssr-preload.md)**: SSR `beforeLoad` uses auth context for server-side data fetching
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
**Status**: Stable
|
|
346
|
+
**Recommendation**: Follow this pattern for all new API routes, server functions, and SSR routes requiring authentication
|
|
347
|
+
**Last Updated**: 2026-03-14
|
|
348
|
+
**Contributors**: Community
|