@girardmedia/bootspring 1.2.0 → 2.0.3
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 +107 -14
- package/bin/bootspring.js +166 -27
- package/cli/agent.js +189 -17
- package/cli/analyze.js +499 -0
- package/cli/audit.js +557 -0
- package/cli/auth.js +495 -38
- package/cli/billing.js +302 -0
- package/cli/build.js +695 -0
- package/cli/business.js +109 -26
- package/cli/checkpoint-utils.js +168 -0
- package/cli/checkpoint.js +639 -0
- package/cli/cloud-sync.js +447 -0
- package/cli/content.js +198 -0
- package/cli/context.js +1 -1
- package/cli/deploy.js +543 -0
- package/cli/fundraise.js +112 -50
- package/cli/github-cmd.js +435 -0
- package/cli/health.js +477 -0
- package/cli/init.js +84 -13
- package/cli/legal.js +107 -95
- package/cli/log.js +2 -2
- package/cli/loop.js +976 -73
- package/cli/manager.js +711 -0
- package/cli/metrics.js +480 -0
- package/cli/monitor.js +812 -0
- package/cli/onboard.js +521 -0
- package/cli/orchestrator.js +12 -24
- package/cli/prd.js +594 -0
- package/cli/preseed-start.js +1483 -0
- package/cli/preseed.js +2302 -0
- package/cli/project.js +436 -0
- package/cli/quality.js +233 -0
- package/cli/security.js +913 -0
- package/cli/seed.js +1441 -5
- package/cli/skill.js +273 -211
- package/cli/suggest.js +989 -0
- package/cli/switch.js +453 -0
- package/cli/visualize.js +527 -0
- package/cli/watch.js +769 -0
- package/cli/workspace.js +607 -0
- package/core/analyze-workflow.js +1134 -0
- package/core/api-client.js +535 -22
- package/core/audit-workflow.js +1350 -0
- package/core/build-orchestrator.js +480 -0
- package/core/build-state.js +577 -0
- package/core/checkpoint-engine.js +408 -0
- package/core/config.js +1109 -26
- package/core/context-loader.js +21 -1
- package/core/deploy-workflow.js +836 -0
- package/core/entitlements.js +93 -22
- package/core/github-sync.js +610 -0
- package/core/index.js +8 -1
- package/core/ingest.js +1111 -0
- package/core/metrics-engine.js +768 -0
- package/core/onboard-workflow.js +1007 -0
- package/core/preseed-workflow.js +934 -0
- package/core/preseed.js +1617 -0
- package/core/project-context.js +325 -0
- package/core/project-state.js +694 -0
- package/core/r2-sync.js +583 -0
- package/core/scaffold.js +525 -7
- package/core/session.js +258 -0
- package/core/task-extractor.js +758 -0
- package/core/telemetry.js +28 -6
- package/core/tier-enforcement.js +737 -0
- package/core/utils.js +38 -14
- package/generators/questionnaire.js +15 -12
- package/generators/sections/ai.js +7 -7
- package/generators/sections/content.js +300 -0
- package/generators/sections/index.js +3 -0
- package/generators/sections/plugins.js +7 -6
- package/generators/templates/build-planning.template.js +596 -0
- package/generators/templates/content.template.js +819 -0
- package/generators/templates/index.js +2 -1
- package/hooks/git-autopilot.js +1250 -0
- package/hooks/index.js +9 -0
- package/intelligence/agent-collab.js +2057 -0
- package/intelligence/auto-suggest.js +634 -0
- package/intelligence/content-gen.js +1589 -0
- package/intelligence/cross-project.js +1647 -0
- package/intelligence/index.js +184 -0
- package/intelligence/learning/insights.json +517 -7
- package/intelligence/learning/pattern-learner.js +1008 -14
- package/intelligence/memory/decision-tracker.js +1431 -31
- package/intelligence/memory/decisions.jsonl +0 -0
- package/intelligence/orchestrator.js +2896 -1
- package/intelligence/prd.js +92 -1
- package/intelligence/recommendation-weights.json +14 -2
- package/intelligence/recommendations.js +463 -9
- package/intelligence/workflow-composer.js +1451 -0
- package/marketplace/index.d.ts +324 -0
- package/marketplace/index.js +1921 -0
- package/mcp/contracts/mcp-contract.v1.json +342 -4
- package/mcp/registry.js +680 -3
- package/mcp/response-formatter.js +23 -0
- package/mcp/tools/assist-tool.js +78 -4
- package/mcp/tools/autopilot-tool.js +408 -0
- package/mcp/tools/content-tool.js +571 -0
- package/mcp/tools/dashboard-tool.js +251 -5
- package/mcp/tools/mvp-tool.js +344 -0
- package/mcp/tools/plugin-tool.js +23 -1
- package/mcp/tools/prd-tool.js +579 -0
- package/mcp/tools/seed-tool.js +447 -0
- package/mcp/tools/skill-tool.js +43 -14
- package/mcp/tools/suggest-tool.js +147 -0
- package/package.json +15 -6
- package/agents/README.md +0 -93
- package/agents/ai-integration-expert/context.md +0 -386
- package/agents/api-expert/context.md +0 -416
- package/agents/architecture-expert/context.md +0 -454
- package/agents/auth-expert/context.md +0 -399
- package/agents/backend-expert/context.md +0 -483
- package/agents/business-strategy-expert/context.md +0 -180
- package/agents/code-review-expert/context.md +0 -365
- package/agents/competitive-analysis-expert/context.md +0 -239
- package/agents/data-modeling-expert/context.md +0 -352
- package/agents/database-expert/context.md +0 -250
- package/agents/devops-expert/context.md +0 -446
- package/agents/email-expert/context.md +0 -379
- package/agents/financial-expert/context.md +0 -213
- package/agents/frontend-expert/context.md +0 -364
- package/agents/fundraising-expert/context.md +0 -257
- package/agents/growth-expert/context.md +0 -249
- package/agents/index.js +0 -140
- package/agents/investor-relations-expert/context.md +0 -266
- package/agents/legal-expert/context.md +0 -284
- package/agents/marketing-expert/context.md +0 -236
- package/agents/monitoring-expert/context.md +0 -362
- package/agents/operations-expert/context.md +0 -279
- package/agents/partnerships-expert/context.md +0 -286
- package/agents/payment-expert/context.md +0 -340
- package/agents/performance-expert/context.md +0 -377
- package/agents/private-equity-expert/context.md +0 -246
- package/agents/railway-expert/context.md +0 -284
- package/agents/research-expert/context.md +0 -245
- package/agents/sales-expert/context.md +0 -241
- package/agents/security-expert/context.md +0 -343
- package/agents/testing-expert/context.md +0 -414
- package/agents/ui-ux-expert/context.md +0 -448
- package/agents/vercel-expert/context.md +0 -426
- package/skills/index.js +0 -787
- package/skills/patterns/README.md +0 -163
- package/skills/patterns/ai/agents.md +0 -281
- package/skills/patterns/ai/claude.md +0 -138
- package/skills/patterns/ai/embeddings.md +0 -150
- package/skills/patterns/ai/rag.md +0 -266
- package/skills/patterns/ai/streaming.md +0 -170
- package/skills/patterns/ai/structured-output.md +0 -162
- package/skills/patterns/ai/tools.md +0 -154
- package/skills/patterns/analytics/tracking.md +0 -220
- package/skills/patterns/api/errors.md +0 -296
- package/skills/patterns/api/graphql.md +0 -440
- package/skills/patterns/api/middleware.md +0 -279
- package/skills/patterns/api/openapi.md +0 -285
- package/skills/patterns/api/rate-limiting.md +0 -231
- package/skills/patterns/api/route-handler.md +0 -217
- package/skills/patterns/api/server-action.md +0 -249
- package/skills/patterns/api/versioning.md +0 -443
- package/skills/patterns/api/webhooks.md +0 -247
- package/skills/patterns/auth/clerk.md +0 -132
- package/skills/patterns/auth/mfa.md +0 -313
- package/skills/patterns/auth/nextauth.md +0 -140
- package/skills/patterns/auth/oauth.md +0 -237
- package/skills/patterns/auth/rbac.md +0 -152
- package/skills/patterns/auth/session-management.md +0 -367
- package/skills/patterns/auth/session.md +0 -120
- package/skills/patterns/database/audit.md +0 -177
- package/skills/patterns/database/migrations.md +0 -177
- package/skills/patterns/database/pagination.md +0 -230
- package/skills/patterns/database/pooling.md +0 -357
- package/skills/patterns/database/prisma.md +0 -180
- package/skills/patterns/database/relations.md +0 -187
- package/skills/patterns/database/seeding.md +0 -246
- package/skills/patterns/database/soft-delete.md +0 -153
- package/skills/patterns/database/transactions.md +0 -162
- package/skills/patterns/deployment/ci-cd.md +0 -231
- package/skills/patterns/deployment/docker.md +0 -188
- package/skills/patterns/deployment/monitoring.md +0 -387
- package/skills/patterns/deployment/vercel.md +0 -160
- package/skills/patterns/email/resend.md +0 -143
- package/skills/patterns/email/templates.md +0 -245
- package/skills/patterns/email/transactional.md +0 -503
- package/skills/patterns/email/verification.md +0 -176
- package/skills/patterns/files/download.md +0 -243
- package/skills/patterns/files/upload.md +0 -239
- package/skills/patterns/i18n/nextintl.md +0 -188
- package/skills/patterns/logging/structured.md +0 -292
- package/skills/patterns/notifications/email-queue.md +0 -248
- package/skills/patterns/notifications/push.md +0 -279
- package/skills/patterns/payments/checkout.md +0 -303
- package/skills/patterns/payments/invoices.md +0 -287
- package/skills/patterns/payments/portal.md +0 -245
- package/skills/patterns/payments/stripe.md +0 -272
- package/skills/patterns/payments/subscriptions.md +0 -300
- package/skills/patterns/payments/usage.md +0 -279
- package/skills/patterns/performance/caching.md +0 -276
- package/skills/patterns/performance/code-splitting.md +0 -233
- package/skills/patterns/performance/edge.md +0 -254
- package/skills/patterns/performance/isr.md +0 -266
- package/skills/patterns/performance/lazy-loading.md +0 -281
- package/skills/patterns/realtime/sse.md +0 -327
- package/skills/patterns/realtime/websockets.md +0 -336
- package/skills/patterns/search/filtering.md +0 -329
- package/skills/patterns/search/fulltext.md +0 -260
- package/skills/patterns/security/audit-logging.md +0 -444
- package/skills/patterns/security/csrf.md +0 -234
- package/skills/patterns/security/headers.md +0 -252
- package/skills/patterns/security/sanitization.md +0 -258
- package/skills/patterns/security/secrets.md +0 -261
- package/skills/patterns/security/validation.md +0 -268
- package/skills/patterns/security/xss.md +0 -229
- package/skills/patterns/seo/metadata.md +0 -252
- package/skills/patterns/state/context.md +0 -349
- package/skills/patterns/state/react-query.md +0 -313
- package/skills/patterns/state/url-state.md +0 -482
- package/skills/patterns/state/zustand.md +0 -262
- package/skills/patterns/testing/api.md +0 -259
- package/skills/patterns/testing/component.md +0 -233
- package/skills/patterns/testing/coverage.md +0 -207
- package/skills/patterns/testing/fixtures.md +0 -225
- package/skills/patterns/testing/integration.md +0 -436
- package/skills/patterns/testing/mocking.md +0 -177
- package/skills/patterns/testing/playwright.md +0 -162
- package/skills/patterns/testing/snapshot.md +0 -175
- package/skills/patterns/testing/vitest.md +0 -307
- package/skills/patterns/ui/accordions.md +0 -395
- package/skills/patterns/ui/cards.md +0 -299
- package/skills/patterns/ui/dropdowns.md +0 -476
- package/skills/patterns/ui/empty-states.md +0 -320
- package/skills/patterns/ui/forms.md +0 -405
- package/skills/patterns/ui/inputs.md +0 -319
- package/skills/patterns/ui/layouts.md +0 -282
- package/skills/patterns/ui/loading.md +0 -291
- package/skills/patterns/ui/modals.md +0 -338
- package/skills/patterns/ui/navigation.md +0 -374
- package/skills/patterns/ui/tables.md +0 -407
- package/skills/patterns/ui/toasts.md +0 -300
- package/skills/patterns/ui/tooltips.md +0 -396
- package/skills/patterns/utils/dates.md +0 -435
- package/skills/patterns/utils/errors.md +0 -451
- package/skills/patterns/utils/formatting.md +0 -345
- package/skills/patterns/utils/validation.md +0 -434
- package/templates/bootspring.config.js +0 -83
- package/templates/business/business-model-canvas.md +0 -246
- package/templates/business/business-plan.md +0 -266
- package/templates/business/competitive-analysis.md +0 -312
- package/templates/fundraising/data-room-checklist.md +0 -300
- package/templates/fundraising/investor-research.md +0 -243
- package/templates/fundraising/pitch-deck-outline.md +0 -253
- package/templates/legal/gdpr-checklist.md +0 -339
- package/templates/legal/privacy-policy.md +0 -285
- package/templates/legal/terms-of-service.md +0 -222
- package/templates/mcp.json +0 -9
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
# Clerk Authentication Patterns
|
|
2
|
-
|
|
3
|
-
Battle-tested patterns for Clerk authentication in Next.js.
|
|
4
|
-
|
|
5
|
-
## Server-Side Auth Check
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// lib/auth.ts
|
|
9
|
-
import { auth } from '@clerk/nextjs/server'
|
|
10
|
-
|
|
11
|
-
export async function requireAuth() {
|
|
12
|
-
const { userId } = await auth()
|
|
13
|
-
|
|
14
|
-
if (!userId) {
|
|
15
|
-
throw new Error('UNAUTHORIZED')
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return userId
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Usage in Server Action
|
|
22
|
-
export async function myAction() {
|
|
23
|
-
const userId = await requireAuth()
|
|
24
|
-
// Continue with authenticated user
|
|
25
|
-
}
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Protected Layout
|
|
29
|
-
|
|
30
|
-
```typescript
|
|
31
|
-
// app/(protected)/layout.tsx
|
|
32
|
-
import { auth } from '@clerk/nextjs/server'
|
|
33
|
-
import { redirect } from 'next/navigation'
|
|
34
|
-
|
|
35
|
-
export default async function ProtectedLayout({
|
|
36
|
-
children
|
|
37
|
-
}: {
|
|
38
|
-
children: React.ReactNode
|
|
39
|
-
}) {
|
|
40
|
-
const { userId } = await auth()
|
|
41
|
-
|
|
42
|
-
if (!userId) {
|
|
43
|
-
redirect('/sign-in')
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return <>{children}</>
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## Get Current User with Metadata
|
|
51
|
-
|
|
52
|
-
```typescript
|
|
53
|
-
// lib/auth.ts
|
|
54
|
-
import { currentUser } from '@clerk/nextjs/server'
|
|
55
|
-
import { prisma } from '@/lib/db'
|
|
56
|
-
|
|
57
|
-
export async function getCurrentUser() {
|
|
58
|
-
const user = await currentUser()
|
|
59
|
-
if (!user) return null
|
|
60
|
-
|
|
61
|
-
// Get or create database user
|
|
62
|
-
const dbUser = await prisma.user.upsert({
|
|
63
|
-
where: { clerkId: user.id },
|
|
64
|
-
update: {
|
|
65
|
-
email: user.emailAddresses[0]?.emailAddress,
|
|
66
|
-
name: `${user.firstName} ${user.lastName}`.trim()
|
|
67
|
-
},
|
|
68
|
-
create: {
|
|
69
|
-
clerkId: user.id,
|
|
70
|
-
email: user.emailAddresses[0]?.emailAddress ?? '',
|
|
71
|
-
name: `${user.firstName} ${user.lastName}`.trim()
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
return dbUser
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Client-Side Auth Hook
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
// hooks/use-user.ts
|
|
83
|
-
'use client'
|
|
84
|
-
import { useUser } from '@clerk/nextjs'
|
|
85
|
-
|
|
86
|
-
export function useCurrentUser() {
|
|
87
|
-
const { user, isLoaded, isSignedIn } = useUser()
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
user: isSignedIn ? user : null,
|
|
91
|
-
isLoading: !isLoaded,
|
|
92
|
-
isAuthenticated: isSignedIn
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## Middleware Configuration
|
|
98
|
-
|
|
99
|
-
```typescript
|
|
100
|
-
// middleware.ts
|
|
101
|
-
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
|
|
102
|
-
|
|
103
|
-
const isProtectedRoute = createRouteMatcher([
|
|
104
|
-
'/dashboard(.*)',
|
|
105
|
-
'/settings(.*)',
|
|
106
|
-
'/api/user(.*)'
|
|
107
|
-
])
|
|
108
|
-
|
|
109
|
-
const isPublicRoute = createRouteMatcher([
|
|
110
|
-
'/',
|
|
111
|
-
'/sign-in(.*)',
|
|
112
|
-
'/sign-up(.*)',
|
|
113
|
-
'/api/webhooks(.*)'
|
|
114
|
-
])
|
|
115
|
-
|
|
116
|
-
export default clerkMiddleware(async (auth, req) => {
|
|
117
|
-
if (isProtectedRoute(req)) {
|
|
118
|
-
await auth.protect()
|
|
119
|
-
}
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
export const config = {
|
|
123
|
-
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)']
|
|
124
|
-
}
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## When to Use
|
|
128
|
-
|
|
129
|
-
- Server Components needing user context
|
|
130
|
-
- Server Actions modifying user data
|
|
131
|
-
- API routes requiring authentication
|
|
132
|
-
- Protected page layouts
|
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
# Multi-Factor Authentication Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for implementing MFA/2FA.
|
|
4
|
-
|
|
5
|
-
## TOTP Setup
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// lib/mfa.ts
|
|
9
|
-
import { authenticator } from 'otplib'
|
|
10
|
-
import QRCode from 'qrcode'
|
|
11
|
-
import { prisma } from '@/lib/db'
|
|
12
|
-
|
|
13
|
-
authenticator.options = {
|
|
14
|
-
window: 1 // Allow 1 step variance for clock drift
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function generateMfaSecret(userId: string, email: string) {
|
|
18
|
-
const secret = authenticator.generateSecret()
|
|
19
|
-
|
|
20
|
-
// Store encrypted secret
|
|
21
|
-
await prisma.user.update({
|
|
22
|
-
where: { id: userId },
|
|
23
|
-
data: {
|
|
24
|
-
mfaSecret: encrypt(secret),
|
|
25
|
-
mfaEnabled: false // Not enabled until verified
|
|
26
|
-
}
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
// Generate QR code
|
|
30
|
-
const otpauth = authenticator.keyuri(email, 'MyApp', secret)
|
|
31
|
-
const qrCode = await QRCode.toDataURL(otpauth)
|
|
32
|
-
|
|
33
|
-
return { secret, qrCode }
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export async function verifyAndEnableMfa(userId: string, token: string) {
|
|
37
|
-
const user = await prisma.user.findUnique({
|
|
38
|
-
where: { id: userId }
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
if (!user?.mfaSecret) {
|
|
42
|
-
throw new Error('MFA not set up')
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const secret = decrypt(user.mfaSecret)
|
|
46
|
-
const isValid = authenticator.verify({ token, secret })
|
|
47
|
-
|
|
48
|
-
if (!isValid) {
|
|
49
|
-
throw new Error('Invalid verification code')
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Generate backup codes
|
|
53
|
-
const backupCodes = generateBackupCodes()
|
|
54
|
-
|
|
55
|
-
await prisma.user.update({
|
|
56
|
-
where: { id: userId },
|
|
57
|
-
data: {
|
|
58
|
-
mfaEnabled: true,
|
|
59
|
-
mfaBackupCodes: backupCodes.map(code => hashBackupCode(code))
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
return backupCodes
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function verifyTotp(secret: string, token: string): boolean {
|
|
67
|
-
return authenticator.verify({ token, secret })
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function generateBackupCodes(count = 10): string[] {
|
|
71
|
-
return Array.from({ length: count }, () =>
|
|
72
|
-
Math.random().toString(36).slice(2, 10).toUpperCase()
|
|
73
|
-
)
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## MFA Setup Flow
|
|
78
|
-
|
|
79
|
-
```tsx
|
|
80
|
-
// app/settings/security/mfa/page.tsx
|
|
81
|
-
'use client'
|
|
82
|
-
|
|
83
|
-
import { useState } from 'react'
|
|
84
|
-
import Image from 'next/image'
|
|
85
|
-
|
|
86
|
-
export default function MfaSetupPage() {
|
|
87
|
-
const [step, setStep] = useState<'setup' | 'verify' | 'backup'>('setup')
|
|
88
|
-
const [qrCode, setQrCode] = useState<string>()
|
|
89
|
-
const [secret, setSecret] = useState<string>()
|
|
90
|
-
const [backupCodes, setBackupCodes] = useState<string[]>()
|
|
91
|
-
const [verifyCode, setVerifyCode] = useState('')
|
|
92
|
-
const [error, setError] = useState('')
|
|
93
|
-
|
|
94
|
-
async function startSetup() {
|
|
95
|
-
const res = await fetch('/api/mfa/setup', { method: 'POST' })
|
|
96
|
-
const data = await res.json()
|
|
97
|
-
|
|
98
|
-
setQrCode(data.qrCode)
|
|
99
|
-
setSecret(data.secret)
|
|
100
|
-
setStep('verify')
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function verifyAndEnable() {
|
|
104
|
-
setError('')
|
|
105
|
-
|
|
106
|
-
const res = await fetch('/api/mfa/verify', {
|
|
107
|
-
method: 'POST',
|
|
108
|
-
body: JSON.stringify({ token: verifyCode })
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
if (!res.ok) {
|
|
112
|
-
setError('Invalid code. Please try again.')
|
|
113
|
-
return
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const data = await res.json()
|
|
117
|
-
setBackupCodes(data.backupCodes)
|
|
118
|
-
setStep('backup')
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (step === 'setup') {
|
|
122
|
-
return (
|
|
123
|
-
<div className="max-w-md">
|
|
124
|
-
<h1 className="mb-4 text-2xl font-bold">Enable Two-Factor Auth</h1>
|
|
125
|
-
<p className="mb-6 text-gray-600">
|
|
126
|
-
Add an extra layer of security to your account.
|
|
127
|
-
</p>
|
|
128
|
-
<button onClick={startSetup} className="rounded bg-blue-600 px-4 py-2 text-white">
|
|
129
|
-
Set Up 2FA
|
|
130
|
-
</button>
|
|
131
|
-
</div>
|
|
132
|
-
)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (step === 'verify') {
|
|
136
|
-
return (
|
|
137
|
-
<div className="max-w-md">
|
|
138
|
-
<h1 className="mb-4 text-2xl font-bold">Scan QR Code</h1>
|
|
139
|
-
<p className="mb-4 text-gray-600">
|
|
140
|
-
Scan this QR code with your authenticator app.
|
|
141
|
-
</p>
|
|
142
|
-
|
|
143
|
-
{qrCode && (
|
|
144
|
-
<Image src={qrCode} alt="QR Code" width={200} height={200} className="mb-4" />
|
|
145
|
-
)}
|
|
146
|
-
|
|
147
|
-
<p className="mb-4 text-sm text-gray-500">
|
|
148
|
-
Or enter manually: <code className="bg-gray-100 px-2 py-1">{secret}</code>
|
|
149
|
-
</p>
|
|
150
|
-
|
|
151
|
-
<div className="mb-4">
|
|
152
|
-
<label className="mb-1 block text-sm font-medium">Verification Code</label>
|
|
153
|
-
<input
|
|
154
|
-
type="text"
|
|
155
|
-
value={verifyCode}
|
|
156
|
-
onChange={(e) => setVerifyCode(e.target.value)}
|
|
157
|
-
placeholder="Enter 6-digit code"
|
|
158
|
-
className="w-full rounded border px-3 py-2"
|
|
159
|
-
/>
|
|
160
|
-
{error && <p className="mt-1 text-sm text-red-500">{error}</p>}
|
|
161
|
-
</div>
|
|
162
|
-
|
|
163
|
-
<button onClick={verifyAndEnable} className="rounded bg-blue-600 px-4 py-2 text-white">
|
|
164
|
-
Verify and Enable
|
|
165
|
-
</button>
|
|
166
|
-
</div>
|
|
167
|
-
)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return (
|
|
171
|
-
<div className="max-w-md">
|
|
172
|
-
<h1 className="mb-4 text-2xl font-bold">Save Backup Codes</h1>
|
|
173
|
-
<p className="mb-4 text-gray-600">
|
|
174
|
-
Save these codes in a secure place. You can use them if you lose access to your authenticator.
|
|
175
|
-
</p>
|
|
176
|
-
|
|
177
|
-
<div className="mb-6 grid grid-cols-2 gap-2 rounded bg-gray-100 p-4 font-mono">
|
|
178
|
-
{backupCodes?.map((code, i) => (
|
|
179
|
-
<div key={i}>{code}</div>
|
|
180
|
-
))}
|
|
181
|
-
</div>
|
|
182
|
-
|
|
183
|
-
<a href="/settings/security" className="rounded bg-blue-600 px-4 py-2 text-white">
|
|
184
|
-
Done
|
|
185
|
-
</a>
|
|
186
|
-
</div>
|
|
187
|
-
)
|
|
188
|
-
}
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
## MFA Login Challenge
|
|
192
|
-
|
|
193
|
-
```tsx
|
|
194
|
-
// app/login/mfa/page.tsx
|
|
195
|
-
'use client'
|
|
196
|
-
|
|
197
|
-
import { useState } from 'react'
|
|
198
|
-
import { useRouter, useSearchParams } from 'next/navigation'
|
|
199
|
-
|
|
200
|
-
export default function MfaChallengePage() {
|
|
201
|
-
const router = useRouter()
|
|
202
|
-
const searchParams = useSearchParams()
|
|
203
|
-
const [code, setCode] = useState('')
|
|
204
|
-
const [error, setError] = useState('')
|
|
205
|
-
const [useBackup, setUseBackup] = useState(false)
|
|
206
|
-
|
|
207
|
-
async function handleSubmit(e: React.FormEvent) {
|
|
208
|
-
e.preventDefault()
|
|
209
|
-
setError('')
|
|
210
|
-
|
|
211
|
-
const res = await fetch('/api/auth/mfa-verify', {
|
|
212
|
-
method: 'POST',
|
|
213
|
-
body: JSON.stringify({
|
|
214
|
-
code,
|
|
215
|
-
isBackupCode: useBackup,
|
|
216
|
-
token: searchParams.get('token')
|
|
217
|
-
})
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
if (!res.ok) {
|
|
221
|
-
setError('Invalid code')
|
|
222
|
-
return
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
router.push('/dashboard')
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return (
|
|
229
|
-
<form onSubmit={handleSubmit} className="mx-auto max-w-md py-12">
|
|
230
|
-
<h1 className="mb-6 text-2xl font-bold">Two-Factor Authentication</h1>
|
|
231
|
-
|
|
232
|
-
<div className="mb-4">
|
|
233
|
-
<label className="mb-1 block text-sm font-medium">
|
|
234
|
-
{useBackup ? 'Backup Code' : 'Authentication Code'}
|
|
235
|
-
</label>
|
|
236
|
-
<input
|
|
237
|
-
type="text"
|
|
238
|
-
value={code}
|
|
239
|
-
onChange={(e) => setCode(e.target.value)}
|
|
240
|
-
placeholder={useBackup ? 'Enter backup code' : 'Enter 6-digit code'}
|
|
241
|
-
className="w-full rounded border px-3 py-2"
|
|
242
|
-
autoFocus
|
|
243
|
-
/>
|
|
244
|
-
{error && <p className="mt-1 text-sm text-red-500">{error}</p>}
|
|
245
|
-
</div>
|
|
246
|
-
|
|
247
|
-
<button type="submit" className="w-full rounded bg-blue-600 py-2 text-white">
|
|
248
|
-
Verify
|
|
249
|
-
</button>
|
|
250
|
-
|
|
251
|
-
<button
|
|
252
|
-
type="button"
|
|
253
|
-
onClick={() => setUseBackup(!useBackup)}
|
|
254
|
-
className="mt-4 text-sm text-blue-600"
|
|
255
|
-
>
|
|
256
|
-
{useBackup ? 'Use authenticator code' : 'Use backup code'}
|
|
257
|
-
</button>
|
|
258
|
-
</form>
|
|
259
|
-
)
|
|
260
|
-
}
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
## SMS Fallback
|
|
264
|
-
|
|
265
|
-
```typescript
|
|
266
|
-
// lib/mfa-sms.ts
|
|
267
|
-
import twilio from 'twilio'
|
|
268
|
-
|
|
269
|
-
const client = twilio(
|
|
270
|
-
process.env.TWILIO_ACCOUNT_SID,
|
|
271
|
-
process.env.TWILIO_AUTH_TOKEN
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
export async function sendSmsCode(phoneNumber: string, code: string) {
|
|
275
|
-
await client.messages.create({
|
|
276
|
-
body: `Your verification code is: ${code}`,
|
|
277
|
-
from: process.env.TWILIO_PHONE_NUMBER,
|
|
278
|
-
to: phoneNumber
|
|
279
|
-
})
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
export async function generateAndSendSmsCode(userId: string) {
|
|
283
|
-
const code = Math.floor(100000 + Math.random() * 900000).toString()
|
|
284
|
-
|
|
285
|
-
const user = await prisma.user.findUnique({
|
|
286
|
-
where: { id: userId },
|
|
287
|
-
select: { phone: true }
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
if (!user?.phone) {
|
|
291
|
-
throw new Error('No phone number')
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Store code with expiry
|
|
295
|
-
await prisma.verificationCode.create({
|
|
296
|
-
data: {
|
|
297
|
-
userId,
|
|
298
|
-
code: hashCode(code),
|
|
299
|
-
type: 'SMS_MFA',
|
|
300
|
-
expiresAt: new Date(Date.now() + 5 * 60 * 1000) // 5 minutes
|
|
301
|
-
}
|
|
302
|
-
})
|
|
303
|
-
|
|
304
|
-
await sendSmsCode(user.phone, code)
|
|
305
|
-
}
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
## When to Use
|
|
309
|
-
|
|
310
|
-
- Account security
|
|
311
|
-
- Compliance requirements
|
|
312
|
-
- High-value accounts
|
|
313
|
-
- Admin access
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
# NextAuth.js (Auth.js) Patterns
|
|
2
|
-
|
|
3
|
-
Authentication patterns for NextAuth.js v5 in Next.js.
|
|
4
|
-
|
|
5
|
-
## Basic Setup
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// auth.ts
|
|
9
|
-
import NextAuth from 'next-auth'
|
|
10
|
-
import GitHub from 'next-auth/providers/github'
|
|
11
|
-
import Google from 'next-auth/providers/google'
|
|
12
|
-
import { PrismaAdapter } from '@auth/prisma-adapter'
|
|
13
|
-
import { prisma } from '@/lib/db'
|
|
14
|
-
|
|
15
|
-
export const { handlers, auth, signIn, signOut } = NextAuth({
|
|
16
|
-
adapter: PrismaAdapter(prisma),
|
|
17
|
-
session: { strategy: 'jwt' },
|
|
18
|
-
providers: [
|
|
19
|
-
GitHub({
|
|
20
|
-
clientId: process.env.GITHUB_ID!,
|
|
21
|
-
clientSecret: process.env.GITHUB_SECRET!
|
|
22
|
-
}),
|
|
23
|
-
Google({
|
|
24
|
-
clientId: process.env.GOOGLE_ID!,
|
|
25
|
-
clientSecret: process.env.GOOGLE_SECRET!
|
|
26
|
-
})
|
|
27
|
-
],
|
|
28
|
-
callbacks: {
|
|
29
|
-
async jwt({ token, user }) {
|
|
30
|
-
if (user) {
|
|
31
|
-
token.id = user.id
|
|
32
|
-
}
|
|
33
|
-
return token
|
|
34
|
-
},
|
|
35
|
-
async session({ session, token }) {
|
|
36
|
-
if (session.user) {
|
|
37
|
-
session.user.id = token.id as string
|
|
38
|
-
}
|
|
39
|
-
return session
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
pages: {
|
|
43
|
-
signIn: '/login',
|
|
44
|
-
error: '/login'
|
|
45
|
-
}
|
|
46
|
-
})
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Route Handler
|
|
50
|
-
|
|
51
|
-
```typescript
|
|
52
|
-
// app/api/auth/[...nextauth]/route.ts
|
|
53
|
-
import { handlers } from '@/auth'
|
|
54
|
-
|
|
55
|
-
export const { GET, POST } = handlers
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## Server-Side Auth Check
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
// lib/auth.ts
|
|
62
|
-
import { auth } from '@/auth'
|
|
63
|
-
|
|
64
|
-
export async function getSession() {
|
|
65
|
-
return auth()
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export async function requireAuth() {
|
|
69
|
-
const session = await auth()
|
|
70
|
-
|
|
71
|
-
if (!session?.user) {
|
|
72
|
-
throw new Error('Unauthorized')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return session
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Credentials Provider
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
// auth.ts (add to providers)
|
|
83
|
-
import Credentials from 'next-auth/providers/credentials'
|
|
84
|
-
import bcrypt from 'bcryptjs'
|
|
85
|
-
|
|
86
|
-
Credentials({
|
|
87
|
-
credentials: {
|
|
88
|
-
email: { type: 'email' },
|
|
89
|
-
password: { type: 'password' }
|
|
90
|
-
},
|
|
91
|
-
async authorize(credentials) {
|
|
92
|
-
const user = await prisma.user.findUnique({
|
|
93
|
-
where: { email: credentials.email as string }
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
if (!user?.password) return null
|
|
97
|
-
|
|
98
|
-
const valid = await bcrypt.compare(
|
|
99
|
-
credentials.password as string,
|
|
100
|
-
user.password
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
if (!valid) return null
|
|
104
|
-
|
|
105
|
-
return { id: user.id, email: user.email, name: user.name }
|
|
106
|
-
}
|
|
107
|
-
})
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## Sign In/Out Components
|
|
111
|
-
|
|
112
|
-
```tsx
|
|
113
|
-
// components/auth-buttons.tsx
|
|
114
|
-
'use client'
|
|
115
|
-
import { signIn, signOut } from 'next-auth/react'
|
|
116
|
-
import { Button } from '@/components/ui/button'
|
|
117
|
-
|
|
118
|
-
export function SignInButton() {
|
|
119
|
-
return (
|
|
120
|
-
<Button onClick={() => signIn('github')}>
|
|
121
|
-
Sign In with GitHub
|
|
122
|
-
</Button>
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export function SignOutButton() {
|
|
127
|
-
return (
|
|
128
|
-
<Button variant="outline" onClick={() => signOut()}>
|
|
129
|
-
Sign Out
|
|
130
|
-
</Button>
|
|
131
|
-
)
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## When to Use
|
|
136
|
-
|
|
137
|
-
- Self-hosted authentication
|
|
138
|
-
- Multiple OAuth providers
|
|
139
|
-
- Database session storage
|
|
140
|
-
- Custom credential authentication
|