autoworkflow 3.1.4 → 3.5.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.
Files changed (123) hide show
  1. package/.claude/commands/analyze.md +19 -0
  2. package/.claude/commands/audit.md +174 -11
  3. package/.claude/commands/build.md +39 -0
  4. package/.claude/commands/commit.md +25 -0
  5. package/.claude/commands/fix.md +23 -0
  6. package/.claude/commands/plan.md +18 -0
  7. package/.claude/commands/suggest.md +23 -0
  8. package/.claude/commands/verify.md +18 -0
  9. package/.claude/hooks/post-bash-router.sh +20 -0
  10. package/.claude/hooks/post-commit.sh +140 -0
  11. package/.claude/hooks/pre-edit.sh +129 -0
  12. package/.claude/hooks/session-check.sh +79 -0
  13. package/.claude/settings.json +40 -6
  14. package/.claude/settings.local.json +3 -1
  15. package/.claude/skills/actix.md +337 -0
  16. package/.claude/skills/alembic.md +504 -0
  17. package/.claude/skills/angular.md +237 -0
  18. package/.claude/skills/api-design.md +187 -0
  19. package/.claude/skills/aspnet-core.md +377 -0
  20. package/.claude/skills/astro.md +245 -0
  21. package/.claude/skills/auth-clerk.md +327 -0
  22. package/.claude/skills/auth-firebase.md +367 -0
  23. package/.claude/skills/auth-nextauth.md +359 -0
  24. package/.claude/skills/auth-supabase.md +368 -0
  25. package/.claude/skills/axum.md +386 -0
  26. package/.claude/skills/blazor.md +456 -0
  27. package/.claude/skills/chi.md +348 -0
  28. package/.claude/skills/code-review.md +133 -0
  29. package/.claude/skills/csharp.md +296 -0
  30. package/.claude/skills/css-modules.md +325 -0
  31. package/.claude/skills/cypress.md +343 -0
  32. package/.claude/skills/debugging.md +133 -0
  33. package/.claude/skills/diesel.md +392 -0
  34. package/.claude/skills/django.md +301 -0
  35. package/.claude/skills/docker.md +319 -0
  36. package/.claude/skills/doctrine.md +473 -0
  37. package/.claude/skills/documentation.md +182 -0
  38. package/.claude/skills/dotnet.md +409 -0
  39. package/.claude/skills/drizzle.md +293 -0
  40. package/.claude/skills/echo.md +321 -0
  41. package/.claude/skills/eloquent.md +256 -0
  42. package/.claude/skills/emotion.md +426 -0
  43. package/.claude/skills/entity-framework.md +370 -0
  44. package/.claude/skills/express.md +316 -0
  45. package/.claude/skills/fastapi.md +329 -0
  46. package/.claude/skills/fastify.md +299 -0
  47. package/.claude/skills/fiber.md +315 -0
  48. package/.claude/skills/flask.md +322 -0
  49. package/.claude/skills/gin.md +342 -0
  50. package/.claude/skills/git.md +116 -0
  51. package/.claude/skills/github-actions.md +353 -0
  52. package/.claude/skills/go.md +377 -0
  53. package/.claude/skills/gorm.md +409 -0
  54. package/.claude/skills/graphql.md +478 -0
  55. package/.claude/skills/hibernate.md +379 -0
  56. package/.claude/skills/hono.md +306 -0
  57. package/.claude/skills/java.md +400 -0
  58. package/.claude/skills/jest.md +313 -0
  59. package/.claude/skills/jpa.md +282 -0
  60. package/.claude/skills/kotlin.md +347 -0
  61. package/.claude/skills/kubernetes.md +363 -0
  62. package/.claude/skills/laravel.md +414 -0
  63. package/.claude/skills/mcp-browser.md +320 -0
  64. package/.claude/skills/mcp-database.md +219 -0
  65. package/.claude/skills/mcp-fetch.md +241 -0
  66. package/.claude/skills/mcp-filesystem.md +204 -0
  67. package/.claude/skills/mcp-github.md +217 -0
  68. package/.claude/skills/mcp-memory.md +240 -0
  69. package/.claude/skills/mcp-search.md +218 -0
  70. package/.claude/skills/mcp-slack.md +262 -0
  71. package/.claude/skills/micronaut.md +388 -0
  72. package/.claude/skills/mongodb.md +319 -0
  73. package/.claude/skills/mongoose.md +355 -0
  74. package/.claude/skills/mysql.md +281 -0
  75. package/.claude/skills/nestjs.md +335 -0
  76. package/.claude/skills/nextjs-app-router.md +260 -0
  77. package/.claude/skills/nextjs-pages.md +172 -0
  78. package/.claude/skills/nuxt.md +202 -0
  79. package/.claude/skills/openapi.md +489 -0
  80. package/.claude/skills/performance.md +199 -0
  81. package/.claude/skills/php.md +398 -0
  82. package/.claude/skills/playwright.md +371 -0
  83. package/.claude/skills/postgresql.md +257 -0
  84. package/.claude/skills/prisma.md +293 -0
  85. package/.claude/skills/pydantic.md +304 -0
  86. package/.claude/skills/pytest.md +313 -0
  87. package/.claude/skills/python.md +272 -0
  88. package/.claude/skills/quarkus.md +377 -0
  89. package/.claude/skills/react.md +230 -0
  90. package/.claude/skills/redis.md +391 -0
  91. package/.claude/skills/refactoring.md +143 -0
  92. package/.claude/skills/remix.md +246 -0
  93. package/.claude/skills/rest-api.md +490 -0
  94. package/.claude/skills/rocket.md +366 -0
  95. package/.claude/skills/rust.md +341 -0
  96. package/.claude/skills/sass.md +380 -0
  97. package/.claude/skills/sea-orm.md +382 -0
  98. package/.claude/skills/security.md +167 -0
  99. package/.claude/skills/sequelize.md +395 -0
  100. package/.claude/skills/spring-boot.md +416 -0
  101. package/.claude/skills/sqlalchemy.md +269 -0
  102. package/.claude/skills/sqlx-rust.md +408 -0
  103. package/.claude/skills/state-jotai.md +346 -0
  104. package/.claude/skills/state-mobx.md +353 -0
  105. package/.claude/skills/state-pinia.md +431 -0
  106. package/.claude/skills/state-redux.md +337 -0
  107. package/.claude/skills/state-tanstack-query.md +434 -0
  108. package/.claude/skills/state-zustand.md +340 -0
  109. package/.claude/skills/styled-components.md +403 -0
  110. package/.claude/skills/svelte.md +238 -0
  111. package/.claude/skills/sveltekit.md +207 -0
  112. package/.claude/skills/symfony.md +437 -0
  113. package/.claude/skills/tailwind.md +279 -0
  114. package/.claude/skills/terraform.md +394 -0
  115. package/.claude/skills/testing-library.md +371 -0
  116. package/.claude/skills/trpc.md +426 -0
  117. package/.claude/skills/typeorm.md +368 -0
  118. package/.claude/skills/vitest.md +330 -0
  119. package/.claude/skills/vue.md +202 -0
  120. package/.claude/skills/warp.md +365 -0
  121. package/README.md +135 -52
  122. package/package.json +1 -1
  123. package/system/triggers.md +152 -11
@@ -0,0 +1,327 @@
1
+ # Clerk Auth Skill
2
+
3
+ ## Setup (Next.js App Router)
4
+ \`\`\`typescript
5
+ // middleware.ts
6
+ import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
7
+
8
+ const isPublicRoute = createRouteMatcher([
9
+ '/',
10
+ '/sign-in(.*)',
11
+ '/sign-up(.*)',
12
+ '/api/webhooks(.*)',
13
+ ]);
14
+
15
+ export default clerkMiddleware((auth, req) => {
16
+ if (!isPublicRoute(req)) {
17
+ auth().protect();
18
+ }
19
+ });
20
+
21
+ export const config = {
22
+ matcher: ['/((?!.*\\\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
23
+ };
24
+
25
+ // app/layout.tsx
26
+ import { ClerkProvider } from '@clerk/nextjs';
27
+
28
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
29
+ return (
30
+ <ClerkProvider>
31
+ <html lang="en">
32
+ <body>{children}</body>
33
+ </html>
34
+ </ClerkProvider>
35
+ );
36
+ }
37
+ \`\`\`
38
+
39
+ ## Server Components
40
+ \`\`\`typescript
41
+ // Get auth in Server Components
42
+ import { auth, currentUser } from '@clerk/nextjs/server';
43
+ import { redirect } from 'next/navigation';
44
+
45
+ export default async function ProtectedPage() {
46
+ const { userId, sessionClaims } = auth();
47
+
48
+ if (!userId) {
49
+ redirect('/sign-in');
50
+ }
51
+
52
+ // Get full user object
53
+ const user = await currentUser();
54
+
55
+ return (
56
+ <div>
57
+ <h1>Welcome, {user?.firstName}</h1>
58
+ <p>Email: {user?.emailAddresses[0]?.emailAddress}</p>
59
+ <p>User ID: {userId}</p>
60
+ </div>
61
+ );
62
+ }
63
+
64
+ // API Route Protection
65
+ import { auth } from '@clerk/nextjs/server';
66
+ import { NextResponse } from 'next/server';
67
+
68
+ export async function GET() {
69
+ const { userId } = auth();
70
+
71
+ if (!userId) {
72
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
73
+ }
74
+
75
+ // Proceed with authenticated request
76
+ return NextResponse.json({ userId });
77
+ }
78
+ \`\`\`
79
+
80
+ ## Client Components
81
+ \`\`\`tsx
82
+ 'use client';
83
+
84
+ import {
85
+ SignIn,
86
+ SignUp,
87
+ SignOutButton,
88
+ SignedIn,
89
+ SignedOut,
90
+ UserButton,
91
+ useUser,
92
+ useAuth,
93
+ useClerk,
94
+ } from '@clerk/nextjs';
95
+
96
+ // Pre-built Components
97
+ export function AuthPage() {
98
+ return (
99
+ <div>
100
+ <SignedOut>
101
+ <SignIn routing="hash" />
102
+ {/* Or SignUp for registration */}
103
+ </SignedOut>
104
+
105
+ <SignedIn>
106
+ <UserButton afterSignOutUrl="/" />
107
+ </SignedIn>
108
+ </div>
109
+ );
110
+ }
111
+
112
+ // Using hooks
113
+ export function UserProfile() {
114
+ const { isLoaded, isSignedIn, user } = useUser();
115
+ const { signOut, getToken } = useAuth();
116
+ const clerk = useClerk();
117
+
118
+ if (!isLoaded) return <div>Loading...</div>;
119
+
120
+ if (!isSignedIn) {
121
+ return <button onClick={() => clerk.openSignIn()}>Sign In</button>;
122
+ }
123
+
124
+ const handleApiCall = async () => {
125
+ const token = await getToken();
126
+ // Use token for authenticated API calls
127
+ const response = await fetch('/api/protected', {
128
+ headers: { Authorization: \`Bearer \${token}\` },
129
+ });
130
+ };
131
+
132
+ return (
133
+ <div>
134
+ <img src={user.imageUrl} alt={user.fullName ?? ''} />
135
+ <h1>{user.fullName}</h1>
136
+ <p>{user.primaryEmailAddress?.emailAddress}</p>
137
+
138
+ <button onClick={handleApiCall}>Make API Call</button>
139
+ <SignOutButton>
140
+ <button>Sign Out</button>
141
+ </SignOutButton>
142
+ </div>
143
+ );
144
+ }
145
+
146
+ // Custom Sign In Page
147
+ // app/sign-in/[[...sign-in]]/page.tsx
148
+ export default function SignInPage() {
149
+ return (
150
+ <div className="flex items-center justify-center min-h-screen">
151
+ <SignIn
152
+ appearance={{
153
+ elements: {
154
+ rootBox: 'mx-auto',
155
+ card: 'shadow-xl',
156
+ },
157
+ }}
158
+ />
159
+ </div>
160
+ );
161
+ }
162
+ \`\`\`
163
+
164
+ ## Organizations & Roles
165
+ \`\`\`typescript
166
+ // Server-side organization check
167
+ import { auth } from '@clerk/nextjs/server';
168
+
169
+ export default async function OrgPage() {
170
+ const { orgId, orgRole, orgSlug } = auth();
171
+
172
+ if (!orgId) {
173
+ return <div>Please select an organization</div>;
174
+ }
175
+
176
+ if (orgRole !== 'org:admin') {
177
+ return <div>Admin access required</div>;
178
+ }
179
+
180
+ return <div>Organization: {orgSlug}</div>;
181
+ }
182
+
183
+ // Client-side organization
184
+ 'use client';
185
+
186
+ import {
187
+ OrganizationSwitcher,
188
+ OrganizationProfile,
189
+ useOrganization,
190
+ useOrganizationList,
191
+ } from '@clerk/nextjs';
192
+
193
+ export function OrgDashboard() {
194
+ const { organization, membership } = useOrganization();
195
+ const { userMemberships } = useOrganizationList();
196
+
197
+ return (
198
+ <div>
199
+ <OrganizationSwitcher />
200
+ {organization && (
201
+ <div>
202
+ <h1>{organization.name}</h1>
203
+ <p>Your role: {membership?.role}</p>
204
+ </div>
205
+ )}
206
+ </div>
207
+ );
208
+ }
209
+ \`\`\`
210
+
211
+ ## Webhooks
212
+ \`\`\`typescript
213
+ // app/api/webhooks/clerk/route.ts
214
+ import { Webhook } from 'svix';
215
+ import { headers } from 'next/headers';
216
+ import { WebhookEvent } from '@clerk/nextjs/server';
217
+
218
+ export async function POST(req: Request) {
219
+ const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET;
220
+
221
+ if (!WEBHOOK_SECRET) {
222
+ throw new Error('Missing CLERK_WEBHOOK_SECRET');
223
+ }
224
+
225
+ const headerPayload = headers();
226
+ const svix_id = headerPayload.get('svix-id');
227
+ const svix_timestamp = headerPayload.get('svix-timestamp');
228
+ const svix_signature = headerPayload.get('svix-signature');
229
+
230
+ if (!svix_id || !svix_timestamp || !svix_signature) {
231
+ return new Response('Missing svix headers', { status: 400 });
232
+ }
233
+
234
+ const payload = await req.json();
235
+ const body = JSON.stringify(payload);
236
+
237
+ const wh = new Webhook(WEBHOOK_SECRET);
238
+ let evt: WebhookEvent;
239
+
240
+ try {
241
+ evt = wh.verify(body, {
242
+ 'svix-id': svix_id,
243
+ 'svix-timestamp': svix_timestamp,
244
+ 'svix-signature': svix_signature,
245
+ }) as WebhookEvent;
246
+ } catch (err) {
247
+ return new Response('Invalid signature', { status: 400 });
248
+ }
249
+
250
+ const eventType = evt.type;
251
+
252
+ switch (eventType) {
253
+ case 'user.created':
254
+ const { id, email_addresses, first_name, last_name } = evt.data;
255
+ // Create user in your database
256
+ await db.user.create({
257
+ data: {
258
+ clerkId: id,
259
+ email: email_addresses[0]?.email_address,
260
+ name: \`\${first_name} \${last_name}\`,
261
+ },
262
+ });
263
+ break;
264
+
265
+ case 'user.updated':
266
+ // Update user in your database
267
+ break;
268
+
269
+ case 'user.deleted':
270
+ // Delete user from your database
271
+ break;
272
+ }
273
+
274
+ return new Response('OK', { status: 200 });
275
+ }
276
+ \`\`\`
277
+
278
+ ## Custom Session Claims
279
+ \`\`\`typescript
280
+ // In Clerk Dashboard, add custom claims to session token
281
+ // Or use middleware to add dynamic claims
282
+
283
+ // middleware.ts
284
+ import { clerkMiddleware } from '@clerk/nextjs/server';
285
+
286
+ export default clerkMiddleware(async (auth, req) => {
287
+ const { userId, sessionClaims } = auth();
288
+
289
+ // Access custom claims
290
+ const role = sessionClaims?.metadata?.role as string;
291
+
292
+ if (req.nextUrl.pathname.startsWith('/admin') && role !== 'admin') {
293
+ return Response.redirect(new URL('/unauthorized', req.url));
294
+ }
295
+ });
296
+
297
+ // Access in components
298
+ const { sessionClaims } = auth();
299
+ const userRole = sessionClaims?.metadata?.role;
300
+ \`\`\`
301
+
302
+ ## Environment Variables
303
+ \`\`\`bash
304
+ # .env.local
305
+ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
306
+ CLERK_SECRET_KEY=sk_test_...
307
+ CLERK_WEBHOOK_SECRET=whsec_...
308
+
309
+ # Custom pages (optional)
310
+ NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
311
+ NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
312
+ NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
313
+ NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/onboarding
314
+ \`\`\`
315
+
316
+ ## ❌ DON'T
317
+ - Expose CLERK_SECRET_KEY to client
318
+ - Skip webhook signature verification
319
+ - Forget middleware for protected routes
320
+ - Store sensitive data in public metadata
321
+
322
+ ## ✅ DO
323
+ - Use clerkMiddleware for route protection
324
+ - Sync user data via webhooks
325
+ - Use organizations for multi-tenant apps
326
+ - Use session claims for role-based access
327
+ - Customize appearance to match your brand
@@ -0,0 +1,367 @@
1
+ # Firebase Auth Skill
2
+
3
+ ## Setup
4
+ \`\`\`typescript
5
+ // lib/firebase.ts
6
+ import { initializeApp, getApps } from 'firebase/app';
7
+ import { getAuth } from 'firebase/auth';
8
+ import { getFirestore } from 'firebase/firestore';
9
+
10
+ const firebaseConfig = {
11
+ apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
12
+ authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
13
+ projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
14
+ storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
15
+ messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
16
+ appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
17
+ };
18
+
19
+ // Initialize Firebase (prevent multiple instances)
20
+ const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];
21
+
22
+ export const auth = getAuth(app);
23
+ export const db = getFirestore(app);
24
+ \`\`\`
25
+
26
+ ## Authentication Methods
27
+ \`\`\`typescript
28
+ import {
29
+ createUserWithEmailAndPassword,
30
+ signInWithEmailAndPassword,
31
+ signInWithPopup,
32
+ signInWithRedirect,
33
+ GoogleAuthProvider,
34
+ GithubAuthProvider,
35
+ sendPasswordResetEmail,
36
+ sendEmailVerification,
37
+ updateProfile,
38
+ signOut,
39
+ } from 'firebase/auth';
40
+ import { auth } from '@/lib/firebase';
41
+
42
+ // Email/Password Sign Up
43
+ async function signUp(email: string, password: string, displayName: string) {
44
+ const { user } = await createUserWithEmailAndPassword(auth, email, password);
45
+
46
+ // Update profile with display name
47
+ await updateProfile(user, { displayName });
48
+
49
+ // Send verification email
50
+ await sendEmailVerification(user);
51
+
52
+ return user;
53
+ }
54
+
55
+ // Email/Password Sign In
56
+ async function signIn(email: string, password: string) {
57
+ const { user } = await signInWithEmailAndPassword(auth, email, password);
58
+ return user;
59
+ }
60
+
61
+ // Google Sign In (Popup)
62
+ async function signInWithGoogle() {
63
+ const provider = new GoogleAuthProvider();
64
+ provider.addScope('profile');
65
+ provider.addScope('email');
66
+
67
+ const { user } = await signInWithPopup(auth, provider);
68
+ return user;
69
+ }
70
+
71
+ // Google Sign In (Redirect - better for mobile)
72
+ async function signInWithGoogleRedirect() {
73
+ const provider = new GoogleAuthProvider();
74
+ await signInWithRedirect(auth, provider);
75
+ }
76
+
77
+ // GitHub Sign In
78
+ async function signInWithGitHub() {
79
+ const provider = new GithubAuthProvider();
80
+ provider.addScope('read:user');
81
+
82
+ const { user } = await signInWithPopup(auth, provider);
83
+ return user;
84
+ }
85
+
86
+ // Password Reset
87
+ async function resetPassword(email: string) {
88
+ await sendPasswordResetEmail(auth, email, {
89
+ url: \`\${window.location.origin}/login\`,
90
+ });
91
+ }
92
+
93
+ // Sign Out
94
+ async function logout() {
95
+ await signOut(auth);
96
+ }
97
+ \`\`\`
98
+
99
+ ## Auth State Listener
100
+ \`\`\`typescript
101
+ import { onAuthStateChanged, User } from 'firebase/auth';
102
+ import { auth } from '@/lib/firebase';
103
+
104
+ // React Hook
105
+ import { useState, useEffect, createContext, useContext } from 'react';
106
+
107
+ interface AuthContextType {
108
+ user: User | null;
109
+ loading: boolean;
110
+ }
111
+
112
+ const AuthContext = createContext<AuthContextType>({ user: null, loading: true });
113
+
114
+ export function AuthProvider({ children }: { children: React.ReactNode }) {
115
+ const [user, setUser] = useState<User | null>(null);
116
+ const [loading, setLoading] = useState(true);
117
+
118
+ useEffect(() => {
119
+ const unsubscribe = onAuthStateChanged(auth, (user) => {
120
+ setUser(user);
121
+ setLoading(false);
122
+ });
123
+
124
+ return () => unsubscribe();
125
+ }, []);
126
+
127
+ return (
128
+ <AuthContext.Provider value={{ user, loading }}>
129
+ {children}
130
+ </AuthContext.Provider>
131
+ );
132
+ }
133
+
134
+ export function useAuth() {
135
+ const context = useContext(AuthContext);
136
+ if (!context) {
137
+ throw new Error('useAuth must be used within AuthProvider');
138
+ }
139
+ return context;
140
+ }
141
+
142
+ // Usage
143
+ function ProtectedComponent() {
144
+ const { user, loading } = useAuth();
145
+
146
+ if (loading) return <div>Loading...</div>;
147
+ if (!user) return <div>Please sign in</div>;
148
+
149
+ return <div>Welcome, {user.displayName}</div>;
150
+ }
151
+ \`\`\`
152
+
153
+ ## Protected Routes
154
+ \`\`\`tsx
155
+ 'use client';
156
+
157
+ import { useAuth } from '@/hooks/useAuth';
158
+ import { useRouter } from 'next/navigation';
159
+ import { useEffect } from 'react';
160
+
161
+ export function ProtectedRoute({ children }: { children: React.ReactNode }) {
162
+ const { user, loading } = useAuth();
163
+ const router = useRouter();
164
+
165
+ useEffect(() => {
166
+ if (!loading && !user) {
167
+ router.push('/login');
168
+ }
169
+ }, [user, loading, router]);
170
+
171
+ if (loading) {
172
+ return <div>Loading...</div>;
173
+ }
174
+
175
+ if (!user) {
176
+ return null;
177
+ }
178
+
179
+ return <>{children}</>;
180
+ }
181
+
182
+ // Usage in page
183
+ export default function DashboardPage() {
184
+ return (
185
+ <ProtectedRoute>
186
+ <Dashboard />
187
+ </ProtectedRoute>
188
+ );
189
+ }
190
+ \`\`\`
191
+
192
+ ## ID Tokens for API Auth
193
+ \`\`\`typescript
194
+ // Client: Get ID token for API calls
195
+ async function getIdToken() {
196
+ const user = auth.currentUser;
197
+ if (!user) throw new Error('Not authenticated');
198
+
199
+ const token = await user.getIdToken();
200
+ return token;
201
+ }
202
+
203
+ // API call with token
204
+ async function fetchProtectedData() {
205
+ const token = await getIdToken();
206
+
207
+ const response = await fetch('/api/protected', {
208
+ headers: {
209
+ Authorization: \`Bearer \${token}\`,
210
+ },
211
+ });
212
+
213
+ return response.json();
214
+ }
215
+
216
+ // Server: Verify ID token (API route)
217
+ // app/api/protected/route.ts
218
+ import { getAuth } from 'firebase-admin/auth';
219
+ import { initializeApp, getApps, cert } from 'firebase-admin/app';
220
+
221
+ // Initialize Firebase Admin
222
+ if (getApps().length === 0) {
223
+ initializeApp({
224
+ credential: cert({
225
+ projectId: process.env.FIREBASE_PROJECT_ID,
226
+ clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
227
+ privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\\\n/g, '\\n'),
228
+ }),
229
+ });
230
+ }
231
+
232
+ export async function GET(request: Request) {
233
+ const authHeader = request.headers.get('Authorization');
234
+
235
+ if (!authHeader?.startsWith('Bearer ')) {
236
+ return Response.json({ error: 'Unauthorized' }, { status: 401 });
237
+ }
238
+
239
+ const token = authHeader.split('Bearer ')[1];
240
+
241
+ try {
242
+ const decodedToken = await getAuth().verifyIdToken(token);
243
+ const userId = decodedToken.uid;
244
+
245
+ // Proceed with authenticated request
246
+ return Response.json({ userId });
247
+ } catch (error) {
248
+ return Response.json({ error: 'Invalid token' }, { status: 401 });
249
+ }
250
+ }
251
+ \`\`\`
252
+
253
+ ## Custom Claims (Roles)
254
+ \`\`\`typescript
255
+ // Server-side: Set custom claims (Firebase Admin)
256
+ import { getAuth } from 'firebase-admin/auth';
257
+
258
+ async function setUserRole(uid: string, role: string) {
259
+ await getAuth().setCustomUserClaims(uid, { role });
260
+ }
261
+
262
+ // Client-side: Check claims
263
+ async function checkAdmin() {
264
+ const user = auth.currentUser;
265
+ if (!user) return false;
266
+
267
+ const tokenResult = await user.getIdTokenResult();
268
+ return tokenResult.claims.role === 'admin';
269
+ }
270
+
271
+ // Force token refresh after claims change
272
+ await auth.currentUser?.getIdToken(true);
273
+ \`\`\`
274
+
275
+ ## Firestore Security Rules
276
+ \`\`\`javascript
277
+ // firestore.rules
278
+ rules_version = '2';
279
+ service cloud.firestore {
280
+ match /databases/{database}/documents {
281
+ // Users can only access their own data
282
+ match /users/{userId} {
283
+ allow read, write: if request.auth != null && request.auth.uid == userId;
284
+ }
285
+
286
+ // Posts are readable by all, writable by author
287
+ match /posts/{postId} {
288
+ allow read: if true;
289
+ allow create: if request.auth != null;
290
+ allow update, delete: if request.auth != null
291
+ && request.auth.uid == resource.data.authorId;
292
+ }
293
+
294
+ // Admin-only access
295
+ match /admin/{document=**} {
296
+ allow read, write: if request.auth != null
297
+ && request.auth.token.role == 'admin';
298
+ }
299
+ }
300
+ }
301
+ \`\`\`
302
+
303
+ ## Phone Authentication
304
+ \`\`\`typescript
305
+ import {
306
+ RecaptchaVerifier,
307
+ signInWithPhoneNumber,
308
+ ConfirmationResult,
309
+ } from 'firebase/auth';
310
+
311
+ let confirmationResult: ConfirmationResult;
312
+
313
+ // Setup reCAPTCHA
314
+ function setupRecaptcha() {
315
+ window.recaptchaVerifier = new RecaptchaVerifier(auth, 'recaptcha-container', {
316
+ size: 'invisible',
317
+ callback: () => {
318
+ // reCAPTCHA solved
319
+ },
320
+ });
321
+ }
322
+
323
+ // Send OTP
324
+ async function sendOTP(phoneNumber: string) {
325
+ const appVerifier = window.recaptchaVerifier;
326
+ confirmationResult = await signInWithPhoneNumber(auth, phoneNumber, appVerifier);
327
+ }
328
+
329
+ // Verify OTP
330
+ async function verifyOTP(code: string) {
331
+ const result = await confirmationResult.confirm(code);
332
+ return result.user;
333
+ }
334
+ \`\`\`
335
+
336
+ ## User Profile Updates
337
+ \`\`\`typescript
338
+ import { updateProfile, updateEmail, updatePassword, User } from 'firebase/auth';
339
+
340
+ async function updateUserProfile(user: User, data: { displayName?: string; photoURL?: string }) {
341
+ await updateProfile(user, data);
342
+ }
343
+
344
+ async function updateUserEmail(user: User, newEmail: string) {
345
+ await updateEmail(user, newEmail);
346
+ // User needs to re-verify email
347
+ }
348
+
349
+ async function updateUserPassword(user: User, newPassword: string) {
350
+ await updatePassword(user, newPassword);
351
+ }
352
+ \`\`\`
353
+
354
+ ## ❌ DON'T
355
+ - Store Firebase config with private keys on client
356
+ - Skip email verification for sensitive apps
357
+ - Use currentUser without waiting for auth state
358
+ - Expose Firebase Admin credentials to client
359
+ - Skip Firestore security rules
360
+
361
+ ## ✅ DO
362
+ - Use onAuthStateChanged for auth state
363
+ - Verify ID tokens on server for API calls
364
+ - Use custom claims for role-based access
365
+ - Implement proper Firestore security rules
366
+ - Handle all auth errors gracefully
367
+ - Force token refresh after claims change