@draftlab/auth 0.0.2 → 0.0.4

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.
Files changed (74) hide show
  1. package/dist/allow.d.ts +58 -1
  2. package/dist/allow.js +61 -2
  3. package/dist/client.d.ts +2 -3
  4. package/dist/client.js +2 -2
  5. package/dist/core.d.ts +128 -8
  6. package/dist/core.js +496 -12
  7. package/dist/error.d.ts +242 -1
  8. package/dist/error.js +235 -1
  9. package/dist/index.d.ts +1 -8
  10. package/dist/index.js +1 -12
  11. package/dist/keys.d.ts +1 -1
  12. package/dist/keys.js +138 -3
  13. package/dist/pkce.js +160 -1
  14. package/dist/provider/code.d.ts +211 -3
  15. package/dist/provider/code.js +1 -1
  16. package/dist/provider/facebook.d.ts +2 -3
  17. package/dist/provider/facebook.js +1 -5
  18. package/dist/provider/github.d.ts +2 -3
  19. package/dist/provider/github.js +1 -5
  20. package/dist/provider/google.d.ts +2 -3
  21. package/dist/provider/google.js +1 -5
  22. package/dist/provider/oauth2.d.ts +175 -3
  23. package/dist/provider/oauth2.js +153 -5
  24. package/dist/provider/password.d.ts +384 -3
  25. package/dist/provider/password.js +4 -4
  26. package/dist/provider/provider.d.ts +226 -2
  27. package/dist/random.js +85 -1
  28. package/dist/storage/memory.d.ts +1 -1
  29. package/dist/storage/memory.js +1 -1
  30. package/dist/storage/storage.d.ts +161 -1
  31. package/dist/storage/storage.js +60 -1
  32. package/dist/storage/turso.d.ts +1 -1
  33. package/dist/storage/turso.js +1 -1
  34. package/dist/storage/unstorage.d.ts +1 -1
  35. package/dist/storage/unstorage.js +1 -1
  36. package/dist/subject.d.ts +61 -2
  37. package/dist/themes/theme.d.ts +208 -1
  38. package/dist/themes/theme.js +118 -1
  39. package/dist/ui/base.js +420 -2
  40. package/dist/ui/code.d.ts +1 -3
  41. package/dist/ui/code.js +3 -4
  42. package/dist/ui/form.js +59 -1
  43. package/dist/ui/icon.js +190 -1
  44. package/dist/ui/password.d.ts +1 -3
  45. package/dist/ui/password.js +2 -3
  46. package/dist/ui/select.js +278 -4
  47. package/dist/util.d.ts +71 -1
  48. package/dist/util.js +106 -1
  49. package/package.json +2 -4
  50. package/dist/allow-CixonwTW.d.ts +0 -59
  51. package/dist/allow-DX5cehSc.js +0 -63
  52. package/dist/base-DRutbxgL.js +0 -422
  53. package/dist/code-DJxdFR7p.d.ts +0 -212
  54. package/dist/core-BZHEAefX.d.ts +0 -129
  55. package/dist/core-CDM5o4rs.js +0 -498
  56. package/dist/error-CWAdNAzm.d.ts +0 -243
  57. package/dist/error-DgAKK7b2.js +0 -237
  58. package/dist/form-6XKM_cOk.js +0 -61
  59. package/dist/icon-Ci5uqGB_.js +0 -192
  60. package/dist/keys-EEfxEGfO.js +0 -140
  61. package/dist/oauth2-B7-6Z7Lc.js +0 -155
  62. package/dist/oauth2-CXHukHf2.d.ts +0 -176
  63. package/dist/password-C4KLmO0O.d.ts +0 -385
  64. package/dist/pkce-276Za_rZ.js +0 -162
  65. package/dist/provider-tndlqCzp.d.ts +0 -227
  66. package/dist/random-SXMYlaVr.js +0 -87
  67. package/dist/select-BjySLL8I.js +0 -280
  68. package/dist/storage-BEaqEPNQ.js +0 -62
  69. package/dist/storage-CxKerLlc.d.ts +0 -162
  70. package/dist/subject-DMIMVtaT.d.ts +0 -62
  71. package/dist/theme-C9by7VXf.d.ts +0 -209
  72. package/dist/theme-CswaLtbW.js +0 -120
  73. package/dist/util-CSdHUFOo.js +0 -108
  74. package/dist/util-DbSKG1Xm.d.ts +0 -72
@@ -1,129 +0,0 @@
1
- import { AllowCheckInput } from "./allow-CixonwTW.js";
2
- import { UnknownStateError } from "./error-CWAdNAzm.js";
3
- import { Prettify } from "./util-DbSKG1Xm.js";
4
- import { SubjectPayload, SubjectSchema } from "./subject-DMIMVtaT.js";
5
- import { StorageAdapter } from "./storage-CxKerLlc.js";
6
- import { Provider } from "./provider-tndlqCzp.js";
7
- import { Theme } from "./theme-C9by7VXf.js";
8
- import { Router } from "@draftlab/auth-router";
9
-
10
- //#region src/core.d.ts
11
-
12
- /**
13
- * Sets the subject payload in the JWT token and returns the response.
14
- */
15
- interface OnSuccessResponder<T extends {
16
- type: string;
17
- properties: unknown;
18
- }> {
19
- subject<Type extends T["type"]>(type: Type, properties: Extract<T, {
20
- type: Type;
21
- }>["properties"], opts?: {
22
- ttl?: {
23
- access?: number;
24
- refresh?: number;
25
- };
26
- subject?: string;
27
- }): Promise<Response>;
28
- }
29
- /**
30
- * Authorization state for OAuth 2.0 flows.
31
- */
32
- interface AuthorizationState {
33
- /** OAuth redirect URI */
34
- redirect_uri: string;
35
- /** OAuth response type */
36
- response_type: string;
37
- /** OAuth state parameter for CSRF protection */
38
- state: string;
39
- /** OAuth client identifier */
40
- client_id: string;
41
- /** OAuth audience parameter */
42
- audience: string;
43
- /** PKCE challenge data for code verification */
44
- pkce?: {
45
- challenge: string;
46
- method: "S256";
47
- };
48
- }
49
- /**
50
- * Main issuer input configuration interface.
51
- */
52
- interface IssuerInput<Providers extends Record<string, Provider<unknown>>, Subjects extends SubjectSchema, Result = { [Key in keyof Providers]: Prettify<{
53
- provider: Key;
54
- } & (Providers[Key] extends Provider<infer T> ? T : Record<string, unknown>)> }[keyof Providers]> {
55
- /** The storage adapter for persisting tokens and sessions */
56
- storage: StorageAdapter;
57
- /** Auth providers configuration */
58
- providers: Providers;
59
- /** Subject schemas for token validation */
60
- subjects: Subjects;
61
- /** Base path for embedded scenarios */
62
- basePath?: string;
63
- /** Success callback for completed authentication */
64
- success(response: OnSuccessResponder<SubjectPayload<Subjects>>, input: Result, req: Request, clientID: string): Promise<Response>;
65
- /** Theme configuration for UI */
66
- theme?: Theme;
67
- /** TTL configuration for tokens and sessions */
68
- ttl?: {
69
- access?: number;
70
- refresh?: number;
71
- reuse?: number;
72
- retention?: number;
73
- };
74
- /** Provider selection UI function */
75
- select?(providers: Record<string, string>, req: Request): Promise<Response>;
76
- /** Optional start callback */
77
- start?(req: Request): Promise<void>;
78
- /** Error handling callback */
79
- error?(error: UnknownStateError, req: Request): Promise<Response>;
80
- /** Client authorization check function */
81
- allow?(input: AllowCheckInput, req: Request): Promise<boolean>;
82
- /**
83
- * Refresh callback for updating user claims.
84
- *
85
- * @example
86
- * ```typescript
87
- * refresh: async (payload, req) => {
88
- * const user = await getUserBySubject(payload.subject)
89
- * if (!user || !user.active) {
90
- * return undefined // Revoke the token
91
- * }
92
- *
93
- * return {
94
- * type: payload.type,
95
- * properties: {
96
- * userID: user.id,
97
- * role: user.role,
98
- * permissions: user.permissions,
99
- * lastLogin: new Date().toISOString()
100
- * }
101
- * }
102
- * }
103
- * ```
104
- */
105
- refresh?(payload: {
106
- type: SubjectPayload<Subjects>["type"];
107
- properties: SubjectPayload<Subjects>["properties"];
108
- subject: string;
109
- clientID: string;
110
- scopes?: string[];
111
- }, req: Request): Promise<{
112
- type: SubjectPayload<Subjects>["type"];
113
- properties: SubjectPayload<Subjects>["properties"];
114
- subject?: string;
115
- scopes?: string[];
116
- } | undefined>;
117
- }
118
- /**
119
- * Create an Draft Auth server, a Router app that handles OAuth 2.0 flows.
120
- */
121
- declare const issuer: <Providers extends Record<string, Provider<unknown>>, Subjects extends SubjectSchema, Result = { [key in keyof Providers]: {
122
- provider: key;
123
- } & (Providers[key] extends Provider<infer T> ? T : Record<string, unknown>) }[keyof Providers]>(input: IssuerInput<Providers, Subjects, Result>) => Router<{
124
- Variables: {
125
- authorization: AuthorizationState;
126
- };
127
- }>;
128
- //#endregion
129
- export { AuthorizationState, OnSuccessResponder, issuer };
@@ -1,498 +0,0 @@
1
- import { getRelativeUrl, lazy } from "./util-CSdHUFOo.js";
2
- import { defaultAllowCheck } from "./allow-DX5cehSc.js";
3
- import { MissingParameterError, OauthError, UnauthorizedClientError, UnknownStateError } from "./error-DgAKK7b2.js";
4
- import { validatePKCE } from "./pkce-276Za_rZ.js";
5
- import { generateSecureToken } from "./random-SXMYlaVr.js";
6
- import { Storage } from "./storage-BEaqEPNQ.js";
7
- import { encryptionKeys, signingKeys } from "./keys-EEfxEGfO.js";
8
- import { setTheme } from "./theme-CswaLtbW.js";
9
- import { Select } from "./select-BjySLL8I.js";
10
- import { CompactEncrypt, SignJWT, compactDecrypt } from "jose";
11
- import { Router } from "@draftlab/auth-router";
12
- import { deleteCookie, getCookie, setCookie } from "@draftlab/auth-router/cookies";
13
- import { cors } from "@draftlab/auth-router/middleware/cors";
14
-
15
- //#region src/core.ts
16
- /**
17
- * Determines if the incoming request is using HTTPS protocol.
18
- * Checks multiple proxy headers to handle load balancers and reverse proxies.
19
- *
20
- * @param ctx - Router context containing request headers and URL
21
- * @returns True if request is HTTPS, false otherwise
22
- *
23
- * @example
24
- * ```ts
25
- * if (isHttpsRequest(ctx)) {
26
- * setCookie(ctx, 'secure-cookie', value, { secure: true })
27
- * }
28
- * ```
29
- */
30
- const isHttpsRequest = (ctx) => {
31
- return ctx.header("x-forwarded-proto") === "https" || ctx.header("x-forwarded-ssl") === "on" || ctx.request.url.startsWith("https://");
32
- };
33
- /**
34
- * Create an Draft Auth server, a Router app that handles OAuth 2.0 flows.
35
- */
36
- const issuer = (input) => {
37
- const error = input.error ?? ((err) => {
38
- return new Response(err.message, {
39
- status: 400,
40
- headers: { "Content-Type": "text/plain" }
41
- });
42
- });
43
- const ttlAccess = input.ttl?.access ?? 60 * 60 * 24 * 30;
44
- const ttlRefresh = input.ttl?.refresh ?? 60 * 60 * 24 * 365;
45
- const ttlRefreshReuse = input.ttl?.reuse ?? 60;
46
- const ttlRefreshRetention = input.ttl?.retention ?? 0;
47
- if (input.theme) setTheme(input.theme);
48
- const storage = input.storage;
49
- const select = lazy(() => input.select ?? Select());
50
- const allSigning = lazy(() => signingKeys(storage));
51
- const allEncryption = lazy(() => encryptionKeys(storage));
52
- const allow = lazy(() => input.allow ?? defaultAllowCheck);
53
- const signingKey = lazy(() => allSigning().then((all) => all[0]));
54
- const encryptionKey = lazy(() => allEncryption().then((all) => all[0]));
55
- /**
56
- * Resolves issuer URL from context.
57
- * Returns the base URL for the OAuth issuer, considering basePath configuration.
58
- */
59
- const issuer$1 = (ctx) => {
60
- const baseUrl = new URL(getRelativeUrl(ctx, "/"));
61
- if (input.basePath) {
62
- const normalizedBasePath = input.basePath.startsWith("/") ? input.basePath : `/${input.basePath}`;
63
- baseUrl.pathname = normalizedBasePath.replace(/\/$/, "");
64
- return baseUrl.href;
65
- }
66
- return baseUrl.origin;
67
- };
68
- /**
69
- * Encrypts value for secure cookie storage.
70
- */
71
- const encrypt = async (value) => {
72
- const key = await encryptionKey();
73
- if (!key) throw new Error("Encryption key not available");
74
- return await new CompactEncrypt(new TextEncoder().encode(JSON.stringify(value))).setProtectedHeader({
75
- alg: "RSA-OAEP-512",
76
- enc: "A256GCM"
77
- }).encrypt(key.public);
78
- };
79
- /**
80
- * Decrypts value from secure cookie storage.
81
- */
82
- const decrypt = async (value) => {
83
- const key = await encryptionKey();
84
- if (!key) throw new Error("Encryption key not available");
85
- return JSON.parse(new TextDecoder().decode(await compactDecrypt(value, key.private).then((result) => result.plaintext)));
86
- };
87
- /**
88
- * Resolves unique subject identifier from type and properties.
89
- */
90
- const resolveSubject = async (type, properties) => {
91
- const jsonString = JSON.stringify(properties);
92
- const encoder = new TextEncoder();
93
- const data = encoder.encode(jsonString);
94
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
95
- const hashArray = Array.from(new Uint8Array(hashBuffer));
96
- const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
97
- return `${type}:${hashHex.slice(0, 16)}`;
98
- };
99
- /**
100
- * Generates access and refresh tokens for OAuth 2.0.
101
- */
102
- const generateTokens = async (ctx, value, opts) => {
103
- const refreshToken = value.nextToken ?? generateSecureToken();
104
- if (opts?.generateRefreshToken ?? true) {
105
- /**
106
- * Generate and store the next refresh token after the one we are currently returning.
107
- * Reserving these in advance avoids concurrency issues with multiple refreshes.
108
- * Similar treatment should be given to any other values that may have race conditions,
109
- * for example if a jti claim was added to the access token.
110
- */
111
- const refreshPayload = {
112
- type: value.type,
113
- properties: value.properties,
114
- clientID: value.clientID,
115
- subject: value.subject,
116
- ttl: value.ttl,
117
- nextToken: generateSecureToken()
118
- };
119
- const refreshKey = [
120
- "oauth:refresh",
121
- value.subject,
122
- refreshToken
123
- ];
124
- await Storage.set(storage, refreshKey, refreshPayload, value.ttl.refresh);
125
- }
126
- const signingKeyData = await signingKey();
127
- if (!signingKeyData) throw new Error("Signing key not available");
128
- const now = Math.floor(Date.now() / 1e3);
129
- if (!value.clientID.trim()) throw new Error("Invalid audience: client ID cannot be empty");
130
- const accessPayload = {
131
- type: value.type,
132
- properties: value.properties,
133
- sub: value.subject,
134
- aud: value.clientID,
135
- iss: issuer$1(ctx),
136
- exp: now + value.ttl.access,
137
- iat: now,
138
- mode: "access"
139
- };
140
- const access = await new SignJWT(accessPayload).setExpirationTime(Math.floor(now + value.ttl.access)).setProtectedHeader({
141
- alg: signingKeyData.alg,
142
- kid: signingKeyData.id,
143
- typ: "JWT"
144
- }).sign(signingKeyData.private);
145
- return {
146
- access,
147
- refresh: [value.subject, refreshToken].join(":"),
148
- expiresIn: Math.floor(now + value.ttl.access - Date.now() / 1e3)
149
- };
150
- };
151
- /**
152
- * Gets authorization state from context.
153
- */
154
- const getAuthorization = async (ctx) => {
155
- const match = await auth.get(ctx, "authorization") || ctx.get("authorization");
156
- if (!match) throw new UnknownStateError();
157
- return match;
158
- };
159
- /**
160
- * Authentication utilities for providers.
161
- */
162
- const auth = {
163
- async success(ctx, properties, successOpts) {
164
- const authorization = await getAuthorization(ctx);
165
- const currentProvider = ctx.get("provider") || "unknown";
166
- if (!authorization.client_id) throw new Error("client_id is required");
167
- return await input.success({ async subject(type, properties$1, subjectOpts) {
168
- const subject = subjectOpts?.subject ?? await resolveSubject(type, properties$1);
169
- await successOpts?.invalidate?.(await resolveSubject(type, properties$1));
170
- if (authorization.response_type === "token") {
171
- if (!authorization.redirect_uri) throw new Error("redirect_uri is required");
172
- const location$1 = new URL(authorization.redirect_uri);
173
- if (!authorization.client_id) throw new Error("client_id is required");
174
- const tokens = await generateTokens(ctx, {
175
- type,
176
- subject,
177
- properties: properties$1,
178
- clientID: authorization.client_id,
179
- ttl: {
180
- access: subjectOpts?.ttl?.access ?? ttlAccess,
181
- refresh: subjectOpts?.ttl?.refresh ?? ttlRefresh
182
- }
183
- });
184
- location$1.hash = new URLSearchParams({
185
- access_token: tokens.access,
186
- token_type: "Bearer",
187
- expires_in: tokens.expiresIn.toString(),
188
- ...authorization.state && { state: authorization.state }
189
- }).toString();
190
- await auth.unset(ctx, "authorization");
191
- return ctx.redirect(location$1.toString(), 302);
192
- }
193
- if (!authorization.redirect_uri) throw new Error("redirect_uri is required");
194
- if (!authorization.client_id) throw new Error("client_id is required");
195
- const code = generateSecureToken();
196
- const codePayload = {
197
- type,
198
- properties: properties$1,
199
- subject,
200
- redirectURI: authorization.redirect_uri,
201
- clientID: authorization.client_id,
202
- pkce: authorization.pkce,
203
- ttl: {
204
- access: subjectOpts?.ttl?.access ?? ttlAccess,
205
- refresh: subjectOpts?.ttl?.refresh ?? ttlRefresh
206
- }
207
- };
208
- await Storage.set(storage, ["oauth:code", code], codePayload, 60);
209
- if (!authorization.redirect_uri) throw new Error("redirect_uri is required");
210
- const location = new URL(authorization.redirect_uri);
211
- location.searchParams.set("code", code);
212
- if (authorization.state) location.searchParams.set("state", authorization.state);
213
- await auth.unset(ctx, "authorization");
214
- return ctx.redirect(location.toString(), 302);
215
- } }, {
216
- provider: currentProvider,
217
- ...properties && typeof properties === "object" ? properties : {}
218
- }, ctx.request, authorization.client_id);
219
- },
220
- forward(ctx, response) {
221
- return ctx.newResponse(response.body ?? void 0, {
222
- status: response.status,
223
- statusText: response.statusText,
224
- headers: response.headers
225
- });
226
- },
227
- async set(ctx, key, maxAge, value) {
228
- const isHttps = isHttpsRequest(ctx);
229
- const encryptedValue = await encrypt(value);
230
- setCookie(ctx, key, encryptedValue, {
231
- maxAge,
232
- httpOnly: true,
233
- secure: isHttps,
234
- sameSite: isHttps ? "None" : "Lax",
235
- path: input.basePath || "/"
236
- });
237
- },
238
- async get(ctx, key) {
239
- const raw = getCookie(ctx, key);
240
- if (!raw) return void 0;
241
- try {
242
- const decrypted = await decrypt(raw);
243
- return decrypted;
244
- } catch {
245
- deleteCookie(ctx, key, { path: input.basePath || "/" });
246
- return void 0;
247
- }
248
- },
249
- async unset(ctx, key) {
250
- deleteCookie(ctx, key, { path: input.basePath || "/" });
251
- },
252
- async invalidate(subject) {
253
- const keys = await Array.fromAsync(Storage.scan(storage, ["oauth:refresh", subject]));
254
- for (const [key] of keys) await Storage.remove(storage, key);
255
- },
256
- storage
257
- };
258
- const app = new Router({ basePath: input.basePath });
259
- for (const [name, value] of Object.entries(input.providers)) {
260
- const route = new Router();
261
- route.use(async (c, next) => {
262
- c.set("provider", name);
263
- return await next();
264
- });
265
- value.init(route, {
266
- name,
267
- ...auth
268
- });
269
- app.mount(`/${name}`, route);
270
- }
271
- app.get("/.well-known/jwks.json", {
272
- middleware: [cors({
273
- origin: "*",
274
- allowHeaders: ["*"],
275
- allowMethods: ["GET"],
276
- credentials: false
277
- })],
278
- handler: async (c) => {
279
- const signingKeys$1 = await allSigning();
280
- const jwksDocument = { keys: signingKeys$1.map((keyInfo) => ({
281
- ...keyInfo.jwk,
282
- alg: keyInfo.alg,
283
- exp: keyInfo.expired ? Math.floor(keyInfo.expired.getTime() / 1e3) : void 0
284
- })) };
285
- return c.json(jwksDocument);
286
- }
287
- });
288
- app.get("/.well-known/oauth-authorization-server", {
289
- middleware: [cors({
290
- origin: "*",
291
- allowHeaders: ["*"],
292
- allowMethods: ["GET"],
293
- credentials: false
294
- })],
295
- handler: (c) => {
296
- const iss = issuer$1(c);
297
- const oauth2Document = {
298
- issuer: iss,
299
- authorization_endpoint: `${iss}/authorize`,
300
- token_endpoint: `${iss}/token`,
301
- jwks_uri: `${iss}/.well-known/jwks.json`,
302
- response_types_supported: ["code", "token"]
303
- };
304
- return c.json(oauth2Document);
305
- }
306
- });
307
- app.post("/token", {
308
- middleware: [cors({
309
- origin: "*",
310
- allowHeaders: ["*"],
311
- allowMethods: ["POST"],
312
- credentials: false
313
- })],
314
- handler: async (c) => {
315
- const form = await c.formData();
316
- const grantType = form.get("grant_type");
317
- if (grantType === "authorization_code") {
318
- const code = form.get("code");
319
- if (!code) {
320
- const error$1 = new OauthError("invalid_request", "Missing code");
321
- return c.json(error$1.toJSON(), { status: 400 });
322
- }
323
- const key = ["oauth:code", code.toString()];
324
- const payload = await Storage.get(storage, key);
325
- if (!payload) {
326
- const error$1 = new OauthError("invalid_grant", "Authorization code has been used or expired");
327
- return c.json(error$1.toJSON(), { status: 400 });
328
- }
329
- if (payload.redirectURI !== form.get("redirect_uri")) {
330
- const error$1 = new OauthError("invalid_redirect_uri", "Redirect URI mismatch");
331
- return c.json(error$1.toJSON(), { status: 400 });
332
- }
333
- if (payload.clientID !== form.get("client_id")) {
334
- const error$1 = new OauthError("unauthorized_client", "Client is not authorized to use this authorization code");
335
- return c.json(error$1.toJSON(), { status: 400 });
336
- }
337
- if (payload.pkce) {
338
- const codeVerifier = form.get("code_verifier")?.toString();
339
- if (!codeVerifier) {
340
- const error$1 = new OauthError("invalid_grant", "Missing code_verifier");
341
- return c.json(error$1.toJSON(), { status: 400 });
342
- }
343
- if (!await validatePKCE(codeVerifier, payload.pkce.challenge, payload.pkce.method)) {
344
- const error$1 = new OauthError("invalid_grant", "Code verifier does not match");
345
- return c.json(error$1.toJSON(), { status: 400 });
346
- }
347
- }
348
- const tokens = await generateTokens(c, payload);
349
- await Storage.remove(storage, key);
350
- const response = {
351
- access_token: tokens.access,
352
- expires_in: tokens.expiresIn,
353
- refresh_token: tokens.refresh
354
- };
355
- return c.json(response);
356
- }
357
- if (grantType === "refresh_token") {
358
- const refreshToken = form.get("refresh_token");
359
- if (!refreshToken) {
360
- const error$1 = new OauthError("invalid_request", "Missing refresh_token");
361
- return c.json(error$1.toJSON(), { status: 400 });
362
- }
363
- const splits = refreshToken.toString().split(":");
364
- const token = splits.pop();
365
- if (!token) throw new Error("Invalid refresh token format");
366
- const subject = splits.join(":");
367
- const key = [
368
- "oauth:refresh",
369
- subject,
370
- token
371
- ];
372
- const payload = await Storage.get(storage, key);
373
- if (!payload) {
374
- const error$1 = new OauthError("invalid_grant", "Refresh token has been used or expired");
375
- return c.json(error$1.toJSON(), { status: 400 });
376
- }
377
- if (input.refresh) try {
378
- const refreshResult = await input.refresh({
379
- type: payload.type,
380
- properties: payload.properties,
381
- subject: payload.subject,
382
- clientID: payload.clientID,
383
- scopes: payload.scopes
384
- }, c.request);
385
- if (!refreshResult) {
386
- await auth.invalidate(subject);
387
- return c.json({
388
- error: "invalid_grant",
389
- error_description: "Refresh token is no longer valid"
390
- }, { status: 400 });
391
- }
392
- payload.type = refreshResult.type;
393
- payload.properties = refreshResult.properties;
394
- if (refreshResult.subject) payload.subject = refreshResult.subject;
395
- if (refreshResult.scopes) payload.scopes = refreshResult.scopes;
396
- payload.properties = refreshResult.properties;
397
- } catch {
398
- return c.json({
399
- error: "server_error",
400
- error_description: "Internal server error during token refresh"
401
- }, { status: 500 });
402
- }
403
- const generateRefreshToken = !payload.timeUsed;
404
- if (ttlRefreshReuse <= 0) await Storage.remove(storage, key);
405
- else if (!payload.timeUsed) {
406
- payload.timeUsed = Date.now();
407
- await Storage.set(storage, key, payload, ttlRefreshReuse + ttlRefreshRetention);
408
- } else if (Date.now() > payload.timeUsed + ttlRefreshReuse * 1e3) {
409
- await auth.invalidate(subject);
410
- return c.json({
411
- error: "invalid_grant",
412
- error_description: "Refresh token has been used or expired"
413
- }, { status: 400 });
414
- }
415
- const tokens = await generateTokens(c, {
416
- type: payload.type,
417
- properties: payload.properties,
418
- subject: payload.subject,
419
- clientID: payload.clientID,
420
- ttl: {
421
- access: ttlAccess,
422
- refresh: ttlRefresh
423
- }
424
- }, { generateRefreshToken });
425
- const response = {
426
- access_token: tokens.access,
427
- refresh_token: tokens.refresh,
428
- expires_in: tokens.expiresIn
429
- };
430
- return c.json(response);
431
- }
432
- return c.json({
433
- error: "unsupported_grant_type",
434
- error_description: "The authorization grant type is not supported by the authorization server"
435
- }, { status: 400 });
436
- }
437
- });
438
- app.get("/authorize", async (c) => {
439
- const provider = c.query("provider");
440
- const response_type = c.query("response_type");
441
- const redirect_uri = c.query("redirect_uri");
442
- const state = c.query("state");
443
- const client_id = c.query("client_id");
444
- const audience = c.query("audience");
445
- const code_challenge = c.query("code_challenge");
446
- const code_challenge_method = c.query("code_challenge_method");
447
- const scope = c.query("scope");
448
- const authorization = {
449
- response_type,
450
- redirect_uri,
451
- state,
452
- client_id,
453
- audience,
454
- scope,
455
- ...code_challenge && code_challenge_method && { pkce: {
456
- challenge: code_challenge,
457
- method: code_challenge_method
458
- } }
459
- };
460
- c.set("authorization", authorization);
461
- if (!redirect_uri) return c.text("Missing redirect_uri", { status: 400 });
462
- if (!response_type) throw new MissingParameterError("response_type");
463
- if (!client_id) throw new MissingParameterError("client_id");
464
- if (input.start) await input.start(c.request);
465
- if (!await allow()({
466
- clientID: client_id,
467
- redirectURI: redirect_uri,
468
- audience
469
- }, c.request)) throw new UnauthorizedClientError(client_id, redirect_uri);
470
- await auth.set(c, "authorization", 60 * 60 * 24, authorization);
471
- if (provider) return c.redirect(`${provider}/authorize`);
472
- const availableProviders = Object.keys(input.providers);
473
- if (availableProviders.length === 1) return c.redirect(`${availableProviders[0]}/authorize`);
474
- return auth.forward(c, await select()(Object.fromEntries(Object.entries(input.providers).map(([key, value]) => [key, value.type])), c.request));
475
- });
476
- app.onError(async (err, c) => {
477
- if (err instanceof UnknownStateError) return auth.forward(c, await error(err, c.request));
478
- try {
479
- const authorization = await getAuthorization(c);
480
- if (!authorization.redirect_uri) throw new Error("redirect_uri is required");
481
- const url = new URL(authorization.redirect_uri);
482
- const oauth = err instanceof OauthError ? err : new OauthError("server_error", err.message);
483
- url.searchParams.set("error", oauth.error);
484
- url.searchParams.set("error_description", oauth.description);
485
- if (authorization.state) url.searchParams.set("state", authorization.state);
486
- return c.redirect(url.toString());
487
- } catch {
488
- return c.json({
489
- error: "server_error",
490
- error_description: err.message
491
- }, { status: 500 });
492
- }
493
- });
494
- return app;
495
- };
496
-
497
- //#endregion
498
- export { issuer };