@better-auth/oauth-provider 1.4.17 → 1.4.19
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/client-resource.d.mts +3 -2
- package/dist/client-resource.mjs +3 -2
- package/dist/client-resource.mjs.map +1 -0
- package/dist/client.d.mts +4 -3
- package/dist/client.mjs +2 -1
- package/dist/client.mjs.map +1 -0
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +253 -145
- package/dist/index.mjs.map +1 -0
- package/dist/{oauth-BrFoF22H.d.mts → oauth-1jow-gRL.d.mts} +78 -1
- package/dist/{oauth-BxSSTB3p.d.mts → oauth-DC4oauK7.d.mts} +30 -4
- package/dist/{utils-LksXOM6z.mjs → utils-CThxvHKd.mjs} +20 -3
- package/dist/utils-CThxvHKd.mjs.map +1 -0
- package/package.json +6 -5
package/dist/index.mjs
CHANGED
|
@@ -1,124 +1,28 @@
|
|
|
1
|
-
import { a as getJwtPlugin, c as
|
|
1
|
+
import { a as getJwtPlugin, c as parseClientMetadata, d as storeToken, f as validateClientCredentials, i as getClient, l as parsePrompt, m as mcpHandler, n as decryptStoredClientSecret, r as deleteFromPrompt, s as getStoredToken, t as basicToClientCredentials, u as storeClientSecret } from "./utils-CThxvHKd.mjs";
|
|
2
2
|
import { generateCodeChallenge, getJwks, verifyJwsAccessToken } from "better-auth/oauth2";
|
|
3
3
|
import { APIError } from "better-call";
|
|
4
|
-
import {
|
|
4
|
+
import { APIError as APIError$1, createAuthEndpoint, createAuthMiddleware, getOAuthState, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
|
|
5
5
|
import { constantTimeEqual, generateRandomString, makeSignature } from "better-auth/crypto";
|
|
6
|
+
import { BetterAuthError } from "@better-auth/core/error";
|
|
6
7
|
import { defineRequestState } from "@better-auth/core/context";
|
|
7
8
|
import { logger } from "@better-auth/core/env";
|
|
8
|
-
import { APIError as APIError$1, createAuthEndpoint, createAuthMiddleware, getOAuthState, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
|
|
9
9
|
import { parseSetCookieHeader } from "better-auth/cookies";
|
|
10
10
|
import { mergeSchema } from "better-auth/db";
|
|
11
11
|
import * as z from "zod";
|
|
12
12
|
import { signJWT, toExpJWT } from "better-auth/plugins";
|
|
13
13
|
import { SignJWT, compactVerify, createLocalJWKSet, decodeJwt } from "jose";
|
|
14
14
|
|
|
15
|
-
//#region src/metadata.ts
|
|
16
|
-
function authServerMetadata(ctx, opts, overrides) {
|
|
17
|
-
const baseURL = ctx.context.baseURL;
|
|
18
|
-
return {
|
|
19
|
-
scopes_supported: overrides?.scopes_supported,
|
|
20
|
-
issuer: opts?.jwt?.issuer ?? baseURL,
|
|
21
|
-
authorization_endpoint: `${baseURL}/oauth2/authorize`,
|
|
22
|
-
token_endpoint: `${baseURL}/oauth2/token`,
|
|
23
|
-
jwks_uri: overrides?.jwt_disabled ? void 0 : opts?.jwks?.remoteUrl ?? `${baseURL}${opts?.jwks?.jwksPath ?? "/jwks"}`,
|
|
24
|
-
registration_endpoint: `${baseURL}/oauth2/register`,
|
|
25
|
-
introspection_endpoint: `${baseURL}/oauth2/introspect`,
|
|
26
|
-
revocation_endpoint: `${baseURL}/oauth2/revoke`,
|
|
27
|
-
response_types_supported: overrides?.grant_types_supported && !overrides.grant_types_supported.includes("authorization_code") ? [] : ["code"],
|
|
28
|
-
response_modes_supported: ["query"],
|
|
29
|
-
grant_types_supported: overrides?.grant_types_supported ?? [
|
|
30
|
-
"authorization_code",
|
|
31
|
-
"client_credentials",
|
|
32
|
-
"refresh_token"
|
|
33
|
-
],
|
|
34
|
-
token_endpoint_auth_methods_supported: [
|
|
35
|
-
...overrides?.public_client_supported ? ["none"] : [],
|
|
36
|
-
"client_secret_basic",
|
|
37
|
-
"client_secret_post"
|
|
38
|
-
],
|
|
39
|
-
introspection_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
|
|
40
|
-
revocation_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
|
|
41
|
-
code_challenge_methods_supported: ["S256"]
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
function oidcServerMetadata(ctx, opts) {
|
|
45
|
-
const baseURL = ctx.context.baseURL;
|
|
46
|
-
const jwtPluginOptions = opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context).options;
|
|
47
|
-
return {
|
|
48
|
-
...authServerMetadata(ctx, jwtPluginOptions, {
|
|
49
|
-
scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes,
|
|
50
|
-
public_client_supported: opts.allowUnauthenticatedClientRegistration,
|
|
51
|
-
grant_types_supported: opts.grantTypes,
|
|
52
|
-
jwt_disabled: opts.disableJwtPlugin
|
|
53
|
-
}),
|
|
54
|
-
claims_supported: opts?.advertisedMetadata?.claims_supported ?? opts?.claims ?? [],
|
|
55
|
-
userinfo_endpoint: `${baseURL}/oauth2/userinfo`,
|
|
56
|
-
subject_types_supported: ["public"],
|
|
57
|
-
id_token_signing_alg_values_supported: jwtPluginOptions?.jwks?.keyPairConfig?.alg ? [jwtPluginOptions?.jwks?.keyPairConfig?.alg] : opts.disableJwtPlugin ? ["HS256"] : ["EdDSA"],
|
|
58
|
-
end_session_endpoint: `${baseURL}/oauth2/end-session`,
|
|
59
|
-
acr_values_supported: ["urn:mace:incommon:iap:bronze"],
|
|
60
|
-
prompt_values_supported: [
|
|
61
|
-
"login",
|
|
62
|
-
"consent",
|
|
63
|
-
"create",
|
|
64
|
-
"select_account"
|
|
65
|
-
]
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Provides an exportable `/.well-known/oauth-authorization-server`.
|
|
70
|
-
*
|
|
71
|
-
* Useful when basePath prevents the endpoint from being located at the root
|
|
72
|
-
* and must be provided manually.
|
|
73
|
-
*
|
|
74
|
-
* @external
|
|
75
|
-
*/
|
|
76
|
-
const oauthProviderAuthServerMetadata = (auth, opts) => {
|
|
77
|
-
return async (_request) => {
|
|
78
|
-
const res = await auth.api.getOAuthServerConfig();
|
|
79
|
-
return new Response(JSON.stringify(res), {
|
|
80
|
-
status: 200,
|
|
81
|
-
headers: {
|
|
82
|
-
"Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
|
|
83
|
-
...opts?.headers,
|
|
84
|
-
"Content-Type": "application/json"
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
};
|
|
88
|
-
};
|
|
89
|
-
/**
|
|
90
|
-
* Provides an exportable `/.well-known/openid-configuration`.
|
|
91
|
-
*
|
|
92
|
-
* Useful when basePath prevents the endpoint from being located at the root
|
|
93
|
-
* and must be provided manually.
|
|
94
|
-
*
|
|
95
|
-
* @external
|
|
96
|
-
*/
|
|
97
|
-
const oauthProviderOpenIdConfigMetadata = (auth, opts) => {
|
|
98
|
-
return async (_request) => {
|
|
99
|
-
const res = await auth.api.getOpenIdConfig();
|
|
100
|
-
return new Response(JSON.stringify(res), {
|
|
101
|
-
status: 200,
|
|
102
|
-
headers: {
|
|
103
|
-
"Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
|
|
104
|
-
...opts?.headers,
|
|
105
|
-
"Content-Type": "application/json"
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
};
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
//#endregion
|
|
112
15
|
//#region src/authorize.ts
|
|
113
16
|
/**
|
|
114
17
|
* Formats an error url
|
|
115
18
|
*/
|
|
116
|
-
function formatErrorURL(url, error, description, state) {
|
|
19
|
+
function formatErrorURL(url, error, description, state, iss) {
|
|
117
20
|
const searchParams = new URLSearchParams({
|
|
118
21
|
error,
|
|
119
22
|
error_description: description
|
|
120
23
|
});
|
|
121
24
|
state && searchParams.append("state", state);
|
|
25
|
+
iss && searchParams.append("iss", iss);
|
|
122
26
|
return `${url}${url.includes("?") ? "&" : "?"}${searchParams.toString()}`;
|
|
123
27
|
}
|
|
124
28
|
const handleRedirect = (ctx, uri) => {
|
|
@@ -129,6 +33,39 @@ const handleRedirect = (ctx, uri) => {
|
|
|
129
33
|
else throw ctx.redirect(uri);
|
|
130
34
|
};
|
|
131
35
|
/**
|
|
36
|
+
* Validates that the issuer URL
|
|
37
|
+
* - MUST use HTTPS scheme (HTTP allowed for localhost in dev)
|
|
38
|
+
* - MUST NOT contain query components
|
|
39
|
+
* - MUST NOT contain fragment components
|
|
40
|
+
*
|
|
41
|
+
* @returns The validated issuer URL, or a sanitized version if invalid
|
|
42
|
+
*/
|
|
43
|
+
function validateIssuerUrl(issuer) {
|
|
44
|
+
try {
|
|
45
|
+
const url = new URL(issuer);
|
|
46
|
+
const isLocalhost$1 = url.hostname === "localhost" || url.hostname === "127.0.0.1";
|
|
47
|
+
if (url.protocol !== "https:" && !isLocalhost$1) url.protocol = "https:";
|
|
48
|
+
url.search = "";
|
|
49
|
+
url.hash = "";
|
|
50
|
+
return url.toString().replace(/\/$/, "");
|
|
51
|
+
} catch {
|
|
52
|
+
return issuer;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Gets the issuer identifier
|
|
57
|
+
*/
|
|
58
|
+
function getIssuer(ctx, opts) {
|
|
59
|
+
let issuer;
|
|
60
|
+
if (opts.disableJwtPlugin) issuer = ctx.context.baseURL;
|
|
61
|
+
else try {
|
|
62
|
+
issuer = getJwtPlugin(ctx.context).options?.jwt?.issuer ?? ctx.context.baseURL;
|
|
63
|
+
} catch {
|
|
64
|
+
issuer = ctx.context.baseURL;
|
|
65
|
+
}
|
|
66
|
+
return validateIssuerUrl(issuer);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
132
69
|
* Error page url if redirect_uri has not been verified yet
|
|
133
70
|
* Generates Url for custom error page
|
|
134
71
|
*/
|
|
@@ -157,14 +94,14 @@ async function authorizeEndpoint(ctx, opts, settings) {
|
|
|
157
94
|
const invalidScopes = requestedScopes.filter((scope) => {
|
|
158
95
|
return !validScopes?.has(scope) || scope === "offline_access" && (query.code_challenge_method !== "S256" || !query.code_challenge);
|
|
159
96
|
});
|
|
160
|
-
if (invalidScopes.length) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_scope", `The following scopes are invalid: ${invalidScopes.join(", ")}`, query.state));
|
|
97
|
+
if (invalidScopes.length) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_scope", `The following scopes are invalid: ${invalidScopes.join(", ")}`, query.state, getIssuer(ctx, opts)));
|
|
161
98
|
}
|
|
162
99
|
if (!requestedScopes) {
|
|
163
100
|
requestedScopes = client.scopes ?? opts.scopes ?? [];
|
|
164
101
|
query.scope = requestedScopes.join(" ");
|
|
165
102
|
}
|
|
166
|
-
if (!query.code_challenge || !query.code_challenge_method) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_request", "pkce is required", query.state));
|
|
167
|
-
if (!["S256"].includes(query.code_challenge_method)) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_request", "invalid code_challenge method", query.state));
|
|
103
|
+
if (!query.code_challenge || !query.code_challenge_method) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_request", "pkce is required", query.state, getIssuer(ctx, opts)));
|
|
104
|
+
if (!["S256"].includes(query.code_challenge_method)) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_request", "invalid code_challenge method", query.state, getIssuer(ctx, opts)));
|
|
168
105
|
const session = await getSessionFromCtx(ctx);
|
|
169
106
|
if (!session || promptSet?.has("login") || promptSet?.has("create")) return redirectWithPromptCode(ctx, opts, promptSet?.has("create") ? "create" : "login");
|
|
170
107
|
if (settings?.isAuthorize && promptSet?.has("select_account")) return redirectWithPromptCode(ctx, opts, "select_account");
|
|
@@ -255,6 +192,7 @@ async function redirectWithAuthorizationCode(ctx, opts, verificationValue) {
|
|
|
255
192
|
const redirectUriWithCode = new URL(verificationValue.query.redirect_uri);
|
|
256
193
|
redirectUriWithCode.searchParams.set("code", code);
|
|
257
194
|
if (verificationValue.query.state) redirectUriWithCode.searchParams.set("state", verificationValue.query.state);
|
|
195
|
+
redirectUriWithCode.searchParams.set("iss", getIssuer(ctx, opts));
|
|
258
196
|
return handleRedirect(ctx, redirectUriWithCode.toString());
|
|
259
197
|
}
|
|
260
198
|
async function redirectWithPromptCode(ctx, opts, type, page) {
|
|
@@ -277,6 +215,104 @@ async function signParams(ctx, opts) {
|
|
|
277
215
|
return params.toString();
|
|
278
216
|
}
|
|
279
217
|
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/metadata.ts
|
|
220
|
+
function authServerMetadata(ctx, opts, overrides) {
|
|
221
|
+
const baseURL = ctx.context.baseURL;
|
|
222
|
+
return {
|
|
223
|
+
scopes_supported: overrides?.scopes_supported,
|
|
224
|
+
issuer: validateIssuerUrl(opts?.jwt?.issuer ?? baseURL),
|
|
225
|
+
authorization_endpoint: `${baseURL}/oauth2/authorize`,
|
|
226
|
+
token_endpoint: `${baseURL}/oauth2/token`,
|
|
227
|
+
jwks_uri: overrides?.jwt_disabled ? void 0 : opts?.jwks?.remoteUrl ?? `${baseURL}${opts?.jwks?.jwksPath ?? "/jwks"}`,
|
|
228
|
+
registration_endpoint: `${baseURL}/oauth2/register`,
|
|
229
|
+
introspection_endpoint: `${baseURL}/oauth2/introspect`,
|
|
230
|
+
revocation_endpoint: `${baseURL}/oauth2/revoke`,
|
|
231
|
+
response_types_supported: overrides?.grant_types_supported && !overrides.grant_types_supported.includes("authorization_code") ? [] : ["code"],
|
|
232
|
+
response_modes_supported: ["query"],
|
|
233
|
+
grant_types_supported: overrides?.grant_types_supported ?? [
|
|
234
|
+
"authorization_code",
|
|
235
|
+
"client_credentials",
|
|
236
|
+
"refresh_token"
|
|
237
|
+
],
|
|
238
|
+
token_endpoint_auth_methods_supported: [
|
|
239
|
+
...overrides?.public_client_supported ? ["none"] : [],
|
|
240
|
+
"client_secret_basic",
|
|
241
|
+
"client_secret_post"
|
|
242
|
+
],
|
|
243
|
+
introspection_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
|
|
244
|
+
revocation_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
|
|
245
|
+
code_challenge_methods_supported: ["S256"],
|
|
246
|
+
authorization_response_iss_parameter_supported: true
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function oidcServerMetadata(ctx, opts) {
|
|
250
|
+
const baseURL = ctx.context.baseURL;
|
|
251
|
+
const jwtPluginOptions = opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context).options;
|
|
252
|
+
return {
|
|
253
|
+
...authServerMetadata(ctx, jwtPluginOptions, {
|
|
254
|
+
scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes,
|
|
255
|
+
public_client_supported: opts.allowUnauthenticatedClientRegistration,
|
|
256
|
+
grant_types_supported: opts.grantTypes,
|
|
257
|
+
jwt_disabled: opts.disableJwtPlugin
|
|
258
|
+
}),
|
|
259
|
+
claims_supported: opts?.advertisedMetadata?.claims_supported ?? opts?.claims ?? [],
|
|
260
|
+
userinfo_endpoint: `${baseURL}/oauth2/userinfo`,
|
|
261
|
+
subject_types_supported: ["public"],
|
|
262
|
+
id_token_signing_alg_values_supported: jwtPluginOptions?.jwks?.keyPairConfig?.alg ? [jwtPluginOptions?.jwks?.keyPairConfig?.alg] : opts.disableJwtPlugin ? ["HS256"] : ["EdDSA"],
|
|
263
|
+
end_session_endpoint: `${baseURL}/oauth2/end-session`,
|
|
264
|
+
acr_values_supported: ["urn:mace:incommon:iap:bronze"],
|
|
265
|
+
prompt_values_supported: [
|
|
266
|
+
"login",
|
|
267
|
+
"consent",
|
|
268
|
+
"create",
|
|
269
|
+
"select_account"
|
|
270
|
+
]
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Provides an exportable `/.well-known/oauth-authorization-server`.
|
|
275
|
+
*
|
|
276
|
+
* Useful when basePath prevents the endpoint from being located at the root
|
|
277
|
+
* and must be provided manually.
|
|
278
|
+
*
|
|
279
|
+
* @external
|
|
280
|
+
*/
|
|
281
|
+
const oauthProviderAuthServerMetadata = (auth, opts) => {
|
|
282
|
+
return async (_request) => {
|
|
283
|
+
const res = await auth.api.getOAuthServerConfig();
|
|
284
|
+
return new Response(JSON.stringify(res), {
|
|
285
|
+
status: 200,
|
|
286
|
+
headers: {
|
|
287
|
+
"Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
|
|
288
|
+
...opts?.headers,
|
|
289
|
+
"Content-Type": "application/json"
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
/**
|
|
295
|
+
* Provides an exportable `/.well-known/openid-configuration`.
|
|
296
|
+
*
|
|
297
|
+
* Useful when basePath prevents the endpoint from being located at the root
|
|
298
|
+
* and must be provided manually.
|
|
299
|
+
*
|
|
300
|
+
* @external
|
|
301
|
+
*/
|
|
302
|
+
const oauthProviderOpenIdConfigMetadata = (auth, opts) => {
|
|
303
|
+
return async (_request) => {
|
|
304
|
+
const res = await auth.api.getOpenIdConfig();
|
|
305
|
+
return new Response(JSON.stringify(res), {
|
|
306
|
+
status: 200,
|
|
307
|
+
headers: {
|
|
308
|
+
"Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
|
|
309
|
+
...opts?.headers,
|
|
310
|
+
"Content-Type": "application/json"
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
};
|
|
314
|
+
};
|
|
315
|
+
|
|
280
316
|
//#endregion
|
|
281
317
|
//#region src/consent.ts
|
|
282
318
|
async function consentEndpoint(ctx, opts) {
|
|
@@ -301,7 +337,7 @@ async function consentEndpoint(ctx, opts) {
|
|
|
301
337
|
}
|
|
302
338
|
if (!(ctx.body.accept === true)) return {
|
|
303
339
|
redirect: true,
|
|
304
|
-
|
|
340
|
+
url: formatErrorURL(query.get("redirect_uri") ?? "", "access_denied", "User denied access", query.get("state") ?? void 0, getIssuer(ctx, opts))
|
|
305
341
|
};
|
|
306
342
|
const session = await getSessionFromCtx(ctx);
|
|
307
343
|
const referenceId = await opts.postLogin?.consentReferenceId?.({
|
|
@@ -358,7 +394,7 @@ async function consentEndpoint(ctx, opts) {
|
|
|
358
394
|
const { url } = await authorizeEndpoint(ctx, opts);
|
|
359
395
|
return {
|
|
360
396
|
redirect: true,
|
|
361
|
-
|
|
397
|
+
url
|
|
362
398
|
};
|
|
363
399
|
}
|
|
364
400
|
|
|
@@ -384,7 +420,7 @@ async function selected(ctx, opts) {
|
|
|
384
420
|
const { url } = await authorizeEndpoint(ctx, opts);
|
|
385
421
|
return {
|
|
386
422
|
redirect: true,
|
|
387
|
-
|
|
423
|
+
url
|
|
388
424
|
};
|
|
389
425
|
}
|
|
390
426
|
async function created(ctx, opts) {
|
|
@@ -397,7 +433,7 @@ async function created(ctx, opts) {
|
|
|
397
433
|
const { url } = await authorizeEndpoint(ctx, opts);
|
|
398
434
|
return {
|
|
399
435
|
redirect: true,
|
|
400
|
-
|
|
436
|
+
url
|
|
401
437
|
};
|
|
402
438
|
}
|
|
403
439
|
async function postLogin(ctx, opts) {
|
|
@@ -412,7 +448,7 @@ async function postLogin(ctx, opts) {
|
|
|
412
448
|
const { url } = await authorizeEndpoint(ctx, opts, { postLogin: true });
|
|
413
449
|
return {
|
|
414
450
|
redirect: true,
|
|
415
|
-
|
|
451
|
+
url
|
|
416
452
|
};
|
|
417
453
|
}
|
|
418
454
|
|
|
@@ -516,7 +552,7 @@ async function createJwtAccessToken(ctx, opts, user, client, audience, scopes, r
|
|
|
516
552
|
scopes,
|
|
517
553
|
resource: ctx.body.resource,
|
|
518
554
|
referenceId,
|
|
519
|
-
metadata:
|
|
555
|
+
metadata: parseClientMetadata(client.metadata)
|
|
520
556
|
}) : {};
|
|
521
557
|
const jwtPluginOptions = getJwtPlugin(ctx.context).options;
|
|
522
558
|
return signJWT(ctx, {
|
|
@@ -547,7 +583,7 @@ async function createIdToken(ctx, opts, user, client, scopes, nonce, sessionId)
|
|
|
547
583
|
const customClaims = opts.customIdTokenClaims ? await opts.customIdTokenClaims({
|
|
548
584
|
user,
|
|
549
585
|
scopes,
|
|
550
|
-
metadata:
|
|
586
|
+
metadata: parseClientMetadata(client.metadata)
|
|
551
587
|
}) : {};
|
|
552
588
|
const jwtPluginOptions = opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context).options;
|
|
553
589
|
const payload = {
|
|
@@ -857,7 +893,7 @@ async function handleClientCredentialsGrant(ctx, opts) {
|
|
|
857
893
|
const customClaims = opts.customAccessTokenClaims ? await opts.customAccessTokenClaims({
|
|
858
894
|
scopes: requestedScopes,
|
|
859
895
|
resource: ctx.body.resource,
|
|
860
|
-
metadata:
|
|
896
|
+
metadata: parseClientMetadata(client.metadata)
|
|
861
897
|
}) : {};
|
|
862
898
|
const accessToken = audience && !opts.disableJwtPlugin ? await signJWT(ctx, {
|
|
863
899
|
options: jwtPluginOptions,
|
|
@@ -1069,7 +1105,7 @@ async function validateOpaqueAccessToken(ctx, opts, token, clientId) {
|
|
|
1069
1105
|
user,
|
|
1070
1106
|
scopes: accessToken.scopes,
|
|
1071
1107
|
referenceId: accessToken?.referenceId,
|
|
1072
|
-
metadata: client?.metadata
|
|
1108
|
+
metadata: parseClientMetadata(client?.metadata)
|
|
1073
1109
|
}) : {};
|
|
1074
1110
|
const jwtPluginOptions = (opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context))?.options;
|
|
1075
1111
|
return {
|
|
@@ -1079,8 +1115,8 @@ async function validateOpaqueAccessToken(ctx, opts, token, clientId) {
|
|
|
1079
1115
|
client_id: accessToken.clientId,
|
|
1080
1116
|
sub: user?.id,
|
|
1081
1117
|
sid: sessionId,
|
|
1082
|
-
exp: Math.floor(accessToken.expiresAt.getTime() / 1e3),
|
|
1083
|
-
iat: Math.floor(accessToken.createdAt.getTime() / 1e3),
|
|
1118
|
+
exp: Math.floor(new Date(accessToken.expiresAt).getTime() / 1e3),
|
|
1119
|
+
iat: Math.floor(new Date(accessToken.createdAt).getTime() / 1e3),
|
|
1084
1120
|
scope: accessToken.scopes?.join(" ")
|
|
1085
1121
|
};
|
|
1086
1122
|
}
|
|
@@ -1123,8 +1159,8 @@ async function validateRefreshToken(ctx, opts, token, clientId) {
|
|
|
1123
1159
|
iss: ((opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context))?.options)?.jwt?.issuer ?? ctx.context.baseURL,
|
|
1124
1160
|
sub: user?.id,
|
|
1125
1161
|
sid: sessionId,
|
|
1126
|
-
exp: Math.floor(refreshToken.expiresAt.getTime() / 1e3),
|
|
1127
|
-
iat: Math.floor(refreshToken.createdAt.getTime() / 1e3),
|
|
1162
|
+
exp: Math.floor(new Date(refreshToken.expiresAt).getTime() / 1e3),
|
|
1163
|
+
iat: Math.floor(new Date(refreshToken.createdAt).getTime() / 1e3),
|
|
1128
1164
|
scope: refreshToken.scopes?.join(" ")
|
|
1129
1165
|
};
|
|
1130
1166
|
}
|
|
@@ -1400,7 +1436,11 @@ async function createOAuthClientEndpoint(ctx, opts, settings) {
|
|
|
1400
1436
|
});
|
|
1401
1437
|
const client = await ctx.context.adapter.create({
|
|
1402
1438
|
model: "oauthClient",
|
|
1403
|
-
data:
|
|
1439
|
+
data: {
|
|
1440
|
+
...schema$1,
|
|
1441
|
+
createdAt: /* @__PURE__ */ new Date(iat * 1e3),
|
|
1442
|
+
updatedAt: /* @__PURE__ */ new Date(iat * 1e3)
|
|
1443
|
+
}
|
|
1404
1444
|
});
|
|
1405
1445
|
return ctx.json(schemaToOAuth({
|
|
1406
1446
|
...client,
|
|
@@ -1420,14 +1460,19 @@ async function createOAuthClientEndpoint(ctx, opts, settings) {
|
|
|
1420
1460
|
* @returns
|
|
1421
1461
|
*/
|
|
1422
1462
|
function oauthToSchema(input) {
|
|
1423
|
-
const { client_id: clientId, client_secret: clientSecret, client_secret_expires_at: _expiresAt, scope: _scope, user_id: userId, client_id_issued_at: _createdAt, client_name: name, client_uri: uri, logo_uri: icon, contacts, tos_uri: tos, policy_uri: policy, jwks: _jwks, jwks_uri: _jwksUri, software_id: softwareId, software_version: softwareVersion, software_statement: softwareStatement, redirect_uris: redirectUris, post_logout_redirect_uris: postLogoutRedirectUris, token_endpoint_auth_method: tokenEndpointAuthMethod, grant_types: grantTypes, response_types: responseTypes, public: _public, type, disabled, skip_consent: skipConsent, enable_end_session: enableEndSession, reference_id: referenceId, ...rest } = input;
|
|
1463
|
+
const { client_id: clientId, client_secret: clientSecret, client_secret_expires_at: _expiresAt, scope: _scope, user_id: userId, client_id_issued_at: _createdAt, client_name: name, client_uri: uri, logo_uri: icon, contacts, tos_uri: tos, policy_uri: policy, jwks: _jwks, jwks_uri: _jwksUri, software_id: softwareId, software_version: softwareVersion, software_statement: softwareStatement, redirect_uris: redirectUris, post_logout_redirect_uris: postLogoutRedirectUris, token_endpoint_auth_method: tokenEndpointAuthMethod, grant_types: grantTypes, response_types: responseTypes, public: _public, type, disabled, skip_consent: skipConsent, enable_end_session: enableEndSession, reference_id: referenceId, metadata: inputMetadata, ...rest } = input;
|
|
1424
1464
|
const expiresAt = _expiresAt ? /* @__PURE__ */ new Date(_expiresAt * 1e3) : void 0;
|
|
1425
1465
|
const createdAt = _createdAt ? /* @__PURE__ */ new Date(_createdAt * 1e3) : void 0;
|
|
1466
|
+
const scopes = _scope?.split(" ");
|
|
1467
|
+
const metadataObj = {
|
|
1468
|
+
...rest && Object.keys(rest).length ? rest : {},
|
|
1469
|
+
...inputMetadata && typeof inputMetadata === "object" ? inputMetadata : {}
|
|
1470
|
+
};
|
|
1426
1471
|
return {
|
|
1427
1472
|
clientId,
|
|
1428
1473
|
clientSecret,
|
|
1429
1474
|
disabled,
|
|
1430
|
-
scopes
|
|
1475
|
+
scopes,
|
|
1431
1476
|
userId,
|
|
1432
1477
|
createdAt,
|
|
1433
1478
|
expiresAt,
|
|
@@ -1450,7 +1495,7 @@ function oauthToSchema(input) {
|
|
|
1450
1495
|
skipConsent,
|
|
1451
1496
|
enableEndSession,
|
|
1452
1497
|
referenceId,
|
|
1453
|
-
metadata:
|
|
1498
|
+
metadata: Object.keys(metadataObj).length ? JSON.stringify(metadataObj) : void 0
|
|
1454
1499
|
};
|
|
1455
1500
|
}
|
|
1456
1501
|
/**
|
|
@@ -1461,11 +1506,11 @@ function oauthToSchema(input) {
|
|
|
1461
1506
|
*/
|
|
1462
1507
|
function schemaToOAuth(input) {
|
|
1463
1508
|
const { clientId, clientSecret, disabled, scopes, userId, createdAt, updatedAt: _updatedAt, expiresAt, name, uri, icon, contacts, tos, policy, softwareId, softwareVersion, softwareStatement, redirectUris, postLogoutRedirectUris, tokenEndpointAuthMethod, grantTypes, responseTypes, public: _public, type, skipConsent, enableEndSession, referenceId, metadata } = input;
|
|
1464
|
-
const _expiresAt = expiresAt ? Math.round(expiresAt.getTime() / 1e3) : void 0;
|
|
1465
|
-
const _createdAt = createdAt ? Math.round(createdAt.getTime() / 1e3) : void 0;
|
|
1509
|
+
const _expiresAt = expiresAt ? Math.round(new Date(expiresAt).getTime() / 1e3) : void 0;
|
|
1510
|
+
const _createdAt = createdAt ? Math.round(new Date(createdAt).getTime() / 1e3) : void 0;
|
|
1466
1511
|
const _scopes = scopes?.join(" ");
|
|
1467
1512
|
return {
|
|
1468
|
-
...
|
|
1513
|
+
...parseClientMetadata(metadata),
|
|
1469
1514
|
client_id: clientId,
|
|
1470
1515
|
client_secret: clientSecret ?? void 0,
|
|
1471
1516
|
client_secret_expires_at: clientSecret ? _expiresAt ?? 0 : void 0,
|
|
@@ -1497,8 +1542,19 @@ function schemaToOAuth(input) {
|
|
|
1497
1542
|
|
|
1498
1543
|
//#endregion
|
|
1499
1544
|
//#region src/types/zod.ts
|
|
1545
|
+
const DANGEROUS_SCHEMES = [
|
|
1546
|
+
"javascript:",
|
|
1547
|
+
"data:",
|
|
1548
|
+
"vbscript:"
|
|
1549
|
+
];
|
|
1550
|
+
function isLocalhost(hostname) {
|
|
1551
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
1552
|
+
}
|
|
1500
1553
|
/**
|
|
1501
|
-
* Reusable URL validation
|
|
1554
|
+
* Reusable URL validation for OAuth redirect URIs.
|
|
1555
|
+
* - Blocks dangerous schemes (javascript:, data:, vbscript:)
|
|
1556
|
+
* - For http/https: requires HTTPS (HTTP allowed only for localhost)
|
|
1557
|
+
* - Allows custom schemes for mobile apps (e.g., myapp://callback)
|
|
1502
1558
|
*/
|
|
1503
1559
|
const SafeUrlSchema = z.url().superRefine((val, ctx) => {
|
|
1504
1560
|
if (!URL.canParse(val)) {
|
|
@@ -1509,19 +1565,30 @@ const SafeUrlSchema = z.url().superRefine((val, ctx) => {
|
|
|
1509
1565
|
});
|
|
1510
1566
|
return z.NEVER;
|
|
1511
1567
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1568
|
+
const u = new URL(val);
|
|
1569
|
+
if (DANGEROUS_SCHEMES.includes(u.protocol)) {
|
|
1570
|
+
ctx.addIssue({
|
|
1571
|
+
code: "custom",
|
|
1572
|
+
message: "URL cannot use javascript:, data:, or vbscript: scheme"
|
|
1573
|
+
});
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
if (u.protocol === "http:" || u.protocol === "https:") {
|
|
1577
|
+
if (u.protocol === "http:" && !isLocalhost(u.hostname)) ctx.addIssue({
|
|
1578
|
+
code: "custom",
|
|
1579
|
+
message: "Redirect URI must use HTTPS (HTTP allowed only for localhost)"
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
});
|
|
1516
1583
|
|
|
1517
1584
|
//#endregion
|
|
1518
1585
|
//#region src/oauthClient/endpoints.ts
|
|
1519
1586
|
async function getClientEndpoint(ctx, opts) {
|
|
1520
1587
|
const session = await getSessionFromCtx(ctx);
|
|
1521
1588
|
if (!session) throw new APIError$1("UNAUTHORIZED");
|
|
1522
|
-
if (!ctx.
|
|
1589
|
+
if (!ctx.headers) throw new APIError$1("BAD_REQUEST");
|
|
1523
1590
|
if (opts.clientPrivileges && !await opts.clientPrivileges({
|
|
1524
|
-
headers: ctx.
|
|
1591
|
+
headers: ctx.headers,
|
|
1525
1592
|
action: "read",
|
|
1526
1593
|
session: session.session,
|
|
1527
1594
|
user: session.user
|
|
@@ -1567,9 +1634,9 @@ async function getClientPublicEndpoint(ctx, opts) {
|
|
|
1567
1634
|
async function getClientsEndpoint(ctx, opts) {
|
|
1568
1635
|
const session = await getSessionFromCtx(ctx);
|
|
1569
1636
|
if (!session) throw new APIError$1("UNAUTHORIZED");
|
|
1570
|
-
if (!ctx.
|
|
1637
|
+
if (!ctx.headers) throw new APIError$1("BAD_REQUEST");
|
|
1571
1638
|
if (opts.clientPrivileges && !await opts.clientPrivileges({
|
|
1572
|
-
headers: ctx.
|
|
1639
|
+
headers: ctx.headers,
|
|
1573
1640
|
action: "list",
|
|
1574
1641
|
session: session.session,
|
|
1575
1642
|
user: session.user
|
|
@@ -1608,9 +1675,9 @@ async function getClientsEndpoint(ctx, opts) {
|
|
|
1608
1675
|
async function deleteClientEndpoint(ctx, opts) {
|
|
1609
1676
|
const session = await getSessionFromCtx(ctx);
|
|
1610
1677
|
if (!session) throw new APIError$1("UNAUTHORIZED");
|
|
1611
|
-
if (!ctx.
|
|
1678
|
+
if (!ctx.headers) throw new APIError$1("BAD_REQUEST");
|
|
1612
1679
|
if (opts.clientPrivileges && !await opts.clientPrivileges({
|
|
1613
|
-
headers: ctx.
|
|
1680
|
+
headers: ctx.headers,
|
|
1614
1681
|
action: "delete",
|
|
1615
1682
|
session: session.session,
|
|
1616
1683
|
user: session.user
|
|
@@ -1641,9 +1708,9 @@ async function deleteClientEndpoint(ctx, opts) {
|
|
|
1641
1708
|
async function updateClientEndpoint(ctx, opts) {
|
|
1642
1709
|
const session = await getSessionFromCtx(ctx);
|
|
1643
1710
|
if (!session) throw new APIError$1("UNAUTHORIZED");
|
|
1644
|
-
if (!ctx.
|
|
1711
|
+
if (!ctx.headers) throw new APIError$1("BAD_REQUEST");
|
|
1645
1712
|
if (opts.clientPrivileges && !await opts.clientPrivileges({
|
|
1646
|
-
headers: ctx.
|
|
1713
|
+
headers: ctx.headers,
|
|
1647
1714
|
action: "update",
|
|
1648
1715
|
session: session.session,
|
|
1649
1716
|
user: session.user
|
|
@@ -1679,7 +1746,10 @@ async function updateClientEndpoint(ctx, opts) {
|
|
|
1679
1746
|
field: "clientId",
|
|
1680
1747
|
value: clientId
|
|
1681
1748
|
}],
|
|
1682
|
-
update:
|
|
1749
|
+
update: {
|
|
1750
|
+
...oauthToSchema(updates),
|
|
1751
|
+
updatedAt: /* @__PURE__ */ new Date(Math.floor(Date.now() / 1e3) * 1e3)
|
|
1752
|
+
}
|
|
1683
1753
|
});
|
|
1684
1754
|
if (!updatedClient) throw new APIError$1("INTERNAL_SERVER_ERROR", {
|
|
1685
1755
|
error_description: "unable to update client",
|
|
@@ -1692,9 +1762,9 @@ async function updateClientEndpoint(ctx, opts) {
|
|
|
1692
1762
|
async function rotateClientSecretEndpoint(ctx, opts) {
|
|
1693
1763
|
const session = await getSessionFromCtx(ctx);
|
|
1694
1764
|
if (!session) throw new APIError$1("UNAUTHORIZED");
|
|
1695
|
-
if (!ctx.
|
|
1765
|
+
if (!ctx.headers) throw new APIError$1("BAD_REQUEST");
|
|
1696
1766
|
if (opts.clientPrivileges && !await opts.clientPrivileges({
|
|
1697
|
-
headers: ctx.
|
|
1767
|
+
headers: ctx.headers,
|
|
1698
1768
|
action: "rotate",
|
|
1699
1769
|
session: session.session,
|
|
1700
1770
|
user: session.user
|
|
@@ -1727,8 +1797,8 @@ async function rotateClientSecretEndpoint(ctx, opts) {
|
|
|
1727
1797
|
value: clientId
|
|
1728
1798
|
}],
|
|
1729
1799
|
update: {
|
|
1730
|
-
|
|
1731
|
-
|
|
1800
|
+
clientSecret: storedClientSecret,
|
|
1801
|
+
updatedAt: /* @__PURE__ */ new Date(Math.floor(Date.now() / 1e3) * 1e3)
|
|
1732
1802
|
}
|
|
1733
1803
|
});
|
|
1734
1804
|
if (!updatedClient) throw new APIError$1("INTERNAL_SERVER_ERROR", {
|
|
@@ -2915,7 +2985,12 @@ const oauthProvider = (options) => {
|
|
|
2915
2985
|
metadata: { SERVER_ONLY: true }
|
|
2916
2986
|
}, async (ctx) => {
|
|
2917
2987
|
if (opts.scopes && opts.scopes.includes("openid")) return oidcServerMetadata(ctx, opts);
|
|
2918
|
-
else return authServerMetadata(ctx, opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context).options, {
|
|
2988
|
+
else return authServerMetadata(ctx, opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context).options, {
|
|
2989
|
+
scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes,
|
|
2990
|
+
public_client_supported: opts.allowUnauthenticatedClientRegistration,
|
|
2991
|
+
grant_types_supported: opts.grantTypes,
|
|
2992
|
+
jwt_disabled: opts.disableJwtPlugin
|
|
2993
|
+
});
|
|
2919
2994
|
}),
|
|
2920
2995
|
getOpenIdConfig: createAuthEndpoint("/.well-known/openid-configuration", {
|
|
2921
2996
|
method: "GET",
|
|
@@ -3710,9 +3785,42 @@ const oauthProvider = (options) => {
|
|
|
3710
3785
|
updateOAuthConsent: updateOAuthConsent(opts),
|
|
3711
3786
|
deleteOAuthConsent: deleteOAuthConsent(opts)
|
|
3712
3787
|
},
|
|
3713
|
-
schema: mergeSchema(schema, opts?.schema)
|
|
3788
|
+
schema: mergeSchema(schema, opts?.schema),
|
|
3789
|
+
rateLimit: [
|
|
3790
|
+
...opts.rateLimit?.token !== false ? [{
|
|
3791
|
+
pathMatcher: (path) => path === "/oauth2/token",
|
|
3792
|
+
window: opts.rateLimit?.token?.window ?? 60,
|
|
3793
|
+
max: opts.rateLimit?.token?.max ?? 20
|
|
3794
|
+
}] : [],
|
|
3795
|
+
...opts.rateLimit?.authorize !== false ? [{
|
|
3796
|
+
pathMatcher: (path) => path === "/oauth2/authorize",
|
|
3797
|
+
window: opts.rateLimit?.authorize?.window ?? 60,
|
|
3798
|
+
max: opts.rateLimit?.authorize?.max ?? 30
|
|
3799
|
+
}] : [],
|
|
3800
|
+
...opts.rateLimit?.introspect !== false ? [{
|
|
3801
|
+
pathMatcher: (path) => path === "/oauth2/introspect",
|
|
3802
|
+
window: opts.rateLimit?.introspect?.window ?? 60,
|
|
3803
|
+
max: opts.rateLimit?.introspect?.max ?? 100
|
|
3804
|
+
}] : [],
|
|
3805
|
+
...opts.rateLimit?.revoke !== false ? [{
|
|
3806
|
+
pathMatcher: (path) => path === "/oauth2/revoke",
|
|
3807
|
+
window: opts.rateLimit?.revoke?.window ?? 60,
|
|
3808
|
+
max: opts.rateLimit?.revoke?.max ?? 30
|
|
3809
|
+
}] : [],
|
|
3810
|
+
...opts.rateLimit?.register !== false ? [{
|
|
3811
|
+
pathMatcher: (path) => path === "/oauth2/register",
|
|
3812
|
+
window: opts.rateLimit?.register?.window ?? 60,
|
|
3813
|
+
max: opts.rateLimit?.register?.max ?? 5
|
|
3814
|
+
}] : [],
|
|
3815
|
+
...opts.rateLimit?.userinfo !== false ? [{
|
|
3816
|
+
pathMatcher: (path) => path === "/oauth2/userinfo",
|
|
3817
|
+
window: opts.rateLimit?.userinfo?.window ?? 60,
|
|
3818
|
+
max: opts.rateLimit?.userinfo?.max ?? 60
|
|
3819
|
+
}] : []
|
|
3820
|
+
]
|
|
3714
3821
|
};
|
|
3715
3822
|
};
|
|
3716
3823
|
|
|
3717
3824
|
//#endregion
|
|
3718
|
-
export { authServerMetadata, mcpHandler, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oidcServerMetadata };
|
|
3825
|
+
export { authServerMetadata, mcpHandler, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oidcServerMetadata };
|
|
3826
|
+
//# sourceMappingURL=index.mjs.map
|