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,359 @@
1
+ # NextAuth.js Skill
2
+
3
+ ## Setup (App Router)
4
+ \`\`\`typescript
5
+ // app/api/auth/[...nextauth]/route.ts
6
+ import NextAuth from 'next-auth';
7
+ import { authOptions } from '@/lib/auth';
8
+
9
+ const handler = NextAuth(authOptions);
10
+ export { handler as GET, handler as POST };
11
+
12
+ // lib/auth.ts
13
+ import { NextAuthOptions } from 'next-auth';
14
+ import GoogleProvider from 'next-auth/providers/google';
15
+ import GitHubProvider from 'next-auth/providers/github';
16
+ import CredentialsProvider from 'next-auth/providers/credentials';
17
+ import { PrismaAdapter } from '@auth/prisma-adapter';
18
+ import { prisma } from '@/lib/prisma';
19
+ import bcrypt from 'bcryptjs';
20
+
21
+ export const authOptions: NextAuthOptions = {
22
+ adapter: PrismaAdapter(prisma),
23
+
24
+ providers: [
25
+ // OAuth Providers
26
+ GoogleProvider({
27
+ clientId: process.env.GOOGLE_CLIENT_ID!,
28
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
29
+ }),
30
+
31
+ GitHubProvider({
32
+ clientId: process.env.GITHUB_ID!,
33
+ clientSecret: process.env.GITHUB_SECRET!,
34
+ }),
35
+
36
+ // Credentials Provider (email/password)
37
+ CredentialsProvider({
38
+ name: 'credentials',
39
+ credentials: {
40
+ email: { label: 'Email', type: 'email' },
41
+ password: { label: 'Password', type: 'password' },
42
+ },
43
+ async authorize(credentials) {
44
+ if (!credentials?.email || !credentials?.password) {
45
+ throw new Error('Invalid credentials');
46
+ }
47
+
48
+ const user = await prisma.user.findUnique({
49
+ where: { email: credentials.email },
50
+ });
51
+
52
+ if (!user || !user.password) {
53
+ throw new Error('Invalid credentials');
54
+ }
55
+
56
+ const isValid = await bcrypt.compare(credentials.password, user.password);
57
+
58
+ if (!isValid) {
59
+ throw new Error('Invalid credentials');
60
+ }
61
+
62
+ return {
63
+ id: user.id,
64
+ email: user.email,
65
+ name: user.name,
66
+ image: user.image,
67
+ };
68
+ },
69
+ }),
70
+ ],
71
+
72
+ session: {
73
+ strategy: 'jwt', // Required for credentials provider
74
+ maxAge: 30 * 24 * 60 * 60, // 30 days
75
+ },
76
+
77
+ pages: {
78
+ signIn: '/auth/signin',
79
+ signOut: '/auth/signout',
80
+ error: '/auth/error',
81
+ verifyRequest: '/auth/verify-request',
82
+ },
83
+
84
+ callbacks: {
85
+ async jwt({ token, user, account }) {
86
+ // First sign in
87
+ if (user) {
88
+ token.id = user.id;
89
+ token.role = user.role;
90
+ }
91
+ if (account) {
92
+ token.accessToken = account.access_token;
93
+ }
94
+ return token;
95
+ },
96
+
97
+ async session({ session, token }) {
98
+ if (session.user) {
99
+ session.user.id = token.id as string;
100
+ session.user.role = token.role as string;
101
+ }
102
+ return session;
103
+ },
104
+
105
+ async signIn({ user, account, profile }) {
106
+ // Custom sign-in logic
107
+ if (account?.provider === 'google') {
108
+ return profile?.email?.endsWith('@company.com') ?? false;
109
+ }
110
+ return true;
111
+ },
112
+
113
+ async redirect({ url, baseUrl }) {
114
+ // Custom redirect logic
115
+ if (url.startsWith(baseUrl)) return url;
116
+ if (url.startsWith('/')) return \`\${baseUrl}\${url}\`;
117
+ return baseUrl;
118
+ },
119
+ },
120
+
121
+ events: {
122
+ async signIn({ user, account, isNewUser }) {
123
+ if (isNewUser) {
124
+ // Send welcome email, create default settings, etc.
125
+ console.log('New user signed up:', user.email);
126
+ }
127
+ },
128
+ async signOut({ token }) {
129
+ // Cleanup on sign out
130
+ },
131
+ },
132
+
133
+ debug: process.env.NODE_ENV === 'development',
134
+ };
135
+ \`\`\`
136
+
137
+ ## TypeScript Types
138
+ \`\`\`typescript
139
+ // types/next-auth.d.ts
140
+ import { DefaultSession, DefaultUser } from 'next-auth';
141
+ import { JWT, DefaultJWT } from 'next-auth/jwt';
142
+
143
+ declare module 'next-auth' {
144
+ interface Session {
145
+ user: {
146
+ id: string;
147
+ role: string;
148
+ } & DefaultSession['user'];
149
+ }
150
+
151
+ interface User extends DefaultUser {
152
+ role: string;
153
+ }
154
+ }
155
+
156
+ declare module 'next-auth/jwt' {
157
+ interface JWT extends DefaultJWT {
158
+ id: string;
159
+ role: string;
160
+ accessToken?: string;
161
+ }
162
+ }
163
+ \`\`\`
164
+
165
+ ## Server Components (App Router)
166
+ \`\`\`typescript
167
+ // Get session in Server Component
168
+ import { getServerSession } from 'next-auth';
169
+ import { authOptions } from '@/lib/auth';
170
+ import { redirect } from 'next/navigation';
171
+
172
+ export default async function ProtectedPage() {
173
+ const session = await getServerSession(authOptions);
174
+
175
+ if (!session) {
176
+ redirect('/auth/signin');
177
+ }
178
+
179
+ return (
180
+ <div>
181
+ <h1>Welcome, {session.user.name}</h1>
182
+ <p>User ID: {session.user.id}</p>
183
+ <p>Role: {session.user.role}</p>
184
+ </div>
185
+ );
186
+ }
187
+
188
+ // API Route Protection
189
+ import { getServerSession } from 'next-auth';
190
+ import { authOptions } from '@/lib/auth';
191
+ import { NextResponse } from 'next/server';
192
+
193
+ export async function GET() {
194
+ const session = await getServerSession(authOptions);
195
+
196
+ if (!session) {
197
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
198
+ }
199
+
200
+ // Proceed with authenticated request
201
+ return NextResponse.json({ user: session.user });
202
+ }
203
+ \`\`\`
204
+
205
+ ## Client Components
206
+ \`\`\`tsx
207
+ 'use client';
208
+
209
+ import { useSession, signIn, signOut } from 'next-auth/react';
210
+
211
+ export function AuthButton() {
212
+ const { data: session, status } = useSession();
213
+
214
+ if (status === 'loading') {
215
+ return <div>Loading...</div>;
216
+ }
217
+
218
+ if (session) {
219
+ return (
220
+ <div>
221
+ <p>Signed in as {session.user?.email}</p>
222
+ <button onClick={() => signOut({ callbackUrl: '/' })}>
223
+ Sign out
224
+ </button>
225
+ </div>
226
+ );
227
+ }
228
+
229
+ return (
230
+ <div>
231
+ <button onClick={() => signIn('google')}>Sign in with Google</button>
232
+ <button onClick={() => signIn('github')}>Sign in with GitHub</button>
233
+ <button onClick={() => signIn('credentials', { email: '', password: '' })}>
234
+ Sign in with Email
235
+ </button>
236
+ </div>
237
+ );
238
+ }
239
+
240
+ // Session Provider (required in layout)
241
+ // app/providers.tsx
242
+ 'use client';
243
+
244
+ import { SessionProvider } from 'next-auth/react';
245
+
246
+ export function Providers({ children }: { children: React.ReactNode }) {
247
+ return <SessionProvider>{children}</SessionProvider>;
248
+ }
249
+
250
+ // app/layout.tsx
251
+ import { Providers } from './providers';
252
+
253
+ export default function RootLayout({ children }) {
254
+ return (
255
+ <html>
256
+ <body>
257
+ <Providers>{children}</Providers>
258
+ </body>
259
+ </html>
260
+ );
261
+ }
262
+ \`\`\`
263
+
264
+ ## Middleware (Route Protection)
265
+ \`\`\`typescript
266
+ // middleware.ts
267
+ import { withAuth } from 'next-auth/middleware';
268
+ import { NextResponse } from 'next/server';
269
+
270
+ export default withAuth(
271
+ function middleware(req) {
272
+ const token = req.nextauth.token;
273
+ const path = req.nextUrl.pathname;
274
+
275
+ // Admin-only routes
276
+ if (path.startsWith('/admin') && token?.role !== 'admin') {
277
+ return NextResponse.redirect(new URL('/unauthorized', req.url));
278
+ }
279
+
280
+ return NextResponse.next();
281
+ },
282
+ {
283
+ callbacks: {
284
+ authorized: ({ token }) => !!token,
285
+ },
286
+ }
287
+ );
288
+
289
+ export const config = {
290
+ matcher: ['/dashboard/:path*', '/admin/:path*', '/api/protected/:path*'],
291
+ };
292
+ \`\`\`
293
+
294
+ ## Prisma Schema
295
+ \`\`\`prisma
296
+ // prisma/schema.prisma
297
+ model User {
298
+ id String @id @default(cuid())
299
+ name String?
300
+ email String? @unique
301
+ emailVerified DateTime?
302
+ image String?
303
+ password String? // For credentials provider
304
+ role String @default("user")
305
+ accounts Account[]
306
+ sessions Session[]
307
+ createdAt DateTime @default(now())
308
+ updatedAt DateTime @updatedAt
309
+ }
310
+
311
+ model Account {
312
+ id String @id @default(cuid())
313
+ userId String
314
+ type String
315
+ provider String
316
+ providerAccountId String
317
+ refresh_token String? @db.Text
318
+ access_token String? @db.Text
319
+ expires_at Int?
320
+ token_type String?
321
+ scope String?
322
+ id_token String? @db.Text
323
+ session_state String?
324
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
325
+
326
+ @@unique([provider, providerAccountId])
327
+ }
328
+
329
+ model Session {
330
+ id String @id @default(cuid())
331
+ sessionToken String @unique
332
+ userId String
333
+ expires DateTime
334
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
335
+ }
336
+
337
+ model VerificationToken {
338
+ identifier String
339
+ token String @unique
340
+ expires DateTime
341
+
342
+ @@unique([identifier, token])
343
+ }
344
+ \`\`\`
345
+
346
+ ## ❌ DON'T
347
+ - Store sensitive data in JWT (it's readable client-side)
348
+ - Use credentials provider without HTTPS
349
+ - Forget to add NEXTAUTH_SECRET to env
350
+ - Skip session validation in API routes
351
+ - Expose internal user IDs unnecessarily
352
+
353
+ ## ✅ DO
354
+ - Use environment variables for secrets
355
+ - Implement proper session callbacks
356
+ - Add TypeScript declarations for extended types
357
+ - Use middleware for route protection
358
+ - Validate roles in callbacks and middleware
359
+ - Use database adapter for persistent sessions
@@ -0,0 +1,368 @@
1
+ # Supabase Auth Skill
2
+
3
+ ## Setup (Next.js App Router)
4
+ \`\`\`typescript
5
+ // lib/supabase/client.ts (Browser client)
6
+ import { createBrowserClient } from '@supabase/ssr';
7
+
8
+ export function createClient() {
9
+ return createBrowserClient(
10
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
11
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
12
+ );
13
+ }
14
+
15
+ // lib/supabase/server.ts (Server client)
16
+ import { createServerClient, type CookieOptions } from '@supabase/ssr';
17
+ import { cookies } from 'next/headers';
18
+
19
+ export function createClient() {
20
+ const cookieStore = cookies();
21
+
22
+ return createServerClient(
23
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
24
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
25
+ {
26
+ cookies: {
27
+ get(name: string) {
28
+ return cookieStore.get(name)?.value;
29
+ },
30
+ set(name: string, value: string, options: CookieOptions) {
31
+ cookieStore.set({ name, value, ...options });
32
+ },
33
+ remove(name: string, options: CookieOptions) {
34
+ cookieStore.set({ name, value: '', ...options });
35
+ },
36
+ },
37
+ }
38
+ );
39
+ }
40
+
41
+ // middleware.ts
42
+ import { createServerClient, type CookieOptions } from '@supabase/ssr';
43
+ import { NextResponse, type NextRequest } from 'next/server';
44
+
45
+ export async function middleware(request: NextRequest) {
46
+ let response = NextResponse.next({ request: { headers: request.headers } });
47
+
48
+ const supabase = createServerClient(
49
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
50
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
51
+ {
52
+ cookies: {
53
+ get(name: string) {
54
+ return request.cookies.get(name)?.value;
55
+ },
56
+ set(name: string, value: string, options: CookieOptions) {
57
+ response.cookies.set({ name, value, ...options });
58
+ },
59
+ remove(name: string, options: CookieOptions) {
60
+ response.cookies.set({ name, value: '', ...options });
61
+ },
62
+ },
63
+ }
64
+ );
65
+
66
+ // Refresh session if needed
67
+ const { data: { user } } = await supabase.auth.getUser();
68
+
69
+ // Protect routes
70
+ if (!user && request.nextUrl.pathname.startsWith('/dashboard')) {
71
+ return NextResponse.redirect(new URL('/login', request.url));
72
+ }
73
+
74
+ return response;
75
+ }
76
+
77
+ export const config = {
78
+ matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
79
+ };
80
+ \`\`\`
81
+
82
+ ## Authentication Methods
83
+ \`\`\`typescript
84
+ const supabase = createClient();
85
+
86
+ // Email/Password Sign Up
87
+ const { data, error } = await supabase.auth.signUp({
88
+ email: 'user@example.com',
89
+ password: 'password123',
90
+ options: {
91
+ data: {
92
+ full_name: 'John Doe',
93
+ avatar_url: 'https://example.com/avatar.png',
94
+ },
95
+ emailRedirectTo: \`\${window.location.origin}/auth/callback\`,
96
+ },
97
+ });
98
+
99
+ // Email/Password Sign In
100
+ const { data, error } = await supabase.auth.signInWithPassword({
101
+ email: 'user@example.com',
102
+ password: 'password123',
103
+ });
104
+
105
+ // OAuth Sign In
106
+ const { data, error } = await supabase.auth.signInWithOAuth({
107
+ provider: 'google', // or 'github', 'discord', etc.
108
+ options: {
109
+ redirectTo: \`\${window.location.origin}/auth/callback\`,
110
+ },
111
+ });
112
+
113
+ // Magic Link
114
+ const { data, error } = await supabase.auth.signInWithOtp({
115
+ email: 'user@example.com',
116
+ options: {
117
+ emailRedirectTo: \`\${window.location.origin}/auth/callback\`,
118
+ },
119
+ });
120
+
121
+ // Phone/SMS OTP
122
+ const { data, error } = await supabase.auth.signInWithOtp({
123
+ phone: '+15551234567',
124
+ });
125
+
126
+ // Verify OTP
127
+ const { data, error } = await supabase.auth.verifyOtp({
128
+ phone: '+15551234567',
129
+ token: '123456',
130
+ type: 'sms',
131
+ });
132
+
133
+ // Sign Out
134
+ await supabase.auth.signOut();
135
+ \`\`\`
136
+
137
+ ## Auth Callback Handler
138
+ \`\`\`typescript
139
+ // app/auth/callback/route.ts
140
+ import { createClient } from '@/lib/supabase/server';
141
+ import { NextResponse } from 'next/server';
142
+
143
+ export async function GET(request: Request) {
144
+ const requestUrl = new URL(request.url);
145
+ const code = requestUrl.searchParams.get('code');
146
+
147
+ if (code) {
148
+ const supabase = createClient();
149
+ await supabase.auth.exchangeCodeForSession(code);
150
+ }
151
+
152
+ return NextResponse.redirect(new URL('/dashboard', request.url));
153
+ }
154
+ \`\`\`
155
+
156
+ ## Server Components
157
+ \`\`\`typescript
158
+ // app/dashboard/page.tsx
159
+ import { createClient } from '@/lib/supabase/server';
160
+ import { redirect } from 'next/navigation';
161
+
162
+ export default async function DashboardPage() {
163
+ const supabase = createClient();
164
+
165
+ const { data: { user }, error } = await supabase.auth.getUser();
166
+
167
+ if (!user) {
168
+ redirect('/login');
169
+ }
170
+
171
+ // Fetch user's data
172
+ const { data: profile } = await supabase
173
+ .from('profiles')
174
+ .select('*')
175
+ .eq('id', user.id)
176
+ .single();
177
+
178
+ return (
179
+ <div>
180
+ <h1>Welcome, {profile?.full_name || user.email}</h1>
181
+ <p>Email: {user.email}</p>
182
+ </div>
183
+ );
184
+ }
185
+ \`\`\`
186
+
187
+ ## Client Components
188
+ \`\`\`tsx
189
+ 'use client';
190
+
191
+ import { createClient } from '@/lib/supabase/client';
192
+ import { useEffect, useState } from 'react';
193
+ import { User, Session } from '@supabase/supabase-js';
194
+
195
+ export function useAuth() {
196
+ const supabase = createClient();
197
+ const [user, setUser] = useState<User | null>(null);
198
+ const [loading, setLoading] = useState(true);
199
+
200
+ useEffect(() => {
201
+ // Get initial session
202
+ supabase.auth.getSession().then(({ data: { session } }) => {
203
+ setUser(session?.user ?? null);
204
+ setLoading(false);
205
+ });
206
+
207
+ // Listen for auth changes
208
+ const { data: { subscription } } = supabase.auth.onAuthStateChange(
209
+ (event, session) => {
210
+ setUser(session?.user ?? null);
211
+
212
+ if (event === 'SIGNED_IN') {
213
+ // Handle sign in
214
+ }
215
+ if (event === 'SIGNED_OUT') {
216
+ // Handle sign out
217
+ }
218
+ }
219
+ );
220
+
221
+ return () => subscription.unsubscribe();
222
+ }, []);
223
+
224
+ return { user, loading };
225
+ }
226
+
227
+ // Login Form Component
228
+ export function LoginForm() {
229
+ const supabase = createClient();
230
+ const [email, setEmail] = useState('');
231
+ const [password, setPassword] = useState('');
232
+ const [loading, setLoading] = useState(false);
233
+ const [error, setError] = useState<string | null>(null);
234
+
235
+ const handleLogin = async (e: React.FormEvent) => {
236
+ e.preventDefault();
237
+ setLoading(true);
238
+ setError(null);
239
+
240
+ const { error } = await supabase.auth.signInWithPassword({
241
+ email,
242
+ password,
243
+ });
244
+
245
+ if (error) {
246
+ setError(error.message);
247
+ }
248
+
249
+ setLoading(false);
250
+ };
251
+
252
+ return (
253
+ <form onSubmit={handleLogin}>
254
+ <input
255
+ type="email"
256
+ value={email}
257
+ onChange={(e) => setEmail(e.target.value)}
258
+ placeholder="Email"
259
+ required
260
+ />
261
+ <input
262
+ type="password"
263
+ value={password}
264
+ onChange={(e) => setPassword(e.target.value)}
265
+ placeholder="Password"
266
+ required
267
+ />
268
+ {error && <p className="error">{error}</p>}
269
+ <button type="submit" disabled={loading}>
270
+ {loading ? 'Loading...' : 'Sign In'}
271
+ </button>
272
+ </form>
273
+ );
274
+ }
275
+ \`\`\`
276
+
277
+ ## Row Level Security (RLS)
278
+ \`\`\`sql
279
+ -- Enable RLS on tables
280
+ ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
281
+ ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
282
+
283
+ -- Users can only read their own profile
284
+ CREATE POLICY "Users can view own profile"
285
+ ON profiles FOR SELECT
286
+ USING (auth.uid() = id);
287
+
288
+ -- Users can update their own profile
289
+ CREATE POLICY "Users can update own profile"
290
+ ON profiles FOR UPDATE
291
+ USING (auth.uid() = id);
292
+
293
+ -- Users can read all published posts
294
+ CREATE POLICY "Anyone can view published posts"
295
+ ON posts FOR SELECT
296
+ USING (published = true);
297
+
298
+ -- Users can CRUD their own posts
299
+ CREATE POLICY "Users can manage own posts"
300
+ ON posts FOR ALL
301
+ USING (auth.uid() = author_id);
302
+
303
+ -- Insert policy for new users
304
+ CREATE POLICY "Users can create profile"
305
+ ON profiles FOR INSERT
306
+ WITH CHECK (auth.uid() = id);
307
+ \`\`\`
308
+
309
+ ## Database Triggers for Auth
310
+ \`\`\`sql
311
+ -- Auto-create profile on user signup
312
+ CREATE OR REPLACE FUNCTION public.handle_new_user()
313
+ RETURNS TRIGGER AS $$
314
+ BEGIN
315
+ INSERT INTO public.profiles (id, email, full_name, avatar_url)
316
+ VALUES (
317
+ new.id,
318
+ new.email,
319
+ new.raw_user_meta_data->>'full_name',
320
+ new.raw_user_meta_data->>'avatar_url'
321
+ );
322
+ RETURN new;
323
+ END;
324
+ $$ LANGUAGE plpgsql SECURITY DEFINER;
325
+
326
+ CREATE TRIGGER on_auth_user_created
327
+ AFTER INSERT ON auth.users
328
+ FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
329
+ \`\`\`
330
+
331
+ ## Password Management
332
+ \`\`\`typescript
333
+ // Update password (when logged in)
334
+ const { data, error } = await supabase.auth.updateUser({
335
+ password: 'new-password',
336
+ });
337
+
338
+ // Reset password (send email)
339
+ const { data, error } = await supabase.auth.resetPasswordForEmail(
340
+ 'user@example.com',
341
+ { redirectTo: \`\${window.location.origin}/auth/reset-password\` }
342
+ );
343
+
344
+ // Handle reset callback
345
+ // app/auth/reset-password/page.tsx
346
+ export default function ResetPasswordPage() {
347
+ const handleReset = async (newPassword: string) => {
348
+ const { error } = await supabase.auth.updateUser({
349
+ password: newPassword,
350
+ });
351
+ };
352
+ }
353
+ \`\`\`
354
+
355
+ ## ❌ DON'T
356
+ - Use anon key for server-side admin operations
357
+ - Skip RLS policies on user data tables
358
+ - Store session data manually (use SSR helpers)
359
+ - Forget to handle auth state changes
360
+ - Expose service role key to client
361
+
362
+ ## ✅ DO
363
+ - Enable RLS on all user-related tables
364
+ - Use SSR package for Next.js App Router
365
+ - Handle auth callback for OAuth/magic links
366
+ - Create database triggers for user profiles
367
+ - Use middleware for session refresh
368
+ - Handle all auth state changes