@better-auth/oauth-provider 1.4.18 → 1.4.20
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 +1 -1
- package/dist/client-resource.mjs +1 -1
- package/dist/client.d.mts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +228 -126
- package/dist/index.mjs.map +1 -1
- package/dist/{oauth-BrFoF22H.d.mts → oauth-1jow-gRL.d.mts} +77 -1
- package/dist/{oauth-BxSSTB3p.d.mts → oauth-DC4oauK7.d.mts} +29 -4
- package/dist/{utils-DnfreTWo.mjs → utils-CThxvHKd.mjs} +9 -3
- package/dist/utils-CThxvHKd.mjs.map +1 -0
- package/package.json +5 -5
- package/dist/utils-DnfreTWo.mjs.map +0 -1
package/dist/client-resource.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as getJwtPlugin, o as getOAuthProviderPlugin, p as handleMcpErrors } from "./utils-
|
|
1
|
+
import { a as getJwtPlugin, o as getOAuthProviderPlugin, p as handleMcpErrors } from "./utils-CThxvHKd.mjs";
|
|
2
2
|
import { verifyAccessToken } from "better-auth/oauth2";
|
|
3
3
|
import { APIError } from "better-call";
|
|
4
4
|
import { BetterAuthError } from "@better-auth/core/error";
|
package/dist/client.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "./oauth-
|
|
2
|
-
import { t as oauthProvider } from "./oauth-
|
|
1
|
+
import "./oauth-1jow-gRL.mjs";
|
|
2
|
+
import { t as oauthProvider } from "./oauth-DC4oauK7.mjs";
|
|
3
3
|
import * as _better_fetch_fetch0 from "@better-fetch/fetch";
|
|
4
4
|
|
|
5
5
|
//#region src/client.d.ts
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as Awaitable, a as ResourceServerMetadata, c as OAuthConsent, d as OAuthRefreshToken, f as Prompt, g as VerificationValue, h as StoreTokenType, i as OIDCMetadata, l as OAuthOpaqueAccessToken, m as Scope, n as GrantType, o as AuthorizePrompt, p as SchemaClient, r as OAuthClient, s as OAuthAuthorizationQuery, t as AuthServerMetadata, u as OAuthOptions } from "./oauth-
|
|
2
|
-
import { t as oauthProvider } from "./oauth-
|
|
1
|
+
import { _ as Awaitable, a as ResourceServerMetadata, c as OAuthConsent, d as OAuthRefreshToken, f as Prompt, g as VerificationValue, h as StoreTokenType, i as OIDCMetadata, l as OAuthOpaqueAccessToken, m as Scope, n as GrantType, o as AuthorizePrompt, p as SchemaClient, r as OAuthClient, s as OAuthAuthorizationQuery, t as AuthServerMetadata, u as OAuthOptions } from "./oauth-1jow-gRL.mjs";
|
|
2
|
+
import { t as oauthProvider } from "./oauth-DC4oauK7.mjs";
|
|
3
3
|
import { verifyAccessToken } from "better-auth/oauth2";
|
|
4
4
|
import { JWSAlgorithms, JwtOptions } from "better-auth/plugins";
|
|
5
5
|
import { JWTPayload } from "jose";
|
package/dist/index.mjs
CHANGED
|
@@ -1,124 +1,28 @@
|
|
|
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-
|
|
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
|
|
|
@@ -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,
|
|
@@ -1466,8 +1506,8 @@ function oauthToSchema(input) {
|
|
|
1466
1506
|
*/
|
|
1467
1507
|
function schemaToOAuth(input) {
|
|
1468
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;
|
|
1469
|
-
const _expiresAt = expiresAt ? Math.round(expiresAt.getTime() / 1e3) : void 0;
|
|
1470
|
-
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;
|
|
1471
1511
|
const _scopes = scopes?.join(" ");
|
|
1472
1512
|
return {
|
|
1473
1513
|
...parseClientMetadata(metadata),
|
|
@@ -1502,8 +1542,19 @@ function schemaToOAuth(input) {
|
|
|
1502
1542
|
|
|
1503
1543
|
//#endregion
|
|
1504
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
|
+
}
|
|
1505
1553
|
/**
|
|
1506
|
-
* 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)
|
|
1507
1558
|
*/
|
|
1508
1559
|
const SafeUrlSchema = z.url().superRefine((val, ctx) => {
|
|
1509
1560
|
if (!URL.canParse(val)) {
|
|
@@ -1514,10 +1565,21 @@ const SafeUrlSchema = z.url().superRefine((val, ctx) => {
|
|
|
1514
1565
|
});
|
|
1515
1566
|
return z.NEVER;
|
|
1516
1567
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
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
|
+
});
|
|
1521
1583
|
|
|
1522
1584
|
//#endregion
|
|
1523
1585
|
//#region src/oauthClient/endpoints.ts
|
|
@@ -1684,7 +1746,10 @@ async function updateClientEndpoint(ctx, opts) {
|
|
|
1684
1746
|
field: "clientId",
|
|
1685
1747
|
value: clientId
|
|
1686
1748
|
}],
|
|
1687
|
-
update:
|
|
1749
|
+
update: {
|
|
1750
|
+
...oauthToSchema(updates),
|
|
1751
|
+
updatedAt: /* @__PURE__ */ new Date(Math.floor(Date.now() / 1e3) * 1e3)
|
|
1752
|
+
}
|
|
1688
1753
|
});
|
|
1689
1754
|
if (!updatedClient) throw new APIError$1("INTERNAL_SERVER_ERROR", {
|
|
1690
1755
|
error_description: "unable to update client",
|
|
@@ -1732,8 +1797,8 @@ async function rotateClientSecretEndpoint(ctx, opts) {
|
|
|
1732
1797
|
value: clientId
|
|
1733
1798
|
}],
|
|
1734
1799
|
update: {
|
|
1735
|
-
|
|
1736
|
-
|
|
1800
|
+
clientSecret: storedClientSecret,
|
|
1801
|
+
updatedAt: /* @__PURE__ */ new Date(Math.floor(Date.now() / 1e3) * 1e3)
|
|
1737
1802
|
}
|
|
1738
1803
|
});
|
|
1739
1804
|
if (!updatedClient) throw new APIError$1("INTERNAL_SERVER_ERROR", {
|
|
@@ -2920,7 +2985,12 @@ const oauthProvider = (options) => {
|
|
|
2920
2985
|
metadata: { SERVER_ONLY: true }
|
|
2921
2986
|
}, async (ctx) => {
|
|
2922
2987
|
if (opts.scopes && opts.scopes.includes("openid")) return oidcServerMetadata(ctx, opts);
|
|
2923
|
-
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
|
+
});
|
|
2924
2994
|
}),
|
|
2925
2995
|
getOpenIdConfig: createAuthEndpoint("/.well-known/openid-configuration", {
|
|
2926
2996
|
method: "GET",
|
|
@@ -3715,7 +3785,39 @@ const oauthProvider = (options) => {
|
|
|
3715
3785
|
updateOAuthConsent: updateOAuthConsent(opts),
|
|
3716
3786
|
deleteOAuthConsent: deleteOAuthConsent(opts)
|
|
3717
3787
|
},
|
|
3718
|
-
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
|
+
]
|
|
3719
3821
|
};
|
|
3720
3822
|
};
|
|
3721
3823
|
|