@kood/claude-code 0.1.6 → 0.1.9
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/dist/index.js +109 -216
- package/package.json +8 -2
- package/templates/hono/CLAUDE.md +59 -328
- package/templates/hono/docs/architecture/architecture.md +93 -747
- package/templates/hono/docs/deployment/cloudflare.md +59 -513
- package/templates/hono/docs/deployment/docker.md +41 -356
- package/templates/hono/docs/deployment/index.md +54 -190
- package/templates/hono/docs/deployment/railway.md +36 -306
- package/templates/hono/docs/deployment/vercel.md +49 -434
- package/templates/hono/docs/library/ai-sdk/index.md +53 -290
- package/templates/hono/docs/library/ai-sdk/openrouter.md +19 -387
- package/templates/hono/docs/library/ai-sdk/providers.md +28 -394
- package/templates/hono/docs/library/ai-sdk/streaming.md +52 -353
- package/templates/hono/docs/library/ai-sdk/structured-output.md +63 -395
- package/templates/hono/docs/library/ai-sdk/tools.md +62 -431
- package/templates/hono/docs/library/hono/env-setup.md +24 -313
- package/templates/hono/docs/library/hono/error-handling.md +34 -295
- package/templates/hono/docs/library/hono/index.md +29 -121
- package/templates/hono/docs/library/hono/middleware.md +21 -188
- package/templates/hono/docs/library/hono/rpc.md +40 -341
- package/templates/hono/docs/library/hono/validation.md +35 -195
- package/templates/hono/docs/library/pino/index.md +42 -333
- package/templates/hono/docs/library/prisma/cloudflare-d1.md +64 -367
- package/templates/hono/docs/library/prisma/config.md +19 -260
- package/templates/hono/docs/library/prisma/index.md +67 -320
- package/templates/hono/docs/library/zod/index.md +53 -257
- package/templates/npx/CLAUDE.md +62 -274
- package/templates/npx/docs/references/patterns.md +160 -0
- package/templates/tanstack-start/CLAUDE.md +100 -256
- package/templates/tanstack-start/docs/architecture/architecture.md +44 -589
- package/templates/tanstack-start/docs/deployment/cloudflare.md +37 -424
- package/templates/tanstack-start/docs/deployment/index.md +57 -286
- package/templates/tanstack-start/docs/deployment/nitro.md +36 -318
- package/templates/tanstack-start/docs/deployment/railway.md +40 -409
- package/templates/tanstack-start/docs/deployment/vercel.md +43 -465
- package/templates/tanstack-start/docs/design/components.md +77 -311
- package/templates/tanstack-start/docs/design/index.md +113 -69
- package/templates/tanstack-start/docs/design/safe-area.md +51 -250
- package/templates/tanstack-start/docs/design/tailwind-setup.md +45 -359
- package/templates/tanstack-start/docs/guides/conventions.md +103 -0
- package/templates/tanstack-start/docs/guides/env-setup.md +34 -340
- package/templates/tanstack-start/docs/guides/getting-started.md +22 -209
- package/templates/tanstack-start/docs/guides/hooks.md +166 -0
- package/templates/tanstack-start/docs/guides/routes.md +166 -0
- package/templates/tanstack-start/docs/guides/services.md +143 -0
- package/templates/tanstack-start/docs/library/better-auth/2fa.md +27 -115
- package/templates/tanstack-start/docs/library/better-auth/advanced.md +22 -105
- package/templates/tanstack-start/docs/library/better-auth/index.md +17 -66
- package/templates/tanstack-start/docs/library/better-auth/plugins.md +11 -88
- package/templates/tanstack-start/docs/library/better-auth/session.md +12 -92
- package/templates/tanstack-start/docs/library/better-auth/setup.md +9 -91
- package/templates/tanstack-start/docs/library/prisma/cloudflare-d1.md +30 -358
- package/templates/tanstack-start/docs/library/prisma/config.md +27 -327
- package/templates/tanstack-start/docs/library/prisma/crud.md +46 -174
- package/templates/tanstack-start/docs/library/prisma/index.md +23 -113
- package/templates/tanstack-start/docs/library/prisma/relations.md +31 -153
- package/templates/tanstack-start/docs/library/prisma/schema.md +40 -217
- package/templates/tanstack-start/docs/library/prisma/setup.md +12 -112
- package/templates/tanstack-start/docs/library/prisma/transactions.md +20 -110
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +26 -97
- package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +28 -107
- package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +44 -146
- package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +33 -127
- package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +49 -149
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +19 -112
- package/templates/tanstack-start/docs/library/tanstack-start/index.md +33 -80
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +28 -106
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +21 -118
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +34 -246
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +6 -39
- package/templates/tanstack-start/docs/library/zod/complex-types.md +32 -156
- package/templates/tanstack-start/docs/library/zod/index.md +31 -144
- package/templates/tanstack-start/docs/library/zod/transforms.md +20 -129
- package/templates/tanstack-start/docs/library/zod/validation.md +39 -155
- package/templates/hono/docs/commands/git.md +0 -145
- package/templates/hono/docs/mcp/context7.md +0 -106
- package/templates/hono/docs/mcp/index.md +0 -176
- package/templates/hono/docs/mcp/sequential-thinking.md +0 -101
- package/templates/hono/docs/mcp/serena.md +0 -269
- package/templates/hono/docs/mcp/sgrep.md +0 -105
- package/templates/hono/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/hono/docs/skills/gemini-review/references/checklists.md +0 -136
- package/templates/hono/docs/skills/gemini-review/references/prompt-templates.md +0 -303
- package/templates/npx/docs/commands/git.md +0 -145
- package/templates/npx/docs/mcp/index.md +0 -60
- package/templates/npx/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/npx/docs/skills/gemini-review/references/checklists.md +0 -134
- package/templates/npx/docs/skills/gemini-review/references/prompt-templates.md +0 -301
- package/templates/tanstack-start/docs/commands/git.md +0 -145
- package/templates/tanstack-start/docs/design/accessibility.md +0 -433
- package/templates/tanstack-start/docs/design/color.md +0 -235
- package/templates/tanstack-start/docs/design/spacing.md +0 -341
- package/templates/tanstack-start/docs/design/typography.md +0 -324
- package/templates/tanstack-start/docs/guides/best-practices.md +0 -950
- package/templates/tanstack-start/docs/guides/husky-lint-staged.md +0 -303
- package/templates/tanstack-start/docs/guides/prettier.md +0 -189
- package/templates/tanstack-start/docs/guides/project-templates.md +0 -710
- package/templates/tanstack-start/docs/library/tanstack-query/setup.md +0 -107
- package/templates/tanstack-start/docs/library/zod/basic-types.md +0 -186
- package/templates/tanstack-start/docs/mcp/context7.md +0 -204
- package/templates/tanstack-start/docs/mcp/index.md +0 -177
- package/templates/tanstack-start/docs/mcp/sequential-thinking.md +0 -180
- package/templates/tanstack-start/docs/mcp/serena.md +0 -269
- package/templates/tanstack-start/docs/mcp/sgrep.md +0 -174
- package/templates/tanstack-start/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/tanstack-start/docs/skills/gemini-review/references/checklists.md +0 -144
- package/templates/tanstack-start/docs/skills/gemini-review/references/prompt-templates.md +0 -292
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Service Layer
|
|
2
|
+
|
|
3
|
+
도메인별 SDK/서비스 레이어 구조.
|
|
4
|
+
|
|
5
|
+
## 폴더 구조
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
services/
|
|
9
|
+
├── user/
|
|
10
|
+
│ ├── index.ts # 진입점 (re-export)
|
|
11
|
+
│ ├── schemas.ts # Zod 스키마
|
|
12
|
+
│ ├── queries.ts # GET 요청 (읽기)
|
|
13
|
+
│ └── mutations.ts # POST 요청 (쓰기)
|
|
14
|
+
├── auth/
|
|
15
|
+
│ ├── index.ts
|
|
16
|
+
│ ├── schemas.ts
|
|
17
|
+
│ ├── queries.ts
|
|
18
|
+
│ └── mutations.ts
|
|
19
|
+
└── post/
|
|
20
|
+
├── index.ts
|
|
21
|
+
├── schemas.ts
|
|
22
|
+
├── queries.ts
|
|
23
|
+
└── mutations.ts
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Schemas 파일
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// services/user/schemas.ts
|
|
30
|
+
import { z } from 'zod'
|
|
31
|
+
|
|
32
|
+
export const createUserSchema = z.object({
|
|
33
|
+
email: z.email(),
|
|
34
|
+
name: z.string().min(1).max(100).trim(),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const updateUserSchema = z.object({
|
|
38
|
+
id: z.string(),
|
|
39
|
+
email: z.email().optional(),
|
|
40
|
+
name: z.string().min(1).max(100).trim().optional(),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export type CreateUserInput = z.infer<typeof createUserSchema>
|
|
44
|
+
export type UpdateUserInput = z.infer<typeof updateUserSchema>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Queries 파일
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// services/user/queries.ts
|
|
51
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
52
|
+
import { prisma } from '@/database/prisma'
|
|
53
|
+
|
|
54
|
+
export const getUsers = createServerFn({ method: 'GET' })
|
|
55
|
+
.handler(async () => {
|
|
56
|
+
return prisma.user.findMany({
|
|
57
|
+
orderBy: { createdAt: 'desc' },
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
export const getUserById = createServerFn({ method: 'GET' })
|
|
62
|
+
.handler(async ({ data: id }: { data: string }) => {
|
|
63
|
+
const user = await prisma.user.findUnique({ where: { id } })
|
|
64
|
+
if (!user) throw new Error('User not found')
|
|
65
|
+
return user
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
export const getUserByEmail = createServerFn({ method: 'GET' })
|
|
69
|
+
.handler(async ({ data: email }: { data: string }) => {
|
|
70
|
+
return prisma.user.findUnique({ where: { email } })
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Mutations 파일
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// services/user/mutations.ts
|
|
78
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
79
|
+
import { prisma } from '@/database/prisma'
|
|
80
|
+
import { createUserSchema, updateUserSchema } from './schemas'
|
|
81
|
+
|
|
82
|
+
export const createUser = createServerFn({ method: 'POST' })
|
|
83
|
+
.inputValidator(createUserSchema)
|
|
84
|
+
.handler(async ({ data }) => {
|
|
85
|
+
return prisma.user.create({ data })
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
export const updateUser = createServerFn({ method: 'POST' })
|
|
89
|
+
.inputValidator(updateUserSchema)
|
|
90
|
+
.handler(async ({ data }) => {
|
|
91
|
+
const { id, ...updateData } = data
|
|
92
|
+
return prisma.user.update({ where: { id }, data: updateData })
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
export const deleteUser = createServerFn({ method: 'POST' })
|
|
96
|
+
.handler(async ({ data: id }: { data: string }) => {
|
|
97
|
+
return prisma.user.delete({ where: { id } })
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 진입점 파일
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// services/user/index.ts
|
|
105
|
+
export * from './schemas'
|
|
106
|
+
export * from './queries'
|
|
107
|
+
export * from './mutations'
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 사용 예시
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// routes/users/-hooks/use-users.ts
|
|
114
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
115
|
+
import { getUsers, createUser, deleteUser } from '@/services/user'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Database 폴더
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
database/
|
|
122
|
+
├── prisma.ts # Prisma Client 싱글톤
|
|
123
|
+
└── seed.ts # 시드 스크립트 (선택)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// database/prisma.ts
|
|
128
|
+
import { PrismaClient } from '../../generated/prisma'
|
|
129
|
+
|
|
130
|
+
const globalForPrisma = globalThis as unknown as {
|
|
131
|
+
prisma: PrismaClient | undefined
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const prisma =
|
|
135
|
+
globalForPrisma.prisma ??
|
|
136
|
+
new PrismaClient({
|
|
137
|
+
log: process.env.NODE_ENV === 'development' ? ['query'] : [],
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
141
|
+
globalForPrisma.prisma = prisma
|
|
142
|
+
}
|
|
143
|
+
```
|
|
@@ -1,136 +1,48 @@
|
|
|
1
1
|
# Better Auth - 2단계 인증 (2FA)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## 서버 설정
|
|
3
|
+
## 서버
|
|
6
4
|
|
|
7
5
|
```typescript
|
|
8
|
-
import { betterAuth } from 'better-auth'
|
|
9
6
|
import { twoFactor } from 'better-auth/plugins'
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
},
|
|
23
|
-
period: 300, // 5분
|
|
24
|
-
length: 6,
|
|
25
|
-
},
|
|
26
|
-
backupCodeLength: 10,
|
|
27
|
-
backupCodeCount: 10,
|
|
28
|
-
}),
|
|
29
|
-
],
|
|
30
|
-
})
|
|
8
|
+
plugins: [
|
|
9
|
+
twoFactor({
|
|
10
|
+
issuer: 'My App',
|
|
11
|
+
otpOptions: {
|
|
12
|
+
async sendOTP({ user, otp }) { await sendEmail({ to: user.email, html: `Code: ${otp}` }) },
|
|
13
|
+
period: 300, length: 6,
|
|
14
|
+
},
|
|
15
|
+
backupCodeLength: 10,
|
|
16
|
+
backupCodeCount: 10,
|
|
17
|
+
}),
|
|
18
|
+
]
|
|
31
19
|
```
|
|
32
20
|
|
|
33
|
-
## 클라이언트
|
|
21
|
+
## 클라이언트
|
|
34
22
|
|
|
35
23
|
```typescript
|
|
36
24
|
import { twoFactorClient } from 'better-auth/client/plugins'
|
|
37
|
-
|
|
38
|
-
const authClient = createAuthClient({
|
|
39
|
-
plugins: [
|
|
40
|
-
twoFactorClient({
|
|
41
|
-
twoFactorPage: '/two-factor',
|
|
42
|
-
}),
|
|
43
|
-
],
|
|
44
|
-
})
|
|
25
|
+
plugins: [twoFactorClient({ twoFactorPage: '/two-factor' })]
|
|
45
26
|
```
|
|
46
27
|
|
|
47
|
-
##
|
|
28
|
+
## 사용법
|
|
48
29
|
|
|
49
30
|
```typescript
|
|
50
|
-
//
|
|
51
|
-
const { data } = await authClient.twoFactor.enable({
|
|
52
|
-
|
|
53
|
-
})
|
|
54
|
-
console.log('TOTP URI:', data.totpURI)
|
|
55
|
-
console.log('Backup codes:', data.backupCodes)
|
|
56
|
-
```
|
|
31
|
+
// 활성화
|
|
32
|
+
const { data } = await authClient.twoFactor.enable({ password })
|
|
33
|
+
// data.totpURI, data.backupCodes
|
|
57
34
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
// TOTP 코드 검증
|
|
62
|
-
await authClient.twoFactor.verifyTotp({
|
|
63
|
-
code: '123456',
|
|
64
|
-
})
|
|
65
|
-
```
|
|
35
|
+
// TOTP 검증
|
|
36
|
+
await authClient.twoFactor.verifyTotp({ code: '123456' })
|
|
66
37
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
```typescript
|
|
70
|
-
// OTP 전송
|
|
38
|
+
// OTP 전송 및 검증
|
|
71
39
|
await authClient.twoFactor.sendOtp()
|
|
40
|
+
await authClient.twoFactor.verifyOtp({ code })
|
|
72
41
|
|
|
73
|
-
//
|
|
74
|
-
await authClient.twoFactor.
|
|
75
|
-
|
|
76
|
-
})
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## 백업 코드
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
// 백업 코드 사용
|
|
83
|
-
await authClient.twoFactor.useBackupCode({
|
|
84
|
-
code: 'ABCD-1234-EFGH',
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
// 백업 코드 재생성
|
|
88
|
-
const { data: newCodes } = await authClient.twoFactor.regenerateBackupCodes({
|
|
89
|
-
password: 'userPassword123',
|
|
90
|
-
})
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## 2FA 비활성화
|
|
94
|
-
|
|
95
|
-
```typescript
|
|
96
|
-
await authClient.twoFactor.disable({
|
|
97
|
-
password: 'userPassword123',
|
|
98
|
-
})
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## 2FA 페이지 구현
|
|
102
|
-
|
|
103
|
-
```tsx
|
|
104
|
-
// pages/two-factor.tsx
|
|
105
|
-
import { useState } from 'react'
|
|
106
|
-
import { authClient } from '@/lib/auth-client'
|
|
107
|
-
|
|
108
|
-
export default function TwoFactorPage() {
|
|
109
|
-
const [code, setCode] = useState('')
|
|
110
|
-
const [error, setError] = useState('')
|
|
111
|
-
|
|
112
|
-
const handleVerify = async () => {
|
|
113
|
-
try {
|
|
114
|
-
await authClient.twoFactor.verifyTotp({ code })
|
|
115
|
-
// 성공 시 대시보드로 이동
|
|
116
|
-
window.location.href = '/dashboard'
|
|
117
|
-
} catch (e) {
|
|
118
|
-
setError('Invalid code')
|
|
119
|
-
}
|
|
120
|
-
}
|
|
42
|
+
// 백업 코드
|
|
43
|
+
await authClient.twoFactor.useBackupCode({ code: 'ABCD-1234' })
|
|
44
|
+
await authClient.twoFactor.regenerateBackupCodes({ password })
|
|
121
45
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
<h1>2단계 인증</h1>
|
|
125
|
-
<input
|
|
126
|
-
type="text"
|
|
127
|
-
value={code}
|
|
128
|
-
onChange={(e) => setCode(e.target.value)}
|
|
129
|
-
placeholder="6자리 코드 입력"
|
|
130
|
-
/>
|
|
131
|
-
<button onClick={handleVerify}>확인</button>
|
|
132
|
-
{error && <p style={{ color: 'red' }}>{error}</p>}
|
|
133
|
-
</div>
|
|
134
|
-
)
|
|
135
|
-
}
|
|
46
|
+
// 비활성화
|
|
47
|
+
await authClient.twoFactor.disable({ password })
|
|
136
48
|
```
|
|
@@ -1,138 +1,55 @@
|
|
|
1
1
|
# Better Auth - 고급 기능
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## CAPTCHA 보호
|
|
3
|
+
## CAPTCHA
|
|
6
4
|
|
|
7
5
|
```typescript
|
|
8
|
-
|
|
6
|
+
// 서버
|
|
9
7
|
import { captcha } from 'better-auth/plugins'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
siteKey: process.env.RECAPTCHA_SITE_KEY!,
|
|
16
|
-
secretKey: process.env.RECAPTCHA_SECRET_KEY!,
|
|
17
|
-
protectEndpoints: ['/sign-up/email', '/sign-in/email'],
|
|
18
|
-
}),
|
|
19
|
-
],
|
|
20
|
-
})
|
|
8
|
+
plugins: [captcha({
|
|
9
|
+
provider: 'recaptcha',
|
|
10
|
+
siteKey: SITE_KEY, secretKey: SECRET_KEY,
|
|
11
|
+
protectEndpoints: ['/sign-up/email', '/sign-in/email'],
|
|
12
|
+
})]
|
|
21
13
|
|
|
22
14
|
// 클라이언트
|
|
23
15
|
import { captchaClient } from 'better-auth/client/plugins'
|
|
24
|
-
|
|
25
|
-
const authClient = createAuthClient({
|
|
26
|
-
plugins: [
|
|
27
|
-
captchaClient({
|
|
28
|
-
siteKey: process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY!,
|
|
29
|
-
}),
|
|
30
|
-
],
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
// CAPTCHA는 보호된 요청에 자동 포함됨
|
|
34
|
-
await authClient.signUp.email({
|
|
35
|
-
email: 'user@example.com',
|
|
36
|
-
password: 'SecurePassword123!',
|
|
37
|
-
})
|
|
16
|
+
plugins: [captchaClient({ siteKey: SITE_KEY })]
|
|
38
17
|
```
|
|
39
18
|
|
|
40
|
-
## SSO
|
|
19
|
+
## SSO
|
|
41
20
|
|
|
42
21
|
```typescript
|
|
43
|
-
|
|
44
|
-
const res = await authClient.signIn.sso({
|
|
45
|
-
providerId: 'example-provider-id',
|
|
46
|
-
callbackURL: '/dashboard',
|
|
47
|
-
})
|
|
22
|
+
await authClient.signIn.sso({ providerId: 'provider-id', callbackURL: '/dashboard' })
|
|
48
23
|
```
|
|
49
24
|
|
|
50
|
-
## SIWE (
|
|
51
|
-
|
|
52
|
-
### 서버 설정
|
|
25
|
+
## SIWE (Ethereum)
|
|
53
26
|
|
|
54
27
|
```typescript
|
|
55
|
-
|
|
28
|
+
// 서버
|
|
56
29
|
import { siwe } from 'better-auth/plugins'
|
|
30
|
+
plugins: [siwe({ domain: 'example.com', uri: 'https://example.com' })]
|
|
57
31
|
|
|
58
|
-
|
|
59
|
-
plugins: [
|
|
60
|
-
siwe({
|
|
61
|
-
domain: 'example.com',
|
|
62
|
-
uri: 'https://example.com',
|
|
63
|
-
statement: 'Sign in with your Ethereum account',
|
|
64
|
-
}),
|
|
65
|
-
],
|
|
66
|
-
})
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### 클라이언트 사용
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
import { siweClient } from 'better-auth/client/plugins'
|
|
73
|
-
import { ethers } from 'ethers'
|
|
74
|
-
|
|
75
|
-
const authClient = createAuthClient({
|
|
76
|
-
plugins: [siweClient()],
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
// Nonce 가져오기
|
|
32
|
+
// 클라이언트
|
|
80
33
|
const { data: nonce } = await authClient.siwe.getNonce()
|
|
81
|
-
|
|
82
|
-
// 메시지 생성 및 서명
|
|
83
|
-
const provider = new ethers.BrowserProvider(window.ethereum)
|
|
84
|
-
const signer = await provider.getSigner()
|
|
85
|
-
const address = await signer.getAddress()
|
|
86
|
-
|
|
87
|
-
const message = await authClient.siwe.prepareMessage({
|
|
88
|
-
address,
|
|
89
|
-
nonce: nonce.nonce,
|
|
90
|
-
})
|
|
91
|
-
|
|
34
|
+
const message = await authClient.siwe.prepareMessage({ address, nonce: nonce.nonce })
|
|
92
35
|
const signature = await signer.signMessage(message)
|
|
93
|
-
|
|
94
|
-
// 검증 및 로그인
|
|
95
|
-
await authClient.siwe.signIn({
|
|
96
|
-
message,
|
|
97
|
-
signature,
|
|
98
|
-
})
|
|
36
|
+
await authClient.siwe.signIn({ message, signature })
|
|
99
37
|
```
|
|
100
38
|
|
|
101
39
|
## Stateless 모드
|
|
102
40
|
|
|
103
|
-
데이터베이스 없이 사용:
|
|
104
|
-
|
|
105
41
|
```typescript
|
|
106
|
-
|
|
107
|
-
|
|
42
|
+
// DB 없이 소셜 로그인만
|
|
108
43
|
export const auth = betterAuth({
|
|
109
|
-
|
|
110
|
-
socialProviders: {
|
|
111
|
-
google: {
|
|
112
|
-
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
113
|
-
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
114
|
-
},
|
|
115
|
-
},
|
|
44
|
+
socialProviders: { google: { clientId, clientSecret } },
|
|
116
45
|
})
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
### Redis와 함께 하이브리드 모드
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
import { betterAuth } from 'better-auth'
|
|
123
|
-
import { redis } from './redis'
|
|
124
46
|
|
|
47
|
+
// Redis 하이브리드
|
|
125
48
|
export const auth = betterAuth({
|
|
126
49
|
secondaryStorage: {
|
|
127
|
-
get:
|
|
128
|
-
set:
|
|
129
|
-
delete:
|
|
130
|
-
},
|
|
131
|
-
session: {
|
|
132
|
-
cookieCache: {
|
|
133
|
-
maxAge: 5 * 60, // 5분
|
|
134
|
-
refreshCache: false,
|
|
135
|
-
},
|
|
50
|
+
get: (key) => redis.get(key),
|
|
51
|
+
set: (key, value, ttl) => redis.set(key, value, 'EX', ttl),
|
|
52
|
+
delete: (key) => redis.del(key),
|
|
136
53
|
},
|
|
137
54
|
})
|
|
138
55
|
```
|
|
@@ -1,83 +1,34 @@
|
|
|
1
1
|
# Better Auth
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
> **Framework**: TypeScript Authentication Library
|
|
5
|
-
> **Source**: [Better Auth Documentation](https://www.better-auth.com)
|
|
3
|
+
> TypeScript Authentication Library
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
@setup.md
|
|
6
|
+
@session.md
|
|
7
|
+
@plugins.md
|
|
8
|
+
@2fa.md
|
|
9
|
+
@advanced.md
|
|
8
10
|
|
|
9
|
-
##
|
|
10
|
-
|
|
11
|
-
- [설치 및 설정](./setup.md) - 서버/클라이언트 설정, 소셜 로그인
|
|
12
|
-
- [세션 관리](./session.md) - 세션 설정, 쿠키 캐시
|
|
13
|
-
- [플러그인](./plugins.md) - Multi-Session, Custom Session
|
|
14
|
-
- [2단계 인증](./2fa.md) - TOTP, OTP, 백업 코드
|
|
15
|
-
- [고급 기능](./advanced.md) - CAPTCHA, SSO, SIWE, Stateless 모드
|
|
16
|
-
|
|
17
|
-
## 빠른 시작
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
yarn add better-auth
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### 기본 서버 설정
|
|
11
|
+
## Quick Reference
|
|
24
12
|
|
|
25
13
|
```typescript
|
|
26
|
-
//
|
|
14
|
+
// 서버
|
|
27
15
|
import { betterAuth } from 'better-auth'
|
|
28
16
|
import { prismaAdapter } from 'better-auth/adapters/prisma'
|
|
29
|
-
import { prisma } from './prisma'
|
|
30
17
|
|
|
31
18
|
export const auth = betterAuth({
|
|
32
19
|
database: prismaAdapter(prisma),
|
|
33
|
-
emailAndPassword: {
|
|
34
|
-
enabled: true,
|
|
35
|
-
requireEmailVerification: false,
|
|
36
|
-
},
|
|
20
|
+
emailAndPassword: { enabled: true },
|
|
37
21
|
})
|
|
38
|
-
```
|
|
39
22
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
43
|
-
// lib/auth-client.ts
|
|
23
|
+
// 클라이언트
|
|
44
24
|
import { createAuthClient } from 'better-auth/react'
|
|
25
|
+
export const authClient = createAuthClient({})
|
|
45
26
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
})
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## 핵심 개념
|
|
27
|
+
// 로그인/가입
|
|
28
|
+
await authClient.signIn.email({ email, password })
|
|
29
|
+
await authClient.signUp.email({ email, password, name })
|
|
52
30
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
await authClient.signIn.email({
|
|
57
|
-
email: 'user@example.com',
|
|
58
|
-
password: 'password123',
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
// 회원가입
|
|
62
|
-
await authClient.signUp.email({
|
|
63
|
-
email: 'user@example.com',
|
|
64
|
-
password: 'password123',
|
|
65
|
-
name: 'John Doe',
|
|
66
|
-
})
|
|
31
|
+
// 세션
|
|
32
|
+
const session = await authClient.getSession() // 클라이언트
|
|
33
|
+
const session = await auth.api.getSession({ headers }) // 서버
|
|
67
34
|
```
|
|
68
|
-
|
|
69
|
-
### 세션 조회
|
|
70
|
-
```typescript
|
|
71
|
-
// 클라이언트
|
|
72
|
-
const session = await authClient.getSession()
|
|
73
|
-
|
|
74
|
-
// 서버
|
|
75
|
-
const session = await auth.api.getSession({
|
|
76
|
-
headers: request.headers,
|
|
77
|
-
})
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## 참고 자료
|
|
81
|
-
|
|
82
|
-
- [Better Auth 공식 문서](https://www.better-auth.com)
|
|
83
|
-
- [Better Auth GitHub](https://github.com/better-auth/better-auth)
|
|
@@ -1,111 +1,34 @@
|
|
|
1
1
|
# Better Auth - 플러그인
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Multi-Session Plugin
|
|
6
|
-
|
|
7
|
-
여러 계정으로 동시 로그인을 지원합니다.
|
|
8
|
-
|
|
9
|
-
### 서버 설정
|
|
3
|
+
## Multi-Session
|
|
10
4
|
|
|
11
5
|
```typescript
|
|
12
|
-
|
|
6
|
+
// 서버
|
|
13
7
|
import { multiSession } from 'better-auth/plugins'
|
|
8
|
+
plugins: [multiSession({ maximumSessions: 5 })]
|
|
14
9
|
|
|
15
|
-
|
|
16
|
-
plugins: [
|
|
17
|
-
multiSession({
|
|
18
|
-
maximumSessions: 5, // 최대 동시 세션 수
|
|
19
|
-
}),
|
|
20
|
-
],
|
|
21
|
-
})
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### 클라이언트 사용
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
import { createAuthClient } from 'better-auth/client'
|
|
10
|
+
// 클라이언트
|
|
28
11
|
import { multiSessionClient } from 'better-auth/client/plugins'
|
|
12
|
+
plugins: [multiSessionClient()]
|
|
29
13
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
// 활성 세션 목록 조회
|
|
35
|
-
const { data: sessions } = await authClient.multiSession.listSessions()
|
|
36
|
-
|
|
37
|
-
sessions?.forEach((session) => {
|
|
38
|
-
console.log(`Device: ${session.userAgent}, Last active: ${session.updatedAt}`)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
// 특정 세션 취소
|
|
42
|
-
await authClient.multiSession.revokeSession({
|
|
43
|
-
sessionId: 'session_456',
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
// 다른 모든 세션 취소
|
|
14
|
+
// 사용
|
|
15
|
+
await authClient.multiSession.listSessions()
|
|
16
|
+
await authClient.multiSession.revokeSession({ sessionId })
|
|
47
17
|
await authClient.multiSession.revokeOtherSessions()
|
|
48
18
|
```
|
|
49
19
|
|
|
50
|
-
## Polar Checkout Plugin
|
|
51
|
-
|
|
52
|
-
결제 통합을 위한 플러그인입니다.
|
|
53
|
-
|
|
54
|
-
```typescript
|
|
55
|
-
import { polar, checkout } from '@polar-sh/better-auth'
|
|
56
|
-
|
|
57
|
-
const auth = betterAuth({
|
|
58
|
-
plugins: [
|
|
59
|
-
polar({
|
|
60
|
-
use: [
|
|
61
|
-
checkout({
|
|
62
|
-
products: [{ productId: '123-456-789', slug: 'pro' }],
|
|
63
|
-
successUrl: '/success?checkout_id={CHECKOUT_ID}',
|
|
64
|
-
authenticatedUsersOnly: true,
|
|
65
|
-
}),
|
|
66
|
-
],
|
|
67
|
-
}),
|
|
68
|
-
],
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
// 클라이언트
|
|
72
|
-
import { polarClient } from '@polar-sh/better-auth'
|
|
73
|
-
|
|
74
|
-
export const authClient = createAuthClient({
|
|
75
|
-
plugins: [polarClient()],
|
|
76
|
-
})
|
|
77
|
-
```
|
|
78
|
-
|
|
79
20
|
## 플러그인 조합
|
|
80
21
|
|
|
81
22
|
```typescript
|
|
82
|
-
import {
|
|
83
|
-
import {
|
|
84
|
-
multiSession,
|
|
85
|
-
customSession,
|
|
86
|
-
twoFactor,
|
|
87
|
-
captcha
|
|
88
|
-
} from 'better-auth/plugins'
|
|
23
|
+
import { multiSession, customSession, twoFactor, captcha } from 'better-auth/plugins'
|
|
89
24
|
|
|
90
25
|
export const auth = betterAuth({
|
|
91
26
|
database: prismaAdapter(prisma),
|
|
92
27
|
plugins: [
|
|
93
28
|
multiSession({ maximumSessions: 5 }),
|
|
94
|
-
customSession({
|
|
95
|
-
schema: {
|
|
96
|
-
session: {
|
|
97
|
-
fields: {
|
|
98
|
-
ipAddress: { type: 'string', required: false },
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
}),
|
|
29
|
+
customSession({ schema: { session: { fields: { ipAddress: { type: 'string' } } } } }),
|
|
103
30
|
twoFactor({ issuer: 'My App' }),
|
|
104
|
-
captcha({
|
|
105
|
-
provider: 'recaptcha',
|
|
106
|
-
siteKey: process.env.RECAPTCHA_SITE_KEY!,
|
|
107
|
-
secretKey: process.env.RECAPTCHA_SECRET_KEY!,
|
|
108
|
-
}),
|
|
31
|
+
captcha({ provider: 'recaptcha', siteKey: SITE_KEY, secretKey: SECRET_KEY }),
|
|
109
32
|
],
|
|
110
33
|
})
|
|
111
34
|
```
|