@better-auth/oauth-provider 1.5.3 → 1.5.5
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 +60 -9
- package/dist/index.mjs.map +1 -1
- package/dist/{oauth-7Jc-EFsq.d.mts → oauth-Chk8ejPr.d.mts} +23 -3
- package/dist/{oauth-C_QoLKZA.d.mts → oauth-wm0HlZm9.d.mts} +14 -2
- package/dist/{utils-D6kv_BUA.mjs → utils-DgozotLg.mjs} +33 -3
- package/dist/utils-DgozotLg.mjs.map +1 -0
- package/package.json +5 -5
- package/dist/utils-D6kv_BUA.mjs.map +0 -1
package/dist/client-resource.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as getJwtPlugin,
|
|
1
|
+
import { a as getJwtPlugin, h as handleMcpErrors, o as getOAuthProviderPlugin } from "./utils-DgozotLg.mjs";
|
|
2
2
|
import { verifyAccessToken } from "better-auth/oauth2";
|
|
3
3
|
import { APIError } from "better-call";
|
|
4
4
|
import { logger } from "@better-auth/core/env";
|
package/dist/client.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "./oauth-
|
|
2
|
-
import { n as oauthProvider } from "./oauth-
|
|
1
|
+
import "./oauth-Chk8ejPr.mjs";
|
|
2
|
+
import { n as oauthProvider } from "./oauth-wm0HlZm9.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 { n as oauthProvider, t as getOAuthProviderState } 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-Chk8ejPr.mjs";
|
|
2
|
+
import { n as oauthProvider, t as getOAuthProviderState } from "./oauth-wm0HlZm9.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,7 +1,8 @@
|
|
|
1
|
-
import { a as getJwtPlugin, c as isPKCERequired, d as
|
|
1
|
+
import { a as getJwtPlugin, c as isPKCERequired, d as resolveSubjectIdentifier, f as storeClientSecret, g as mcpHandler, i as getClient, l as parseClientMetadata, m as validateClientCredentials, n as decryptStoredClientSecret, p as storeToken, r as deleteFromPrompt, s as getStoredToken, t as basicToClientCredentials, u as parsePrompt } from "./utils-DgozotLg.mjs";
|
|
2
2
|
import { APIError, createAuthEndpoint, createAuthMiddleware, getOAuthState, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
|
|
3
3
|
import { generateCodeChallenge, getJwks, verifyJwsAccessToken } from "better-auth/oauth2";
|
|
4
4
|
import { APIError as APIError$1 } from "better-call";
|
|
5
|
+
import { isBrowserFetchRequest } from "@better-auth/core/utils/fetch-metadata";
|
|
5
6
|
import { constantTimeEqual, generateRandomString, makeSignature } from "better-auth/crypto";
|
|
6
7
|
import { defineRequestState } from "@better-auth/core/context";
|
|
7
8
|
import { logger } from "@better-auth/core/env";
|
|
@@ -206,6 +207,13 @@ async function userInfoEndpoint(ctx, opts) {
|
|
|
206
207
|
error: "invalid_request"
|
|
207
208
|
});
|
|
208
209
|
const baseUserClaims = userNormalClaims(user, scopes ?? []);
|
|
210
|
+
if (opts.pairwiseSecret) {
|
|
211
|
+
const clientId = jwt.client_id ?? jwt.azp;
|
|
212
|
+
if (clientId) {
|
|
213
|
+
const client = await getClient(ctx, opts, clientId);
|
|
214
|
+
if (client) baseUserClaims.sub = await resolveSubjectIdentifier(user.id, client, opts);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
209
217
|
const additionalInfoUserClaims = opts.customUserInfoClaims && scopes?.length ? await opts.customUserInfoClaims({
|
|
210
218
|
user,
|
|
211
219
|
scopes,
|
|
@@ -277,6 +285,7 @@ async function createIdToken(ctx, opts, user, client, scopes, nonce, sessionId,
|
|
|
277
285
|
const iat = Math.floor(Date.now() / 1e3);
|
|
278
286
|
const exp = iat + (opts.idTokenExpiresIn ?? 36e3);
|
|
279
287
|
const userClaims = userNormalClaims(user, scopes);
|
|
288
|
+
const resolvedSub = await resolveSubjectIdentifier(user.id, client, opts);
|
|
280
289
|
const authTimeSec = authTime != null ? Math.floor(authTime.getTime() / 1e3) : void 0;
|
|
281
290
|
const acr = "urn:mace:incommon:iap:bronze";
|
|
282
291
|
const customClaims = opts.customIdTokenClaims ? await opts.customIdTokenClaims({
|
|
@@ -291,7 +300,7 @@ async function createIdToken(ctx, opts, user, client, scopes, nonce, sessionId,
|
|
|
291
300
|
auth_time: authTimeSec,
|
|
292
301
|
acr,
|
|
293
302
|
iss: jwtPluginOptions?.jwt?.issuer ?? ctx.context.baseURL,
|
|
294
|
-
sub:
|
|
303
|
+
sub: resolvedSub,
|
|
295
304
|
aud: client.clientId,
|
|
296
305
|
nonce,
|
|
297
306
|
iat,
|
|
@@ -908,6 +917,21 @@ async function validateAccessToken(ctx, opts, token, clientId) {
|
|
|
908
917
|
error: "invalid_request"
|
|
909
918
|
});
|
|
910
919
|
}
|
|
920
|
+
/**
|
|
921
|
+
* Resolves pairwise sub on an introspection payload.
|
|
922
|
+
* Applied at the presentation layer so internal validation functions
|
|
923
|
+
* keep real user.id (needed for user lookup in /userinfo).
|
|
924
|
+
*/
|
|
925
|
+
async function resolveIntrospectionSub(opts, payload, client) {
|
|
926
|
+
if (payload.active && payload.sub) {
|
|
927
|
+
const resolvedSub = await resolveSubjectIdentifier(payload.sub, client, opts);
|
|
928
|
+
return {
|
|
929
|
+
...payload,
|
|
930
|
+
sub: resolvedSub
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
return payload;
|
|
934
|
+
}
|
|
911
935
|
async function introspectEndpoint(ctx, opts) {
|
|
912
936
|
let { client_id, client_secret, token, token_type_hint } = ctx.body;
|
|
913
937
|
const authorization = ctx.request?.headers.get("authorization") || null;
|
|
@@ -928,7 +952,7 @@ async function introspectEndpoint(ctx, opts) {
|
|
|
928
952
|
const client = await validateClientCredentials(ctx, opts, client_id, client_secret);
|
|
929
953
|
try {
|
|
930
954
|
if (token_type_hint === void 0 || token_type_hint === "access_token") try {
|
|
931
|
-
return await validateAccessToken(ctx, opts, token, client.clientId);
|
|
955
|
+
return resolveIntrospectionSub(opts, await validateAccessToken(ctx, opts, token, client.clientId), client);
|
|
932
956
|
} catch (error) {
|
|
933
957
|
if (error instanceof APIError$1) {
|
|
934
958
|
if (token_type_hint === "access_token") throw error;
|
|
@@ -936,7 +960,7 @@ async function introspectEndpoint(ctx, opts) {
|
|
|
936
960
|
else throw new Error(error);
|
|
937
961
|
}
|
|
938
962
|
if (token_type_hint === void 0 || token_type_hint === "refresh_token") try {
|
|
939
|
-
return await validateRefreshToken(ctx, opts, (await decodeRefreshToken(opts, token)).token, client.clientId);
|
|
963
|
+
return resolveIntrospectionSub(opts, await validateRefreshToken(ctx, opts, (await decodeRefreshToken(opts, token)).token, client.clientId), client);
|
|
940
964
|
} catch (error) {
|
|
941
965
|
if (error instanceof APIError$1) {
|
|
942
966
|
if (token_type_hint === "refresh_token") throw error;
|
|
@@ -1116,6 +1140,22 @@ async function checkOAuthClient(client, opts, settings) {
|
|
|
1116
1140
|
error: "invalid_client_metadata",
|
|
1117
1141
|
error_description: "When 'authorization_code' grant type is used, 'code' response type must be included"
|
|
1118
1142
|
});
|
|
1143
|
+
if (client.subject_type !== void 0) {
|
|
1144
|
+
if (client.subject_type !== "public" && client.subject_type !== "pairwise") throw new APIError("BAD_REQUEST", {
|
|
1145
|
+
error: "invalid_client_metadata",
|
|
1146
|
+
error_description: `subject_type must be "public" or "pairwise"`
|
|
1147
|
+
});
|
|
1148
|
+
if (client.subject_type === "pairwise" && !opts.pairwiseSecret) throw new APIError("BAD_REQUEST", {
|
|
1149
|
+
error: "invalid_client_metadata",
|
|
1150
|
+
error_description: "pairwise subject_type requires server pairwiseSecret configuration"
|
|
1151
|
+
});
|
|
1152
|
+
if (client.subject_type === "pairwise" && client.redirect_uris && client.redirect_uris.length > 1) {
|
|
1153
|
+
if (new Set(client.redirect_uris.map((uri) => new URL(uri).host)).size > 1) throw new APIError("BAD_REQUEST", {
|
|
1154
|
+
error: "invalid_client_metadata",
|
|
1155
|
+
error_description: "pairwise clients with redirect_uris on different hosts require a sector_identifier_uri, which is not yet supported. All redirect_uris must share the same host."
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1119
1159
|
const requestedScopes = (client?.scope)?.split(" ").filter((v) => v.length);
|
|
1120
1160
|
const allowedScopes = settings?.isRegister ? opts.clientRegistrationAllowedScopes ?? opts.scopes : opts.scopes;
|
|
1121
1161
|
if (allowedScopes) {
|
|
@@ -1182,7 +1222,7 @@ async function createOAuthClientEndpoint(ctx, opts, settings) {
|
|
|
1182
1222
|
* @returns
|
|
1183
1223
|
*/
|
|
1184
1224
|
function oauthToSchema(input) {
|
|
1185
|
-
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, require_pkce: requirePKCE, reference_id: referenceId, metadata: inputMetadata, ...rest } = input;
|
|
1225
|
+
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, require_pkce: requirePKCE, subject_type: subjectType, reference_id: referenceId, metadata: inputMetadata, ...rest } = input;
|
|
1186
1226
|
const expiresAt = _expiresAt ? /* @__PURE__ */ new Date(_expiresAt * 1e3) : void 0;
|
|
1187
1227
|
const createdAt = _createdAt ? /* @__PURE__ */ new Date(_createdAt * 1e3) : void 0;
|
|
1188
1228
|
const scopes = _scope?.split(" ");
|
|
@@ -1217,6 +1257,7 @@ function oauthToSchema(input) {
|
|
|
1217
1257
|
skipConsent,
|
|
1218
1258
|
enableEndSession,
|
|
1219
1259
|
requirePKCE,
|
|
1260
|
+
subjectType,
|
|
1220
1261
|
referenceId,
|
|
1221
1262
|
metadata: Object.keys(metadataObj).length ? JSON.stringify(metadataObj) : void 0
|
|
1222
1263
|
};
|
|
@@ -1228,7 +1269,7 @@ function oauthToSchema(input) {
|
|
|
1228
1269
|
* @returns
|
|
1229
1270
|
*/
|
|
1230
1271
|
function schemaToOAuth(input) {
|
|
1231
|
-
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, requirePKCE, referenceId, metadata } = input;
|
|
1272
|
+
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, requirePKCE, subjectType, referenceId, metadata } = input;
|
|
1232
1273
|
const _expiresAt = expiresAt ? Math.round(new Date(expiresAt).getTime() / 1e3) : void 0;
|
|
1233
1274
|
const _createdAt = createdAt ? Math.round(new Date(createdAt).getTime() / 1e3) : void 0;
|
|
1234
1275
|
const _scopes = scopes?.join(" ");
|
|
@@ -1260,6 +1301,7 @@ function schemaToOAuth(input) {
|
|
|
1260
1301
|
skip_consent: skipConsent ?? void 0,
|
|
1261
1302
|
enable_end_session: enableEndSession ?? void 0,
|
|
1262
1303
|
require_pkce: requirePKCE ?? void 0,
|
|
1304
|
+
subject_type: subjectType ?? void 0,
|
|
1263
1305
|
reference_id: referenceId ?? void 0
|
|
1264
1306
|
};
|
|
1265
1307
|
}
|
|
@@ -1572,6 +1614,7 @@ const adminCreateOAuthClient = (opts) => createAuthEndpoint("/admin/oauth2/creat
|
|
|
1572
1614
|
skip_consent: z.boolean().optional(),
|
|
1573
1615
|
enable_end_session: z.boolean().optional(),
|
|
1574
1616
|
require_pkce: z.boolean().optional(),
|
|
1617
|
+
subject_type: z.enum(["public", "pairwise"]).optional(),
|
|
1575
1618
|
metadata: z.record(z.string(), z.unknown()).optional()
|
|
1576
1619
|
}),
|
|
1577
1620
|
metadata: {
|
|
@@ -2366,6 +2409,10 @@ const schema = {
|
|
|
2366
2409
|
type: "boolean",
|
|
2367
2410
|
required: false
|
|
2368
2411
|
},
|
|
2412
|
+
subjectType: {
|
|
2413
|
+
type: "string",
|
|
2414
|
+
required: false
|
|
2415
|
+
},
|
|
2369
2416
|
scopes: {
|
|
2370
2417
|
type: "string[]",
|
|
2371
2418
|
required: false
|
|
@@ -2662,6 +2709,7 @@ const oauthProvider = (options) => {
|
|
|
2662
2709
|
claims: Array.from(claims),
|
|
2663
2710
|
clientRegistrationAllowedScopes
|
|
2664
2711
|
};
|
|
2712
|
+
if (opts.pairwiseSecret && opts.pairwiseSecret.length < 32) throw new BetterAuthError("pairwiseSecret must be at least 32 characters long for adequate HMAC-SHA256 security");
|
|
2665
2713
|
if (opts.grantTypes && opts.grantTypes.includes("refresh_token") && !opts.grantTypes.includes("authorization_code")) throw new BetterAuthError("refresh_token grant requires authorization_code grant");
|
|
2666
2714
|
if (opts.disableJwtPlugin && (opts.storeClientSecret === "hashed" || typeof opts.storeClientSecret === "object" && "hash" in opts.storeClientSecret)) throw new BetterAuthError("unable to store hashed secrets because id tokens will be signed with secret");
|
|
2667
2715
|
if (!opts.disableJwtPlugin && (opts.storeClientSecret === "encrypted" || typeof opts.storeClientSecret === "object" && ("encrypt" in opts.storeClientSecret || "decrypt" in opts.storeClientSecret))) throw new BetterAuthError("encryption method not recommended, please use 'hashed' or the 'hash' function");
|
|
@@ -3371,7 +3419,8 @@ const oauthProvider = (options) => {
|
|
|
3371
3419
|
"web",
|
|
3372
3420
|
"native",
|
|
3373
3421
|
"user-agent-based"
|
|
3374
|
-
]).optional()
|
|
3422
|
+
]).optional(),
|
|
3423
|
+
subject_type: z.enum(["public", "pairwise"]).optional()
|
|
3375
3424
|
}),
|
|
3376
3425
|
metadata: { openapi: {
|
|
3377
3426
|
description: "Register an OAuth2 application",
|
|
@@ -3576,7 +3625,9 @@ function formatErrorURL(url, error, description, state, iss) {
|
|
|
3576
3625
|
return `${url}${url.includes("?") ? "&" : "?"}${searchParams.toString()}`;
|
|
3577
3626
|
}
|
|
3578
3627
|
const handleRedirect = (ctx, uri) => {
|
|
3579
|
-
|
|
3628
|
+
const fromFetch = isBrowserFetchRequest(ctx.request?.headers);
|
|
3629
|
+
const acceptJson = ctx.headers?.get("accept")?.includes("application/json");
|
|
3630
|
+
if (fromFetch || acceptJson) return {
|
|
3580
3631
|
redirect: true,
|
|
3581
3632
|
url: uri.toString()
|
|
3582
3633
|
};
|
|
@@ -3818,7 +3869,7 @@ function oidcServerMetadata(ctx, opts) {
|
|
|
3818
3869
|
}),
|
|
3819
3870
|
claims_supported: opts?.advertisedMetadata?.claims_supported ?? opts?.claims ?? [],
|
|
3820
3871
|
userinfo_endpoint: `${baseURL}/oauth2/userinfo`,
|
|
3821
|
-
subject_types_supported: ["public"],
|
|
3872
|
+
subject_types_supported: opts.pairwiseSecret ? ["public", "pairwise"] : ["public"],
|
|
3822
3873
|
id_token_signing_alg_values_supported: jwtPluginOptions?.jwks?.keyPairConfig?.alg ? [jwtPluginOptions?.jwks?.keyPairConfig?.alg] : opts.disableJwtPlugin ? ["HS256"] : ["EdDSA"],
|
|
3823
3874
|
end_session_endpoint: `${baseURL}/oauth2/end-session`,
|
|
3824
3875
|
acr_values_supported: ["urn:mace:incommon:iap:bronze"],
|