@smicolon/ai-kit 0.3.1 → 0.4.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 +73 -40
- package/dist/index.js +260 -126
- package/package.json +5 -5
- package/.claude-plugin/marketplace.json +0 -373
- package/packs/architect/CHANGELOG.md +0 -17
- package/packs/architect/README.md +0 -58
- package/packs/architect/agents/system-architect.md +0 -768
- package/packs/architect/commands/diagram-create.md +0 -300
- package/packs/better-auth/.mcp.json +0 -14
- package/packs/better-auth/CHANGELOG.md +0 -26
- package/packs/better-auth/README.md +0 -125
- package/packs/better-auth/agents/auth-architect.md +0 -278
- package/packs/better-auth/commands/auth-provider-add.md +0 -265
- package/packs/better-auth/commands/auth-setup.md +0 -298
- package/packs/better-auth/skills/auth-security/SKILL.md +0 -425
- package/packs/better-auth/skills/better-auth-patterns/SKILL.md +0 -455
- package/packs/dev-loop/CHANGELOG.md +0 -69
- package/packs/dev-loop/README.md +0 -155
- package/packs/dev-loop/commands/cancel-dev.md +0 -21
- package/packs/dev-loop/commands/dev-loop.md +0 -72
- package/packs/dev-loop/commands/dev-plan.md +0 -351
- package/packs/dev-loop/hooks/hooks.json +0 -15
- package/packs/dev-loop/hooks/stop-hook.sh +0 -178
- package/packs/dev-loop/scripts/setup-dev-loop.sh +0 -194
- package/packs/dev-loop/skills/tdd-planner/SKILL.md +0 -249
- package/packs/dev-loop/skills/tdd-planner/references/framework-patterns.md +0 -874
- package/packs/dev-loop/skills/tdd-planner/references/good-example.md +0 -260
- package/packs/dev-loop/skills/tdd-planner/references/plan-template.md +0 -275
- package/packs/django/CHANGELOG.md +0 -39
- package/packs/django/README.md +0 -92
- package/packs/django/agents/django-architect.md +0 -182
- package/packs/django/agents/django-builder.md +0 -250
- package/packs/django/agents/django-feature-based.md +0 -420
- package/packs/django/agents/django-reviewer.md +0 -253
- package/packs/django/agents/django-tester.md +0 -230
- package/packs/django/commands/api-endpoint.md +0 -285
- package/packs/django/commands/model-create.md +0 -178
- package/packs/django/commands/test-generate.md +0 -325
- package/packs/django/rules/migrations.md +0 -138
- package/packs/django/rules/models.md +0 -167
- package/packs/django/rules/serializers.md +0 -126
- package/packs/django/rules/services.md +0 -131
- package/packs/django/rules/tests.md +0 -140
- package/packs/django/rules/views.md +0 -102
- package/packs/django/skills/import-convention-enforcer/SKILL.md +0 -226
- package/packs/django/skills/import-convention-enforcer/patterns/django-imports.md +0 -343
- package/packs/django/skills/migration-safety-checker/SKILL.md +0 -375
- package/packs/django/skills/model-entity-validator/SKILL.md +0 -298
- package/packs/django/skills/performance-optimizer/SKILL.md +0 -447
- package/packs/django/skills/red-phase-verifier/SKILL.md +0 -180
- package/packs/django/skills/security-first-validator/SKILL.md +0 -435
- package/packs/django/skills/test-coverage-advisor/SKILL.md +0 -394
- package/packs/django/skills/test-validity-checker/SKILL.md +0 -194
- package/packs/failure-log/CHANGELOG.md +0 -20
- package/packs/failure-log/README.md +0 -168
- package/packs/failure-log/commands/failure-add.md +0 -106
- package/packs/failure-log/commands/failure-list.md +0 -89
- package/packs/failure-log/hooks/hooks.json +0 -16
- package/packs/failure-log/hooks/scripts/inject-failures.sh +0 -64
- package/packs/failure-log/skills/failure-log-manager/SKILL.md +0 -164
- package/packs/flutter/CHANGELOG.md +0 -19
- package/packs/flutter/README.md +0 -170
- package/packs/flutter/agents/flutter-architect.md +0 -166
- package/packs/flutter/agents/flutter-builder.md +0 -303
- package/packs/flutter/agents/release-manager.md +0 -355
- package/packs/flutter/commands/fastlane-setup.md +0 -188
- package/packs/flutter/commands/flutter-build.md +0 -90
- package/packs/flutter/commands/flutter-deploy.md +0 -133
- package/packs/flutter/commands/flutter-test.md +0 -117
- package/packs/flutter/commands/signing-setup.md +0 -209
- package/packs/flutter/hooks/hooks.json +0 -17
- package/packs/flutter/skills/fastlane-knowledge/SKILL.md +0 -193
- package/packs/flutter/skills/flutter-architecture/SKILL.md +0 -127
- package/packs/flutter/skills/store-publishing/SKILL.md +0 -163
- package/packs/hono/CHANGELOG.md +0 -19
- package/packs/hono/README.md +0 -143
- package/packs/hono/agents/hono-architect.md +0 -240
- package/packs/hono/agents/hono-builder.md +0 -285
- package/packs/hono/agents/hono-reviewer.md +0 -279
- package/packs/hono/agents/hono-tester.md +0 -346
- package/packs/hono/commands/middleware-create.md +0 -223
- package/packs/hono/commands/project-init.md +0 -306
- package/packs/hono/commands/route-create.md +0 -153
- package/packs/hono/commands/rpc-client.md +0 -263
- package/packs/hono/hooks/hooks.json +0 -4
- package/packs/hono/skills/cloudflare-bindings/SKILL.md +0 -408
- package/packs/hono/skills/hono-patterns/SKILL.md +0 -309
- package/packs/hono/skills/rpc-typesafe/SKILL.md +0 -388
- package/packs/hono/skills/zod-validation/SKILL.md +0 -332
- package/packs/nestjs/CHANGELOG.md +0 -29
- package/packs/nestjs/README.md +0 -75
- package/packs/nestjs/agents/nestjs-architect.md +0 -402
- package/packs/nestjs/agents/nestjs-builder.md +0 -301
- package/packs/nestjs/agents/nestjs-tester.md +0 -437
- package/packs/nestjs/commands/module-create.md +0 -369
- package/packs/nestjs/rules/controllers.md +0 -92
- package/packs/nestjs/rules/dto.md +0 -124
- package/packs/nestjs/rules/entities.md +0 -102
- package/packs/nestjs/rules/services.md +0 -106
- package/packs/nestjs/skills/barrel-export-manager/SKILL.md +0 -389
- package/packs/nestjs/skills/import-convention-enforcer/SKILL.md +0 -365
- package/packs/nextjs/CHANGELOG.md +0 -36
- package/packs/nextjs/README.md +0 -76
- package/packs/nextjs/agents/frontend-tester.md +0 -680
- package/packs/nextjs/agents/frontend-visual.md +0 -820
- package/packs/nextjs/agents/nextjs-architect.md +0 -331
- package/packs/nextjs/agents/nextjs-modular.md +0 -433
- package/packs/nextjs/commands/component-create.md +0 -398
- package/packs/nextjs/rules/api-routes.md +0 -129
- package/packs/nextjs/rules/components.md +0 -106
- package/packs/nextjs/rules/hooks.md +0 -132
- package/packs/nextjs/skills/accessibility-validator/SKILL.md +0 -445
- package/packs/nextjs/skills/import-convention-enforcer/SKILL.md +0 -399
- package/packs/nextjs/skills/react-form-validator/SKILL.md +0 -569
- package/packs/nuxtjs/CHANGELOG.md +0 -30
- package/packs/nuxtjs/README.md +0 -56
- package/packs/nuxtjs/agents/frontend-tester.md +0 -680
- package/packs/nuxtjs/agents/frontend-visual.md +0 -820
- package/packs/nuxtjs/agents/nuxtjs-architect.md +0 -537
- package/packs/nuxtjs/commands/component-create.md +0 -223
- package/packs/nuxtjs/rules/components.md +0 -101
- package/packs/nuxtjs/rules/composables.md +0 -118
- package/packs/nuxtjs/rules/server-routes.md +0 -127
- package/packs/nuxtjs/skills/accessibility-validator/SKILL.md +0 -183
- package/packs/nuxtjs/skills/import-convention-enforcer/SKILL.md +0 -196
- package/packs/nuxtjs/skills/veevalidate-form-validator/SKILL.md +0 -190
- package/packs/onboard/CHANGELOG.md +0 -22
- package/packs/onboard/README.md +0 -103
- package/packs/onboard/agents/onboard-guide.md +0 -118
- package/packs/onboard/commands/onboard.md +0 -313
- package/packs/onboard/skills/onboard-context-provider/SKILL.md +0 -98
- package/packs/tanstack-router/CHANGELOG.md +0 -30
- package/packs/tanstack-router/README.md +0 -113
- package/packs/tanstack-router/agents/tanstack-architect.md +0 -173
- package/packs/tanstack-router/agents/tanstack-builder.md +0 -360
- package/packs/tanstack-router/agents/tanstack-tester.md +0 -454
- package/packs/tanstack-router/commands/form-create.md +0 -313
- package/packs/tanstack-router/commands/query-create.md +0 -263
- package/packs/tanstack-router/commands/route-create.md +0 -190
- package/packs/tanstack-router/commands/table-create.md +0 -413
- package/packs/tanstack-router/skills/ai-patterns/SKILL.md +0 -370
- package/packs/tanstack-router/skills/db-patterns/SKILL.md +0 -346
- package/packs/tanstack-router/skills/devtools-patterns/SKILL.md +0 -415
- package/packs/tanstack-router/skills/form-patterns/SKILL.md +0 -425
- package/packs/tanstack-router/skills/pacer-patterns/SKILL.md +0 -341
- package/packs/tanstack-router/skills/query-patterns/SKILL.md +0 -359
- package/packs/tanstack-router/skills/router-patterns/SKILL.md +0 -285
- package/packs/tanstack-router/skills/store-patterns/SKILL.md +0 -351
- package/packs/tanstack-router/skills/table-patterns/SKILL.md +0 -531
- package/packs/tanstack-router/skills/tanstack-conventions/SKILL.md +0 -428
- package/packs/tanstack-router/skills/virtual-patterns/SKILL.md +0 -490
- package/packs/worktree/CHANGELOG.md +0 -45
- package/packs/worktree/README.md +0 -219
- package/packs/worktree/commands/wt.md +0 -93
- package/packs/worktree/scripts/wt.sh +0 -957
- package/packs/worktree/skills/worktree-manager/SKILL.md +0 -113
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: auth-setup
|
|
3
|
-
description: Initialize Better Auth with configuration and optional auth pages
|
|
4
|
-
args:
|
|
5
|
-
- name: with-pages
|
|
6
|
-
description: Include login, register, and forgot-password pages
|
|
7
|
-
required: false
|
|
8
|
-
- name: providers
|
|
9
|
-
description: Comma-separated list of social providers (google,github,discord)
|
|
10
|
-
required: false
|
|
11
|
-
- name: 2fa
|
|
12
|
-
description: Enable two-factor authentication
|
|
13
|
-
required: false
|
|
14
|
-
- name: passkeys
|
|
15
|
-
description: Enable passkey/WebAuthn support
|
|
16
|
-
required: false
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
# Setup Better Auth
|
|
20
|
-
|
|
21
|
-
Initialize Better Auth with full configuration for your React application.
|
|
22
|
-
|
|
23
|
-
## Instructions
|
|
24
|
-
|
|
25
|
-
1. **Install Dependencies**:
|
|
26
|
-
```bash
|
|
27
|
-
bun add better-auth
|
|
28
|
-
bun add -D @types/better-auth
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
2. **Create Server Configuration** at `src/lib/auth.ts`:
|
|
32
|
-
```typescript
|
|
33
|
-
import { betterAuth } from 'better-auth'
|
|
34
|
-
import { prismaAdapter } from 'better-auth/adapters/prisma'
|
|
35
|
-
import { prisma } from './prisma'
|
|
36
|
-
|
|
37
|
-
export const auth = betterAuth({
|
|
38
|
-
database: prismaAdapter(prisma, {
|
|
39
|
-
provider: 'postgresql', // or 'mysql', 'sqlite'
|
|
40
|
-
}),
|
|
41
|
-
|
|
42
|
-
emailAndPassword: {
|
|
43
|
-
enabled: true,
|
|
44
|
-
requireEmailVerification: true,
|
|
45
|
-
password: {
|
|
46
|
-
minLength: 12,
|
|
47
|
-
requireUppercase: true,
|
|
48
|
-
requireNumber: true,
|
|
49
|
-
},
|
|
50
|
-
sendVerificationEmail: async (user, token, url) => {
|
|
51
|
-
// TODO: Implement email sending
|
|
52
|
-
console.log('Verification URL:', url)
|
|
53
|
-
},
|
|
54
|
-
sendResetPasswordToken: async (user, token, url) => {
|
|
55
|
-
// TODO: Implement email sending
|
|
56
|
-
console.log('Reset URL:', url)
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
session: {
|
|
61
|
-
expiresIn: 60 * 60 * 24 * 7, // 7 days
|
|
62
|
-
updateAge: 60 * 60 * 24, // Extend daily
|
|
63
|
-
cookie: {
|
|
64
|
-
httpOnly: true,
|
|
65
|
-
secure: process.env.NODE_ENV === 'production',
|
|
66
|
-
sameSite: 'lax',
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
|
|
70
|
-
// Add social providers if requested
|
|
71
|
-
socialProviders: {
|
|
72
|
-
// Configured based on --providers flag
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
// Add plugins if requested
|
|
76
|
-
plugins: [
|
|
77
|
-
// Added based on --2fa and --passkeys flags
|
|
78
|
-
],
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
export type Auth = typeof auth
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
3. **Create Auth Client** at `src/auth/client.ts`:
|
|
85
|
-
```typescript
|
|
86
|
-
import { createAuthClient } from 'better-auth/react'
|
|
87
|
-
import type { Auth } from '@/lib/auth'
|
|
88
|
-
|
|
89
|
-
export const authClient = createAuthClient<Auth>({
|
|
90
|
-
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
export const {
|
|
94
|
-
signIn,
|
|
95
|
-
signUp,
|
|
96
|
-
signOut,
|
|
97
|
-
useSession,
|
|
98
|
-
getSession,
|
|
99
|
-
resetPassword,
|
|
100
|
-
verifyEmail,
|
|
101
|
-
} = authClient
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
4. **Create Auth Hooks** at `src/auth/hooks.ts`:
|
|
105
|
-
```typescript
|
|
106
|
-
import { useSession, signOut } from './client'
|
|
107
|
-
import { useNavigate } from '@tanstack/react-router'
|
|
108
|
-
import { useEffect } from 'react'
|
|
109
|
-
|
|
110
|
-
export function useAuth() {
|
|
111
|
-
const { data: session, isPending, error } = useSession()
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
user: session?.user ?? null,
|
|
115
|
-
session: session?.session ?? null,
|
|
116
|
-
isAuthenticated: !!session?.user,
|
|
117
|
-
isLoading: isPending,
|
|
118
|
-
error,
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function useRequireAuth() {
|
|
123
|
-
const auth = useAuth()
|
|
124
|
-
const navigate = useNavigate()
|
|
125
|
-
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
if (!auth.isLoading && !auth.isAuthenticated) {
|
|
128
|
-
navigate({ to: '/login' })
|
|
129
|
-
}
|
|
130
|
-
}, [auth.isLoading, auth.isAuthenticated, navigate])
|
|
131
|
-
|
|
132
|
-
return auth
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function useLogout() {
|
|
136
|
-
const navigate = useNavigate()
|
|
137
|
-
|
|
138
|
-
return async () => {
|
|
139
|
-
await signOut()
|
|
140
|
-
navigate({ to: '/login' })
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
5. **Update Root Route** at `src/routes/__root.tsx`:
|
|
146
|
-
```typescript
|
|
147
|
-
import { createRootRouteWithContext, Outlet } from '@tanstack/react-router'
|
|
148
|
-
import { getSession } from '@/auth/client'
|
|
149
|
-
import type { QueryClient } from '@tanstack/react-query'
|
|
150
|
-
|
|
151
|
-
interface RouterContext {
|
|
152
|
-
queryClient: QueryClient
|
|
153
|
-
session: Awaited<ReturnType<typeof getSession>>['data'] | null
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export const Route = createRootRouteWithContext<RouterContext>()({
|
|
157
|
-
beforeLoad: async () => {
|
|
158
|
-
const result = await getSession()
|
|
159
|
-
return { session: result?.data ?? null }
|
|
160
|
-
},
|
|
161
|
-
component: RootComponent,
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
function RootComponent() {
|
|
165
|
-
return (
|
|
166
|
-
<>
|
|
167
|
-
<Outlet />
|
|
168
|
-
</>
|
|
169
|
-
)
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
6. **Create Protected Route Layout** at `src/routes/_auth.tsx`:
|
|
174
|
-
```typescript
|
|
175
|
-
import { createFileRoute, Outlet, redirect } from '@tanstack/react-router'
|
|
176
|
-
|
|
177
|
-
export const Route = createFileRoute('/_auth')({
|
|
178
|
-
beforeLoad: async ({ context, location }) => {
|
|
179
|
-
if (!context.session) {
|
|
180
|
-
throw redirect({
|
|
181
|
-
to: '/login',
|
|
182
|
-
search: { redirect: location.pathname },
|
|
183
|
-
})
|
|
184
|
-
}
|
|
185
|
-
},
|
|
186
|
-
component: () => <Outlet />,
|
|
187
|
-
})
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
7. **Create Guest Route Layout** at `src/routes/_guest.tsx`:
|
|
191
|
-
```typescript
|
|
192
|
-
import { createFileRoute, Outlet, redirect } from '@tanstack/react-router'
|
|
193
|
-
|
|
194
|
-
export const Route = createFileRoute('/_guest')({
|
|
195
|
-
beforeLoad: async ({ context }) => {
|
|
196
|
-
if (context.session) {
|
|
197
|
-
throw redirect({ to: '/dashboard' })
|
|
198
|
-
}
|
|
199
|
-
},
|
|
200
|
-
component: () => <Outlet />,
|
|
201
|
-
})
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
8. **If --with-pages, Create Auth Pages**:
|
|
205
|
-
|
|
206
|
-
`src/routes/_guest.login.tsx`:
|
|
207
|
-
```typescript
|
|
208
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
209
|
-
import { LoginForm } from '@/features/auth/components/LoginForm'
|
|
210
|
-
import { SocialLoginButtons } from '@/features/auth/components/SocialLoginButtons'
|
|
211
|
-
|
|
212
|
-
export const Route = createFileRoute('/_guest/login')({
|
|
213
|
-
component: LoginPage,
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
function LoginPage() {
|
|
217
|
-
return (
|
|
218
|
-
<div className="min-h-screen flex items-center justify-center">
|
|
219
|
-
<div className="w-full max-w-md space-y-8">
|
|
220
|
-
<h1 className="text-2xl font-bold text-center">Sign In</h1>
|
|
221
|
-
<LoginForm />
|
|
222
|
-
<div className="relative">
|
|
223
|
-
<div className="absolute inset-0 flex items-center">
|
|
224
|
-
<div className="w-full border-t" />
|
|
225
|
-
</div>
|
|
226
|
-
<div className="relative flex justify-center text-sm">
|
|
227
|
-
<span className="bg-white px-2 text-gray-500">Or continue with</span>
|
|
228
|
-
</div>
|
|
229
|
-
</div>
|
|
230
|
-
<SocialLoginButtons />
|
|
231
|
-
</div>
|
|
232
|
-
</div>
|
|
233
|
-
)
|
|
234
|
-
}
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
`src/routes/_guest.register.tsx`:
|
|
238
|
-
```typescript
|
|
239
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
240
|
-
import { RegisterForm } from '@/features/auth/components/RegisterForm'
|
|
241
|
-
|
|
242
|
-
export const Route = createFileRoute('/_guest/register')({
|
|
243
|
-
component: RegisterPage,
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
function RegisterPage() {
|
|
247
|
-
return (
|
|
248
|
-
<div className="min-h-screen flex items-center justify-center">
|
|
249
|
-
<div className="w-full max-w-md space-y-8">
|
|
250
|
-
<h1 className="text-2xl font-bold text-center">Create Account</h1>
|
|
251
|
-
<RegisterForm />
|
|
252
|
-
</div>
|
|
253
|
-
</div>
|
|
254
|
-
)
|
|
255
|
-
}
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
9. **Create Auth Components** in `src/features/auth/components/`:
|
|
259
|
-
|
|
260
|
-
- `LoginForm.tsx` - Email/password login form
|
|
261
|
-
- `RegisterForm.tsx` - Registration form
|
|
262
|
-
- `ForgotPasswordForm.tsx` - Password reset request
|
|
263
|
-
- `ResetPasswordForm.tsx` - New password form
|
|
264
|
-
- `SocialLoginButtons.tsx` - OAuth provider buttons
|
|
265
|
-
|
|
266
|
-
10. **Add Environment Variables** to `.env`:
|
|
267
|
-
```bash
|
|
268
|
-
# Better Auth
|
|
269
|
-
BETTER_AUTH_SECRET=your-secret-key-min-32-chars
|
|
270
|
-
|
|
271
|
-
# Database
|
|
272
|
-
DATABASE_URL=postgresql://...
|
|
273
|
-
|
|
274
|
-
# Social Providers (if enabled)
|
|
275
|
-
GOOGLE_CLIENT_ID=
|
|
276
|
-
GOOGLE_CLIENT_SECRET=
|
|
277
|
-
GITHUB_CLIENT_ID=
|
|
278
|
-
GITHUB_CLIENT_SECRET=
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
11. **Run Database Migrations**:
|
|
282
|
-
```bash
|
|
283
|
-
bunx prisma db push
|
|
284
|
-
# or
|
|
285
|
-
bunx prisma migrate dev
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
## Quality Checklist
|
|
289
|
-
|
|
290
|
-
- [ ] Server auth config at `lib/auth.ts`
|
|
291
|
-
- [ ] Client at `auth/client.ts` with typed exports
|
|
292
|
-
- [ ] Session loaded in `__root.tsx` beforeLoad
|
|
293
|
-
- [ ] Protected routes use `_auth.tsx` layout
|
|
294
|
-
- [ ] Guest routes use `_guest.tsx` layout
|
|
295
|
-
- [ ] Environment variables documented
|
|
296
|
-
- [ ] Database adapter configured
|
|
297
|
-
- [ ] Email verification enabled
|
|
298
|
-
- [ ] Secure session cookies configured
|
|
@@ -1,425 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: Auth Security Patterns
|
|
3
|
-
description: >-
|
|
4
|
-
Auto-enforce authentication security best practices. Activates when
|
|
5
|
-
implementing password policies, rate limiting, session security, CSRF
|
|
6
|
-
protection, or security headers in auth flows.
|
|
7
|
-
version: 1.0.0
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# Auth Security Patterns
|
|
11
|
-
|
|
12
|
-
This skill enforces authentication security best practices for Better Auth implementations.
|
|
13
|
-
|
|
14
|
-
## Password Security
|
|
15
|
-
|
|
16
|
-
### Strong Password Policy
|
|
17
|
-
```typescript
|
|
18
|
-
import { betterAuth } from 'better-auth'
|
|
19
|
-
|
|
20
|
-
export const auth = betterAuth({
|
|
21
|
-
emailAndPassword: {
|
|
22
|
-
enabled: true,
|
|
23
|
-
password: {
|
|
24
|
-
minLength: 12,
|
|
25
|
-
maxLength: 128,
|
|
26
|
-
requireLowercase: true,
|
|
27
|
-
requireUppercase: true,
|
|
28
|
-
requireNumber: true,
|
|
29
|
-
requireSpecialChar: true,
|
|
30
|
-
// Custom validation
|
|
31
|
-
validate: (password) => {
|
|
32
|
-
// Check against common passwords
|
|
33
|
-
if (commonPasswords.includes(password.toLowerCase())) {
|
|
34
|
-
return 'Password is too common'
|
|
35
|
-
}
|
|
36
|
-
// Check for repeated characters
|
|
37
|
-
if (/(.)\1{2,}/.test(password)) {
|
|
38
|
-
return 'Password cannot have 3+ repeated characters'
|
|
39
|
-
}
|
|
40
|
-
return true
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
})
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### Password Hashing
|
|
48
|
-
```typescript
|
|
49
|
-
// Better Auth uses bcrypt by default with cost factor 10
|
|
50
|
-
// For higher security requirements:
|
|
51
|
-
export const auth = betterAuth({
|
|
52
|
-
advanced: {
|
|
53
|
-
password: {
|
|
54
|
-
hash: async (password) => {
|
|
55
|
-
const salt = await bcrypt.genSalt(12) // Higher cost
|
|
56
|
-
return bcrypt.hash(password, salt)
|
|
57
|
-
},
|
|
58
|
-
verify: async (password, hash) => {
|
|
59
|
-
return bcrypt.compare(password, hash)
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
})
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Rate Limiting
|
|
67
|
-
|
|
68
|
-
### Global Rate Limiting
|
|
69
|
-
```typescript
|
|
70
|
-
import { rateLimit } from 'better-auth/plugins/rate-limit'
|
|
71
|
-
|
|
72
|
-
export const auth = betterAuth({
|
|
73
|
-
plugins: [
|
|
74
|
-
rateLimit({
|
|
75
|
-
window: 60, // 1 minute window
|
|
76
|
-
max: 100, // 100 requests per window
|
|
77
|
-
keyGenerator: (req) => {
|
|
78
|
-
// Rate limit by IP
|
|
79
|
-
return req.headers.get('x-forwarded-for') || req.ip
|
|
80
|
-
},
|
|
81
|
-
}),
|
|
82
|
-
],
|
|
83
|
-
})
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### Endpoint-Specific Limits
|
|
87
|
-
```typescript
|
|
88
|
-
rateLimit({
|
|
89
|
-
endpoints: {
|
|
90
|
-
// Strict limits for auth endpoints
|
|
91
|
-
'sign-in': {
|
|
92
|
-
window: 300, // 5 minutes
|
|
93
|
-
max: 5, // 5 attempts
|
|
94
|
-
},
|
|
95
|
-
'sign-up': {
|
|
96
|
-
window: 3600, // 1 hour
|
|
97
|
-
max: 3, // 3 registrations
|
|
98
|
-
},
|
|
99
|
-
'reset-password': {
|
|
100
|
-
window: 3600, // 1 hour
|
|
101
|
-
max: 3, // 3 reset requests
|
|
102
|
-
},
|
|
103
|
-
'verify-email': {
|
|
104
|
-
window: 60, // 1 minute
|
|
105
|
-
max: 5, // 5 verification attempts
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
})
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### Progressive Delays
|
|
112
|
-
```typescript
|
|
113
|
-
rateLimit({
|
|
114
|
-
endpoints: {
|
|
115
|
-
'sign-in': {
|
|
116
|
-
window: 300,
|
|
117
|
-
max: 5,
|
|
118
|
-
// Add delay after failed attempts
|
|
119
|
-
onRateLimitExceeded: async (req, res) => {
|
|
120
|
-
const attempts = await getFailedAttempts(req.ip)
|
|
121
|
-
const delay = Math.min(attempts * 1000, 30000) // Max 30s
|
|
122
|
-
await new Promise(resolve => setTimeout(resolve, delay))
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
})
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
## Session Security
|
|
130
|
-
|
|
131
|
-
### Secure Session Configuration
|
|
132
|
-
```typescript
|
|
133
|
-
export const auth = betterAuth({
|
|
134
|
-
session: {
|
|
135
|
-
expiresIn: 60 * 60 * 24 * 7, // 7 days max
|
|
136
|
-
updateAge: 60 * 60 * 24, // Extend daily on activity
|
|
137
|
-
|
|
138
|
-
// Cookie settings
|
|
139
|
-
cookie: {
|
|
140
|
-
name: '__session',
|
|
141
|
-
httpOnly: true,
|
|
142
|
-
secure: process.env.NODE_ENV === 'production',
|
|
143
|
-
sameSite: 'lax',
|
|
144
|
-
path: '/',
|
|
145
|
-
domain: process.env.COOKIE_DOMAIN,
|
|
146
|
-
},
|
|
147
|
-
|
|
148
|
-
// Session cache (reduce DB lookups)
|
|
149
|
-
cookieCache: {
|
|
150
|
-
enabled: true,
|
|
151
|
-
maxAge: 60 * 5, // 5 minute cache
|
|
152
|
-
},
|
|
153
|
-
|
|
154
|
-
// Require fresh session for sensitive ops
|
|
155
|
-
freshAge: 60 * 10, // 10 minutes
|
|
156
|
-
},
|
|
157
|
-
})
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Session Invalidation
|
|
161
|
-
```typescript
|
|
162
|
-
import { signOut, useSession } from '@/auth/client'
|
|
163
|
-
|
|
164
|
-
// Sign out from current device
|
|
165
|
-
await signOut()
|
|
166
|
-
|
|
167
|
-
// Sign out from all devices
|
|
168
|
-
await signOut({ revokeAllSessions: true })
|
|
169
|
-
|
|
170
|
-
// Server-side: Invalidate specific session
|
|
171
|
-
await auth.api.invalidateSession({ sessionId })
|
|
172
|
-
|
|
173
|
-
// Server-side: Invalidate all user sessions
|
|
174
|
-
await auth.api.invalidateUserSessions({ userId })
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### Session Binding
|
|
178
|
-
```typescript
|
|
179
|
-
// Bind session to device fingerprint
|
|
180
|
-
export const auth = betterAuth({
|
|
181
|
-
session: {
|
|
182
|
-
// Store device info
|
|
183
|
-
onSessionCreated: async (session, user, request) => {
|
|
184
|
-
await prisma.session.update({
|
|
185
|
-
where: { id: session.id },
|
|
186
|
-
data: {
|
|
187
|
-
userAgent: request.headers.get('user-agent'),
|
|
188
|
-
ipAddress: request.ip,
|
|
189
|
-
},
|
|
190
|
-
})
|
|
191
|
-
},
|
|
192
|
-
// Validate on each request
|
|
193
|
-
onSessionValidate: async (session, request) => {
|
|
194
|
-
const storedIp = session.ipAddress
|
|
195
|
-
const currentIp = request.ip
|
|
196
|
-
|
|
197
|
-
// Warn on IP change (but don't block for mobile users)
|
|
198
|
-
if (storedIp !== currentIp) {
|
|
199
|
-
await logSecurityEvent('session_ip_change', {
|
|
200
|
-
sessionId: session.id,
|
|
201
|
-
oldIp: storedIp,
|
|
202
|
-
newIp: currentIp,
|
|
203
|
-
})
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return true
|
|
207
|
-
},
|
|
208
|
-
},
|
|
209
|
-
})
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
## CSRF Protection
|
|
213
|
-
|
|
214
|
-
### Token-Based CSRF
|
|
215
|
-
```typescript
|
|
216
|
-
export const auth = betterAuth({
|
|
217
|
-
csrf: {
|
|
218
|
-
enabled: true,
|
|
219
|
-
// Double submit cookie pattern
|
|
220
|
-
cookieName: '__csrf',
|
|
221
|
-
headerName: 'x-csrf-token',
|
|
222
|
-
// Token rotation
|
|
223
|
-
rotateOnAuthentication: true,
|
|
224
|
-
},
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
// Client: Include CSRF token
|
|
228
|
-
const csrfToken = getCookie('__csrf')
|
|
229
|
-
await fetch('/api/auth/sign-out', {
|
|
230
|
-
method: 'POST',
|
|
231
|
-
headers: {
|
|
232
|
-
'x-csrf-token': csrfToken,
|
|
233
|
-
},
|
|
234
|
-
})
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### SameSite Cookie Protection
|
|
238
|
-
```typescript
|
|
239
|
-
session: {
|
|
240
|
-
cookie: {
|
|
241
|
-
sameSite: 'strict', // Strictest CSRF protection
|
|
242
|
-
// Or 'lax' for balance between security and usability
|
|
243
|
-
},
|
|
244
|
-
}
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
## Security Headers
|
|
248
|
-
|
|
249
|
-
### Recommended Headers
|
|
250
|
-
```typescript
|
|
251
|
-
// Middleware or server config
|
|
252
|
-
const securityHeaders = {
|
|
253
|
-
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
|
|
254
|
-
'X-Content-Type-Options': 'nosniff',
|
|
255
|
-
'X-Frame-Options': 'DENY',
|
|
256
|
-
'X-XSS-Protection': '1; mode=block',
|
|
257
|
-
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
258
|
-
'Content-Security-Policy': [
|
|
259
|
-
"default-src 'self'",
|
|
260
|
-
"script-src 'self'",
|
|
261
|
-
"style-src 'self' 'unsafe-inline'",
|
|
262
|
-
"img-src 'self' data: https:",
|
|
263
|
-
"font-src 'self'",
|
|
264
|
-
"connect-src 'self'",
|
|
265
|
-
"frame-ancestors 'none'",
|
|
266
|
-
].join('; '),
|
|
267
|
-
}
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
## Account Security
|
|
271
|
-
|
|
272
|
-
### Account Lockout
|
|
273
|
-
```typescript
|
|
274
|
-
export const auth = betterAuth({
|
|
275
|
-
emailAndPassword: {
|
|
276
|
-
lockout: {
|
|
277
|
-
enabled: true,
|
|
278
|
-
maxAttempts: 5,
|
|
279
|
-
lockoutDuration: 15 * 60, // 15 minutes
|
|
280
|
-
// Notify user
|
|
281
|
-
onLockout: async (user) => {
|
|
282
|
-
await sendEmail({
|
|
283
|
-
to: user.email,
|
|
284
|
-
subject: 'Account Locked',
|
|
285
|
-
html: 'Your account has been locked due to multiple failed login attempts.',
|
|
286
|
-
})
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
|
-
},
|
|
290
|
-
})
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
### Email Verification
|
|
294
|
-
```typescript
|
|
295
|
-
export const auth = betterAuth({
|
|
296
|
-
emailAndPassword: {
|
|
297
|
-
requireEmailVerification: true,
|
|
298
|
-
verificationTokenExpiry: 60 * 60 * 24, // 24 hours
|
|
299
|
-
sendVerificationEmail: async (user, token, url) => {
|
|
300
|
-
await sendEmail({
|
|
301
|
-
to: user.email,
|
|
302
|
-
subject: 'Verify your email',
|
|
303
|
-
html: `<a href="${url}">Verify email</a>`,
|
|
304
|
-
})
|
|
305
|
-
},
|
|
306
|
-
},
|
|
307
|
-
})
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### Password Reset Security
|
|
311
|
-
```typescript
|
|
312
|
-
export const auth = betterAuth({
|
|
313
|
-
emailAndPassword: {
|
|
314
|
-
resetPasswordTokenExpiry: 60 * 60, // 1 hour
|
|
315
|
-
sendResetPasswordToken: async (user, token, url) => {
|
|
316
|
-
await sendEmail({
|
|
317
|
-
to: user.email,
|
|
318
|
-
subject: 'Reset your password',
|
|
319
|
-
html: `<a href="${url}">Reset password</a>`,
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
// Log for security audit
|
|
323
|
-
await logSecurityEvent('password_reset_requested', {
|
|
324
|
-
userId: user.id,
|
|
325
|
-
email: user.email,
|
|
326
|
-
})
|
|
327
|
-
},
|
|
328
|
-
onPasswordReset: async (user) => {
|
|
329
|
-
// Invalidate all existing sessions
|
|
330
|
-
await auth.api.invalidateUserSessions({ userId: user.id })
|
|
331
|
-
|
|
332
|
-
// Notify user
|
|
333
|
-
await sendEmail({
|
|
334
|
-
to: user.email,
|
|
335
|
-
subject: 'Password changed',
|
|
336
|
-
html: 'Your password was recently changed.',
|
|
337
|
-
})
|
|
338
|
-
},
|
|
339
|
-
},
|
|
340
|
-
})
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
## Security Logging
|
|
344
|
-
|
|
345
|
-
### Audit Trail
|
|
346
|
-
```typescript
|
|
347
|
-
export const auth = betterAuth({
|
|
348
|
-
advanced: {
|
|
349
|
-
hooks: {
|
|
350
|
-
onSignIn: async (user, session) => {
|
|
351
|
-
await logSecurityEvent('sign_in', {
|
|
352
|
-
userId: user.id,
|
|
353
|
-
sessionId: session.id,
|
|
354
|
-
method: session.method, // 'email', 'google', etc.
|
|
355
|
-
})
|
|
356
|
-
},
|
|
357
|
-
onSignOut: async (user, session) => {
|
|
358
|
-
await logSecurityEvent('sign_out', {
|
|
359
|
-
userId: user.id,
|
|
360
|
-
sessionId: session.id,
|
|
361
|
-
})
|
|
362
|
-
},
|
|
363
|
-
onSignUp: async (user) => {
|
|
364
|
-
await logSecurityEvent('sign_up', {
|
|
365
|
-
userId: user.id,
|
|
366
|
-
email: user.email,
|
|
367
|
-
})
|
|
368
|
-
},
|
|
369
|
-
},
|
|
370
|
-
},
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
async function logSecurityEvent(event: string, data: Record<string, any>) {
|
|
374
|
-
await prisma.securityLog.create({
|
|
375
|
-
data: {
|
|
376
|
-
event,
|
|
377
|
-
data,
|
|
378
|
-
timestamp: new Date(),
|
|
379
|
-
ip: data.ip,
|
|
380
|
-
userAgent: data.userAgent,
|
|
381
|
-
},
|
|
382
|
-
})
|
|
383
|
-
}
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
## Security Checklist
|
|
387
|
-
|
|
388
|
-
- [ ] Password minimum 12 characters with complexity
|
|
389
|
-
- [ ] Rate limiting on all auth endpoints
|
|
390
|
-
- [ ] Session cookies are httpOnly and secure
|
|
391
|
-
- [ ] CSRF protection enabled
|
|
392
|
-
- [ ] Email verification required
|
|
393
|
-
- [ ] Account lockout after failed attempts
|
|
394
|
-
- [ ] Security headers configured
|
|
395
|
-
- [ ] Password changes invalidate sessions
|
|
396
|
-
- [ ] Security events logged
|
|
397
|
-
- [ ] 2FA available for users
|
|
398
|
-
|
|
399
|
-
## Anti-Patterns
|
|
400
|
-
|
|
401
|
-
```typescript
|
|
402
|
-
// ❌ WRONG: Weak password policy
|
|
403
|
-
password: { minLength: 6 }
|
|
404
|
-
|
|
405
|
-
// ✅ CORRECT: Strong policy
|
|
406
|
-
password: { minLength: 12, requireUppercase: true, ... }
|
|
407
|
-
|
|
408
|
-
// ❌ WRONG: No rate limiting
|
|
409
|
-
emailAndPassword: { enabled: true }
|
|
410
|
-
|
|
411
|
-
// ✅ CORRECT: With rate limiting
|
|
412
|
-
plugins: [rateLimit({ ... })]
|
|
413
|
-
|
|
414
|
-
// ❌ WRONG: Long-lived sessions
|
|
415
|
-
session: { expiresIn: 60 * 60 * 24 * 365 } // 1 year
|
|
416
|
-
|
|
417
|
-
// ✅ CORRECT: Reasonable expiry
|
|
418
|
-
session: { expiresIn: 60 * 60 * 24 * 7 } // 7 days
|
|
419
|
-
|
|
420
|
-
// ❌ WRONG: Cookies without flags
|
|
421
|
-
cookie: { name: 'session' }
|
|
422
|
-
|
|
423
|
-
// ✅ CORRECT: Secure cookie flags
|
|
424
|
-
cookie: { httpOnly: true, secure: true, sameSite: 'lax' }
|
|
425
|
-
```
|