@better-auth/oauth-provider 1.5.4 → 1.5.6
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 +2 -2
- package/dist/client-resource.mjs.map +1 -1
- package/dist/client.d.mts +1 -2
- package/dist/client.mjs +2 -3
- package/dist/client.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +117 -28
- package/dist/index.mjs.map +1 -1
- package/dist/{oauth-7Jc-EFsq.d.mts → oauth-4vgZlF-I.d.mts} +28 -3
- package/dist/{oauth-C_QoLKZA.d.mts → oauth-E89Dh-ZC.d.mts} +27 -3
- package/dist/{utils-D6kv_BUA.mjs → utils-BDSjyzic.mjs} +42 -4
- package/dist/utils-BDSjyzic.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, g as handleMcpErrors, o as getOAuthProviderPlugin } from "./utils-BDSjyzic.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";
|
|
@@ -16,10 +16,10 @@ const oauthProviderResourceClient = (auth) => {
|
|
|
16
16
|
if (!jwtPlugin) jwtPlugin = auth && !(await getOauthProviderPlugin())?.options?.disableJwtPlugin ? getJwtPlugin(await auth.$context) : void 0;
|
|
17
17
|
return jwtPlugin?.options;
|
|
18
18
|
};
|
|
19
|
+
const authServerBaseUrl = typeof auth?.options.baseURL === "string" ? auth.options.baseURL : void 0;
|
|
19
20
|
const getAuthorizationServer = async () => {
|
|
20
21
|
return (await getJwtPluginOptions())?.jwt?.issuer ?? authServerBaseUrl;
|
|
21
22
|
};
|
|
22
|
-
const authServerBaseUrl = auth?.options.baseURL;
|
|
23
23
|
const authServerBasePath = auth?.options.basePath;
|
|
24
24
|
return {
|
|
25
25
|
id: "oauth-provider-resource-client",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-resource.mjs","names":[],"sources":["../src/client-resource.ts"],"sourcesContent":["import { logger } from \"@better-auth/core/env\";\nimport { BetterAuthError } from \"@better-auth/core/error\";\nimport { verifyAccessToken } from \"better-auth/oauth2\";\nimport type { Auth, BetterAuthClientPlugin } from \"better-auth/types\";\nimport { APIError } from \"better-call\";\nimport type { JWTPayload, JWTVerifyOptions } from \"jose\";\nimport { handleMcpErrors } from \"./mcp\";\nimport type { ResourceServerMetadata } from \"./types/oauth\";\nimport { getJwtPlugin, getOAuthProviderPlugin } from \"./utils\";\n\nexport const oauthProviderResourceClient = <T extends Auth | undefined>(\n\tauth?: T,\n) => {\n\tlet oauthProviderPlugin:\n\t\t| ReturnType<typeof getOAuthProviderPlugin>\n\t\t| undefined;\n\tconst getOauthProviderPlugin = async () => {\n\t\tif (!oauthProviderPlugin) {\n\t\t\toauthProviderPlugin = auth\n\t\t\t\t? getOAuthProviderPlugin(await auth.$context)\n\t\t\t\t: undefined;\n\t\t}\n\t\treturn oauthProviderPlugin;\n\t};\n\tlet jwtPlugin: ReturnType<typeof getJwtPlugin> | undefined;\n\tconst getJwtPluginOptions = async () => {\n\t\tif (!jwtPlugin) {\n\t\t\tjwtPlugin =\n\t\t\t\tauth && !(await getOauthProviderPlugin())?.options?.disableJwtPlugin\n\t\t\t\t\t? getJwtPlugin(await auth.$context)\n\t\t\t\t\t: undefined;\n\t\t}\n\t\treturn jwtPlugin?.options;\n\t};\n\tconst getAuthorizationServer = async () => {\n\t\tconst jwtPluginOptions = await getJwtPluginOptions();\n\t\treturn jwtPluginOptions?.jwt?.issuer ?? authServerBaseUrl;\n\t};\n\tconst authServerBaseUrl = auth?.options.baseURL;\n\tconst authServerBasePath = auth?.options.basePath;\n\n\treturn {\n\t\tid: \"oauth-provider-resource-client\",\n\t\tgetActions() {\n\t\t\treturn {\n\t\t\t\t/**\n\t\t\t\t * Performs verification of an access token for your APIs. Can perform\n\t\t\t\t * local verification using `jwksUrl` by default. Can also be configured\n\t\t\t\t * for remote introspection using `remoteVerify` if a confidential client\n\t\t\t\t * is set up for this API.\n\t\t\t\t *\n\t\t\t\t * The optional auth parameter can fill known values automatically.\n\t\t\t\t */\n\t\t\t\tverifyAccessToken: (async (\n\t\t\t\t\ttoken: string | undefined,\n\t\t\t\t\topts?: {\n\t\t\t\t\t\tverifyOptions?: JWTVerifyOptions &\n\t\t\t\t\t\t\tRequired<Pick<JWTVerifyOptions, \"audience\" | \"issuer\">>;\n\t\t\t\t\t\tscopes?: string[];\n\t\t\t\t\t\tjwksUrl?: string;\n\t\t\t\t\t\tremoteVerify?: VerifyAccessTokenRemote;\n\t\t\t\t\t\t/** Maps non-url (ie urn, client) resources to resource_metadata */\n\t\t\t\t\t\tresourceMetadataMappings?: Record<string, string>;\n\t\t\t\t\t},\n\t\t\t\t): Promise<JWTPayload> => {\n\t\t\t\t\tconst jwtPluginOptions = await getJwtPluginOptions();\n\t\t\t\t\tconst audience = opts?.verifyOptions?.audience ?? authServerBaseUrl;\n\t\t\t\t\tconst issuer =\n\t\t\t\t\t\topts?.verifyOptions?.issuer ??\n\t\t\t\t\t\tjwtPluginOptions?.jwt?.issuer ??\n\t\t\t\t\t\tauthServerBaseUrl;\n\t\t\t\t\tif (!audience) {\n\t\t\t\t\t\tthrow Error(\"please define opts.verifyOptions.audience\");\n\t\t\t\t\t}\n\t\t\t\t\tif (!issuer) {\n\t\t\t\t\t\tthrow Error(\"please define opts.verifyOptions.issuer\");\n\t\t\t\t\t}\n\t\t\t\t\tconst jwksUrl =\n\t\t\t\t\t\topts?.jwksUrl ??\n\t\t\t\t\t\tjwtPluginOptions?.jwks?.remoteUrl ??\n\t\t\t\t\t\t(authServerBaseUrl\n\t\t\t\t\t\t\t? `${authServerBaseUrl + (authServerBasePath ?? \"\")}${jwtPluginOptions?.jwks?.jwksPath ?? \"/jwks\"}`\n\t\t\t\t\t\t\t: undefined);\n\t\t\t\t\tconst introspectUrl =\n\t\t\t\t\t\topts?.remoteVerify?.introspectUrl ??\n\t\t\t\t\t\t(authServerBaseUrl\n\t\t\t\t\t\t\t? `${authServerBaseUrl}${authServerBasePath ?? \"\"}/oauth2/introspect`\n\t\t\t\t\t\t\t: undefined);\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (!token?.length) {\n\t\t\t\t\t\t\tthrow new APIError(\"UNAUTHORIZED\", {\n\t\t\t\t\t\t\t\tmessage: \"missing authorization header\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn await verifyAccessToken(token, {\n\t\t\t\t\t\t\t...opts,\n\t\t\t\t\t\t\tjwksUrl,\n\t\t\t\t\t\t\tverifyOptions: {\n\t\t\t\t\t\t\t\t...opts?.verifyOptions,\n\t\t\t\t\t\t\t\taudience,\n\t\t\t\t\t\t\t\tissuer,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tremoteVerify:\n\t\t\t\t\t\t\t\topts?.remoteVerify && introspectUrl\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\t...opts.remoteVerify,\n\t\t\t\t\t\t\t\t\t\t\tintrospectUrl,\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tthrow handleMcpErrors(error, audience, {\n\t\t\t\t\t\t\tresourceMetadataMappings: opts?.resourceMetadataMappings,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}) as VerifyAccessTokenOutput<T>,\n\t\t\t\t/**\n\t\t\t\t * An authorization server does not typically publish\n\t\t\t\t * the `/.well-known/oauth-protected-resource` themselves.\n\t\t\t\t * Thus, we provide a client-only endpoint to help set up\n\t\t\t\t * your protected resource metadata.\n\t\t\t\t *\n\t\t\t\t * The optional auth parameter can fill known values automatically.\n\t\t\t\t *\n\t\t\t\t * @see https://datatracker.ietf.org/doc/html/rfc8414#section-2\n\t\t\t\t */\n\t\t\t\tgetProtectedResourceMetadata: (async (\n\t\t\t\t\toverrides: Partial<ResourceServerMetadata> | undefined,\n\t\t\t\t\topts:\n\t\t\t\t\t\t| {\n\t\t\t\t\t\t\t\tsilenceWarnings?: {\n\t\t\t\t\t\t\t\t\toidcScopes?: boolean;\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\texternalScopes?: string[];\n\t\t\t\t\t\t }\n\t\t\t\t\t\t| undefined,\n\t\t\t\t): Promise<ResourceServerMetadata> => {\n\t\t\t\t\tconst resource = overrides?.resource ?? authServerBaseUrl;\n\t\t\t\t\tconst oauthProviderOptions = (await getOauthProviderPlugin())\n\t\t\t\t\t\t?.options;\n\t\t\t\t\tif (!resource) {\n\t\t\t\t\t\tthrow Error(\"missing required resource\");\n\t\t\t\t\t}\n\t\t\t\t\tif (\n\t\t\t\t\t\toauthProviderOptions?.scopes &&\n\t\t\t\t\t\topts?.externalScopes &&\n\t\t\t\t\t\t(overrides?.authorization_servers?.length ?? 0) <= 1\n\t\t\t\t\t) {\n\t\t\t\t\t\tthrow new BetterAuthError(\n\t\t\t\t\t\t\t\"external scopes should not be provided with one authorization server\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\t// Resource server should not mention specific scopes\n\t\t\t\t\tif (overrides?.scopes_supported) {\n\t\t\t\t\t\tconst allValidScopes = new Set([\n\t\t\t\t\t\t\t...(oauthProviderOptions?.scopes ?? []),\n\t\t\t\t\t\t\t...(opts?.externalScopes ?? []),\n\t\t\t\t\t\t]);\n\t\t\t\t\t\tfor (const sc of overrides.scopes_supported) {\n\t\t\t\t\t\t\tif (sc === \"openid\") {\n\t\t\t\t\t\t\t\tthrow new BetterAuthError(\n\t\t\t\t\t\t\t\t\t\"Only the Auth Server should utilize the openid scope\",\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ([\"profile\", \"email\", \"phone\", \"address\"].includes(sc)) {\n\t\t\t\t\t\t\t\tif (!opts?.silenceWarnings?.oidcScopes) {\n\t\t\t\t\t\t\t\t\tlogger.warn(\n\t\t\t\t\t\t\t\t\t\t`\"${sc}\" is typically restricted for the authorization server, a resource server typically shouldn't handle this scope`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!allValidScopes.has(sc)) {\n\t\t\t\t\t\t\t\tthrow new BetterAuthError(\n\t\t\t\t\t\t\t\t\t`Unsupported scope ${sc}. If external, please add to \"externalScopes\"`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst authorizationServer = await getAuthorizationServer();\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tresource,\n\t\t\t\t\t\tauthorization_servers: authorizationServer\n\t\t\t\t\t\t\t? [authorizationServer]\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\t...overrides,\n\t\t\t\t\t};\n\t\t\t\t}) as ProtectedResourceMetadataOutput<T>,\n\t\t\t};\n\t\t},\n\t} satisfies BetterAuthClientPlugin;\n};\n\nexport interface VerifyAccessTokenRemote {\n\t/** Full url of the introspect endpoint. Should end with `/oauth2/introspect` */\n\tintrospectUrl: string;\n\t/** Client Secret */\n\tclientId: string;\n\t/** Client Secret */\n\tclientSecret: string;\n\t/**\n\t * Forces remote verification of a token.\n\t * This ensures attached session (if applicable)\n\t * is also still active.\n\t */\n\tforce?: boolean;\n}\n\ntype VerifyAccessTokenOutput<T> = T extends Auth\n\t? (\n\t\t\ttoken: string | undefined,\n\t\t\topts?: VerifyAccessTokenAuthOpts,\n\t\t) => Promise<JWTPayload>\n\t: (\n\t\t\ttoken: string | undefined,\n\t\t\topts: VerifyAccessTokenNoAuthOpts,\n\t\t) => Promise<JWTPayload>;\ntype VerifyAccessTokenAuthOpts = {\n\tverifyOptions?: JWTVerifyOptions &\n\t\tRequired<Pick<JWTVerifyOptions, \"audience\">>;\n\tscopes?: string[];\n\tjwksUrl?: string;\n\tremoteVerify?: VerifyAccessTokenRemote;\n\t/** Maps non-url (ie urn, client) resources to resource_metadata */\n\tresourceMetadataMappings?: Record<string, string>;\n};\ntype VerifyAccessTokenNoAuthOpts =\n\t| {\n\t\t\tverifyOptions: JWTVerifyOptions &\n\t\t\t\tRequired<Pick<JWTVerifyOptions, \"audience\" | \"issuer\">>;\n\t\t\tscopes?: string[];\n\t\t\tjwksUrl: string;\n\t\t\tremoteVerify?: VerifyAccessTokenRemote;\n\t\t\t/** Maps non-url (ie urn, client) resources to resource_metadata */\n\t\t\tresourceMetadataMappings?: Record<string, string>;\n\t }\n\t| {\n\t\t\tverifyOptions: JWTVerifyOptions &\n\t\t\t\tRequired<Pick<JWTVerifyOptions, \"audience\" | \"issuer\">>;\n\t\t\tscopes?: string[];\n\t\t\tjwksUrl?: string;\n\t\t\tremoteVerify: VerifyAccessTokenRemote;\n\t\t\t/** Maps non-url (ie urn, client) resources to resource_metadata */\n\t\t\tresourceMetadataMappings?: Record<string, string>;\n\t };\n\ntype ProtectedResourceMetadataOutput<T> = T extends Auth\n\t? (\n\t\t\toverrides?: Partial<ResourceServerMetadata>,\n\t\t\topts?: {\n\t\t\t\tsilenceWarnings?: {\n\t\t\t\t\toidcScopes?: boolean;\n\t\t\t\t};\n\t\t\t\texternalScopes?: string[];\n\t\t\t},\n\t\t) => Promise<ResourceServerMetadata>\n\t: (\n\t\t\toverrides: ResourceServerMetadata,\n\t\t\topts?: {\n\t\t\t\tsilenceWarnings?: {\n\t\t\t\t\toidcScopes?: boolean;\n\t\t\t\t};\n\t\t\t\texternalScopes?: string[];\n\t\t\t},\n\t\t) => Promise<ResourceServerMetadata>;\n"],"mappings":";;;;;;;AAUA,MAAa,+BACZ,SACI;CACJ,IAAI;CAGJ,MAAM,yBAAyB,YAAY;AAC1C,MAAI,CAAC,oBACJ,uBAAsB,OACnB,uBAAuB,MAAM,KAAK,SAAS,GAC3C;AAEJ,SAAO;;CAER,IAAI;CACJ,MAAM,sBAAsB,YAAY;AACvC,MAAI,CAAC,UACJ,aACC,QAAQ,EAAE,MAAM,wBAAwB,GAAG,SAAS,mBACjD,aAAa,MAAM,KAAK,SAAS,GACjC;AAEL,SAAO,WAAW;;CAEnB,MAAM,yBAAyB,YAAY;AAE1C,UADyB,MAAM,qBAAqB,GAC3B,KAAK,UAAU;;CAEzC,MAAM,oBAAoB,MAAM,QAAQ;CACxC,MAAM,qBAAqB,MAAM,QAAQ;AAEzC,QAAO;EACN,IAAI;EACJ,aAAa;AACZ,UAAO;IASN,oBAAoB,OACnB,OACA,SASyB;KACzB,MAAM,mBAAmB,MAAM,qBAAqB;KACpD,MAAM,WAAW,MAAM,eAAe,YAAY;KAClD,MAAM,SACL,MAAM,eAAe,UACrB,kBAAkB,KAAK,UACvB;AACD,SAAI,CAAC,SACJ,OAAM,MAAM,4CAA4C;AAEzD,SAAI,CAAC,OACJ,OAAM,MAAM,0CAA0C;KAEvD,MAAM,UACL,MAAM,WACN,kBAAkB,MAAM,cACvB,oBACE,GAAG,qBAAqB,sBAAsB,MAAM,kBAAkB,MAAM,YAAY,YACxF;KACJ,MAAM,gBACL,MAAM,cAAc,kBACnB,oBACE,GAAG,oBAAoB,sBAAsB,GAAG,sBAChD;AAEJ,SAAI;AACH,UAAI,CAAC,OAAO,OACX,OAAM,IAAI,SAAS,gBAAgB,EAClC,SAAS,gCACT,CAAC;AAEH,aAAO,MAAM,kBAAkB,OAAO;OACrC,GAAG;OACH;OACA,eAAe;QACd,GAAG,MAAM;QACT;QACA;QACA;OACD,cACC,MAAM,gBAAgB,gBACnB;QACA,GAAG,KAAK;QACR;QACA,GACA;OACJ,CAAC;cACM,OAAO;AACf,YAAM,gBAAgB,OAAO,UAAU,EACtC,0BAA0B,MAAM,0BAChC,CAAC;;;IAaJ,+BAA+B,OAC9B,WACA,SAQqC;KACrC,MAAM,WAAW,WAAW,YAAY;KACxC,MAAM,wBAAwB,MAAM,wBAAwB,GACzD;AACH,SAAI,CAAC,SACJ,OAAM,MAAM,4BAA4B;AAEzC,SACC,sBAAsB,UACtB,MAAM,mBACL,WAAW,uBAAuB,UAAU,MAAM,EAEnD,OAAM,IAAI,gBACT,uEACA;AAGF,SAAI,WAAW,kBAAkB;MAChC,MAAM,iBAAiB,IAAI,IAAI,CAC9B,GAAI,sBAAsB,UAAU,EAAE,EACtC,GAAI,MAAM,kBAAkB,EAAE,CAC9B,CAAC;AACF,WAAK,MAAM,MAAM,UAAU,kBAAkB;AAC5C,WAAI,OAAO,SACV,OAAM,IAAI,gBACT,uDACA;AAEF,WAAI;QAAC;QAAW;QAAS;QAAS;QAAU,CAAC,SAAS,GAAG,EACxD;YAAI,CAAC,MAAM,iBAAiB,WAC3B,QAAO,KACN,IAAI,GAAG,iHACP;;AAGH,WAAI,CAAC,eAAe,IAAI,GAAG,CAC1B,OAAM,IAAI,gBACT,qBAAqB,GAAG,+CACxB;;;KAKJ,MAAM,sBAAsB,MAAM,wBAAwB;AAE1D,YAAO;MACN;MACA,uBAAuB,sBACpB,CAAC,oBAAoB,GACrB;MACH,GAAG;MACH;;IAEF;;EAEF"}
|
|
1
|
+
{"version":3,"file":"client-resource.mjs","names":[],"sources":["../src/client-resource.ts"],"sourcesContent":["import { logger } from \"@better-auth/core/env\";\nimport { BetterAuthError } from \"@better-auth/core/error\";\nimport { verifyAccessToken } from \"better-auth/oauth2\";\nimport type { Auth, BetterAuthClientPlugin } from \"better-auth/types\";\nimport { APIError } from \"better-call\";\nimport type { JWTPayload, JWTVerifyOptions } from \"jose\";\nimport { handleMcpErrors } from \"./mcp\";\nimport type { ResourceServerMetadata } from \"./types/oauth\";\nimport { getJwtPlugin, getOAuthProviderPlugin } from \"./utils\";\n\nexport const oauthProviderResourceClient = <T extends Auth | undefined>(\n\tauth?: T,\n) => {\n\tlet oauthProviderPlugin:\n\t\t| ReturnType<typeof getOAuthProviderPlugin>\n\t\t| undefined;\n\tconst getOauthProviderPlugin = async () => {\n\t\tif (!oauthProviderPlugin) {\n\t\t\toauthProviderPlugin = auth\n\t\t\t\t? getOAuthProviderPlugin(await auth.$context)\n\t\t\t\t: undefined;\n\t\t}\n\t\treturn oauthProviderPlugin;\n\t};\n\tlet jwtPlugin: ReturnType<typeof getJwtPlugin> | undefined;\n\tconst getJwtPluginOptions = async () => {\n\t\tif (!jwtPlugin) {\n\t\t\tjwtPlugin =\n\t\t\t\tauth && !(await getOauthProviderPlugin())?.options?.disableJwtPlugin\n\t\t\t\t\t? getJwtPlugin(await auth.$context)\n\t\t\t\t\t: undefined;\n\t\t}\n\t\treturn jwtPlugin?.options;\n\t};\n\tconst authServerBaseUrl =\n\t\ttypeof auth?.options.baseURL === \"string\"\n\t\t\t? auth.options.baseURL\n\t\t\t: undefined;\n\tconst getAuthorizationServer = async (): Promise<string | undefined> => {\n\t\tconst jwtPluginOptions = await getJwtPluginOptions();\n\t\treturn jwtPluginOptions?.jwt?.issuer ?? authServerBaseUrl;\n\t};\n\tconst authServerBasePath = auth?.options.basePath;\n\n\treturn {\n\t\tid: \"oauth-provider-resource-client\",\n\t\tgetActions() {\n\t\t\treturn {\n\t\t\t\t/**\n\t\t\t\t * Performs verification of an access token for your APIs. Can perform\n\t\t\t\t * local verification using `jwksUrl` by default. Can also be configured\n\t\t\t\t * for remote introspection using `remoteVerify` if a confidential client\n\t\t\t\t * is set up for this API.\n\t\t\t\t *\n\t\t\t\t * The optional auth parameter can fill known values automatically.\n\t\t\t\t */\n\t\t\t\tverifyAccessToken: (async (\n\t\t\t\t\ttoken: string | undefined,\n\t\t\t\t\topts?: {\n\t\t\t\t\t\tverifyOptions?: JWTVerifyOptions &\n\t\t\t\t\t\t\tRequired<Pick<JWTVerifyOptions, \"audience\" | \"issuer\">>;\n\t\t\t\t\t\tscopes?: string[];\n\t\t\t\t\t\tjwksUrl?: string;\n\t\t\t\t\t\tremoteVerify?: VerifyAccessTokenRemote;\n\t\t\t\t\t\t/** Maps non-url (ie urn, client) resources to resource_metadata */\n\t\t\t\t\t\tresourceMetadataMappings?: Record<string, string>;\n\t\t\t\t\t},\n\t\t\t\t): Promise<JWTPayload> => {\n\t\t\t\t\tconst jwtPluginOptions = await getJwtPluginOptions();\n\t\t\t\t\tconst audience = opts?.verifyOptions?.audience ?? authServerBaseUrl;\n\t\t\t\t\tconst issuer =\n\t\t\t\t\t\topts?.verifyOptions?.issuer ??\n\t\t\t\t\t\tjwtPluginOptions?.jwt?.issuer ??\n\t\t\t\t\t\tauthServerBaseUrl;\n\t\t\t\t\tif (!audience) {\n\t\t\t\t\t\tthrow Error(\"please define opts.verifyOptions.audience\");\n\t\t\t\t\t}\n\t\t\t\t\tif (!issuer) {\n\t\t\t\t\t\tthrow Error(\"please define opts.verifyOptions.issuer\");\n\t\t\t\t\t}\n\t\t\t\t\tconst jwksUrl =\n\t\t\t\t\t\topts?.jwksUrl ??\n\t\t\t\t\t\tjwtPluginOptions?.jwks?.remoteUrl ??\n\t\t\t\t\t\t(authServerBaseUrl\n\t\t\t\t\t\t\t? `${authServerBaseUrl + (authServerBasePath ?? \"\")}${jwtPluginOptions?.jwks?.jwksPath ?? \"/jwks\"}`\n\t\t\t\t\t\t\t: undefined);\n\t\t\t\t\tconst introspectUrl =\n\t\t\t\t\t\topts?.remoteVerify?.introspectUrl ??\n\t\t\t\t\t\t(authServerBaseUrl\n\t\t\t\t\t\t\t? `${authServerBaseUrl}${authServerBasePath ?? \"\"}/oauth2/introspect`\n\t\t\t\t\t\t\t: undefined);\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (!token?.length) {\n\t\t\t\t\t\t\tthrow new APIError(\"UNAUTHORIZED\", {\n\t\t\t\t\t\t\t\tmessage: \"missing authorization header\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn await verifyAccessToken(token, {\n\t\t\t\t\t\t\t...opts,\n\t\t\t\t\t\t\tjwksUrl,\n\t\t\t\t\t\t\tverifyOptions: {\n\t\t\t\t\t\t\t\t...opts?.verifyOptions,\n\t\t\t\t\t\t\t\taudience,\n\t\t\t\t\t\t\t\tissuer,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tremoteVerify:\n\t\t\t\t\t\t\t\topts?.remoteVerify && introspectUrl\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\t...opts.remoteVerify,\n\t\t\t\t\t\t\t\t\t\t\tintrospectUrl,\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tthrow handleMcpErrors(error, audience, {\n\t\t\t\t\t\t\tresourceMetadataMappings: opts?.resourceMetadataMappings,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}) as VerifyAccessTokenOutput<T>,\n\t\t\t\t/**\n\t\t\t\t * An authorization server does not typically publish\n\t\t\t\t * the `/.well-known/oauth-protected-resource` themselves.\n\t\t\t\t * Thus, we provide a client-only endpoint to help set up\n\t\t\t\t * your protected resource metadata.\n\t\t\t\t *\n\t\t\t\t * The optional auth parameter can fill known values automatically.\n\t\t\t\t *\n\t\t\t\t * @see https://datatracker.ietf.org/doc/html/rfc8414#section-2\n\t\t\t\t */\n\t\t\t\tgetProtectedResourceMetadata: (async (\n\t\t\t\t\toverrides: Partial<ResourceServerMetadata> | undefined,\n\t\t\t\t\topts:\n\t\t\t\t\t\t| {\n\t\t\t\t\t\t\t\tsilenceWarnings?: {\n\t\t\t\t\t\t\t\t\toidcScopes?: boolean;\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\texternalScopes?: string[];\n\t\t\t\t\t\t }\n\t\t\t\t\t\t| undefined,\n\t\t\t\t): Promise<ResourceServerMetadata> => {\n\t\t\t\t\tconst resource = overrides?.resource ?? authServerBaseUrl;\n\t\t\t\t\tconst oauthProviderOptions = (await getOauthProviderPlugin())\n\t\t\t\t\t\t?.options;\n\t\t\t\t\tif (!resource) {\n\t\t\t\t\t\tthrow Error(\"missing required resource\");\n\t\t\t\t\t}\n\t\t\t\t\tif (\n\t\t\t\t\t\toauthProviderOptions?.scopes &&\n\t\t\t\t\t\topts?.externalScopes &&\n\t\t\t\t\t\t(overrides?.authorization_servers?.length ?? 0) <= 1\n\t\t\t\t\t) {\n\t\t\t\t\t\tthrow new BetterAuthError(\n\t\t\t\t\t\t\t\"external scopes should not be provided with one authorization server\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\t// Resource server should not mention specific scopes\n\t\t\t\t\tif (overrides?.scopes_supported) {\n\t\t\t\t\t\tconst allValidScopes = new Set([\n\t\t\t\t\t\t\t...(oauthProviderOptions?.scopes ?? []),\n\t\t\t\t\t\t\t...(opts?.externalScopes ?? []),\n\t\t\t\t\t\t]);\n\t\t\t\t\t\tfor (const sc of overrides.scopes_supported) {\n\t\t\t\t\t\t\tif (sc === \"openid\") {\n\t\t\t\t\t\t\t\tthrow new BetterAuthError(\n\t\t\t\t\t\t\t\t\t\"Only the Auth Server should utilize the openid scope\",\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ([\"profile\", \"email\", \"phone\", \"address\"].includes(sc)) {\n\t\t\t\t\t\t\t\tif (!opts?.silenceWarnings?.oidcScopes) {\n\t\t\t\t\t\t\t\t\tlogger.warn(\n\t\t\t\t\t\t\t\t\t\t`\"${sc}\" is typically restricted for the authorization server, a resource server typically shouldn't handle this scope`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!allValidScopes.has(sc)) {\n\t\t\t\t\t\t\t\tthrow new BetterAuthError(\n\t\t\t\t\t\t\t\t\t`Unsupported scope ${sc}. If external, please add to \"externalScopes\"`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst authorizationServer = await getAuthorizationServer();\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tresource,\n\t\t\t\t\t\tauthorization_servers: authorizationServer\n\t\t\t\t\t\t\t? [authorizationServer]\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\t...overrides,\n\t\t\t\t\t};\n\t\t\t\t}) as ProtectedResourceMetadataOutput<T>,\n\t\t\t};\n\t\t},\n\t} satisfies BetterAuthClientPlugin;\n};\n\nexport interface VerifyAccessTokenRemote {\n\t/** Full url of the introspect endpoint. Should end with `/oauth2/introspect` */\n\tintrospectUrl: string;\n\t/** Client Secret */\n\tclientId: string;\n\t/** Client Secret */\n\tclientSecret: string;\n\t/**\n\t * Forces remote verification of a token.\n\t * This ensures attached session (if applicable)\n\t * is also still active.\n\t */\n\tforce?: boolean;\n}\n\ntype VerifyAccessTokenOutput<T> = T extends Auth\n\t? (\n\t\t\ttoken: string | undefined,\n\t\t\topts?: VerifyAccessTokenAuthOpts,\n\t\t) => Promise<JWTPayload>\n\t: (\n\t\t\ttoken: string | undefined,\n\t\t\topts: VerifyAccessTokenNoAuthOpts,\n\t\t) => Promise<JWTPayload>;\ntype VerifyAccessTokenAuthOpts = {\n\tverifyOptions?: JWTVerifyOptions &\n\t\tRequired<Pick<JWTVerifyOptions, \"audience\">>;\n\tscopes?: string[];\n\tjwksUrl?: string;\n\tremoteVerify?: VerifyAccessTokenRemote;\n\t/** Maps non-url (ie urn, client) resources to resource_metadata */\n\tresourceMetadataMappings?: Record<string, string>;\n};\ntype VerifyAccessTokenNoAuthOpts =\n\t| {\n\t\t\tverifyOptions: JWTVerifyOptions &\n\t\t\t\tRequired<Pick<JWTVerifyOptions, \"audience\" | \"issuer\">>;\n\t\t\tscopes?: string[];\n\t\t\tjwksUrl: string;\n\t\t\tremoteVerify?: VerifyAccessTokenRemote;\n\t\t\t/** Maps non-url (ie urn, client) resources to resource_metadata */\n\t\t\tresourceMetadataMappings?: Record<string, string>;\n\t }\n\t| {\n\t\t\tverifyOptions: JWTVerifyOptions &\n\t\t\t\tRequired<Pick<JWTVerifyOptions, \"audience\" | \"issuer\">>;\n\t\t\tscopes?: string[];\n\t\t\tjwksUrl?: string;\n\t\t\tremoteVerify: VerifyAccessTokenRemote;\n\t\t\t/** Maps non-url (ie urn, client) resources to resource_metadata */\n\t\t\tresourceMetadataMappings?: Record<string, string>;\n\t };\n\ntype ProtectedResourceMetadataOutput<T> = T extends Auth\n\t? (\n\t\t\toverrides?: Partial<ResourceServerMetadata>,\n\t\t\topts?: {\n\t\t\t\tsilenceWarnings?: {\n\t\t\t\t\toidcScopes?: boolean;\n\t\t\t\t};\n\t\t\t\texternalScopes?: string[];\n\t\t\t},\n\t\t) => Promise<ResourceServerMetadata>\n\t: (\n\t\t\toverrides: ResourceServerMetadata,\n\t\t\topts?: {\n\t\t\t\tsilenceWarnings?: {\n\t\t\t\t\toidcScopes?: boolean;\n\t\t\t\t};\n\t\t\t\texternalScopes?: string[];\n\t\t\t},\n\t\t) => Promise<ResourceServerMetadata>;\n"],"mappings":";;;;;;;AAUA,MAAa,+BACZ,SACI;CACJ,IAAI;CAGJ,MAAM,yBAAyB,YAAY;AAC1C,MAAI,CAAC,oBACJ,uBAAsB,OACnB,uBAAuB,MAAM,KAAK,SAAS,GAC3C;AAEJ,SAAO;;CAER,IAAI;CACJ,MAAM,sBAAsB,YAAY;AACvC,MAAI,CAAC,UACJ,aACC,QAAQ,EAAE,MAAM,wBAAwB,GAAG,SAAS,mBACjD,aAAa,MAAM,KAAK,SAAS,GACjC;AAEL,SAAO,WAAW;;CAEnB,MAAM,oBACL,OAAO,MAAM,QAAQ,YAAY,WAC9B,KAAK,QAAQ,UACb;CACJ,MAAM,yBAAyB,YAAyC;AAEvE,UADyB,MAAM,qBAAqB,GAC3B,KAAK,UAAU;;CAEzC,MAAM,qBAAqB,MAAM,QAAQ;AAEzC,QAAO;EACN,IAAI;EACJ,aAAa;AACZ,UAAO;IASN,oBAAoB,OACnB,OACA,SASyB;KACzB,MAAM,mBAAmB,MAAM,qBAAqB;KACpD,MAAM,WAAW,MAAM,eAAe,YAAY;KAClD,MAAM,SACL,MAAM,eAAe,UACrB,kBAAkB,KAAK,UACvB;AACD,SAAI,CAAC,SACJ,OAAM,MAAM,4CAA4C;AAEzD,SAAI,CAAC,OACJ,OAAM,MAAM,0CAA0C;KAEvD,MAAM,UACL,MAAM,WACN,kBAAkB,MAAM,cACvB,oBACE,GAAG,qBAAqB,sBAAsB,MAAM,kBAAkB,MAAM,YAAY,YACxF;KACJ,MAAM,gBACL,MAAM,cAAc,kBACnB,oBACE,GAAG,oBAAoB,sBAAsB,GAAG,sBAChD;AAEJ,SAAI;AACH,UAAI,CAAC,OAAO,OACX,OAAM,IAAI,SAAS,gBAAgB,EAClC,SAAS,gCACT,CAAC;AAEH,aAAO,MAAM,kBAAkB,OAAO;OACrC,GAAG;OACH;OACA,eAAe;QACd,GAAG,MAAM;QACT;QACA;QACA;OACD,cACC,MAAM,gBAAgB,gBACnB;QACA,GAAG,KAAK;QACR;QACA,GACA;OACJ,CAAC;cACM,OAAO;AACf,YAAM,gBAAgB,OAAO,UAAU,EACtC,0BAA0B,MAAM,0BAChC,CAAC;;;IAaJ,+BAA+B,OAC9B,WACA,SAQqC;KACrC,MAAM,WAAW,WAAW,YAAY;KACxC,MAAM,wBAAwB,MAAM,wBAAwB,GACzD;AACH,SAAI,CAAC,SACJ,OAAM,MAAM,4BAA4B;AAEzC,SACC,sBAAsB,UACtB,MAAM,mBACL,WAAW,uBAAuB,UAAU,MAAM,EAEnD,OAAM,IAAI,gBACT,uEACA;AAGF,SAAI,WAAW,kBAAkB;MAChC,MAAM,iBAAiB,IAAI,IAAI,CAC9B,GAAI,sBAAsB,UAAU,EAAE,EACtC,GAAI,MAAM,kBAAkB,EAAE,CAC9B,CAAC;AACF,WAAK,MAAM,MAAM,UAAU,kBAAkB;AAC5C,WAAI,OAAO,SACV,OAAM,IAAI,gBACT,uDACA;AAEF,WAAI;QAAC;QAAW;QAAS;QAAS;QAAU,CAAC,SAAS,GAAG,EACxD;YAAI,CAAC,MAAM,iBAAiB,WAC3B,QAAO,KACN,IAAI,GAAG,iHACP;;AAGH,WAAI,CAAC,eAAe,IAAI,GAAG,CAC1B,OAAM,IAAI,gBACT,qBAAqB,GAAG,+CACxB;;;KAKJ,MAAM,sBAAsB,MAAM,wBAAwB;AAE1D,YAAO;MACN;MACA,uBAAuB,sBACpB,CAAC,oBAAoB,GACrB;MACH,GAAG;MACH;;IAEF;;EAEF"}
|
package/dist/client.d.mts
CHANGED
package/dist/client.mjs
CHANGED
|
@@ -23,10 +23,9 @@ const oauthProviderClient = () => {
|
|
|
23
23
|
const headers = ctx.headers;
|
|
24
24
|
const body = typeof ctx.body === "string" ? headers.get("content-type") === "application/x-www-form-urlencoded" ? Object.fromEntries(new URLSearchParams(ctx.body)) : safeJSONParse(ctx.body ?? "{}") : ctx.body;
|
|
25
25
|
if (body?.oauth_query) return;
|
|
26
|
-
|
|
27
|
-
if (pathname.endsWith("/sign-in/email") || pathname.endsWith("/sign-in/social") || pathname.endsWith("/sign-in/oauth2") || pathname.endsWith("/oauth2/consent") || pathname.endsWith("/oauth2/continue")) ctx.body = JSON.stringify({
|
|
26
|
+
if (typeof window !== "undefined" && window?.location?.search && !(ctx.method === "GET" || ctx.method === "DELETE")) ctx.body = JSON.stringify({
|
|
28
27
|
...body,
|
|
29
|
-
oauth_query:
|
|
28
|
+
oauth_query: parseSignedQuery(window.location.search)
|
|
30
29
|
});
|
|
31
30
|
} }
|
|
32
31
|
}],
|
package/dist/client.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import { safeJSONParse } from \"@better-auth/core/utils/json\";\nimport type { BetterAuthClientPlugin } from \"better-auth/types\";\nimport type { oauthProvider } from \"./oauth\";\n\nfunction parseSignedQuery(search: string) {\n\tconst params = new URLSearchParams(search);\n\tif (params.has(\"sig\")) {\n\t\tconst signedParams = new URLSearchParams();\n\t\tfor (const [key, value] of params.entries()) {\n\t\t\tsignedParams.append(key, value);\n\t\t\tif (key === \"sig\") break;\n\t\t}\n\t\treturn signedParams.toString();\n\t}\n}\n\nexport const oauthProviderClient = () => {\n\treturn {\n\t\tid: \"oauth-provider-client\",\n\t\tfetchPlugins: [\n\t\t\t{\n\t\t\t\tid: \"oauth-provider-signin\",\n\t\t\t\tname: \"oauth-provider-signin\",\n\t\t\t\tdescription: \"Adds the current page query to oauth requests\",\n\t\t\t\thooks: {\n\t\t\t\t\tasync onRequest(ctx) {\n\t\t\t\t\t\tconst headers = ctx.headers;\n\t\t\t\t\t\tconst body =\n\t\t\t\t\t\t\ttypeof ctx.body === \"string\"\n\t\t\t\t\t\t\t\t? headers.get(\"content-type\") ===\n\t\t\t\t\t\t\t\t\t\"application/x-www-form-urlencoded\"\n\t\t\t\t\t\t\t\t\t? Object.fromEntries(new URLSearchParams(ctx.body))\n\t\t\t\t\t\t\t\t\t: safeJSONParse<Record<string, unknown>>(ctx.body ?? \"{}\")\n\t\t\t\t\t\t\t\t: ctx.body;\n\t\t\t\t\t\tif (body?.oauth_query) return;\n\t\t\t\t\t\
|
|
1
|
+
{"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import { safeJSONParse } from \"@better-auth/core/utils/json\";\nimport type { BetterAuthClientPlugin } from \"better-auth/types\";\nimport type { oauthProvider } from \"./oauth\";\n\nfunction parseSignedQuery(search: string) {\n\tconst params = new URLSearchParams(search);\n\tif (params.has(\"sig\")) {\n\t\tconst signedParams = new URLSearchParams();\n\t\tfor (const [key, value] of params.entries()) {\n\t\t\tsignedParams.append(key, value);\n\t\t\tif (key === \"sig\") break;\n\t\t}\n\t\treturn signedParams.toString();\n\t}\n}\n\nexport const oauthProviderClient = () => {\n\treturn {\n\t\tid: \"oauth-provider-client\",\n\t\tfetchPlugins: [\n\t\t\t{\n\t\t\t\tid: \"oauth-provider-signin\",\n\t\t\t\tname: \"oauth-provider-signin\",\n\t\t\t\tdescription: \"Adds the current page query to oauth requests\",\n\t\t\t\thooks: {\n\t\t\t\t\tasync onRequest(ctx) {\n\t\t\t\t\t\tconst headers = ctx.headers;\n\t\t\t\t\t\tconst body =\n\t\t\t\t\t\t\ttypeof ctx.body === \"string\"\n\t\t\t\t\t\t\t\t? headers.get(\"content-type\") ===\n\t\t\t\t\t\t\t\t\t\"application/x-www-form-urlencoded\"\n\t\t\t\t\t\t\t\t\t? Object.fromEntries(new URLSearchParams(ctx.body))\n\t\t\t\t\t\t\t\t\t: safeJSONParse<Record<string, unknown>>(ctx.body ?? \"{}\")\n\t\t\t\t\t\t\t\t: ctx.body;\n\t\t\t\t\t\tif (body?.oauth_query) return;\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\ttypeof window !== \"undefined\" &&\n\t\t\t\t\t\t\twindow?.location?.search &&\n\t\t\t\t\t\t\t!(ctx.method === \"GET\" || ctx.method === \"DELETE\")\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tctx.body = JSON.stringify({\n\t\t\t\t\t\t\t\t...body,\n\t\t\t\t\t\t\t\toauth_query: parseSignedQuery(window.location.search),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t\t$InferServerPlugin: {} as ReturnType<typeof oauthProvider>,\n\t} satisfies BetterAuthClientPlugin;\n};\n"],"mappings":";;;AAIA,SAAS,iBAAiB,QAAgB;CACzC,MAAM,SAAS,IAAI,gBAAgB,OAAO;AAC1C,KAAI,OAAO,IAAI,MAAM,EAAE;EACtB,MAAM,eAAe,IAAI,iBAAiB;AAC1C,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,SAAS,EAAE;AAC5C,gBAAa,OAAO,KAAK,MAAM;AAC/B,OAAI,QAAQ,MAAO;;AAEpB,SAAO,aAAa,UAAU;;;AAIhC,MAAa,4BAA4B;AACxC,QAAO;EACN,IAAI;EACJ,cAAc,CACb;GACC,IAAI;GACJ,MAAM;GACN,aAAa;GACb,OAAO,EACN,MAAM,UAAU,KAAK;IACpB,MAAM,UAAU,IAAI;IACpB,MAAM,OACL,OAAO,IAAI,SAAS,WACjB,QAAQ,IAAI,eAAe,KAC5B,sCACE,OAAO,YAAY,IAAI,gBAAgB,IAAI,KAAK,CAAC,GACjD,cAAuC,IAAI,QAAQ,KAAK,GACzD,IAAI;AACR,QAAI,MAAM,YAAa;AACvB,QACC,OAAO,WAAW,eAClB,QAAQ,UAAU,UAClB,EAAE,IAAI,WAAW,SAAS,IAAI,WAAW,UAEzC,KAAI,OAAO,KAAK,UAAU;KACzB,GAAG;KACH,aAAa,iBAAiB,OAAO,SAAS,OAAO;KACrD,CAAC;MAGJ;GACD,CACD;EACD,oBAAoB,EAAE;EACtB"}
|
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-4vgZlF-I.mjs";
|
|
2
|
+
import { n as oauthProvider, t as getOAuthProviderState } from "./oauth-E89Dh-ZC.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,8 +1,9 @@
|
|
|
1
|
-
import { a as getJwtPlugin, c as isPKCERequired, d as
|
|
1
|
+
import { _ as mcpHandler, a as getJwtPlugin, c as isPKCERequired, d as resolveSubjectIdentifier, f as storeClientSecret, h as verifyOAuthQueryParams, 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-BDSjyzic.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 {
|
|
5
|
+
import { isBrowserFetchRequest } from "@better-auth/core/utils/fetch-metadata";
|
|
6
|
+
import { generateRandomString, makeSignature } from "better-auth/crypto";
|
|
6
7
|
import { defineRequestState } from "@better-auth/core/context";
|
|
7
8
|
import { logger } from "@better-auth/core/env";
|
|
8
9
|
import { BetterAuthError } from "@better-auth/core/error";
|
|
@@ -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;
|
|
@@ -1073,6 +1097,14 @@ async function rpInitiatedLogoutEndpoint(ctx, opts) {
|
|
|
1073
1097
|
}
|
|
1074
1098
|
}
|
|
1075
1099
|
|
|
1100
|
+
//#endregion
|
|
1101
|
+
//#region src/middleware/index.ts
|
|
1102
|
+
const publicSessionMiddleware = (opts) => createAuthMiddleware(async (ctx) => {
|
|
1103
|
+
if (!opts.allowPublicClientPrelogin) throw new APIError("BAD_REQUEST");
|
|
1104
|
+
const query = ctx.body.oauth_query;
|
|
1105
|
+
if (!await verifyOAuthQueryParams(query, ctx.context.secret)) throw new APIError("UNAUTHORIZED", { error: "invalid_signature" });
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1076
1108
|
//#endregion
|
|
1077
1109
|
//#region src/register.ts
|
|
1078
1110
|
async function registerEndpoint(ctx, opts) {
|
|
@@ -1116,6 +1148,22 @@ async function checkOAuthClient(client, opts, settings) {
|
|
|
1116
1148
|
error: "invalid_client_metadata",
|
|
1117
1149
|
error_description: "When 'authorization_code' grant type is used, 'code' response type must be included"
|
|
1118
1150
|
});
|
|
1151
|
+
if (client.subject_type !== void 0) {
|
|
1152
|
+
if (client.subject_type !== "public" && client.subject_type !== "pairwise") throw new APIError("BAD_REQUEST", {
|
|
1153
|
+
error: "invalid_client_metadata",
|
|
1154
|
+
error_description: `subject_type must be "public" or "pairwise"`
|
|
1155
|
+
});
|
|
1156
|
+
if (client.subject_type === "pairwise" && !opts.pairwiseSecret) throw new APIError("BAD_REQUEST", {
|
|
1157
|
+
error: "invalid_client_metadata",
|
|
1158
|
+
error_description: "pairwise subject_type requires server pairwiseSecret configuration"
|
|
1159
|
+
});
|
|
1160
|
+
if (client.subject_type === "pairwise" && client.redirect_uris && client.redirect_uris.length > 1) {
|
|
1161
|
+
if (new Set(client.redirect_uris.map((uri) => new URL(uri).host)).size > 1) throw new APIError("BAD_REQUEST", {
|
|
1162
|
+
error: "invalid_client_metadata",
|
|
1163
|
+
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."
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1119
1167
|
const requestedScopes = (client?.scope)?.split(" ").filter((v) => v.length);
|
|
1120
1168
|
const allowedScopes = settings?.isRegister ? opts.clientRegistrationAllowedScopes ?? opts.scopes : opts.scopes;
|
|
1121
1169
|
if (allowedScopes) {
|
|
@@ -1182,7 +1230,7 @@ async function createOAuthClientEndpoint(ctx, opts, settings) {
|
|
|
1182
1230
|
* @returns
|
|
1183
1231
|
*/
|
|
1184
1232
|
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;
|
|
1233
|
+
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
1234
|
const expiresAt = _expiresAt ? /* @__PURE__ */ new Date(_expiresAt * 1e3) : void 0;
|
|
1187
1235
|
const createdAt = _createdAt ? /* @__PURE__ */ new Date(_createdAt * 1e3) : void 0;
|
|
1188
1236
|
const scopes = _scope?.split(" ");
|
|
@@ -1217,6 +1265,7 @@ function oauthToSchema(input) {
|
|
|
1217
1265
|
skipConsent,
|
|
1218
1266
|
enableEndSession,
|
|
1219
1267
|
requirePKCE,
|
|
1268
|
+
subjectType,
|
|
1220
1269
|
referenceId,
|
|
1221
1270
|
metadata: Object.keys(metadataObj).length ? JSON.stringify(metadataObj) : void 0
|
|
1222
1271
|
};
|
|
@@ -1228,7 +1277,7 @@ function oauthToSchema(input) {
|
|
|
1228
1277
|
* @returns
|
|
1229
1278
|
*/
|
|
1230
1279
|
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;
|
|
1280
|
+
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
1281
|
const _expiresAt = expiresAt ? Math.round(new Date(expiresAt).getTime() / 1e3) : void 0;
|
|
1233
1282
|
const _createdAt = createdAt ? Math.round(new Date(createdAt).getTime() / 1e3) : void 0;
|
|
1234
1283
|
const _scopes = scopes?.join(" ");
|
|
@@ -1249,7 +1298,7 @@ function schemaToOAuth(input) {
|
|
|
1249
1298
|
software_id: softwareId ?? void 0,
|
|
1250
1299
|
software_version: softwareVersion ?? void 0,
|
|
1251
1300
|
software_statement: softwareStatement ?? void 0,
|
|
1252
|
-
redirect_uris: redirectUris ??
|
|
1301
|
+
redirect_uris: redirectUris ?? [],
|
|
1253
1302
|
post_logout_redirect_uris: postLogoutRedirectUris ?? void 0,
|
|
1254
1303
|
token_endpoint_auth_method: tokenEndpointAuthMethod ?? void 0,
|
|
1255
1304
|
grant_types: grantTypes ?? void 0,
|
|
@@ -1260,6 +1309,7 @@ function schemaToOAuth(input) {
|
|
|
1260
1309
|
skip_consent: skipConsent ?? void 0,
|
|
1261
1310
|
enable_end_session: enableEndSession ?? void 0,
|
|
1262
1311
|
require_pkce: requirePKCE ?? void 0,
|
|
1312
|
+
subject_type: subjectType ?? void 0,
|
|
1263
1313
|
reference_id: referenceId ?? void 0
|
|
1264
1314
|
};
|
|
1265
1315
|
}
|
|
@@ -1335,8 +1385,8 @@ async function getClientEndpoint(ctx, opts) {
|
|
|
1335
1385
|
* Provides public client fields for any logged-in user.
|
|
1336
1386
|
* This is commonly used to display information on login flow pages.
|
|
1337
1387
|
*/
|
|
1338
|
-
async function getClientPublicEndpoint(ctx, opts) {
|
|
1339
|
-
const client = await getClient(ctx, opts,
|
|
1388
|
+
async function getClientPublicEndpoint(ctx, opts, clientId) {
|
|
1389
|
+
const client = await getClient(ctx, opts, clientId);
|
|
1340
1390
|
if (!client) throw new APIError("NOT_FOUND", {
|
|
1341
1391
|
error_description: "client not found",
|
|
1342
1392
|
error: "not_found"
|
|
@@ -1572,6 +1622,7 @@ const adminCreateOAuthClient = (opts) => createAuthEndpoint("/admin/oauth2/creat
|
|
|
1572
1622
|
skip_consent: z.boolean().optional(),
|
|
1573
1623
|
enable_end_session: z.boolean().optional(),
|
|
1574
1624
|
require_pkce: z.boolean().optional(),
|
|
1625
|
+
subject_type: z.enum(["public", "pairwise"]).optional(),
|
|
1575
1626
|
metadata: z.record(z.string(), z.unknown()).optional()
|
|
1576
1627
|
}),
|
|
1577
1628
|
metadata: {
|
|
@@ -1899,9 +1950,22 @@ const getOAuthClientPublic = (opts) => createAuthEndpoint("/oauth2/public-client
|
|
|
1899
1950
|
method: "GET",
|
|
1900
1951
|
use: [sessionMiddleware],
|
|
1901
1952
|
query: z.object({ client_id: z.string() }),
|
|
1902
|
-
metadata: { openapi: { description: "Gets
|
|
1953
|
+
metadata: { openapi: { description: "Gets publicly available client fields" } }
|
|
1903
1954
|
}, async (ctx) => {
|
|
1904
|
-
|
|
1955
|
+
const clientId = ctx.query.client_id;
|
|
1956
|
+
return getClientPublicEndpoint(ctx, opts, clientId);
|
|
1957
|
+
});
|
|
1958
|
+
const getOAuthClientPublicPrelogin = (opts) => createAuthEndpoint("/oauth2/public-client-prelogin", {
|
|
1959
|
+
method: "POST",
|
|
1960
|
+
use: [publicSessionMiddleware(opts)],
|
|
1961
|
+
body: z.object({
|
|
1962
|
+
client_id: z.string(),
|
|
1963
|
+
oauth_query: z.string().optional()
|
|
1964
|
+
}),
|
|
1965
|
+
metadata: { openapi: { description: "Gets publicly available client fields (prior to login)" } }
|
|
1966
|
+
}, async (ctx) => {
|
|
1967
|
+
const clientId = ctx.body.client_id;
|
|
1968
|
+
return getClientPublicEndpoint(ctx, opts, clientId);
|
|
1905
1969
|
});
|
|
1906
1970
|
const getOAuthClients = (opts) => createAuthEndpoint("/oauth2/get-clients", {
|
|
1907
1971
|
method: "GET",
|
|
@@ -2366,6 +2430,10 @@ const schema = {
|
|
|
2366
2430
|
type: "boolean",
|
|
2367
2431
|
required: false
|
|
2368
2432
|
},
|
|
2433
|
+
subjectType: {
|
|
2434
|
+
type: "string",
|
|
2435
|
+
required: false
|
|
2436
|
+
},
|
|
2369
2437
|
scopes: {
|
|
2370
2438
|
type: "string[]",
|
|
2371
2439
|
required: false
|
|
@@ -2662,6 +2730,7 @@ const oauthProvider = (options) => {
|
|
|
2662
2730
|
claims: Array.from(claims),
|
|
2663
2731
|
clientRegistrationAllowedScopes
|
|
2664
2732
|
};
|
|
2733
|
+
if (opts.pairwiseSecret && opts.pairwiseSecret.length < 32) throw new BetterAuthError("pairwiseSecret must be at least 32 characters long for adequate HMAC-SHA256 security");
|
|
2665
2734
|
if (opts.grantTypes && opts.grantTypes.includes("refresh_token") && !opts.grantTypes.includes("authorization_code")) throw new BetterAuthError("refresh_token grant requires authorization_code grant");
|
|
2666
2735
|
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
2736
|
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");
|
|
@@ -2684,15 +2753,11 @@ const oauthProvider = (options) => {
|
|
|
2684
2753
|
},
|
|
2685
2754
|
handler: createAuthMiddleware(async (ctx) => {
|
|
2686
2755
|
const query = ctx.body.oauth_query;
|
|
2687
|
-
|
|
2688
|
-
const
|
|
2689
|
-
const exp = Number(queryParams.get("exp"));
|
|
2756
|
+
if (!await verifyOAuthQueryParams(query, ctx.context.secret)) throw new APIError("BAD_REQUEST", { error: "invalid_signature" });
|
|
2757
|
+
const queryParams = new URLSearchParams(query);
|
|
2690
2758
|
queryParams.delete("sig");
|
|
2691
|
-
queryParams = new URLSearchParams(queryParams);
|
|
2692
|
-
const verifySig = await makeSignature(queryParams.toString(), ctx.context.secret);
|
|
2693
|
-
if (!sig || !constantTimeEqual(sig, verifySig) || /* @__PURE__ */ new Date(exp * 1e3) < /* @__PURE__ */ new Date()) throw new APIError("BAD_REQUEST", { error: "invalid_signature" });
|
|
2694
2759
|
queryParams.delete("exp");
|
|
2695
|
-
await oAuthState.set({ query:
|
|
2760
|
+
await oAuthState.set({ query: queryParams.toString() });
|
|
2696
2761
|
if (ctx.path === "/sign-in/social" || ctx.path === "/sign-in/oauth2") {
|
|
2697
2762
|
if (ctx.body.additionalData?.query) return;
|
|
2698
2763
|
if (!ctx.body.additionalData) ctx.body.additionalData = {};
|
|
@@ -3371,7 +3436,8 @@ const oauthProvider = (options) => {
|
|
|
3371
3436
|
"web",
|
|
3372
3437
|
"native",
|
|
3373
3438
|
"user-agent-based"
|
|
3374
|
-
]).optional()
|
|
3439
|
+
]).optional(),
|
|
3440
|
+
subject_type: z.enum(["public", "pairwise"]).optional()
|
|
3375
3441
|
}),
|
|
3376
3442
|
metadata: { openapi: {
|
|
3377
3443
|
description: "Register an OAuth2 application",
|
|
@@ -3515,6 +3581,7 @@ const oauthProvider = (options) => {
|
|
|
3515
3581
|
createOAuthClient: createOAuthClient(opts),
|
|
3516
3582
|
getOAuthClient: getOAuthClient(opts),
|
|
3517
3583
|
getOAuthClientPublic: getOAuthClientPublic(opts),
|
|
3584
|
+
getOAuthClientPublicPrelogin: getOAuthClientPublicPrelogin(opts),
|
|
3518
3585
|
getOAuthClients: getOAuthClients(opts),
|
|
3519
3586
|
adminUpdateOAuthClient: adminUpdateOAuthClient(opts),
|
|
3520
3587
|
updateOAuthClient: updateOAuthClient(opts),
|
|
@@ -3576,12 +3643,17 @@ function formatErrorURL(url, error, description, state, iss) {
|
|
|
3576
3643
|
return `${url}${url.includes("?") ? "&" : "?"}${searchParams.toString()}`;
|
|
3577
3644
|
}
|
|
3578
3645
|
const handleRedirect = (ctx, uri) => {
|
|
3579
|
-
|
|
3646
|
+
const fromFetch = isBrowserFetchRequest(ctx.request?.headers);
|
|
3647
|
+
const acceptJson = ctx.headers?.get("accept")?.includes("application/json");
|
|
3648
|
+
if (fromFetch || acceptJson) return {
|
|
3580
3649
|
redirect: true,
|
|
3581
3650
|
url: uri.toString()
|
|
3582
3651
|
};
|
|
3583
3652
|
else throw ctx.redirect(uri);
|
|
3584
3653
|
};
|
|
3654
|
+
function redirectWithPromptNoneError(ctx, opts, query, error, description) {
|
|
3655
|
+
return handleRedirect(ctx, formatErrorURL(query.redirect_uri, error, description, query.state, getIssuer(ctx, opts)));
|
|
3656
|
+
}
|
|
3585
3657
|
/**
|
|
3586
3658
|
* Validates that the issuer URL
|
|
3587
3659
|
* - MUST use HTTPS scheme (HTTP allowed for localhost in dev)
|
|
@@ -3633,6 +3705,7 @@ async function authorizeEndpoint(ctx, opts, settings) {
|
|
|
3633
3705
|
if (!query.client_id) throw ctx.redirect(getErrorURL(ctx, "invalid_client", "client_id is required"));
|
|
3634
3706
|
if (!query.response_type) throw ctx.redirect(getErrorURL(ctx, "invalid_request", "response_type is required"));
|
|
3635
3707
|
const promptSet = ctx.query?.prompt ? parsePrompt(ctx.query?.prompt) : void 0;
|
|
3708
|
+
const promptNone = promptSet?.has("none") ?? false;
|
|
3636
3709
|
if (promptSet?.has("select_account") && !opts.selectAccount?.page) throw ctx.redirect(getErrorURL(ctx, `unsupported_prompt_select_account`, "unsupported prompt type"));
|
|
3637
3710
|
if (!(query.response_type === "code")) throw ctx.redirect(getErrorURL(ctx, "unsupported_response_type", "unsupported response type"));
|
|
3638
3711
|
const client = await getClient(ctx, opts, query.client_id);
|
|
@@ -3660,7 +3733,10 @@ async function authorizeEndpoint(ctx, opts, settings) {
|
|
|
3660
3733
|
if (!["S256"].includes(query.code_challenge_method)) throw ctx.redirect(formatErrorURL(query.redirect_uri, "invalid_request", "invalid code_challenge method, only S256 is supported", query.state, getIssuer(ctx, opts)));
|
|
3661
3734
|
}
|
|
3662
3735
|
const session = await getSessionFromCtx(ctx);
|
|
3663
|
-
if (!session || promptSet?.has("login") || promptSet?.has("create"))
|
|
3736
|
+
if (!session || promptSet?.has("login") || promptSet?.has("create")) {
|
|
3737
|
+
if (promptNone) return redirectWithPromptNoneError(ctx, opts, query, "login_required", "authentication required");
|
|
3738
|
+
return redirectWithPromptCode(ctx, opts, promptSet?.has("create") ? "create" : "login");
|
|
3739
|
+
}
|
|
3664
3740
|
if (settings?.isAuthorize && promptSet?.has("select_account")) return redirectWithPromptCode(ctx, opts, "select_account");
|
|
3665
3741
|
if (settings?.isAuthorize && opts.selectAccount) {
|
|
3666
3742
|
if (await opts.selectAccount.shouldRedirect({
|
|
@@ -3668,7 +3744,10 @@ async function authorizeEndpoint(ctx, opts, settings) {
|
|
|
3668
3744
|
user: session.user,
|
|
3669
3745
|
session: session.session,
|
|
3670
3746
|
scopes: requestedScopes
|
|
3671
|
-
}))
|
|
3747
|
+
})) {
|
|
3748
|
+
if (promptNone) return redirectWithPromptNoneError(ctx, opts, query, "account_selection_required", "End-User account selection is required");
|
|
3749
|
+
return redirectWithPromptCode(ctx, opts, "select_account");
|
|
3750
|
+
}
|
|
3672
3751
|
}
|
|
3673
3752
|
if (opts.signup?.shouldRedirect) {
|
|
3674
3753
|
const signupRedirect = await opts.signup.shouldRedirect({
|
|
@@ -3677,7 +3756,10 @@ async function authorizeEndpoint(ctx, opts, settings) {
|
|
|
3677
3756
|
session: session.session,
|
|
3678
3757
|
scopes: requestedScopes
|
|
3679
3758
|
});
|
|
3680
|
-
if (signupRedirect)
|
|
3759
|
+
if (signupRedirect) {
|
|
3760
|
+
if (promptNone) return redirectWithPromptNoneError(ctx, opts, query, "interaction_required", "End-User interaction is required");
|
|
3761
|
+
return redirectWithPromptCode(ctx, opts, "create", typeof signupRedirect === "string" ? signupRedirect : void 0);
|
|
3762
|
+
}
|
|
3681
3763
|
}
|
|
3682
3764
|
if (!settings?.postLogin && opts.postLogin) {
|
|
3683
3765
|
if (await opts.postLogin.shouldRedirect({
|
|
@@ -3685,7 +3767,10 @@ async function authorizeEndpoint(ctx, opts, settings) {
|
|
|
3685
3767
|
user: session.user,
|
|
3686
3768
|
session: session.session,
|
|
3687
3769
|
scopes: requestedScopes
|
|
3688
|
-
}))
|
|
3770
|
+
})) {
|
|
3771
|
+
if (promptNone) return redirectWithPromptNoneError(ctx, opts, query, "interaction_required", "End-User interaction is required");
|
|
3772
|
+
return redirectWithPromptCode(ctx, opts, "post_login");
|
|
3773
|
+
}
|
|
3689
3774
|
}
|
|
3690
3775
|
if (promptSet?.has("consent")) return redirectWithPromptCode(ctx, opts, "consent");
|
|
3691
3776
|
const referenceId = await opts.postLogin?.consentReferenceId?.({
|
|
@@ -3718,7 +3803,10 @@ async function authorizeEndpoint(ctx, opts, settings) {
|
|
|
3718
3803
|
}] : []
|
|
3719
3804
|
]
|
|
3720
3805
|
});
|
|
3721
|
-
if (!consent || !requestedScopes.every((val) => consent.scopes.includes(val)))
|
|
3806
|
+
if (!consent || !requestedScopes.every((val) => consent.scopes.includes(val))) {
|
|
3807
|
+
if (promptNone) return redirectWithPromptNoneError(ctx, opts, query, "consent_required", "End-User consent is required");
|
|
3808
|
+
return redirectWithPromptCode(ctx, opts, "consent");
|
|
3809
|
+
}
|
|
3722
3810
|
return redirectWithAuthorizationCode(ctx, opts, {
|
|
3723
3811
|
query,
|
|
3724
3812
|
clientId: client.clientId,
|
|
@@ -3818,7 +3906,7 @@ function oidcServerMetadata(ctx, opts) {
|
|
|
3818
3906
|
}),
|
|
3819
3907
|
claims_supported: opts?.advertisedMetadata?.claims_supported ?? opts?.claims ?? [],
|
|
3820
3908
|
userinfo_endpoint: `${baseURL}/oauth2/userinfo`,
|
|
3821
|
-
subject_types_supported: ["public"],
|
|
3909
|
+
subject_types_supported: opts.pairwiseSecret ? ["public", "pairwise"] : ["public"],
|
|
3822
3910
|
id_token_signing_alg_values_supported: jwtPluginOptions?.jwks?.keyPairConfig?.alg ? [jwtPluginOptions?.jwks?.keyPairConfig?.alg] : opts.disableJwtPlugin ? ["HS256"] : ["EdDSA"],
|
|
3823
3911
|
end_session_endpoint: `${baseURL}/oauth2/end-session`,
|
|
3824
3912
|
acr_values_supported: ["urn:mace:incommon:iap:bronze"],
|
|
@@ -3826,7 +3914,8 @@ function oidcServerMetadata(ctx, opts) {
|
|
|
3826
3914
|
"login",
|
|
3827
3915
|
"consent",
|
|
3828
3916
|
"create",
|
|
3829
|
-
"select_account"
|
|
3917
|
+
"select_account",
|
|
3918
|
+
"none"
|
|
3830
3919
|
]
|
|
3831
3920
|
};
|
|
3832
3921
|
}
|