@draftlab/auth 0.0.1
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/adapters/node.d.ts +18 -0
- package/dist/adapters/node.js +71 -0
- package/dist/allow-CixonwTW.d.ts +59 -0
- package/dist/allow-DX5cehSc.js +63 -0
- package/dist/allow.d.ts +2 -0
- package/dist/allow.js +4 -0
- package/dist/base-DRutbxgL.js +422 -0
- package/dist/client.d.ts +413 -0
- package/dist/client.js +209 -0
- package/dist/code-l_uvMR1j.d.ts +212 -0
- package/dist/core-8WTqfnb4.d.ts +129 -0
- package/dist/core-CncE5rPg.js +498 -0
- package/dist/core.d.ts +9 -0
- package/dist/core.js +14 -0
- package/dist/error-CWAdNAzm.d.ts +243 -0
- package/dist/error-DgAKK7b2.js +237 -0
- package/dist/error.d.ts +2 -0
- package/dist/error.js +3 -0
- package/dist/form-6XKM_cOk.js +61 -0
- package/dist/icon-Ci5uqGB_.js +192 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +14 -0
- package/dist/keys-EEfxEGfO.js +140 -0
- package/dist/keys.d.ts +67 -0
- package/dist/keys.js +5 -0
- package/dist/oauth2-B7-6Z7Lc.js +155 -0
- package/dist/oauth2-DtKwtl8p.d.ts +176 -0
- package/dist/password-Cm0dRMwa.d.ts +385 -0
- package/dist/pkce-276Za_rZ.js +162 -0
- package/dist/pkce.d.ts +72 -0
- package/dist/pkce.js +3 -0
- package/dist/provider/code.d.ts +4 -0
- package/dist/provider/code.js +145 -0
- package/dist/provider/facebook.d.ts +137 -0
- package/dist/provider/facebook.js +85 -0
- package/dist/provider/github.d.ts +141 -0
- package/dist/provider/github.js +88 -0
- package/dist/provider/google.d.ts +113 -0
- package/dist/provider/google.js +62 -0
- package/dist/provider/oauth2.d.ts +4 -0
- package/dist/provider/oauth2.js +7 -0
- package/dist/provider/password.d.ts +4 -0
- package/dist/provider/password.js +366 -0
- package/dist/provider/provider.d.ts +3 -0
- package/dist/provider/provider.js +44 -0
- package/dist/provider-CwWMG-1l.d.ts +227 -0
- package/dist/random-SXMYlaVr.js +87 -0
- package/dist/random.d.ts +66 -0
- package/dist/random.js +3 -0
- package/dist/select-BjySLL8I.js +280 -0
- package/dist/storage/memory.d.ts +82 -0
- package/dist/storage/memory.js +127 -0
- package/dist/storage/storage.d.ts +2 -0
- package/dist/storage/storage.js +3 -0
- package/dist/storage/turso.d.ts +31 -0
- package/dist/storage/turso.js +117 -0
- package/dist/storage/unstorage.d.ts +38 -0
- package/dist/storage/unstorage.js +97 -0
- package/dist/storage-BEaqEPNQ.js +62 -0
- package/dist/storage-CxKerLlc.d.ts +162 -0
- package/dist/subject-DiQdRWGt.d.ts +62 -0
- package/dist/subject.d.ts +3 -0
- package/dist/subject.js +36 -0
- package/dist/theme-C9by7VXf.d.ts +209 -0
- package/dist/theme-CswaLtbW.js +120 -0
- package/dist/themes/theme.d.ts +2 -0
- package/dist/themes/theme.js +3 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.js +0 -0
- package/dist/ui/base.d.ts +43 -0
- package/dist/ui/base.js +4 -0
- package/dist/ui/code.d.ts +158 -0
- package/dist/ui/code.js +197 -0
- package/dist/ui/form.d.ts +31 -0
- package/dist/ui/form.js +3 -0
- package/dist/ui/icon.d.ts +98 -0
- package/dist/ui/icon.js +3 -0
- package/dist/ui/password.d.ts +54 -0
- package/dist/ui/password.js +300 -0
- package/dist/ui/select.d.ts +233 -0
- package/dist/ui/select.js +6 -0
- package/dist/util-CSdHUFOo.js +108 -0
- package/dist/util-ChlgVqPN.d.ts +72 -0
- package/dist/util.d.ts +2 -0
- package/dist/util.js +3 -0
- package/package.json +63 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { InvalidAccessTokenError, InvalidAuthorizationCodeError, InvalidRefreshTokenError, InvalidSubjectError } from "./error-CWAdNAzm.js";
|
|
2
|
+
import "./util-ChlgVqPN.js";
|
|
3
|
+
import { SubjectSchema } from "./subject-DiQdRWGt.js";
|
|
4
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
5
|
+
|
|
6
|
+
//#region src/client.d.ts
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Result type for operations that can succeed or fail.
|
|
10
|
+
*
|
|
11
|
+
* @template T - The success data type
|
|
12
|
+
* @template E - The error type
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const result = await client.exchange(code, redirectUri)
|
|
17
|
+
* if (result.success) {
|
|
18
|
+
* // Access token available: result.data.access
|
|
19
|
+
* } else {
|
|
20
|
+
* // Handle error: result.error.message
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
type Result<T, E = Error> = {
|
|
25
|
+
success: true;
|
|
26
|
+
data: T;
|
|
27
|
+
} | {
|
|
28
|
+
success: false;
|
|
29
|
+
error: E;
|
|
30
|
+
};
|
|
31
|
+
interface FetchResponse {
|
|
32
|
+
ok: boolean;
|
|
33
|
+
text(): Promise<string>;
|
|
34
|
+
json(): Promise<unknown>;
|
|
35
|
+
}
|
|
36
|
+
type FetchLike = (url: string, init?: RequestInit) => Promise<FetchResponse>;
|
|
37
|
+
/**
|
|
38
|
+
* Authorization server metadata from well-known endpoints.
|
|
39
|
+
*/
|
|
40
|
+
interface WellKnown {
|
|
41
|
+
/**
|
|
42
|
+
* URI to the JWKS endpoint for token verification.
|
|
43
|
+
*/
|
|
44
|
+
jwks_uri: string;
|
|
45
|
+
/**
|
|
46
|
+
* URI to the token endpoint for authorization code exchange.
|
|
47
|
+
*/
|
|
48
|
+
token_endpoint: string;
|
|
49
|
+
/**
|
|
50
|
+
* URI to the authorization endpoint for starting flows.
|
|
51
|
+
*/
|
|
52
|
+
authorization_endpoint: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Tokens returned by the authorization server.
|
|
56
|
+
*/
|
|
57
|
+
interface Tokens {
|
|
58
|
+
/**
|
|
59
|
+
* Access token for making authenticated API requests.
|
|
60
|
+
*/
|
|
61
|
+
access: string;
|
|
62
|
+
/**
|
|
63
|
+
* Refresh token for obtaining new access tokens.
|
|
64
|
+
*/
|
|
65
|
+
refresh: string;
|
|
66
|
+
/**
|
|
67
|
+
* Number of seconds until the access token expires.
|
|
68
|
+
*/
|
|
69
|
+
expiresIn: number;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Challenge data for PKCE flows.
|
|
73
|
+
*/
|
|
74
|
+
type Challenge = {
|
|
75
|
+
/**
|
|
76
|
+
* State parameter for CSRF protection.
|
|
77
|
+
*/
|
|
78
|
+
state: string;
|
|
79
|
+
/**
|
|
80
|
+
* PKCE code verifier for token exchange.
|
|
81
|
+
*/
|
|
82
|
+
verifier?: string;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Client configuration options.
|
|
86
|
+
*/
|
|
87
|
+
interface ClientInput {
|
|
88
|
+
/**
|
|
89
|
+
* Client ID that identifies your application.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* {
|
|
94
|
+
* clientID: "my-web-app"
|
|
95
|
+
* }
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
clientID: string;
|
|
99
|
+
/**
|
|
100
|
+
* Base URL of your Draft Auth server.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* {
|
|
105
|
+
* issuer: "https://auth.myserver.com"
|
|
106
|
+
* }
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
issuer: string;
|
|
110
|
+
/**
|
|
111
|
+
* Optionally, override the internally used fetch function.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* {
|
|
116
|
+
* fetch: customFetch
|
|
117
|
+
* }
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
fetch?: FetchLike;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Options for starting an authorization flow.
|
|
124
|
+
*/
|
|
125
|
+
interface AuthorizeOptions {
|
|
126
|
+
/**
|
|
127
|
+
* Enable PKCE flow for enhanced security.
|
|
128
|
+
*
|
|
129
|
+
* Recommended for single-page applications and mobile apps.
|
|
130
|
+
*
|
|
131
|
+
* @default false
|
|
132
|
+
* @example
|
|
133
|
+
* ```ts
|
|
134
|
+
* {
|
|
135
|
+
* pkce: true
|
|
136
|
+
* }
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
pkce?: boolean;
|
|
140
|
+
/**
|
|
141
|
+
* Specific authentication provider to use.
|
|
142
|
+
*
|
|
143
|
+
* If not specified, users see a provider selection screen
|
|
144
|
+
* or are redirected to the single configured provider.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* {
|
|
149
|
+
* provider: "google"
|
|
150
|
+
* }
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
provider?: string;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Result of starting an authorization flow.
|
|
157
|
+
*/
|
|
158
|
+
interface AuthorizeResult {
|
|
159
|
+
/**
|
|
160
|
+
* Challenge data needed for PKCE flows.
|
|
161
|
+
*
|
|
162
|
+
* Store this securely and use when exchanging the code.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* sessionStorage.setItem("challenge", JSON.stringify(challenge))
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
challenge: Challenge;
|
|
170
|
+
/**
|
|
171
|
+
* Authorization URL to redirect the user to.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```ts
|
|
175
|
+
* window.location.href = url
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
url: string;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Options for token refresh operations.
|
|
182
|
+
*/
|
|
183
|
+
interface RefreshOptions {
|
|
184
|
+
/**
|
|
185
|
+
* Current access token to check before refreshing.
|
|
186
|
+
*
|
|
187
|
+
* Helps avoid unnecessary refresh requests.
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```ts
|
|
191
|
+
* {
|
|
192
|
+
* access: currentAccessToken
|
|
193
|
+
* }
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
access?: string;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Options for token verification.
|
|
200
|
+
*/
|
|
201
|
+
interface VerifyOptions {
|
|
202
|
+
/**
|
|
203
|
+
* Refresh token for automatic refresh if access token is expired.
|
|
204
|
+
*
|
|
205
|
+
* If passed in, this will automatically refresh the access token if it has expired.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```ts
|
|
209
|
+
* {
|
|
210
|
+
* refresh: refreshToken
|
|
211
|
+
* }
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
refresh?: string;
|
|
215
|
+
/**
|
|
216
|
+
* Expected issuer for validation.
|
|
217
|
+
* @internal
|
|
218
|
+
*/
|
|
219
|
+
issuer?: string;
|
|
220
|
+
/**
|
|
221
|
+
* Expected audience for validation.
|
|
222
|
+
* @internal
|
|
223
|
+
*/
|
|
224
|
+
audience?: string;
|
|
225
|
+
/**
|
|
226
|
+
* Custom fetch for HTTP requests.
|
|
227
|
+
*
|
|
228
|
+
* Optionally, override the internally used fetch function.
|
|
229
|
+
*/
|
|
230
|
+
fetch?: FetchLike;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Result of successful token verification.
|
|
234
|
+
*/
|
|
235
|
+
interface VerifyResult<T extends SubjectSchema> {
|
|
236
|
+
/**
|
|
237
|
+
* New tokens if access token was refreshed during verification.
|
|
238
|
+
*/
|
|
239
|
+
tokens?: Tokens;
|
|
240
|
+
/**
|
|
241
|
+
* Audience (client ID) the token was issued for.
|
|
242
|
+
* @internal
|
|
243
|
+
*/
|
|
244
|
+
aud: string;
|
|
245
|
+
/**
|
|
246
|
+
* Decoded subject information from the access token.
|
|
247
|
+
*
|
|
248
|
+
* Contains user data that was encoded when the token was issued.
|
|
249
|
+
*/
|
|
250
|
+
subject: { [K in keyof T]: {
|
|
251
|
+
type: K;
|
|
252
|
+
properties: StandardSchemaV1.InferOutput<T[K]>;
|
|
253
|
+
} }[keyof T];
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Options for UserInfo requests.
|
|
257
|
+
*/
|
|
258
|
+
/**
|
|
259
|
+
* Draft Auth client with OAuth 2.0 operations.
|
|
260
|
+
*/
|
|
261
|
+
interface Client {
|
|
262
|
+
/**
|
|
263
|
+
* Start an OAuth authorization flow.
|
|
264
|
+
*
|
|
265
|
+
* @param redirectURI - Where users will be sent after authorization
|
|
266
|
+
* @param response - Response type ("code" or "token")
|
|
267
|
+
* @param opts - Additional authorization options
|
|
268
|
+
* @returns Authorization URL and challenge data
|
|
269
|
+
*
|
|
270
|
+
* @example Basic flow
|
|
271
|
+
* ```ts
|
|
272
|
+
* const result = await client.authorize(
|
|
273
|
+
* "https://myapp.com/callback",
|
|
274
|
+
* "code"
|
|
275
|
+
* )
|
|
276
|
+
* if (result.success) {
|
|
277
|
+
* window.location.href = result.data.url
|
|
278
|
+
* }
|
|
279
|
+
* ```
|
|
280
|
+
*
|
|
281
|
+
* @example PKCE flow
|
|
282
|
+
* ```ts
|
|
283
|
+
* const result = await client.authorize(
|
|
284
|
+
* "https://spa.example.com/callback",
|
|
285
|
+
* "code",
|
|
286
|
+
* { pkce: true, scopes: ["read", "write"] }
|
|
287
|
+
* )
|
|
288
|
+
* if (result.success) {
|
|
289
|
+
* sessionStorage.setItem("challenge", JSON.stringify(result.data.challenge))
|
|
290
|
+
* window.location.href = result.data.url
|
|
291
|
+
* }
|
|
292
|
+
* ```
|
|
293
|
+
*/
|
|
294
|
+
authorize(redirectURI: string, response: "code" | "token", opts?: AuthorizeOptions): Promise<Result<AuthorizeResult>>;
|
|
295
|
+
/**
|
|
296
|
+
* Exchange authorization code for tokens.
|
|
297
|
+
*
|
|
298
|
+
* @param code - Authorization code from the callback
|
|
299
|
+
* @param redirectURI - Same redirect URI used in authorization
|
|
300
|
+
* @param verifier - PKCE code verifier (required for PKCE flows)
|
|
301
|
+
* @returns Access tokens and metadata
|
|
302
|
+
*
|
|
303
|
+
* @example Basic exchange
|
|
304
|
+
* ```ts
|
|
305
|
+
* const urlParams = new URLSearchParams(window.location.search)
|
|
306
|
+
* const code = urlParams.get('code')
|
|
307
|
+
*
|
|
308
|
+
* if (code) {
|
|
309
|
+
* const result = await client.exchange(code, "https://myapp.com/callback")
|
|
310
|
+
* if (result.success) {
|
|
311
|
+
* const { access, refresh } = result.data
|
|
312
|
+
* // Store tokens securely
|
|
313
|
+
* }
|
|
314
|
+
* }
|
|
315
|
+
* ```
|
|
316
|
+
*
|
|
317
|
+
* @example PKCE exchange
|
|
318
|
+
* ```ts
|
|
319
|
+
* const challenge = JSON.parse(sessionStorage.getItem("challenge") || "{}")
|
|
320
|
+
* const code = new URLSearchParams(window.location.search).get('code')
|
|
321
|
+
*
|
|
322
|
+
* if (code && challenge.verifier) {
|
|
323
|
+
* const result = await client.exchange(
|
|
324
|
+
* code,
|
|
325
|
+
* "https://spa.example.com/callback",
|
|
326
|
+
* challenge.verifier
|
|
327
|
+
* )
|
|
328
|
+
* if (result.success) {
|
|
329
|
+
* sessionStorage.removeItem("challenge")
|
|
330
|
+
* // Handle tokens
|
|
331
|
+
* }
|
|
332
|
+
* }
|
|
333
|
+
* ```
|
|
334
|
+
*/
|
|
335
|
+
exchange(code: string, redirectURI: string, verifier?: string): Promise<Result<Tokens, InvalidAuthorizationCodeError>>;
|
|
336
|
+
/**
|
|
337
|
+
* Refresh an access token using a refresh token.
|
|
338
|
+
*
|
|
339
|
+
* @param refresh - Refresh token to use
|
|
340
|
+
* @param opts - Additional refresh options
|
|
341
|
+
* @returns New tokens if refresh was needed
|
|
342
|
+
*
|
|
343
|
+
* @example Basic refresh
|
|
344
|
+
* ```ts
|
|
345
|
+
* const result = await client.refresh(storedRefreshToken)
|
|
346
|
+
*
|
|
347
|
+
* if (result.success && result.data.tokens) {
|
|
348
|
+
* const { access, refresh: newRefresh } = result.data.tokens
|
|
349
|
+
* updateStoredTokens(access, newRefresh)
|
|
350
|
+
* } else if (result.success) {
|
|
351
|
+
* // Token still valid
|
|
352
|
+
* } else {
|
|
353
|
+
* redirectToLogin()
|
|
354
|
+
* }
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
357
|
+
refresh(refresh: string, opts?: RefreshOptions): Promise<Result<{
|
|
358
|
+
tokens?: Tokens;
|
|
359
|
+
}, InvalidRefreshTokenError | InvalidAccessTokenError>>;
|
|
360
|
+
/**
|
|
361
|
+
* Verify and decode an access token.
|
|
362
|
+
*
|
|
363
|
+
* @param subjects - Subject schema used when creating the issuer
|
|
364
|
+
* @param token - Access token to verify
|
|
365
|
+
* @param options - Additional verification options
|
|
366
|
+
* @returns Decoded token data and user information
|
|
367
|
+
*
|
|
368
|
+
* @example Basic verification
|
|
369
|
+
* ```ts
|
|
370
|
+
* const result = await client.verify(subjects, accessToken)
|
|
371
|
+
*
|
|
372
|
+
* if (result.success) {
|
|
373
|
+
* const { subject, scopes } = result.data
|
|
374
|
+
* // Access user ID: subject.properties.userID
|
|
375
|
+
* // Access scopes: scopes?.join(', ')
|
|
376
|
+
* }
|
|
377
|
+
* ```
|
|
378
|
+
*
|
|
379
|
+
* @example With automatic refresh
|
|
380
|
+
* ```ts
|
|
381
|
+
* const result = await client.verify(subjects, accessToken, {
|
|
382
|
+
* refresh: refreshToken
|
|
383
|
+
* })
|
|
384
|
+
*
|
|
385
|
+
* if (result.success) {
|
|
386
|
+
* if (result.data.tokens) {
|
|
387
|
+
* // Tokens were refreshed
|
|
388
|
+
* updateStoredTokens(result.data.tokens.access, result.data.tokens.refresh)
|
|
389
|
+
* }
|
|
390
|
+
* // Use verified subject data
|
|
391
|
+
* const user = result.data.subject.properties
|
|
392
|
+
* }
|
|
393
|
+
* ```
|
|
394
|
+
*/
|
|
395
|
+
verify<T extends SubjectSchema>(subjects: T, token: string, options?: VerifyOptions): Promise<Result<VerifyResult<T>, InvalidRefreshTokenError | InvalidAccessTokenError | InvalidSubjectError>>;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Create a Draft Auth client.
|
|
399
|
+
*
|
|
400
|
+
* @param input - Client configuration
|
|
401
|
+
* @returns Configured client instance
|
|
402
|
+
*
|
|
403
|
+
* @example Basic setup
|
|
404
|
+
* ```ts
|
|
405
|
+
* const client = createClient({
|
|
406
|
+
* clientID: "my-web-app",
|
|
407
|
+
* issuer: "https://auth.mycompany.com"
|
|
408
|
+
* })
|
|
409
|
+
* ```
|
|
410
|
+
*/
|
|
411
|
+
declare const createClient: (input: ClientInput) => Client;
|
|
412
|
+
//#endregion
|
|
413
|
+
export { AuthorizeOptions, AuthorizeResult, Challenge, Client, ClientInput, RefreshOptions, Result, Tokens, VerifyOptions, VerifyResult, WellKnown, createClient };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { InvalidAccessTokenError, InvalidAuthorizationCodeError, InvalidRefreshTokenError, InvalidSubjectError } from "./error-DgAKK7b2.js";
|
|
2
|
+
import { generatePKCE } from "./pkce-276Za_rZ.js";
|
|
3
|
+
import { createLocalJWKSet, errors, jwtVerify } from "jose";
|
|
4
|
+
|
|
5
|
+
//#region src/client.ts
|
|
6
|
+
/**
|
|
7
|
+
* Create a Draft Auth client.
|
|
8
|
+
*
|
|
9
|
+
* @param input - Client configuration
|
|
10
|
+
* @returns Configured client instance
|
|
11
|
+
*
|
|
12
|
+
* @example Basic setup
|
|
13
|
+
* ```ts
|
|
14
|
+
* const client = createClient({
|
|
15
|
+
* clientID: "my-web-app",
|
|
16
|
+
* issuer: "https://auth.mycompany.com"
|
|
17
|
+
* })
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
const createClient = (input) => {
|
|
21
|
+
const jwksCache = /* @__PURE__ */ new Map();
|
|
22
|
+
const issuerCache = /* @__PURE__ */ new Map();
|
|
23
|
+
const issuer = input.issuer;
|
|
24
|
+
if (!issuer) throw new Error("No issuer configured");
|
|
25
|
+
const f = input.fetch ?? fetch;
|
|
26
|
+
const getIssuer = async () => {
|
|
27
|
+
const cached = issuerCache.get(issuer);
|
|
28
|
+
if (cached) return cached;
|
|
29
|
+
const wellKnown = await f(`${issuer}/.well-known/oauth-authorization-server`).then((r) => r.json());
|
|
30
|
+
issuerCache.set(issuer, wellKnown);
|
|
31
|
+
return wellKnown;
|
|
32
|
+
};
|
|
33
|
+
const getJWKS = async () => {
|
|
34
|
+
const wk = await getIssuer();
|
|
35
|
+
const cached = jwksCache.get(issuer);
|
|
36
|
+
if (cached) return cached;
|
|
37
|
+
const keyset = await f(wk.jwks_uri).then((r) => r.json());
|
|
38
|
+
const result = createLocalJWKSet(keyset);
|
|
39
|
+
jwksCache.set(issuer, result);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
const client = {
|
|
43
|
+
async authorize(redirectURI, response, opts) {
|
|
44
|
+
try {
|
|
45
|
+
const wk = await getIssuer();
|
|
46
|
+
const authUrl = new URL(wk.authorization_endpoint);
|
|
47
|
+
const challenge = { state: crypto.randomUUID() };
|
|
48
|
+
authUrl.searchParams.set("client_id", input.clientID);
|
|
49
|
+
authUrl.searchParams.set("redirect_uri", redirectURI);
|
|
50
|
+
authUrl.searchParams.set("response_type", response);
|
|
51
|
+
authUrl.searchParams.set("state", challenge.state);
|
|
52
|
+
if (opts?.provider) authUrl.searchParams.set("provider", opts.provider);
|
|
53
|
+
if (opts?.pkce && response === "code") {
|
|
54
|
+
const pkce = await generatePKCE();
|
|
55
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
56
|
+
authUrl.searchParams.set("code_challenge", pkce.challenge);
|
|
57
|
+
challenge.verifier = pkce.verifier;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
success: true,
|
|
61
|
+
data: {
|
|
62
|
+
challenge,
|
|
63
|
+
url: authUrl.toString()
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
error
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
async exchange(code, redirectURI, verifier) {
|
|
74
|
+
try {
|
|
75
|
+
const wk = await getIssuer();
|
|
76
|
+
const response = await f(wk.token_endpoint, {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
79
|
+
body: new URLSearchParams({
|
|
80
|
+
code,
|
|
81
|
+
redirect_uri: redirectURI,
|
|
82
|
+
grant_type: "authorization_code",
|
|
83
|
+
client_id: input.clientID,
|
|
84
|
+
...verifier ? { code_verifier: verifier } : {}
|
|
85
|
+
}).toString()
|
|
86
|
+
});
|
|
87
|
+
if (!response.ok) return {
|
|
88
|
+
success: false,
|
|
89
|
+
error: new InvalidAuthorizationCodeError()
|
|
90
|
+
};
|
|
91
|
+
const responseText = await response.text();
|
|
92
|
+
let json;
|
|
93
|
+
try {
|
|
94
|
+
json = JSON.parse(responseText);
|
|
95
|
+
} catch {
|
|
96
|
+
return {
|
|
97
|
+
success: false,
|
|
98
|
+
error: new InvalidAuthorizationCodeError()
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const tokenResponse = json;
|
|
102
|
+
return {
|
|
103
|
+
success: true,
|
|
104
|
+
data: {
|
|
105
|
+
access: tokenResponse.access_token,
|
|
106
|
+
refresh: tokenResponse.refresh_token,
|
|
107
|
+
expiresIn: tokenResponse.expires_in
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
} catch {
|
|
111
|
+
return {
|
|
112
|
+
success: false,
|
|
113
|
+
error: new InvalidAuthorizationCodeError()
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
async refresh(refresh, opts) {
|
|
118
|
+
try {
|
|
119
|
+
if (opts?.access) try {
|
|
120
|
+
const jwks = await getJWKS();
|
|
121
|
+
await jwtVerify(opts.access, jwks, { issuer });
|
|
122
|
+
return {
|
|
123
|
+
success: true,
|
|
124
|
+
data: {}
|
|
125
|
+
};
|
|
126
|
+
} catch {}
|
|
127
|
+
const wk = await getIssuer();
|
|
128
|
+
const response = await f(wk.token_endpoint, {
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
131
|
+
body: new URLSearchParams({
|
|
132
|
+
refresh_token: refresh,
|
|
133
|
+
grant_type: "refresh_token"
|
|
134
|
+
}).toString()
|
|
135
|
+
});
|
|
136
|
+
if (!response.ok) return {
|
|
137
|
+
success: false,
|
|
138
|
+
error: new InvalidRefreshTokenError()
|
|
139
|
+
};
|
|
140
|
+
const tokenResponse = await response.json();
|
|
141
|
+
return {
|
|
142
|
+
success: true,
|
|
143
|
+
data: { tokens: {
|
|
144
|
+
access: tokenResponse.access_token,
|
|
145
|
+
refresh: tokenResponse.refresh_token,
|
|
146
|
+
expiresIn: tokenResponse.expires_in
|
|
147
|
+
} }
|
|
148
|
+
};
|
|
149
|
+
} catch {
|
|
150
|
+
return {
|
|
151
|
+
success: false,
|
|
152
|
+
error: new InvalidRefreshTokenError()
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
async verify(subjects, token, options) {
|
|
157
|
+
try {
|
|
158
|
+
const jwks = await getJWKS();
|
|
159
|
+
const jwtResult = await jwtVerify(token, jwks, { issuer });
|
|
160
|
+
const validated = await subjects[jwtResult.payload.type]?.["~standard"].validate(jwtResult.payload.properties);
|
|
161
|
+
if (!validated?.issues && jwtResult.payload.mode === "access") return {
|
|
162
|
+
success: true,
|
|
163
|
+
data: {
|
|
164
|
+
aud: jwtResult.payload.aud,
|
|
165
|
+
subject: {
|
|
166
|
+
type: jwtResult.payload.type,
|
|
167
|
+
properties: validated?.value
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
return {
|
|
172
|
+
success: false,
|
|
173
|
+
error: new InvalidSubjectError()
|
|
174
|
+
};
|
|
175
|
+
} catch (e) {
|
|
176
|
+
if (e instanceof errors.JWTExpired && options?.refresh) {
|
|
177
|
+
const refreshed = await client.refresh(options.refresh);
|
|
178
|
+
if (!refreshed.success) return refreshed;
|
|
179
|
+
if (!refreshed.data.tokens) return {
|
|
180
|
+
success: false,
|
|
181
|
+
error: new InvalidAccessTokenError()
|
|
182
|
+
};
|
|
183
|
+
const verified = await client.verify(subjects, refreshed.data.tokens.access, {
|
|
184
|
+
refresh: refreshed.data.tokens.refresh,
|
|
185
|
+
issuer: options?.issuer,
|
|
186
|
+
audience: options?.audience,
|
|
187
|
+
fetch: options?.fetch
|
|
188
|
+
});
|
|
189
|
+
if (!verified.success) return verified;
|
|
190
|
+
return {
|
|
191
|
+
success: true,
|
|
192
|
+
data: {
|
|
193
|
+
...verified.data,
|
|
194
|
+
tokens: refreshed.data.tokens
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
success: false,
|
|
200
|
+
error: new InvalidAccessTokenError()
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
return client;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
//#endregion
|
|
209
|
+
export { createClient };
|