@spfn/auth 0.2.0-beta.11 → 0.2.0-beta.13
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 +338 -8
- package/dist/{authenticate-CU6_zQaa.d.ts → authenticate-Cz2FjLdB.d.ts} +113 -1
- package/dist/config.d.ts +120 -0
- package/dist/config.js +72 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +46 -2
- package/dist/nextjs/api.js +186 -0
- package/dist/nextjs/api.js.map +1 -1
- package/dist/nextjs/client.js +80 -0
- package/dist/nextjs/client.js.map +1 -0
- package/dist/nextjs/server.d.ts +68 -2
- package/dist/nextjs/server.js +125 -2
- package/dist/nextjs/server.js.map +1 -1
- package/dist/server.d.ts +243 -3
- package/dist/server.js +594 -23
- package/dist/server.js.map +1 -1
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @spfn/auth - Technical Documentation
|
|
2
2
|
|
|
3
|
-
**Version:** 0.2.0-beta.
|
|
3
|
+
**Version:** 0.2.0-beta.13
|
|
4
4
|
**Status:** Alpha - Internal Development
|
|
5
5
|
|
|
6
6
|
> **Note:** This is a technical documentation for developers working on the @spfn/auth package.
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
- [Module Exports](#module-exports)
|
|
19
19
|
- [Email & SMS Services](#email--sms-services)
|
|
20
20
|
- [Server-Side API](#server-side-api)
|
|
21
|
+
- [OAuth Authentication](#oauth-authentication)
|
|
21
22
|
- [Database Schema](#database-schema)
|
|
22
23
|
- [RBAC System](#rbac-system)
|
|
23
24
|
- [Next.js Adapter](#nextjs-adapter)
|
|
@@ -34,10 +35,11 @@
|
|
|
34
35
|
|
|
35
36
|
- **Asymmetric JWT Authentication** - Client-signed tokens using ES256/RS256
|
|
36
37
|
- **User Management** - Email/phone-based identity with bcrypt hashing
|
|
38
|
+
- **OAuth Authentication** - Google OAuth 2.0 (Authorization Code Flow), extensible to other providers
|
|
37
39
|
- **Multi-Factor Authentication** - OTP verification via email/SMS
|
|
38
40
|
- **Session Management** - Public key rotation with 90-day expiry
|
|
39
41
|
- **Role-Based Access Control** - Flexible RBAC with runtime role/permission management
|
|
40
|
-
- **Next.js Integration** - Session helpers
|
|
42
|
+
- **Next.js Integration** - Session helpers, server-side guards, and OAuth interceptors
|
|
41
43
|
|
|
42
44
|
### Design Principles
|
|
43
45
|
|
|
@@ -130,6 +132,17 @@ SPFN_AUTH_JWT_EXPIRES_IN=7d
|
|
|
130
132
|
SPFN_AUTH_BCRYPT_SALT_ROUNDS=10
|
|
131
133
|
SPFN_AUTH_SESSION_TTL=7d
|
|
132
134
|
|
|
135
|
+
# Google OAuth
|
|
136
|
+
SPFN_AUTH_GOOGLE_CLIENT_ID=123456789-abc.apps.googleusercontent.com
|
|
137
|
+
SPFN_AUTH_GOOGLE_CLIENT_SECRET=GOCSPX-...
|
|
138
|
+
SPFN_APP_URL=http://localhost:3000
|
|
139
|
+
|
|
140
|
+
# Google OAuth (Optional)
|
|
141
|
+
SPFN_AUTH_GOOGLE_SCOPES=email,profile,https://www.googleapis.com/auth/gmail.readonly
|
|
142
|
+
SPFN_AUTH_GOOGLE_REDIRECT_URI=http://localhost:8790/_auth/oauth/google/callback
|
|
143
|
+
SPFN_AUTH_OAUTH_SUCCESS_URL=/auth/callback
|
|
144
|
+
SPFN_AUTH_OAUTH_ERROR_URL=http://localhost:3000/auth/error?error={error}
|
|
145
|
+
|
|
133
146
|
# AWS SES (Email)
|
|
134
147
|
SPFN_AUTH_AWS_REGION=ap-northeast-2
|
|
135
148
|
SPFN_AUTH_AWS_SES_ACCESS_KEY_ID=AKIA...
|
|
@@ -571,6 +584,9 @@ import {
|
|
|
571
584
|
// User Profile
|
|
572
585
|
getUserProfileService,
|
|
573
586
|
updateUserProfileService,
|
|
587
|
+
|
|
588
|
+
// OAuth - Google API Access
|
|
589
|
+
getGoogleAccessToken,
|
|
574
590
|
} from '@spfn/auth/server';
|
|
575
591
|
```
|
|
576
592
|
|
|
@@ -719,9 +735,11 @@ import {
|
|
|
719
735
|
loginRegisterInterceptor,
|
|
720
736
|
generalAuthInterceptor,
|
|
721
737
|
keyRotationInterceptor,
|
|
738
|
+
oauthUrlInterceptor,
|
|
739
|
+
oauthFinalizeInterceptor,
|
|
722
740
|
} from '@spfn/auth/nextjs/api';
|
|
723
741
|
|
|
724
|
-
// Auto-registers interceptors on import
|
|
742
|
+
// Auto-registers interceptors on import (including OAuth)
|
|
725
743
|
import '@spfn/auth/nextjs/api';
|
|
726
744
|
```
|
|
727
745
|
|
|
@@ -1019,6 +1037,306 @@ Change password.
|
|
|
1019
1037
|
|
|
1020
1038
|
---
|
|
1021
1039
|
|
|
1040
|
+
## OAuth Authentication
|
|
1041
|
+
|
|
1042
|
+
### Overview
|
|
1043
|
+
|
|
1044
|
+
`@spfn/auth`는 OAuth 2.0 Authorization Code Flow를 지원합니다. 현재 Google OAuth가 구현되어 있으며, 다른 provider (GitHub, Kakao, Naver)는 동일한 패턴으로 확장 가능합니다.
|
|
1045
|
+
|
|
1046
|
+
**핵심 설계:**
|
|
1047
|
+
- 환경 변수만으로 설정 (`SPFN_AUTH_GOOGLE_CLIENT_ID`, `SPFN_AUTH_GOOGLE_CLIENT_SECRET`)
|
|
1048
|
+
- Next.js 인터셉터 기반 자동 세션 관리 (키쌍 생성 → pending session → full session)
|
|
1049
|
+
- 기존 이메일 계정과 자동 연결 (Google verified_email 확인 시에만)
|
|
1050
|
+
|
|
1051
|
+
---
|
|
1052
|
+
|
|
1053
|
+
### Authentication Flow
|
|
1054
|
+
|
|
1055
|
+
```
|
|
1056
|
+
┌──────────┐ ┌──────────────┐ ┌──────────┐ ┌──────────┐
|
|
1057
|
+
│ Client │ │ Next.js RPC │ │ Backend │ │ Google │
|
|
1058
|
+
│ (Browser)│ │ (Interceptor)│ │ (SPFN) │ │ OAuth │
|
|
1059
|
+
└────┬─────┘ └──────┬───────┘ └────┬─────┘ └────┬─────┘
|
|
1060
|
+
│ │ │ │
|
|
1061
|
+
│ 1. Click Login │ │ │
|
|
1062
|
+
├──────────────────>│ │ │
|
|
1063
|
+
│ │ │ │
|
|
1064
|
+
│ 2. Generate keypair (ES256) │ │
|
|
1065
|
+
│ 3. Create encrypted state │ │
|
|
1066
|
+
│ (publicKey, keyId in JWE) │ │
|
|
1067
|
+
│ 4. Save privateKey to │ │
|
|
1068
|
+
│ pending session cookie │ │
|
|
1069
|
+
│ │ │ │
|
|
1070
|
+
│ │ 5. Forward with │ │
|
|
1071
|
+
│ │ state in body │ │
|
|
1072
|
+
│ ├─────────────────>│ │
|
|
1073
|
+
│ │ │ │
|
|
1074
|
+
│ │ 6. Return Google │ │
|
|
1075
|
+
│ │ Auth URL │ │
|
|
1076
|
+
│ │<─────────────────┤ │
|
|
1077
|
+
│ │ │ │
|
|
1078
|
+
│ 7. Redirect to Google │ │
|
|
1079
|
+
│<──────────────────┤ │ │
|
|
1080
|
+
│ │ │ │
|
|
1081
|
+
│ 8. User consents │ │ │
|
|
1082
|
+
├───────────────────┼──────────────────┼────────────────>│
|
|
1083
|
+
│ │ │ │
|
|
1084
|
+
│ │ 9. Callback with code + state │
|
|
1085
|
+
│ │ │<────────────────┤
|
|
1086
|
+
│ │ │ │
|
|
1087
|
+
│ │ 10. Verify state, exchange code │
|
|
1088
|
+
│ │ Create/link user account │
|
|
1089
|
+
│ │ Register publicKey │
|
|
1090
|
+
│ │ │ │
|
|
1091
|
+
│ 11. Redirect to /auth/callback │ │
|
|
1092
|
+
│ ?userId=X&keyId=Y&returnUrl=/ │ │
|
|
1093
|
+
│<─────────────────────────────────────┤ │
|
|
1094
|
+
│ │ │ │
|
|
1095
|
+
│ 12. OAuthCallback │ │ │
|
|
1096
|
+
│ component │ │ │
|
|
1097
|
+
│ calls finalize│ │ │
|
|
1098
|
+
├──────────────────>│ │ │
|
|
1099
|
+
│ │ │ │
|
|
1100
|
+
│ 13. Interceptor reads pending │ │
|
|
1101
|
+
│ session cookie, verifies │ │
|
|
1102
|
+
│ keyId match, creates full │ │
|
|
1103
|
+
│ session cookie │ │
|
|
1104
|
+
│ │ │ │
|
|
1105
|
+
│ 14. Session set, │ │ │
|
|
1106
|
+
│ redirect to │ │ │
|
|
1107
|
+
│ returnUrl │ │ │
|
|
1108
|
+
│<──────────────────┤ │ │
|
|
1109
|
+
│ │ │ │
|
|
1110
|
+
```
|
|
1111
|
+
|
|
1112
|
+
---
|
|
1113
|
+
|
|
1114
|
+
### Setup
|
|
1115
|
+
|
|
1116
|
+
#### 1. Google Cloud Console
|
|
1117
|
+
|
|
1118
|
+
1. [Google Cloud Console](https://console.cloud.google.com/) > APIs & Services > Credentials
|
|
1119
|
+
2. Create OAuth 2.0 Client ID (Web application)
|
|
1120
|
+
3. Add Authorized redirect URI: `http://localhost:8790/_auth/oauth/google/callback`
|
|
1121
|
+
4. Copy Client ID and Client Secret
|
|
1122
|
+
|
|
1123
|
+
#### 2. Environment Variables
|
|
1124
|
+
|
|
1125
|
+
```bash
|
|
1126
|
+
# Required
|
|
1127
|
+
SPFN_AUTH_GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
|
|
1128
|
+
SPFN_AUTH_GOOGLE_CLIENT_SECRET=GOCSPX-your-secret
|
|
1129
|
+
|
|
1130
|
+
# Next.js app URL (for OAuth callback redirect)
|
|
1131
|
+
SPFN_APP_URL=http://localhost:3000
|
|
1132
|
+
|
|
1133
|
+
# Optional
|
|
1134
|
+
SPFN_AUTH_GOOGLE_SCOPES=email,profile # default (comma-separated)
|
|
1135
|
+
SPFN_AUTH_GOOGLE_REDIRECT_URI=http://localhost:8790/_auth/oauth/google/callback # default
|
|
1136
|
+
SPFN_AUTH_OAUTH_SUCCESS_URL=/auth/callback # default
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
#### 3. Next.js Callback Page
|
|
1140
|
+
|
|
1141
|
+
```tsx
|
|
1142
|
+
// app/auth/callback/page.tsx
|
|
1143
|
+
export { OAuthCallback as default } from '@spfn/auth/nextjs/client';
|
|
1144
|
+
```
|
|
1145
|
+
|
|
1146
|
+
#### 4. Login Button
|
|
1147
|
+
|
|
1148
|
+
```typescript
|
|
1149
|
+
import { authApi } from '@spfn/auth';
|
|
1150
|
+
|
|
1151
|
+
const handleGoogleLogin = async () =>
|
|
1152
|
+
{
|
|
1153
|
+
const response = await authApi.getGoogleOAuthUrl.call({
|
|
1154
|
+
body: { returnUrl: '/dashboard' },
|
|
1155
|
+
});
|
|
1156
|
+
window.location.href = response.authUrl;
|
|
1157
|
+
};
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
---
|
|
1161
|
+
|
|
1162
|
+
### OAuth Routes
|
|
1163
|
+
|
|
1164
|
+
#### `GET /_auth/oauth/google`
|
|
1165
|
+
|
|
1166
|
+
Google OAuth 시작 (리다이렉트 방식). 브라우저를 Google 로그인 페이지로 직접 리다이렉트합니다.
|
|
1167
|
+
|
|
1168
|
+
**Query:**
|
|
1169
|
+
```typescript
|
|
1170
|
+
{
|
|
1171
|
+
state: string; // Encrypted OAuth state (JWE)
|
|
1172
|
+
}
|
|
1173
|
+
```
|
|
1174
|
+
|
|
1175
|
+
---
|
|
1176
|
+
|
|
1177
|
+
#### `POST /_auth/oauth/google/url`
|
|
1178
|
+
|
|
1179
|
+
Google OAuth URL 획득 (인터셉터 방식). 인터셉터가 state를 자동 생성하여 주입합니다.
|
|
1180
|
+
|
|
1181
|
+
**Request:**
|
|
1182
|
+
```typescript
|
|
1183
|
+
{
|
|
1184
|
+
returnUrl?: string; // Default: '/'
|
|
1185
|
+
}
|
|
1186
|
+
```
|
|
1187
|
+
|
|
1188
|
+
**Response:**
|
|
1189
|
+
```typescript
|
|
1190
|
+
{
|
|
1191
|
+
authUrl: string; // Google OAuth URL
|
|
1192
|
+
}
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
---
|
|
1196
|
+
|
|
1197
|
+
#### `GET /_auth/oauth/google/callback`
|
|
1198
|
+
|
|
1199
|
+
Google에서 리다이렉트되는 콜백. code를 token으로 교환하고 사용자를 생성/연결합니다.
|
|
1200
|
+
|
|
1201
|
+
**Query (from Google):**
|
|
1202
|
+
```typescript
|
|
1203
|
+
{
|
|
1204
|
+
code?: string; // Authorization code
|
|
1205
|
+
state?: string; // OAuth state
|
|
1206
|
+
error?: string; // Error code
|
|
1207
|
+
error_description?: string; // Error description
|
|
1208
|
+
}
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
**Result:** Next.js 콜백 페이지로 리다이렉트 (`/auth/callback?userId=X&keyId=Y&returnUrl=/`)
|
|
1212
|
+
|
|
1213
|
+
---
|
|
1214
|
+
|
|
1215
|
+
#### `POST /_auth/oauth/finalize`
|
|
1216
|
+
|
|
1217
|
+
OAuth 세션 완료. 인터셉터가 pending session에서 full session을 생성합니다.
|
|
1218
|
+
|
|
1219
|
+
**Request:**
|
|
1220
|
+
```typescript
|
|
1221
|
+
{
|
|
1222
|
+
userId: string;
|
|
1223
|
+
keyId: string;
|
|
1224
|
+
returnUrl?: string;
|
|
1225
|
+
}
|
|
1226
|
+
```
|
|
1227
|
+
|
|
1228
|
+
**Response:**
|
|
1229
|
+
```typescript
|
|
1230
|
+
{
|
|
1231
|
+
success: boolean;
|
|
1232
|
+
returnUrl: string;
|
|
1233
|
+
}
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1236
|
+
---
|
|
1237
|
+
|
|
1238
|
+
#### `GET /_auth/oauth/providers`
|
|
1239
|
+
|
|
1240
|
+
활성화된 OAuth provider 목록을 반환합니다.
|
|
1241
|
+
|
|
1242
|
+
**Response:**
|
|
1243
|
+
```typescript
|
|
1244
|
+
{
|
|
1245
|
+
providers: ('google' | 'github' | 'kakao' | 'naver')[];
|
|
1246
|
+
}
|
|
1247
|
+
```
|
|
1248
|
+
|
|
1249
|
+
---
|
|
1250
|
+
|
|
1251
|
+
### Google API Access
|
|
1252
|
+
|
|
1253
|
+
OAuth 로그인 후 저장된 access token으로 Google API를 호출할 수 있습니다.
|
|
1254
|
+
|
|
1255
|
+
#### Custom Scopes 설정
|
|
1256
|
+
|
|
1257
|
+
`SPFN_AUTH_GOOGLE_SCOPES` 환경변수로 추가 스코프를 요청합니다. 미설정 시 `email,profile`이 기본값입니다.
|
|
1258
|
+
|
|
1259
|
+
```bash
|
|
1260
|
+
# Gmail + Calendar 읽기 권한 추가
|
|
1261
|
+
SPFN_AUTH_GOOGLE_SCOPES=email,profile,https://www.googleapis.com/auth/gmail.readonly,https://www.googleapis.com/auth/calendar.readonly
|
|
1262
|
+
```
|
|
1263
|
+
|
|
1264
|
+
> **Note:** Google Cloud Console에서 해당 API를 활성화해야 합니다.
|
|
1265
|
+
|
|
1266
|
+
#### Access Token 사용
|
|
1267
|
+
|
|
1268
|
+
`getGoogleAccessToken(userId)`은 유효한 access token을 반환합니다. 토큰이 만료 임박(5분 이내) 또는 만료 상태이면 자동으로 refresh token을 사용하여 갱신합니다.
|
|
1269
|
+
|
|
1270
|
+
```typescript
|
|
1271
|
+
import { getGoogleAccessToken } from '@spfn/auth/server';
|
|
1272
|
+
|
|
1273
|
+
// 항상 유효한 토큰 반환 (만료 시 자동 갱신)
|
|
1274
|
+
const token = await getGoogleAccessToken(userId);
|
|
1275
|
+
|
|
1276
|
+
// Gmail API 호출
|
|
1277
|
+
const response = await fetch(
|
|
1278
|
+
'https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=10',
|
|
1279
|
+
{ headers: { Authorization: `Bearer ${token}` } }
|
|
1280
|
+
);
|
|
1281
|
+
const data = await response.json();
|
|
1282
|
+
```
|
|
1283
|
+
|
|
1284
|
+
**에러 케이스:**
|
|
1285
|
+
- Google 계정 미연결 → `'No Google account linked'`
|
|
1286
|
+
- Refresh token 없음 → `'Google refresh token not available'` (재로그인 필요)
|
|
1287
|
+
|
|
1288
|
+
---
|
|
1289
|
+
|
|
1290
|
+
### Security
|
|
1291
|
+
|
|
1292
|
+
- **State 암호화**: JWE (A256GCM)로 state 파라미터 암호화. CSRF 방지용 nonce 포함.
|
|
1293
|
+
- **Pending Session**: OAuth 리다이렉트 중 privateKey를 JWE로 암호화한 HttpOnly 쿠키에 저장. 10분 TTL.
|
|
1294
|
+
- **KeyId 검증**: finalize 시 pending session의 keyId와 응답의 keyId 일치 확인.
|
|
1295
|
+
- **Email 검증**: `verified_email`이 true인 경우에만 기존 계정에 자동 연결. 미검증 이메일로 기존 계정 연결 시도 시 에러.
|
|
1296
|
+
- **Session Cookie**: `HttpOnly`, `Secure` (production), `SameSite=strict`.
|
|
1297
|
+
|
|
1298
|
+
---
|
|
1299
|
+
|
|
1300
|
+
### OAuthCallback Component
|
|
1301
|
+
|
|
1302
|
+
`@spfn/auth/nextjs/client`에서 제공하는 클라이언트 컴포넌트입니다.
|
|
1303
|
+
|
|
1304
|
+
```tsx
|
|
1305
|
+
import { OAuthCallback } from '@spfn/auth/nextjs/client';
|
|
1306
|
+
|
|
1307
|
+
// 기본 사용
|
|
1308
|
+
export default function CallbackPage()
|
|
1309
|
+
{
|
|
1310
|
+
return <OAuthCallback />;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// 커스터마이징
|
|
1314
|
+
export default function CallbackPage()
|
|
1315
|
+
{
|
|
1316
|
+
return (
|
|
1317
|
+
<OAuthCallback
|
|
1318
|
+
apiBasePath="/api/rpc"
|
|
1319
|
+
loadingComponent={<MySpinner />}
|
|
1320
|
+
errorComponent={(error) => <MyError message={error} />}
|
|
1321
|
+
onSuccess={(userId) => console.log('Logged in:', userId)}
|
|
1322
|
+
onError={(error) => console.error(error)}
|
|
1323
|
+
/>
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
```
|
|
1327
|
+
|
|
1328
|
+
**Props:**
|
|
1329
|
+
|
|
1330
|
+
| Prop | Type | Default | Description |
|
|
1331
|
+
|------|------|---------|-------------|
|
|
1332
|
+
| `apiBasePath` | `string` | `'/api/rpc'` | RPC API base path |
|
|
1333
|
+
| `loadingComponent` | `ReactNode` | Built-in | 로딩 중 표시할 컴포넌트 |
|
|
1334
|
+
| `errorComponent` | `(error: string) => ReactNode` | Built-in | 에러 표시 컴포넌트 |
|
|
1335
|
+
| `onSuccess` | `(userId: string) => void` | - | 성공 콜백 |
|
|
1336
|
+
| `onError` | `(error: string) => void` | - | 에러 콜백 |
|
|
1337
|
+
|
|
1338
|
+
---
|
|
1339
|
+
|
|
1022
1340
|
## Database Schema
|
|
1023
1341
|
|
|
1024
1342
|
### Core Tables
|
|
@@ -1252,7 +1570,7 @@ CREATE TABLE user_profiles (
|
|
|
1252
1570
|
|
|
1253
1571
|
#### `user_social_accounts`
|
|
1254
1572
|
|
|
1255
|
-
OAuth provider accounts (
|
|
1573
|
+
OAuth provider accounts (Google, GitHub, etc.).
|
|
1256
1574
|
|
|
1257
1575
|
```sql
|
|
1258
1576
|
CREATE TABLE user_social_accounts (
|
|
@@ -1469,10 +1787,22 @@ import '@spfn/auth/nextjs/api';
|
|
|
1469
1787
|
**Target Routes:**
|
|
1470
1788
|
- `/_auth/login`, `/_auth/register` - Login/register interceptor
|
|
1471
1789
|
- `/_auth/keys/rotate` - Key rotation interceptor
|
|
1790
|
+
- `/_auth/oauth/:provider/url` - OAuth URL interceptor (keypair + state generation)
|
|
1791
|
+
- `/_auth/oauth/finalize` - OAuth finalize interceptor (pending session → full session)
|
|
1472
1792
|
- All other authenticated routes - General auth interceptor
|
|
1473
1793
|
|
|
1474
1794
|
---
|
|
1475
1795
|
|
|
1796
|
+
### OAuth Client Component (`@spfn/auth/nextjs/client`)
|
|
1797
|
+
|
|
1798
|
+
```typescript
|
|
1799
|
+
import { OAuthCallback, type OAuthCallbackProps } from '@spfn/auth/nextjs/client';
|
|
1800
|
+
```
|
|
1801
|
+
|
|
1802
|
+
OAuth 콜백 페이지용 `'use client'` 컴포넌트. 자세한 사용법은 [OAuth Authentication](#oauth-authentication) 섹션 참조.
|
|
1803
|
+
|
|
1804
|
+
---
|
|
1805
|
+
|
|
1476
1806
|
## Testing
|
|
1477
1807
|
|
|
1478
1808
|
### Setup Test Environment
|
|
@@ -1818,7 +2148,7 @@ ls migrations/
|
|
|
1818
2148
|
|
|
1819
2149
|
- [ ] **React hooks** - useAuth, useSession, usePermissions
|
|
1820
2150
|
- [ ] **UI components** - LoginForm, RegisterForm, AuthProvider
|
|
1821
|
-
- [
|
|
2151
|
+
- [x] **OAuth integration** - Google (implemented), GitHub/Kakao/Naver (planned)
|
|
1822
2152
|
- [ ] **2FA support** - TOTP/authenticator apps
|
|
1823
2153
|
- [ ] **Password reset flow** - Complete email-based reset
|
|
1824
2154
|
- [ ] **Email change flow** - Verification for email updates
|
|
@@ -1958,6 +2288,6 @@ MIT License - See LICENSE file for details.
|
|
|
1958
2288
|
|
|
1959
2289
|
---
|
|
1960
2290
|
|
|
1961
|
-
**Last Updated:** 2026-01-
|
|
1962
|
-
**Document Version:** 2.
|
|
1963
|
-
**Package Version:** 0.2.0-beta.
|
|
2291
|
+
**Last Updated:** 2026-01-27
|
|
2292
|
+
**Document Version:** 2.4.0 (Technical Documentation)
|
|
2293
|
+
**Package Version:** 0.2.0-beta.13
|
|
@@ -376,6 +376,73 @@ interface AuthInitOptions {
|
|
|
376
376
|
sessionTtl?: string | number;
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
+
/**
|
|
380
|
+
* @spfn/auth - OAuth Service
|
|
381
|
+
*
|
|
382
|
+
* OAuth 인증 비즈니스 로직
|
|
383
|
+
* - Google OAuth Authorization Code Flow
|
|
384
|
+
* - 소셜 계정 연결/생성
|
|
385
|
+
* - publicKey는 state에서 추출하여 등록
|
|
386
|
+
*/
|
|
387
|
+
|
|
388
|
+
interface OAuthStartParams {
|
|
389
|
+
provider: SocialProvider;
|
|
390
|
+
returnUrl: string;
|
|
391
|
+
publicKey: string;
|
|
392
|
+
keyId: string;
|
|
393
|
+
fingerprint: string;
|
|
394
|
+
algorithm: KeyAlgorithmType;
|
|
395
|
+
}
|
|
396
|
+
interface OAuthStartResult {
|
|
397
|
+
authUrl: string;
|
|
398
|
+
}
|
|
399
|
+
interface OAuthCallbackParams {
|
|
400
|
+
provider: SocialProvider;
|
|
401
|
+
code: string;
|
|
402
|
+
state: string;
|
|
403
|
+
}
|
|
404
|
+
interface OAuthCallbackResult {
|
|
405
|
+
redirectUrl: string;
|
|
406
|
+
userId: string;
|
|
407
|
+
keyId: string;
|
|
408
|
+
isNewUser: boolean;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* OAuth 로그인 시작 - Provider 로그인 페이지로 리다이렉트할 URL 생성
|
|
412
|
+
*
|
|
413
|
+
* Next.js에서 키쌍을 생성한 후, publicKey를 state에 포함하여 호출
|
|
414
|
+
*/
|
|
415
|
+
declare function oauthStartService(params: OAuthStartParams): Promise<OAuthStartResult>;
|
|
416
|
+
/**
|
|
417
|
+
* OAuth 콜백 처리 - Code를 Token으로 교환하고 사용자 생성/연결
|
|
418
|
+
*
|
|
419
|
+
* state에서 publicKey를 추출하여 서버에 등록
|
|
420
|
+
* Next.js는 반환된 userId, keyId로 세션을 구성
|
|
421
|
+
*/
|
|
422
|
+
declare function oauthCallbackService(params: OAuthCallbackParams): Promise<OAuthCallbackResult>;
|
|
423
|
+
/**
|
|
424
|
+
* OAuth 에러 리다이렉트 URL 생성
|
|
425
|
+
*/
|
|
426
|
+
declare function buildOAuthErrorUrl(error: string): string;
|
|
427
|
+
/**
|
|
428
|
+
* OAuth provider가 활성화되어 있는지 확인
|
|
429
|
+
*/
|
|
430
|
+
declare function isOAuthProviderEnabled(provider: SocialProvider): boolean;
|
|
431
|
+
/**
|
|
432
|
+
* 활성화된 모든 OAuth provider 목록
|
|
433
|
+
*/
|
|
434
|
+
declare function getEnabledOAuthProviders(): SocialProvider[];
|
|
435
|
+
/**
|
|
436
|
+
* Google access token 조회 (만료 시 자동 리프레시)
|
|
437
|
+
*
|
|
438
|
+
* 저장된 토큰이 만료 임박(5분 이내) 또는 만료 상태이면
|
|
439
|
+
* refresh token으로 자동 갱신 후 DB 업데이트하여 유효한 토큰 반환.
|
|
440
|
+
*
|
|
441
|
+
* @param userId - 사용자 ID
|
|
442
|
+
* @returns 유효한 Google access token
|
|
443
|
+
*/
|
|
444
|
+
declare function getGoogleAccessToken(userId: number): Promise<string>;
|
|
445
|
+
|
|
379
446
|
/**
|
|
380
447
|
* @spfn/auth - Main Router
|
|
381
448
|
*
|
|
@@ -387,6 +454,7 @@ interface AuthInitOptions {
|
|
|
387
454
|
*
|
|
388
455
|
* Routes:
|
|
389
456
|
* - Auth: /_auth/exists, /_auth/codes, /_auth/login, /_auth/logout, etc.
|
|
457
|
+
* - OAuth: /_auth/oauth/google, /_auth/oauth/google/callback, etc.
|
|
390
458
|
* - Invitations: /_auth/invitations/*
|
|
391
459
|
* - Users: /_auth/users/*
|
|
392
460
|
*/
|
|
@@ -479,6 +547,50 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
479
547
|
emailVerified: boolean;
|
|
480
548
|
phoneVerified: boolean;
|
|
481
549
|
}>;
|
|
550
|
+
oauthGoogleStart: _spfn_core_route.RouteDef<{
|
|
551
|
+
query: _sinclair_typebox.TObject<{
|
|
552
|
+
state: _sinclair_typebox.TString;
|
|
553
|
+
}>;
|
|
554
|
+
}, {}, Response>;
|
|
555
|
+
oauthGoogleCallback: _spfn_core_route.RouteDef<{
|
|
556
|
+
query: _sinclair_typebox.TObject<{
|
|
557
|
+
code: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
558
|
+
state: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
559
|
+
error: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
560
|
+
error_description: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
561
|
+
}>;
|
|
562
|
+
}, {}, Response>;
|
|
563
|
+
oauthStart: _spfn_core_route.RouteDef<{
|
|
564
|
+
body: _sinclair_typebox.TObject<{
|
|
565
|
+
provider: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"google" | "github" | "kakao" | "naver">[]>;
|
|
566
|
+
returnUrl: _sinclair_typebox.TString;
|
|
567
|
+
publicKey: _sinclair_typebox.TString;
|
|
568
|
+
keyId: _sinclair_typebox.TString;
|
|
569
|
+
fingerprint: _sinclair_typebox.TString;
|
|
570
|
+
algorithm: _sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ES256" | "RS256">[]>;
|
|
571
|
+
}>;
|
|
572
|
+
}, {}, OAuthStartResult>;
|
|
573
|
+
oauthProviders: _spfn_core_route.RouteDef<{}, {}, {
|
|
574
|
+
providers: ("google" | "github" | "kakao" | "naver")[];
|
|
575
|
+
}>;
|
|
576
|
+
getGoogleOAuthUrl: _spfn_core_route.RouteDef<{
|
|
577
|
+
body: _sinclair_typebox.TObject<{
|
|
578
|
+
returnUrl: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
579
|
+
state: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
580
|
+
}>;
|
|
581
|
+
}, {}, {
|
|
582
|
+
authUrl: string;
|
|
583
|
+
}>;
|
|
584
|
+
oauthFinalize: _spfn_core_route.RouteDef<{
|
|
585
|
+
body: _sinclair_typebox.TObject<{
|
|
586
|
+
userId: _sinclair_typebox.TString;
|
|
587
|
+
keyId: _sinclair_typebox.TString;
|
|
588
|
+
returnUrl: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
|
|
589
|
+
}>;
|
|
590
|
+
}, {}, {
|
|
591
|
+
success: boolean;
|
|
592
|
+
returnUrl: string;
|
|
593
|
+
}>;
|
|
482
594
|
getInvitation: _spfn_core_route.RouteDef<{
|
|
483
595
|
params: _sinclair_typebox.TObject<{
|
|
484
596
|
token: _sinclair_typebox.TString;
|
|
@@ -642,4 +754,4 @@ declare module 'hono' {
|
|
|
642
754
|
*/
|
|
643
755
|
declare const authenticate: _spfn_core_route.NamedMiddleware<"auth">;
|
|
644
756
|
|
|
645
|
-
export {
|
|
757
|
+
export { getEnabledOAuthProviders as $, type AuthSession as A, type ChangePasswordParams as B, type CheckAccountExistsResult as C, sendVerificationCodeService as D, verifyCodeService as E, type SendVerificationCodeParams as F, type VerifyCodeParams as G, type VerifyCodeResult as H, INVITATION_STATUSES as I, registerPublicKeyService as J, KEY_ALGORITHM as K, type LoginResult as L, rotateKeyService as M, revokeKeyService as N, type OAuthStartResult as O, type PermissionConfig as P, type RegisterPublicKeyParams as Q, type RoleConfig as R, type SendVerificationCodeResult as S, type RotateKeyParams as T, type UserProfile as U, type VerificationTargetType as V, type RevokeKeyParams as W, oauthStartService as X, oauthCallbackService as Y, buildOAuthErrorUrl as Z, isOAuthProviderEnabled as _, type RegisterResult as a, getGoogleAccessToken as a0, type OAuthStartParams as a1, type OAuthCallbackParams as a2, type OAuthCallbackResult as a3, authenticate as a4, EmailSchema as a5, PhoneSchema as a6, PasswordSchema as a7, TargetTypeSchema as a8, VerificationPurposeSchema as a9, type RotateKeyResult as b, type ProfileInfo as c, USER_STATUSES as d, SOCIAL_PROVIDERS as e, type VerificationPurpose as f, VERIFICATION_TARGET_TYPES as g, VERIFICATION_PURPOSES as h, PERMISSION_CATEGORIES as i, type PermissionCategory as j, type AuthInitOptions as k, type KeyAlgorithmType as l, mainAuthRouter as m, type InvitationStatus as n, type UserStatus as o, type SocialProvider as p, type AuthContext as q, checkAccountExistsService as r, registerService as s, loginService as t, logoutService as u, changePasswordService as v, type CheckAccountExistsParams as w, type RegisterParams as x, type LoginParams as y, type LogoutParams as z };
|
package/dist/config.d.ts
CHANGED
|
@@ -214,6 +214,66 @@ declare const authEnvSchema: {
|
|
|
214
214
|
} & {
|
|
215
215
|
key: "SPFN_AUTH_AWS_SES_FROM_NAME";
|
|
216
216
|
};
|
|
217
|
+
SPFN_APP_URL: {
|
|
218
|
+
description: string;
|
|
219
|
+
default: string;
|
|
220
|
+
required: boolean;
|
|
221
|
+
examples: string[];
|
|
222
|
+
type: "string";
|
|
223
|
+
} & {
|
|
224
|
+
key: "SPFN_APP_URL";
|
|
225
|
+
};
|
|
226
|
+
SPFN_AUTH_GOOGLE_CLIENT_ID: {
|
|
227
|
+
description: string;
|
|
228
|
+
required: boolean;
|
|
229
|
+
examples: string[];
|
|
230
|
+
type: "string";
|
|
231
|
+
} & {
|
|
232
|
+
key: "SPFN_AUTH_GOOGLE_CLIENT_ID";
|
|
233
|
+
};
|
|
234
|
+
SPFN_AUTH_GOOGLE_CLIENT_SECRET: {
|
|
235
|
+
description: string;
|
|
236
|
+
required: boolean;
|
|
237
|
+
sensitive: boolean;
|
|
238
|
+
examples: string[];
|
|
239
|
+
type: "string";
|
|
240
|
+
} & {
|
|
241
|
+
key: "SPFN_AUTH_GOOGLE_CLIENT_SECRET";
|
|
242
|
+
};
|
|
243
|
+
SPFN_AUTH_GOOGLE_SCOPES: {
|
|
244
|
+
description: string;
|
|
245
|
+
required: boolean;
|
|
246
|
+
examples: string[];
|
|
247
|
+
type: "string";
|
|
248
|
+
} & {
|
|
249
|
+
key: "SPFN_AUTH_GOOGLE_SCOPES";
|
|
250
|
+
};
|
|
251
|
+
SPFN_AUTH_GOOGLE_REDIRECT_URI: {
|
|
252
|
+
description: string;
|
|
253
|
+
required: boolean;
|
|
254
|
+
examples: string[];
|
|
255
|
+
type: "string";
|
|
256
|
+
} & {
|
|
257
|
+
key: "SPFN_AUTH_GOOGLE_REDIRECT_URI";
|
|
258
|
+
};
|
|
259
|
+
SPFN_AUTH_OAUTH_SUCCESS_URL: {
|
|
260
|
+
description: string;
|
|
261
|
+
required: boolean;
|
|
262
|
+
default: string;
|
|
263
|
+
examples: string[];
|
|
264
|
+
type: "string";
|
|
265
|
+
} & {
|
|
266
|
+
key: "SPFN_AUTH_OAUTH_SUCCESS_URL";
|
|
267
|
+
};
|
|
268
|
+
SPFN_AUTH_OAUTH_ERROR_URL: {
|
|
269
|
+
description: string;
|
|
270
|
+
required: boolean;
|
|
271
|
+
default: string;
|
|
272
|
+
examples: string[];
|
|
273
|
+
type: "string";
|
|
274
|
+
} & {
|
|
275
|
+
key: "SPFN_AUTH_OAUTH_ERROR_URL";
|
|
276
|
+
};
|
|
217
277
|
};
|
|
218
278
|
|
|
219
279
|
declare const env: _spfn_core_env.InferEnvType<{
|
|
@@ -404,6 +464,66 @@ declare const env: _spfn_core_env.InferEnvType<{
|
|
|
404
464
|
} & {
|
|
405
465
|
key: "SPFN_AUTH_AWS_SES_FROM_NAME";
|
|
406
466
|
};
|
|
467
|
+
SPFN_APP_URL: {
|
|
468
|
+
description: string;
|
|
469
|
+
default: string;
|
|
470
|
+
required: boolean;
|
|
471
|
+
examples: string[];
|
|
472
|
+
type: "string";
|
|
473
|
+
} & {
|
|
474
|
+
key: "SPFN_APP_URL";
|
|
475
|
+
};
|
|
476
|
+
SPFN_AUTH_GOOGLE_CLIENT_ID: {
|
|
477
|
+
description: string;
|
|
478
|
+
required: boolean;
|
|
479
|
+
examples: string[];
|
|
480
|
+
type: "string";
|
|
481
|
+
} & {
|
|
482
|
+
key: "SPFN_AUTH_GOOGLE_CLIENT_ID";
|
|
483
|
+
};
|
|
484
|
+
SPFN_AUTH_GOOGLE_CLIENT_SECRET: {
|
|
485
|
+
description: string;
|
|
486
|
+
required: boolean;
|
|
487
|
+
sensitive: boolean;
|
|
488
|
+
examples: string[];
|
|
489
|
+
type: "string";
|
|
490
|
+
} & {
|
|
491
|
+
key: "SPFN_AUTH_GOOGLE_CLIENT_SECRET";
|
|
492
|
+
};
|
|
493
|
+
SPFN_AUTH_GOOGLE_SCOPES: {
|
|
494
|
+
description: string;
|
|
495
|
+
required: boolean;
|
|
496
|
+
examples: string[];
|
|
497
|
+
type: "string";
|
|
498
|
+
} & {
|
|
499
|
+
key: "SPFN_AUTH_GOOGLE_SCOPES";
|
|
500
|
+
};
|
|
501
|
+
SPFN_AUTH_GOOGLE_REDIRECT_URI: {
|
|
502
|
+
description: string;
|
|
503
|
+
required: boolean;
|
|
504
|
+
examples: string[];
|
|
505
|
+
type: "string";
|
|
506
|
+
} & {
|
|
507
|
+
key: "SPFN_AUTH_GOOGLE_REDIRECT_URI";
|
|
508
|
+
};
|
|
509
|
+
SPFN_AUTH_OAUTH_SUCCESS_URL: {
|
|
510
|
+
description: string;
|
|
511
|
+
required: boolean;
|
|
512
|
+
default: string;
|
|
513
|
+
examples: string[];
|
|
514
|
+
type: "string";
|
|
515
|
+
} & {
|
|
516
|
+
key: "SPFN_AUTH_OAUTH_SUCCESS_URL";
|
|
517
|
+
};
|
|
518
|
+
SPFN_AUTH_OAUTH_ERROR_URL: {
|
|
519
|
+
description: string;
|
|
520
|
+
required: boolean;
|
|
521
|
+
default: string;
|
|
522
|
+
examples: string[];
|
|
523
|
+
type: "string";
|
|
524
|
+
} & {
|
|
525
|
+
key: "SPFN_AUTH_OAUTH_ERROR_URL";
|
|
526
|
+
};
|
|
407
527
|
}>;
|
|
408
528
|
|
|
409
529
|
export { env, authEnvSchema as envSchema };
|