@qlover/oauth-wrapper 0.2.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/CHANGELOG.md +27 -0
- package/README.md +313 -0
- package/README_EN.md +313 -0
- package/dist/client.cjs +835 -0
- package/dist/client.d.ts +341 -0
- package/dist/client.js +791 -0
- package/dist/core.cjs +301 -0
- package/dist/core.d.ts +460 -0
- package/dist/core.js +250 -0
- package/dist/index.cjs +1043 -0
- package/dist/index.d.ts +194 -0
- package/dist/index.js +977 -0
- package/package.json +72 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
#### โจ Features
|
|
8
|
+
|
|
9
|
+
- **oauth-wrapper:** extract OAuth 2.0 core into @qlover/oauth-wrapper package ([d7f7f5c](https://github.com/qlover/fe-base/commit/d7f7f5c3a1d1f46b1ebf10f586426c3797c8c061)) ([#616](https://github.com/qlover/fe-base/pull/616))
|
|
10
|
+
|
|
11
|
+
Move server, client, and core modules from the next-oauth-wrapper example into a standalone publishable package with PKCE support, tests, and documentation.
|
|
12
|
+
|
|
13
|
+
- **react-seed:** add useLocale hook for locale management ([9fcd9f8](https://github.com/qlover/fe-base/commit/9fcd9f8645d5454e516fe0fdd4e75fca00598f76)) ([#616](https://github.com/qlover/fe-base/pull/616))
|
|
14
|
+
|
|
15
|
+
Introduced a new `useLocale` hook to manage locale settings within the application. This hook retrieves the current locale from route parameters or defaults to the configured locale, and provides a method to change the locale, updating the navigation accordingly. Additionally, updated `useOAuthLogin` to utilize the new `useLocale` hook for locale configuration during OAuth login processes.
|
|
16
|
+
|
|
17
|
+
Also, added a `patchConfig` method in the `OAuthClient` class to allow dynamic updates to the client configuration, including locale settings.
|
|
18
|
+
|
|
19
|
+
#### ๐ Bug Fixes
|
|
20
|
+
|
|
21
|
+
- **next-oauth-wrapper:** update local server ports in configuration files ([4fbb447](https://github.com/qlover/fe-base/commit/4fbb447e2a333435ae2824327b2d066eb3355972)) ([#616](https://github.com/qlover/fe-base/pull/616))
|
|
22
|
+
|
|
23
|
+
Changed the default local server ports in `robots.txt` and various files from 3120 to 3102 for consistency across the next-oauth-wrapper example. Additionally, added titles to `LocaleLink` components in the login page for better accessibility and user experience. This update ensures a smoother development environment and improves the clarity of navigation links.
|
|
24
|
+
|
|
25
|
+
## 0.1.0
|
|
26
|
+
|
|
27
|
+
- Initial publish: extract OAuth 2.0 protocol core from `examples/next-oauth-wrapper/shared/oauth-wrapper`.
|
package/README.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# @qlover/oauth-wrapper
|
|
2
|
+
|
|
3
|
+
> English: [README_EN.md](./README_EN.md)
|
|
4
|
+
|
|
5
|
+
ไธไธๆธธ็ปๅฝ API ๆ ๅ
ณ็ **OAuth 2.0 ๆๆๆๅกๅจๅ่ฎฎๅ
ๆ ธ**๏ผๆๆ็ ๆตใPKCEใconsent ็ผๆใๆข็ฅจใ`userinfo` ๆ ๅฐใOAuth Client ็ฎก็้ป่พใ
|
|
6
|
+
|
|
7
|
+
้่ฟ Port ๆฅๅฃๆฅๅ
ฅไปปๆ็จๆท็ณป็ป๏ผ`OAuthUserAdapterInterface`๏ผใๆไน
ๅ๏ผ`OAuthWrapperRepositoryInterface`๏ผไธไผ่ฏ๏ผ`OAuthSessionInterface`๏ผใๆฌๅ
**ไธๅ
ๅซ**ๅ
ทไฝ Adapterใๆฐๆฎๅบๅฎ็ฐๆ Web ๆกๆถ่ทฏ็ฑใ
|
|
8
|
+
|
|
9
|
+
## ็นๆง
|
|
10
|
+
|
|
11
|
+
| ่ฝๅ | ่ฏดๆ |
|
|
12
|
+
| ----------- | ------------------------------------------------------------ |
|
|
13
|
+
| ๆๆ็ ๆต | ไป
ๆฏๆ `response_type=code` |
|
|
14
|
+
| PKCE | ๅ
ฌๅผๅฎขๆท็ซฏๅผบๅถ S256๏ผๆบๅฏๅฎขๆท็ซฏๅฏ้ PKCE ๆ `client_secret` |
|
|
15
|
+
| Token ็ซฏ็น | `authorization_code` / `refresh_token` ๆข็ฅจ |
|
|
16
|
+
| Token ๆค้ | RFC 7009๏ผๆค้ middleware ๅฑ `refresh_token`๏ผๅน็ญ๏ผ |
|
|
17
|
+
| UserInfo | ๅฐ Adapter ็จๆท่ตๆๆ ๅฐไธบ `sub` / `email` / `name` / `roles` |
|
|
18
|
+
| Client ็ฎก็ | ๅผๅ่
ๆงๅถๅฐ CRUDใๅฏ้ฅ่ฝฎๆข |
|
|
19
|
+
| ๆต่งๅจ SDK | `OAuthClient` + `OAuthGateway`๏ผๅ
็ฝฎ PKCE ไผ่ฏไธๅ่ฐๅป้ |
|
|
20
|
+
|
|
21
|
+
## ๅฎ่ฃ
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add @qlover/oauth-wrapper zod
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`zod` ไธบ peer dependency๏ผ`^3` ๆ `^4`๏ผ๏ผ็จไบ่ฏทๆฑ/ๅๅบ Schema ๆ ก้ชใ
|
|
28
|
+
|
|
29
|
+
## ๅ
ๅ
ฅๅฃ
|
|
30
|
+
|
|
31
|
+
| ๅญ่ทฏๅพ | ็จ้ |
|
|
32
|
+
| ------------------------------ | -------------------------------------------------- |
|
|
33
|
+
| `@qlover/oauth-wrapper` | ๆๅก็ซฏ๏ผServicesใUtilsใCore ๅๅฏผๅบ |
|
|
34
|
+
| `@qlover/oauth-wrapper/core` | ๅ
ฑไบซๅฅ็บฆ๏ผPort ๆฅๅฃใZod SchemaใRFC ้่ฏฏ็ |
|
|
35
|
+
| `@qlover/oauth-wrapper/client` | ๆต่งๅจ็ซฏ๏ผ`OAuthClient`ใ`OAuthGateway`ใPKCE ๅทฅๅ
ท |
|
|
36
|
+
|
|
37
|
+
## ๆถๆๆฆ่ง
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
41
|
+
โ ไฝ ็ HTTP ๆกๆถๅฑ โ
|
|
42
|
+
โ (Next.js Route / Express / ็ญ โ ่ช่กๆ่ฝฝ) โ
|
|
43
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
44
|
+
โ
|
|
45
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
46
|
+
โ OAuthWrapperService โ OAuthTokenService โ OAuthClientsService โ
|
|
47
|
+
โโโโโโโโโโโโฌโโโโโโโโโโโโโดโโโโโโโโโโโฌโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโ
|
|
48
|
+
โ โ
|
|
49
|
+
โโโโโโโโโโโโผโโโโโโโโโโโ โโโโโโโโโโโผโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ
|
|
50
|
+
โ OAuthSessionInterfaceโ โOAuthUserAdapter โ โOAuthWrapperRepositoryโ
|
|
51
|
+
โ (็ปๅฝไผ่ฏ) โ โInterface (ไธๆธธ็จๆท)โ โInterface (ๆไน
ๅ) โ
|
|
52
|
+
โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**่ฎพ่ฎกๅๅ**๏ผๅ่ฎฎ้ป่พไธๅบ็ก่ฎพๆฝ่งฃ่ฆใไฝ ๅฎ็ฐไธไธช Port๏ผ็ป่ฃ
Service๏ผๅๆ HTTP ่ทฏ็ฑๆฅๅฐไปปๆๆกๆถๅณๅฏใ
|
|
56
|
+
|
|
57
|
+
## ๆๆ็ ๆต๏ผๆๅก็ซฏ + ๆต่งๅจ๏ผ
|
|
58
|
+
|
|
59
|
+
```mermaid
|
|
60
|
+
sequenceDiagram
|
|
61
|
+
participant App as ็ฌฌไธๆนๅบ็จ (OAuthClient)
|
|
62
|
+
participant Auth as ๆๆๆๅกๅจ (OAuthWrapperService)
|
|
63
|
+
participant User as ่ตๆบๆๆ่
|
|
64
|
+
participant Provider as ไธๆธธ็จๆท็ณป็ป (Adapter)
|
|
65
|
+
|
|
66
|
+
App->>Auth: GET /oauth/authorize?client_id&redirect_uri&PKCE...
|
|
67
|
+
Auth->>User: ๅฑ็คบ consent ้กต (resolveAuthorizePage)
|
|
68
|
+
User->>Auth: POST consent (allow/deny)
|
|
69
|
+
Auth->>App: redirect_uri?code&state
|
|
70
|
+
App->>Auth: POST /oauth/token (code + code_verifier)
|
|
71
|
+
Auth->>Provider: exchangeAccessToken(sessionToken)
|
|
72
|
+
Auth->>App: access_token + refresh_token
|
|
73
|
+
App->>Auth: GET /userinfo (Bearer access_token)
|
|
74
|
+
Auth->>Provider: getUserInfoByAccessToken
|
|
75
|
+
Auth->>App: sub, email, name, roles?
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## ๆๅก็ซฏๅฟซ้ๅผๅง
|
|
79
|
+
|
|
80
|
+
### 1. ๅฎ็ฐไธไธช Port
|
|
81
|
+
|
|
82
|
+
**`OAuthUserAdapterInterface`** โ ๅฏนๆฅไธๆธธ็จๆท/่บซไปฝ็ณป็ป๏ผ
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
interface OAuthUserAdapterInterface {
|
|
86
|
+
login(email: string, password: string): Promise<OAuthUserCredentials>;
|
|
87
|
+
exchangeAccessToken(params: {
|
|
88
|
+
token: string;
|
|
89
|
+
lang?: string;
|
|
90
|
+
}): Promise<OAuthUserAccessToken>;
|
|
91
|
+
getUserInfo(sessionToken: string): Promise<OAuthUserProfile>;
|
|
92
|
+
getUserInfoByAccessToken(accessToken: string): Promise<OAuthUserProfile>;
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**`OAuthSessionInterface`** โ ๆๆๆๅกๅจไพง็ปๅฝไผ่ฏ๏ผconsent ๅ็จๆทๅฟ
้กปๅทฒ็ปๅฝ๏ผ๏ผ
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
type OAuthSessionPayload = {
|
|
100
|
+
userId: number;
|
|
101
|
+
email: string;
|
|
102
|
+
name: string;
|
|
103
|
+
providerSessionToken: string;
|
|
104
|
+
};
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**`OAuthWrapperRepositoryInterface`** โ ๆๆ็ ใrefresh tokenใ็จๆทๅญ่ฏใClient ๆณจๅไฟกๆฏๆไน
ๅ๏ผ็ปงๆฟ `OAuthClientsRepositoryInterface`๏ผใ
|
|
108
|
+
|
|
109
|
+
### 2. ็ป่ฃ
Services
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import {
|
|
113
|
+
OAuthWrapperService,
|
|
114
|
+
OAuthTokenService,
|
|
115
|
+
OAuthClientsService,
|
|
116
|
+
type OAuthUserAdapterInterface,
|
|
117
|
+
type OAuthWrapperRepositoryInterface,
|
|
118
|
+
type OAuthSessionInterface
|
|
119
|
+
} from '@qlover/oauth-wrapper';
|
|
120
|
+
|
|
121
|
+
const tokenService = new OAuthTokenService(
|
|
122
|
+
tokenEncryption, // EncryptorInterface โ ๅ ๅฏ provider refresh_token
|
|
123
|
+
userAdapter,
|
|
124
|
+
oauthRepo
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const oauthService = new OAuthWrapperService(
|
|
128
|
+
oauthSession,
|
|
129
|
+
userAdapter,
|
|
130
|
+
tokenService,
|
|
131
|
+
oauthRepo
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const clientsService = new OAuthClientsService(oauthRepo);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 3. ๆ่ฝฝ HTTP ็ซฏ็น
|
|
138
|
+
|
|
139
|
+
็บฆๅฎ่ทฏๅพ๏ผๅฏ้่ฟ `OAuthWrapperEndpoints` ๅผ็จ๏ผ๏ผ
|
|
140
|
+
|
|
141
|
+
| ๆนๆณ | ่ทฏๅพ | Service ๆนๆณ | ่ฏดๆ |
|
|
142
|
+
| ---- | ------------------ | ----------------------------- | ------------------------------------------- |
|
|
143
|
+
| GET | `/oauth/authorize` | `resolveAuthorizePage(query)` | ๆ ก้ชๅๆฐ๏ผ่ฟๅ consent ้กตๆฐๆฎ |
|
|
144
|
+
| POST | consent ่ทฏ็ฑ | `processConsent(body)` | `action: allow \| deny`๏ผ่ฟๅ `redirectUrl` |
|
|
145
|
+
| POST | `/oauth/token` | `exchangeToken(formFields)` | `application/x-www-form-urlencoded` |
|
|
146
|
+
| POST | `/oauth/revoke` | `revokeToken(formFields)` | RFC 7009๏ผๅน็ญ |
|
|
147
|
+
| GET | `/userinfo` | `getUserInfo(bearerToken)` | `Authorization: Bearer โฆ` |
|
|
148
|
+
|
|
149
|
+
**`processConsent` ่ฏทๆฑไฝ**๏ผ`OAuthConsentBodySchema`๏ผ๏ผ
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
{
|
|
153
|
+
action: 'allow' | 'deny';
|
|
154
|
+
client_id: string;
|
|
155
|
+
redirect_uri: string;
|
|
156
|
+
scope?: string;
|
|
157
|
+
state?: string;
|
|
158
|
+
code_challenge?: string; // ไธ authorize ไธ่ด
|
|
159
|
+
code_challenge_method?: 'S256';
|
|
160
|
+
trust?: boolean; // ้ข็๏ผๆๆชๆไน
ๅ
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Token ่ฏทๆฑ**๏ผ`OAuthTokenRequestSchema`๏ผๆฏๆ๏ผ
|
|
165
|
+
|
|
166
|
+
- `grant_type=authorization_code` โ ้ `code`ใ`redirect_uri`ใ`client_id`๏ผๅ
ฌๅผๅฎขๆท็ซฏ้ `code_verifier`๏ผๆบๅฏๅฎขๆท็ซฏๆ PKCE ๆถ้ `client_secret`
|
|
167
|
+
- `grant_type=refresh_token` โ ้ `refresh_token`ใ`client_id`
|
|
168
|
+
|
|
169
|
+
ๆๆ็ TTL๏ผ**5 ๅ้**ใMiddleware refresh token TTL๏ผ**90 ๅคฉ**๏ผ่ฝฎๆขๅผ refresh๏ผๆง token ไฝฟ็จๅๅณๆค้๏ผใ
|
|
170
|
+
|
|
171
|
+
### 4. ้่ฏฏๅค็
|
|
172
|
+
|
|
173
|
+
- ๆๆ้กตๆ ก้ชๅคฑ่ดฅ๏ผ`resolveAuthorizePage` ่ฟๅ `{ ok: false, error: { errorKey, message } }`๏ผ`errorKey` ไธบ `OAuthRfcCodes` ๅธธ้
|
|
174
|
+
- Token / UserInfo ๅคฑ่ดฅ๏ผๆๅบ `OAuthWrapperError`๏ผ็ปงๆฟ `ExecutorError`๏ผ๏ผๅซ RFC `error` ไธ HTTP `status`
|
|
175
|
+
- Consent ๅคฑ่ดฅ๏ผๆๅบ `ExecutorError`
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { OAuthRfcCodes, OAuthWrapperError } from '@qlover/oauth-wrapper';
|
|
179
|
+
|
|
180
|
+
// OAuthRfcCodes: invalid_request, invalid_client, invalid_grant,
|
|
181
|
+
// invalid_token, unauthorized_client, invalid_scope, access_denied, โฆ
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## ๆต่งๅจ็ซฏๅฟซ้ๅผๅง
|
|
185
|
+
|
|
186
|
+
ไป `@qlover/oauth-wrapper/client` ๅฏผๅ
ฅ๏ผ
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import { OAuthClient } from '@qlover/oauth-wrapper/client';
|
|
190
|
+
|
|
191
|
+
const oauthClient = new OAuthClient({
|
|
192
|
+
serverUrl: 'https://auth.example.com',
|
|
193
|
+
clientId: 'your-client-id',
|
|
194
|
+
scope: 'openid profile email',
|
|
195
|
+
redirectPath: 'oauth/callback',
|
|
196
|
+
origin: window.location.origin,
|
|
197
|
+
routerPrefix: '/app', // ๅฏ้
|
|
198
|
+
locale: 'en', // ๅฏ้๏ผๆฏๆ path/query/header
|
|
199
|
+
mapUser: (userinfo, accessToken, refreshToken) => ({
|
|
200
|
+
id: userinfo.sub,
|
|
201
|
+
email: userinfo.email,
|
|
202
|
+
name: userinfo.name
|
|
203
|
+
})
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// 1. ๅ่ตท็ปๅฝ๏ผ็ๆ PKCEใstate๏ผ่ทณ่ฝฌ authorize๏ผ
|
|
207
|
+
await oauthClient.startOAuthLogin();
|
|
208
|
+
|
|
209
|
+
// 2. ๅ่ฐ้กตๅค็
|
|
210
|
+
const params = oauthClient.parseOAuthCallbackSearchParams(
|
|
211
|
+
new URLSearchParams(window.location.search)
|
|
212
|
+
);
|
|
213
|
+
const user = await oauthClient.completeOAuthCallback(params);
|
|
214
|
+
|
|
215
|
+
// 3. ๆค้ refresh token๏ผ็ปๅบๆถ๏ผ
|
|
216
|
+
await oauthClient.revokeToken(refreshToken);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### OAuthClient ๆ ธๅฟ API
|
|
220
|
+
|
|
221
|
+
| ๆนๆณ | ่ฏดๆ |
|
|
222
|
+
| ---------------------------------- | --------------------------------------------- |
|
|
223
|
+
| `isConfigured()` | `serverUrl` + `clientId` ๆฏๅฆๅฐฑ็ปช |
|
|
224
|
+
| `startOAuthLogin(params?)` | ไฟๅญ PKCE ไผ่ฏๅนถ่ทณ่ฝฌ authorize |
|
|
225
|
+
| `completeOAuthCallback(params?)` | ๆ ก้ช stateใๆข็ฅจใๆ userinfoใ่ฐ็จ `mapUser` |
|
|
226
|
+
| `parseOAuthCallbackSearchParams()` | ไป URL ่งฃๆ `code` / `state` / `error` |
|
|
227
|
+
| `fetchUserInfo(accessToken)` | ๅ็ฌๆๅ userinfo |
|
|
228
|
+
| `revokeToken(refreshToken?)` | POST `/oauth/revoke` |
|
|
229
|
+
| `getGateway()` | ่ทๅๅบๅฑ `OAuthGateway` |
|
|
230
|
+
| `patchConfig(partial)` | ่ฟ่กๆถๆดๆฐ้
็ฝฎ๏ผๅฆๅๆข locale๏ผ |
|
|
231
|
+
|
|
232
|
+
`OAuthGateway` ๅ็ฌไฝฟ็จๆถ้ๅ
้่ฟ `PKCESessionStore` ๅๅ
ฅ PKCE ไผ่ฏ๏ผ`OAuthClient` ๅทฒๅฐ่ฃ
ๆญคๆต็จใๅ่ฐๅค็ๅ
็ฝฎ **code+state ๅป้**๏ผ้ฟๅ
React Strict Mode ๅ่ฐ็จใ
|
|
233
|
+
|
|
234
|
+
### ้
็ฝฎ้กน๏ผ`OAuthClientConfig`๏ผ
|
|
235
|
+
|
|
236
|
+
| ๅญๆฎต | ้ป่ฎค | ่ฏดๆ |
|
|
237
|
+
| --------------------- | ----------------------------- | ----------------------------------------------- |
|
|
238
|
+
| `serverUrl` | `''` | ๆๆๆๅกๅจๆ น URL |
|
|
239
|
+
| `clientId` | `''` | OAuth Client ID |
|
|
240
|
+
| `scope` | `'openid profile email'` | ๆๆ scope |
|
|
241
|
+
| `redirectPath` | `'oauth/callback'` | ๅ่ฐ่ทฏๅพ๏ผ็ธๅฏน origin๏ผ |
|
|
242
|
+
| `origin` | ๅฝๅ `window.location.origin` | ็จไบๆผ redirect_uri |
|
|
243
|
+
| `routerPrefix` | โ | ่ทฏ็ฑๅ็ผ๏ผๅฆ `/en`๏ผ |
|
|
244
|
+
| `locale` / `localeIn` | `'en'` / `'path'` | ๅค่ฏญ่จ๏ผ`path` \| `query` \| `header` \| `none` |
|
|
245
|
+
|
|
246
|
+
PKCE ไผ่ฏ้ป่ฎคๅญไบ `sessionStorage`๏ผ้ฎๅ `oauth-wrapper-pkcesession`๏ผๅฏ้่ฟ `pkceStorage` / `pkceStorageKey` ่ชๅฎไนใ
|
|
247
|
+
|
|
248
|
+
## Client ็ฎก็๏ผๅผๅ่
ๆงๅถๅฐ๏ผ
|
|
249
|
+
|
|
250
|
+
`OAuthClientsService` ๆไพๆ `ownerUserId` ้็ฆป็ CRUD๏ผ
|
|
251
|
+
|
|
252
|
+
| ๆนๆณ | ่ฏดๆ |
|
|
253
|
+
| --------------------------------- | ------------------------------------------ |
|
|
254
|
+
| `listForOwner(userId)` | ๅๅบๆๅฑ Client |
|
|
255
|
+
| `getByClientId(userId, clientId)` | ่ฏฆๆ
|
|
|
256
|
+
| `create(userId, input)` | ๅๅปบ๏ผๆบๅฏๅฎขๆท็ซฏ่ฟๅไธๆฌกๆง `client_secret` |
|
|
257
|
+
| `update(userId, clientId, input)` | ๆดๆฐๅ็งฐใURIใredirect_uris |
|
|
258
|
+
| `rotateSecret(userId, clientId)` | ่ฝฎๆขๅฏ้ฅ๏ผไป
confidential๏ผ |
|
|
259
|
+
| `delete(userId, clientId)` | ๅ ้ค |
|
|
260
|
+
|
|
261
|
+
ๅๅปบ/ๆดๆฐ Schema๏ผ`OAuthClientCreateSchema`ใ`OAuthClientUpdateSchema`๏ผ`redirect_uris` ๆฏๆ HTTPS ไธ custom scheme๏ผใ
|
|
262
|
+
|
|
263
|
+
## Core ๆจกๅๅ่
|
|
264
|
+
|
|
265
|
+
### Services๏ผ`@qlover/oauth-wrapper`๏ผ
|
|
266
|
+
|
|
267
|
+
| ็ฑป | ่่ดฃ |
|
|
268
|
+
| --------------------- | --------------------------------------------------------------------------- |
|
|
269
|
+
| `OAuthWrapperService` | ๆๆ้กต่งฃๆใ`processConsent`ใๆข็ฅจ/revoke ๅงๆใ`getUserInfo`ใ`logoutUser` |
|
|
270
|
+
| `OAuthTokenService` | Token ็ซฏ็น๏ผๆ ก้ช clientใๆถ่ดนๆๆ็ ใPKCEใ็ญพๅ middleware refresh token |
|
|
271
|
+
| `OAuthClientsService` | ๅผๅ่
ๆงๅถๅฐ Client ไธๅก้ป่พ |
|
|
272
|
+
|
|
273
|
+
### Schema๏ผ`@qlover/oauth-wrapper/core`๏ผ
|
|
274
|
+
|
|
275
|
+
| Schema | ็จ้ |
|
|
276
|
+
| ------------------------------------------ | --------------------------------- |
|
|
277
|
+
| `OAuthAuthorizeQuerySchema` | GET authorize ๆฅ่ฏขๅๆฐ |
|
|
278
|
+
| `OAuthConsentBodySchema` | Consent POST body |
|
|
279
|
+
| `OAuthTokenRequestSchema` | Token ็ซฏ็น๏ผdiscriminated union๏ผ |
|
|
280
|
+
| `OAuthTokenRevokeSchema` | Revoke ็ซฏ็น |
|
|
281
|
+
| `OAuthClientCreateSchema` / `UpdateSchema` | Client ็ฎก็ |
|
|
282
|
+
| `OAuthUserInfoResponseSchema` | UserInfo ๅๅบ |
|
|
283
|
+
| `OAuthTokenResponseSchema` | Token ๅๅบ |
|
|
284
|
+
|
|
285
|
+
### Utils๏ผๆๅก็ซฏ๏ผ
|
|
286
|
+
|
|
287
|
+
| ๅทฅๅ
ท | ่ฏดๆ |
|
|
288
|
+
| ---------------------------------------------------------------- | -------------------------- |
|
|
289
|
+
| `validatePkceParams` / `normalizeQuery` / `isRedirectUriAllowed` | ๆๆ่ฏทๆฑๆ ก้ช |
|
|
290
|
+
| `buildOAuthRedirectUrl` / `parseScopeList` | Redirect ๆๅปบไธ scope ่งฃๆ |
|
|
291
|
+
| `verifyPkceS256` / `isValidCodeChallenge` | PKCE S256 |
|
|
292
|
+
| `hashClientSecret` / `verifyClientSecret` | Client secret ๅๅธ |
|
|
293
|
+
| `OAuthWrapperError` | ๅธฆ HTTP status ็ RFC ้่ฏฏ |
|
|
294
|
+
|
|
295
|
+
### Utils๏ผๆต่งๅจ็ซฏ `@qlover/oauth-wrapper/client`๏ผ
|
|
296
|
+
|
|
297
|
+
| ๅทฅๅ
ท | ่ฏดๆ |
|
|
298
|
+
| ------------------------------------------------------------------------ | ------------------ |
|
|
299
|
+
| `generatePkceVerifier` / `computePkceS256Challenge` / `randomOAuthState` | PKCE ไธ CSRF state |
|
|
300
|
+
| `buildOAuthAuthorizeUrl` / `buildOAuthRedirectUri` | URL ๆๅปบ |
|
|
301
|
+
| `parseOAuthTokenResponse` / `parseOAuthUserInfoResponse` | ๅๅบ่งฃๆ |
|
|
302
|
+
| `PKCESessionStore` | ไผ่ฏ็บง PKCE ๅญๅจ |
|
|
303
|
+
|
|
304
|
+
## Monorepo ็คบไพ
|
|
305
|
+
|
|
306
|
+
| ็คบไพ | ่ฏดๆ |
|
|
307
|
+
| ------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
|
|
308
|
+
| [`examples/next-oauth-wrapper`](../../examples/next-oauth-wrapper) | ๅฎๆดๆๆๆๅกๅจ้จ็ฝฒ๏ผAdapterใRepositoryใSessionใNext.js ่ทฏ็ฑใๅผๅ่
ๆงๅถๅฐ UI |
|
|
309
|
+
| [`examples/react-seed`](../../examples/react-seed) | ๆต่งๅจ็ซฏ `OAuthClient` ้ๆ๏ผ`SeedOAuthClient` + `useOAuthLogin`๏ผ |
|
|
310
|
+
|
|
311
|
+
## ็ๆฌ
|
|
312
|
+
|
|
313
|
+
ๅฝๅ็ๆฌ่ง [package.json](./package.json) ไธ [CHANGELOG.md](./CHANGELOG.md)ใ
|
package/README_EN.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# @qlover/oauth-wrapper
|
|
2
|
+
|
|
3
|
+
> ไธญๆ: [README.md](./README.md)
|
|
4
|
+
|
|
5
|
+
Provider-agnostic **OAuth 2.0 authorization server core**: authorization code flow, PKCE, consent orchestration, token exchange, userinfo mapping, and OAuth client management.
|
|
6
|
+
|
|
7
|
+
Integrate any user system via `OAuthUserAdapterInterface`, storage via `OAuthWrapperRepositoryInterface`, and sessions via `OAuthSessionInterface`. This package does **not** ship concrete adapters, databases, or web framework routes.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
| Capability | Description |
|
|
12
|
+
| ---------- | ----------- |
|
|
13
|
+
| Authorization code flow | `response_type=code` only |
|
|
14
|
+
| PKCE | S256 required for public clients; confidential clients may use PKCE or `client_secret` |
|
|
15
|
+
| Token endpoint | `authorization_code` / `refresh_token` exchange |
|
|
16
|
+
| Token revocation | RFC 7009, revokes middleware-layer `refresh_token` (idempotent) |
|
|
17
|
+
| UserInfo | Maps adapter profiles to `sub` / `email` / `name` / `roles` |
|
|
18
|
+
| Client management | Developer console CRUD and secret rotation |
|
|
19
|
+
| Browser SDK | `OAuthClient` + `OAuthGateway` with PKCE session storage and callback deduplication |
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add @qlover/oauth-wrapper zod
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`zod` is a peer dependency (`^3` or `^4`) used for request/response schema validation.
|
|
28
|
+
|
|
29
|
+
## Package entry points
|
|
30
|
+
|
|
31
|
+
| Subpath | Purpose |
|
|
32
|
+
| ------- | ------- |
|
|
33
|
+
| `@qlover/oauth-wrapper` | Server: Services, Utils, re-exports Core |
|
|
34
|
+
| `@qlover/oauth-wrapper/core` | Shared contracts: Port interfaces, Zod schemas, RFC error codes |
|
|
35
|
+
| `@qlover/oauth-wrapper/client` | Browser: `OAuthClient`, `OAuthGateway`, PKCE utilities |
|
|
36
|
+
|
|
37
|
+
## Architecture overview
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
41
|
+
โ Your HTTP framework layer โ
|
|
42
|
+
โ (Next.js Route / Express / etc. โ mount yourself) โ
|
|
43
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
44
|
+
โ
|
|
45
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
46
|
+
โ OAuthWrapperService โ OAuthTokenService โ OAuthClientsService โ
|
|
47
|
+
โโโโโโโโโโโโฌโโโโโโโโโโโโโดโโโโโโโโโโโฌโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโ
|
|
48
|
+
โ โ
|
|
49
|
+
โโโโโโโโโโโโผโโโโโโโโโโโ โโโโโโโโโโโผโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ
|
|
50
|
+
โ OAuthSessionInterfaceโ โOAuthUserAdapter โ โOAuthWrapperRepositoryโ
|
|
51
|
+
โ (login session) โ โInterface (upstream)โ โInterface (persistence)โ
|
|
52
|
+
โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Design principle**: protocol logic is decoupled from infrastructure. Implement the three ports, compose services, and wire HTTP routes in any framework.
|
|
56
|
+
|
|
57
|
+
## Authorization code flow (server + browser)
|
|
58
|
+
|
|
59
|
+
```mermaid
|
|
60
|
+
sequenceDiagram
|
|
61
|
+
participant App as Third-party app (OAuthClient)
|
|
62
|
+
participant Auth as Authorization server (OAuthWrapperService)
|
|
63
|
+
participant User as Resource owner
|
|
64
|
+
participant Provider as Upstream user system (Adapter)
|
|
65
|
+
|
|
66
|
+
App->>Auth: GET /oauth/authorize?client_id&redirect_uri&PKCE...
|
|
67
|
+
Auth->>User: Show consent page (resolveAuthorizePage)
|
|
68
|
+
User->>Auth: POST consent (allow/deny)
|
|
69
|
+
Auth->>App: redirect_uri?code&state
|
|
70
|
+
App->>Auth: POST /oauth/token (code + code_verifier)
|
|
71
|
+
Auth->>Provider: exchangeAccessToken(sessionToken)
|
|
72
|
+
Auth->>App: access_token + refresh_token
|
|
73
|
+
App->>Auth: GET /userinfo (Bearer access_token)
|
|
74
|
+
Auth->>Provider: getUserInfoByAccessToken
|
|
75
|
+
Auth->>App: sub, email, name, roles?
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Server quick start
|
|
79
|
+
|
|
80
|
+
### 1. Implement three ports
|
|
81
|
+
|
|
82
|
+
**`OAuthUserAdapterInterface`** โ connect to your upstream user/identity system:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
interface OAuthUserAdapterInterface {
|
|
86
|
+
login(email: string, password: string): Promise<OAuthUserCredentials>;
|
|
87
|
+
exchangeAccessToken(params: {
|
|
88
|
+
token: string;
|
|
89
|
+
lang?: string;
|
|
90
|
+
}): Promise<OAuthUserAccessToken>;
|
|
91
|
+
getUserInfo(sessionToken: string): Promise<OAuthUserProfile>;
|
|
92
|
+
getUserInfoByAccessToken(accessToken: string): Promise<OAuthUserProfile>;
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**`OAuthSessionInterface`** โ authorization-server login session (user must be signed in before consent):
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
type OAuthSessionPayload = {
|
|
100
|
+
userId: number;
|
|
101
|
+
email: string;
|
|
102
|
+
name: string;
|
|
103
|
+
providerSessionToken: string;
|
|
104
|
+
};
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**`OAuthWrapperRepositoryInterface`** โ persist authorization codes, refresh tokens, user credentials, and registered clients (extends `OAuthClientsRepositoryInterface`).
|
|
108
|
+
|
|
109
|
+
### 2. Compose services
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import {
|
|
113
|
+
OAuthWrapperService,
|
|
114
|
+
OAuthTokenService,
|
|
115
|
+
OAuthClientsService,
|
|
116
|
+
type OAuthUserAdapterInterface,
|
|
117
|
+
type OAuthWrapperRepositoryInterface,
|
|
118
|
+
type OAuthSessionInterface
|
|
119
|
+
} from '@qlover/oauth-wrapper';
|
|
120
|
+
|
|
121
|
+
const tokenService = new OAuthTokenService(
|
|
122
|
+
tokenEncryption, // EncryptorInterface โ encrypts provider refresh_token
|
|
123
|
+
userAdapter,
|
|
124
|
+
oauthRepo
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const oauthService = new OAuthWrapperService(
|
|
128
|
+
oauthSession,
|
|
129
|
+
userAdapter,
|
|
130
|
+
tokenService,
|
|
131
|
+
oauthRepo
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const clientsService = new OAuthClientsService(oauthRepo);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 3. Mount HTTP endpoints
|
|
138
|
+
|
|
139
|
+
Conventional paths (reference via `OAuthWrapperEndpoints`):
|
|
140
|
+
|
|
141
|
+
| Method | Path | Service method | Description |
|
|
142
|
+
| ------ | ---- | -------------- | ----------- |
|
|
143
|
+
| GET | `/oauth/authorize` | `resolveAuthorizePage(query)` | Validate params, return consent page data |
|
|
144
|
+
| POST | consent route | `processConsent(body)` | `action: allow \| deny`, returns `redirectUrl` |
|
|
145
|
+
| POST | `/oauth/token` | `exchangeToken(formFields)` | `application/x-www-form-urlencoded` |
|
|
146
|
+
| POST | `/oauth/revoke` | `revokeToken(formFields)` | RFC 7009, idempotent |
|
|
147
|
+
| GET | `/userinfo` | `getUserInfo(bearerToken)` | `Authorization: Bearer โฆ` |
|
|
148
|
+
|
|
149
|
+
**`processConsent` request body** (`OAuthConsentBodySchema`):
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
{
|
|
153
|
+
action: 'allow' | 'deny';
|
|
154
|
+
client_id: string;
|
|
155
|
+
redirect_uri: string;
|
|
156
|
+
scope?: string;
|
|
157
|
+
state?: string;
|
|
158
|
+
code_challenge?: string; // must match authorize request
|
|
159
|
+
code_challenge_method?: 'S256';
|
|
160
|
+
trust?: boolean; // reserved, not persisted yet
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Token requests** (`OAuthTokenRequestSchema`) support:
|
|
165
|
+
|
|
166
|
+
- `grant_type=authorization_code` โ requires `code`, `redirect_uri`, `client_id`; public clients need `code_verifier`; confidential clients without PKCE need `client_secret`
|
|
167
|
+
- `grant_type=refresh_token` โ requires `refresh_token`, `client_id`
|
|
168
|
+
|
|
169
|
+
Authorization code TTL: **5 minutes**. Middleware refresh token TTL: **90 days** (rotating refresh โ old token revoked after use).
|
|
170
|
+
|
|
171
|
+
### 4. Error handling
|
|
172
|
+
|
|
173
|
+
- Authorize page validation failure: `resolveAuthorizePage` returns `{ ok: false, error: { errorKey, message } }` where `errorKey` is an `OAuthRfcCodes` constant
|
|
174
|
+
- Token / UserInfo failure: throws `OAuthWrapperError` (extends `ExecutorError`) with RFC `error` and HTTP `status`
|
|
175
|
+
- Consent failure: throws `ExecutorError`
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { OAuthRfcCodes, OAuthWrapperError } from '@qlover/oauth-wrapper';
|
|
179
|
+
|
|
180
|
+
// OAuthRfcCodes: invalid_request, invalid_client, invalid_grant,
|
|
181
|
+
// invalid_token, unauthorized_client, invalid_scope, access_denied, โฆ
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Browser quick start
|
|
185
|
+
|
|
186
|
+
Import from `@qlover/oauth-wrapper/client`:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import { OAuthClient } from '@qlover/oauth-wrapper/client';
|
|
190
|
+
|
|
191
|
+
const oauthClient = new OAuthClient({
|
|
192
|
+
serverUrl: 'https://auth.example.com',
|
|
193
|
+
clientId: 'your-client-id',
|
|
194
|
+
scope: 'openid profile email',
|
|
195
|
+
redirectPath: 'oauth/callback',
|
|
196
|
+
origin: window.location.origin,
|
|
197
|
+
routerPrefix: '/app', // optional
|
|
198
|
+
locale: 'en', // optional; supports path/query/header
|
|
199
|
+
mapUser: (userinfo, accessToken, refreshToken) => ({
|
|
200
|
+
id: userinfo.sub,
|
|
201
|
+
email: userinfo.email,
|
|
202
|
+
name: userinfo.name
|
|
203
|
+
})
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// 1. Start login (generate PKCE + state, redirect to authorize)
|
|
207
|
+
await oauthClient.startOAuthLogin();
|
|
208
|
+
|
|
209
|
+
// 2. Handle callback page
|
|
210
|
+
const params = oauthClient.parseOAuthCallbackSearchParams(
|
|
211
|
+
new URLSearchParams(window.location.search)
|
|
212
|
+
);
|
|
213
|
+
const user = await oauthClient.completeOAuthCallback(params);
|
|
214
|
+
|
|
215
|
+
// 3. Revoke refresh token (on logout)
|
|
216
|
+
await oauthClient.revokeToken(refreshToken);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### OAuthClient core API
|
|
220
|
+
|
|
221
|
+
| Method | Description |
|
|
222
|
+
| ------ | ----------- |
|
|
223
|
+
| `isConfigured()` | Whether `serverUrl` + `clientId` are ready |
|
|
224
|
+
| `startOAuthLogin(params?)` | Save PKCE session and redirect to authorize |
|
|
225
|
+
| `completeOAuthCallback(params?)` | Validate state, exchange code, fetch userinfo, call `mapUser` |
|
|
226
|
+
| `parseOAuthCallbackSearchParams()` | Parse `code` / `state` / `error` from URL |
|
|
227
|
+
| `fetchUserInfo(accessToken)` | Fetch userinfo only |
|
|
228
|
+
| `revokeToken(refreshToken?)` | POST `/oauth/revoke` |
|
|
229
|
+
| `getGateway()` | Access underlying `OAuthGateway` |
|
|
230
|
+
| `patchConfig(partial)` | Update config at runtime (e.g. switch locale) |
|
|
231
|
+
|
|
232
|
+
When using `OAuthGateway` directly, you must write the PKCE session via `PKCESessionStore` first; `OAuthClient` wraps this flow. Callback handling includes **code+state deduplication** to avoid double invocation under React Strict Mode.
|
|
233
|
+
|
|
234
|
+
### Configuration (`OAuthClientConfig`)
|
|
235
|
+
|
|
236
|
+
| Field | Default | Description |
|
|
237
|
+
| ----- | ------- | ----------- |
|
|
238
|
+
| `serverUrl` | `''` | Authorization server base URL |
|
|
239
|
+
| `clientId` | `''` | OAuth Client ID |
|
|
240
|
+
| `scope` | `'openid profile email'` | Authorization scope |
|
|
241
|
+
| `redirectPath` | `'oauth/callback'` | Callback path (relative to origin) |
|
|
242
|
+
| `origin` | current `window.location.origin` | Used to build redirect_uri |
|
|
243
|
+
| `routerPrefix` | โ | Route prefix (e.g. `/en`) |
|
|
244
|
+
| `locale` / `localeIn` | `'en'` / `'path'` | i18n: `path` \| `query` \| `header` \| `none` |
|
|
245
|
+
|
|
246
|
+
PKCE session defaults to `sessionStorage` under key `oauth-wrapper-pkcesession`; customize via `pkceStorage` / `pkceStorageKey`.
|
|
247
|
+
|
|
248
|
+
## Client management (developer console)
|
|
249
|
+
|
|
250
|
+
`OAuthClientsService` provides owner-scoped CRUD by `ownerUserId`:
|
|
251
|
+
|
|
252
|
+
| Method | Description |
|
|
253
|
+
| ------ | ----------- |
|
|
254
|
+
| `listForOwner(userId)` | List owned clients |
|
|
255
|
+
| `getByClientId(userId, clientId)` | Get details |
|
|
256
|
+
| `create(userId, input)` | Create; confidential clients return a one-time `client_secret` |
|
|
257
|
+
| `update(userId, clientId, input)` | Update name, URI, redirect_uris |
|
|
258
|
+
| `rotateSecret(userId, clientId)` | Rotate secret (confidential only) |
|
|
259
|
+
| `delete(userId, clientId)` | Delete |
|
|
260
|
+
|
|
261
|
+
Create/update schemas: `OAuthClientCreateSchema`, `OAuthClientUpdateSchema` (`redirect_uris` supports HTTPS and custom URL schemes).
|
|
262
|
+
|
|
263
|
+
## Core module reference
|
|
264
|
+
|
|
265
|
+
### Services (`@qlover/oauth-wrapper`)
|
|
266
|
+
|
|
267
|
+
| Class | Responsibility |
|
|
268
|
+
| ----- | -------------- |
|
|
269
|
+
| `OAuthWrapperService` | Authorize page parsing, `processConsent`, token/revoke delegation, `getUserInfo`, `logoutUser` |
|
|
270
|
+
| `OAuthTokenService` | Token endpoint: client validation, code consumption, PKCE, middleware refresh token issuance |
|
|
271
|
+
| `OAuthClientsService` | Developer console client business logic |
|
|
272
|
+
|
|
273
|
+
### Schemas (`@qlover/oauth-wrapper/core`)
|
|
274
|
+
|
|
275
|
+
| Schema | Purpose |
|
|
276
|
+
| ------ | ------- |
|
|
277
|
+
| `OAuthAuthorizeQuerySchema` | GET authorize query params |
|
|
278
|
+
| `OAuthConsentBodySchema` | Consent POST body |
|
|
279
|
+
| `OAuthTokenRequestSchema` | Token endpoint (discriminated union) |
|
|
280
|
+
| `OAuthTokenRevokeSchema` | Revoke endpoint |
|
|
281
|
+
| `OAuthClientCreateSchema` / `UpdateSchema` | Client management |
|
|
282
|
+
| `OAuthUserInfoResponseSchema` | UserInfo response |
|
|
283
|
+
| `OAuthTokenResponseSchema` | Token response |
|
|
284
|
+
|
|
285
|
+
### Utils (server)
|
|
286
|
+
|
|
287
|
+
| Utility | Description |
|
|
288
|
+
| ------- | ----------- |
|
|
289
|
+
| `validatePkceParams` / `normalizeQuery` / `isRedirectUriAllowed` | Authorize request validation |
|
|
290
|
+
| `buildOAuthRedirectUrl` / `parseScopeList` | Redirect building and scope parsing |
|
|
291
|
+
| `verifyPkceS256` / `isValidCodeChallenge` | PKCE S256 |
|
|
292
|
+
| `hashClientSecret` / `verifyClientSecret` | Client secret hashing |
|
|
293
|
+
| `OAuthWrapperError` | RFC errors with HTTP status |
|
|
294
|
+
|
|
295
|
+
### Utils (browser `@qlover/oauth-wrapper/client`)
|
|
296
|
+
|
|
297
|
+
| Utility | Description |
|
|
298
|
+
| ------- | ----------- |
|
|
299
|
+
| `generatePkceVerifier` / `computePkceS256Challenge` / `randomOAuthState` | PKCE and CSRF state |
|
|
300
|
+
| `buildOAuthAuthorizeUrl` / `buildOAuthRedirectUri` | URL building |
|
|
301
|
+
| `parseOAuthTokenResponse` / `parseOAuthUserInfoResponse` | Response parsing |
|
|
302
|
+
| `PKCESessionStore` | Session-scoped PKCE storage |
|
|
303
|
+
|
|
304
|
+
## Monorepo examples
|
|
305
|
+
|
|
306
|
+
| Example | Description |
|
|
307
|
+
| ------- | ----------- |
|
|
308
|
+
| [`examples/next-oauth-wrapper`](../../examples/next-oauth-wrapper) | Full authorization server: Adapter, Repository, Session, Next.js routes, developer console UI |
|
|
309
|
+
| [`examples/react-seed`](../../examples/react-seed) | Browser `OAuthClient` integration (`SeedOAuthClient` + `useOAuthLogin`) |
|
|
310
|
+
|
|
311
|
+
## Version
|
|
312
|
+
|
|
313
|
+
See [package.json](./package.json) and [CHANGELOG.md](./CHANGELOG.md) for the current version.
|