@better-auth/core 1.7.0-beta.3 → 1.7.0-beta.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/api/index.d.mts +3 -3
- package/dist/context/global.mjs +1 -1
- package/dist/db/adapter/factory.mjs +62 -0
- package/dist/db/adapter/index.d.mts +35 -1
- package/dist/db/adapter/types.d.mts +1 -1
- package/dist/db/get-tables.mjs +3 -3
- package/dist/db/schema/account.d.mts +1 -1
- package/dist/db/schema/account.mjs +1 -1
- package/dist/db/type.d.mts +12 -0
- package/dist/env/env-impl.mjs +1 -1
- package/dist/error/codes.d.mts +6 -0
- package/dist/error/codes.mjs +6 -0
- package/dist/index.d.mts +2 -2
- package/dist/instrumentation/tracer.mjs +1 -1
- package/dist/oauth2/authorization-params.d.mts +12 -0
- package/dist/oauth2/authorization-params.mjs +12 -0
- package/dist/oauth2/basic-credentials.d.mts +30 -0
- package/dist/oauth2/basic-credentials.mjs +64 -0
- package/dist/oauth2/client-assertion.d.mts +38 -22
- package/dist/oauth2/client-assertion.mjs +63 -28
- package/dist/oauth2/client-credentials-token.d.mts +19 -40
- package/dist/oauth2/client-credentials-token.mjs +18 -29
- package/dist/oauth2/create-authorization-url.d.mts +13 -2
- package/dist/oauth2/create-authorization-url.mjs +28 -7
- package/dist/oauth2/index.d.mts +13 -8
- package/dist/oauth2/index.mjs +11 -7
- package/dist/oauth2/oauth-provider.d.mts +149 -11
- package/dist/oauth2/refresh-access-token.d.mts +20 -40
- package/dist/oauth2/refresh-access-token.mjs +20 -33
- package/dist/oauth2/scopes.d.mts +76 -0
- package/dist/oauth2/scopes.mjs +96 -0
- package/dist/oauth2/token-endpoint-auth.d.mts +17 -0
- package/dist/oauth2/token-endpoint-auth.mjs +89 -0
- package/dist/oauth2/utils.d.mts +9 -1
- package/dist/oauth2/utils.mjs +14 -2
- package/dist/oauth2/validate-authorization-code.d.mts +17 -52
- package/dist/oauth2/validate-authorization-code.mjs +17 -30
- package/dist/oauth2/verify-id-token.d.mts +26 -0
- package/dist/oauth2/verify-id-token.mjs +62 -0
- package/dist/oauth2/verify.d.mts +14 -0
- package/dist/oauth2/verify.mjs +38 -12
- package/dist/social-providers/apple.d.mts +18 -20
- package/dist/social-providers/apple.mjs +15 -28
- package/dist/social-providers/atlassian.d.mts +8 -2
- package/dist/social-providers/atlassian.mjs +9 -6
- package/dist/social-providers/cognito.d.mts +29 -3
- package/dist/social-providers/cognito.mjs +30 -34
- package/dist/social-providers/discord.d.mts +8 -2
- package/dist/social-providers/discord.mjs +20 -6
- package/dist/social-providers/dropbox.d.mts +8 -2
- package/dist/social-providers/dropbox.mjs +10 -9
- package/dist/social-providers/facebook.d.mts +24 -3
- package/dist/social-providers/facebook.mjs +51 -24
- package/dist/social-providers/figma.d.mts +8 -2
- package/dist/social-providers/figma.mjs +8 -7
- package/dist/social-providers/github.d.mts +8 -2
- package/dist/social-providers/github.mjs +9 -8
- package/dist/social-providers/gitlab.d.mts +8 -2
- package/dist/social-providers/gitlab.mjs +8 -7
- package/dist/social-providers/google.d.mts +32 -4
- package/dist/social-providers/google.mjs +26 -29
- package/dist/social-providers/huggingface.d.mts +8 -2
- package/dist/social-providers/huggingface.mjs +11 -10
- package/dist/social-providers/index.d.mts +322 -75
- package/dist/social-providers/kakao.d.mts +8 -2
- package/dist/social-providers/kakao.mjs +11 -10
- package/dist/social-providers/kick.d.mts +8 -2
- package/dist/social-providers/kick.mjs +7 -6
- package/dist/social-providers/line.d.mts +11 -3
- package/dist/social-providers/line.mjs +14 -15
- package/dist/social-providers/linear.d.mts +8 -2
- package/dist/social-providers/linear.mjs +7 -6
- package/dist/social-providers/linkedin.d.mts +8 -2
- package/dist/social-providers/linkedin.mjs +12 -11
- package/dist/social-providers/microsoft-entra-id.d.mts +33 -7
- package/dist/social-providers/microsoft-entra-id.mjs +28 -38
- package/dist/social-providers/naver.d.mts +8 -2
- package/dist/social-providers/naver.mjs +7 -6
- package/dist/social-providers/notion.d.mts +8 -2
- package/dist/social-providers/notion.mjs +9 -6
- package/dist/social-providers/paybin.d.mts +8 -2
- package/dist/social-providers/paybin.mjs +12 -11
- package/dist/social-providers/paypal.d.mts +8 -3
- package/dist/social-providers/paypal.mjs +10 -14
- package/dist/social-providers/polar.d.mts +8 -2
- package/dist/social-providers/polar.mjs +11 -10
- package/dist/social-providers/railway.d.mts +8 -2
- package/dist/social-providers/railway.mjs +11 -10
- package/dist/social-providers/reddit.d.mts +8 -2
- package/dist/social-providers/reddit.mjs +11 -9
- package/dist/social-providers/roblox.d.mts +8 -2
- package/dist/social-providers/roblox.mjs +15 -5
- package/dist/social-providers/salesforce.d.mts +8 -2
- package/dist/social-providers/salesforce.mjs +11 -10
- package/dist/social-providers/slack.d.mts +8 -2
- package/dist/social-providers/slack.mjs +18 -15
- package/dist/social-providers/spotify.d.mts +8 -2
- package/dist/social-providers/spotify.mjs +7 -6
- package/dist/social-providers/tiktok.d.mts +8 -2
- package/dist/social-providers/tiktok.mjs +21 -5
- package/dist/social-providers/twitch.d.mts +8 -2
- package/dist/social-providers/twitch.mjs +7 -6
- package/dist/social-providers/twitter.d.mts +7 -2
- package/dist/social-providers/twitter.mjs +11 -10
- package/dist/social-providers/vercel.d.mts +8 -2
- package/dist/social-providers/vercel.mjs +7 -9
- package/dist/social-providers/vk.d.mts +8 -2
- package/dist/social-providers/vk.mjs +7 -6
- package/dist/social-providers/wechat.d.mts +8 -2
- package/dist/social-providers/wechat.mjs +16 -6
- package/dist/social-providers/zoom.d.mts +10 -3
- package/dist/social-providers/zoom.mjs +14 -15
- package/dist/types/context.d.mts +33 -11
- package/dist/types/index.d.mts +1 -1
- package/dist/types/init-options.d.mts +121 -6
- package/dist/utils/ip.d.mts +5 -4
- package/dist/utils/ip.mjs +3 -3
- package/dist/utils/redirect-uri.d.mts +20 -0
- package/dist/utils/redirect-uri.mjs +48 -0
- package/dist/utils/string.d.mts +5 -1
- package/dist/utils/string.mjs +20 -1
- package/dist/utils/url.d.mts +18 -1
- package/dist/utils/url.mjs +30 -1
- package/package.json +13 -12
- package/src/db/adapter/factory.ts +126 -0
- package/src/db/adapter/index.ts +32 -0
- package/src/db/adapter/types.ts +1 -0
- package/src/db/get-tables.ts +8 -3
- package/src/db/schema/account.ts +14 -2
- package/src/db/type.ts +12 -0
- package/src/env/env-impl.ts +1 -2
- package/src/error/codes.ts +6 -0
- package/src/oauth2/authorization-params.ts +28 -0
- package/src/oauth2/basic-credentials.ts +87 -0
- package/src/oauth2/client-assertion.ts +131 -58
- package/src/oauth2/client-credentials-token.ts +48 -72
- package/src/oauth2/create-authorization-url.ts +30 -8
- package/src/oauth2/index.ts +42 -10
- package/src/oauth2/oauth-provider.ts +161 -12
- package/src/oauth2/refresh-access-token.ts +52 -78
- package/src/oauth2/scopes.ts +118 -0
- package/src/oauth2/token-endpoint-auth.ts +221 -0
- package/src/oauth2/utils.ts +21 -5
- package/src/oauth2/validate-authorization-code.ts +55 -85
- package/src/oauth2/verify-id-token.ts +111 -0
- package/src/oauth2/verify.ts +82 -15
- package/src/social-providers/apple.ts +32 -45
- package/src/social-providers/atlassian.ts +20 -9
- package/src/social-providers/cognito.ts +51 -48
- package/src/social-providers/discord.ts +37 -22
- package/src/social-providers/dropbox.ts +20 -12
- package/src/social-providers/facebook.ts +108 -57
- package/src/social-providers/figma.ts +21 -10
- package/src/social-providers/github.ts +16 -10
- package/src/social-providers/gitlab.ts +16 -8
- package/src/social-providers/google.ts +67 -46
- package/src/social-providers/huggingface.ts +20 -9
- package/src/social-providers/kakao.ts +18 -9
- package/src/social-providers/kick.ts +20 -8
- package/src/social-providers/line.ts +39 -37
- package/src/social-providers/linear.ts +20 -7
- package/src/social-providers/linkedin.ts +16 -10
- package/src/social-providers/microsoft-entra-id.ts +66 -64
- package/src/social-providers/naver.ts +14 -7
- package/src/social-providers/notion.ts +20 -7
- package/src/social-providers/paybin.ts +16 -11
- package/src/social-providers/paypal.ts +12 -25
- package/src/social-providers/polar.ts +20 -9
- package/src/social-providers/railway.ts +20 -9
- package/src/social-providers/reddit.ts +22 -10
- package/src/social-providers/roblox.ts +31 -15
- package/src/social-providers/salesforce.ts +21 -10
- package/src/social-providers/slack.ts +31 -16
- package/src/social-providers/spotify.ts +20 -7
- package/src/social-providers/tiktok.ts +32 -13
- package/src/social-providers/twitch.ts +14 -9
- package/src/social-providers/twitter.ts +18 -8
- package/src/social-providers/vercel.ts +24 -11
- package/src/social-providers/vk.ts +20 -7
- package/src/social-providers/wechat.ts +28 -8
- package/src/social-providers/zoom.ts +28 -19
- package/src/types/context.ts +33 -12
- package/src/types/index.ts +7 -0
- package/src/types/init-options.ts +148 -5
- package/src/utils/ip.ts +12 -13
- package/src/utils/redirect-uri.ts +54 -0
- package/src/utils/string.ts +37 -0
- package/src/utils/url.ts +28 -0
|
@@ -1,39 +1,32 @@
|
|
|
1
|
-
import { resolveAssertionParams } from "./client-assertion.mjs";
|
|
2
1
|
import { getOAuth2Tokens } from "./utils.mjs";
|
|
2
|
+
import { applyTokenEndpointAuth } from "./token-endpoint-auth.mjs";
|
|
3
3
|
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
4
|
-
import { base64 } from "@better-auth/utils/base64";
|
|
5
4
|
import { betterFetch } from "@better-fetch/fetch";
|
|
6
5
|
//#region src/oauth2/validate-authorization-code.ts
|
|
7
|
-
async function authorizationCodeRequest({ code, codeVerifier, redirectURI, options, authentication,
|
|
6
|
+
async function authorizationCodeRequest({ code, codeVerifier, redirectURI, options, authentication, tokenEndpointAuth, tokenEndpoint, deviceId, headers, additionalParams = {}, resource }) {
|
|
8
7
|
options = typeof options === "function" ? await options() : options;
|
|
9
|
-
|
|
10
|
-
if (!clientAssertion) throw new Error("private_key_jwt authentication requires a clientAssertion configuration");
|
|
11
|
-
const assertionParams = await resolveAssertionParams({
|
|
12
|
-
clientAssertion,
|
|
13
|
-
clientId: Array.isArray(options.clientId) ? options.clientId[0] : options.clientId,
|
|
14
|
-
tokenEndpoint
|
|
15
|
-
});
|
|
16
|
-
additionalParams = {
|
|
17
|
-
...additionalParams,
|
|
18
|
-
...assertionParams
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
return createAuthorizationCodeRequest({
|
|
8
|
+
const request = buildAuthorizationCodeRequest({
|
|
22
9
|
code,
|
|
23
10
|
codeVerifier,
|
|
24
11
|
redirectURI,
|
|
25
12
|
options,
|
|
26
|
-
authentication,
|
|
27
13
|
deviceId,
|
|
28
14
|
headers,
|
|
29
15
|
additionalParams,
|
|
30
16
|
resource
|
|
31
17
|
});
|
|
18
|
+
await applyTokenEndpointAuth({
|
|
19
|
+
body: request.body,
|
|
20
|
+
headers: request.headers,
|
|
21
|
+
options,
|
|
22
|
+
tokenEndpoint: tokenEndpoint ?? "",
|
|
23
|
+
grantType: "authorization_code",
|
|
24
|
+
tokenEndpointAuth,
|
|
25
|
+
authentication
|
|
26
|
+
});
|
|
27
|
+
return request;
|
|
32
28
|
}
|
|
33
|
-
|
|
34
|
-
* @deprecated use async'd authorizationCodeRequest instead
|
|
35
|
-
*/
|
|
36
|
-
function createAuthorizationCodeRequest({ code, codeVerifier, redirectURI, options, authentication, deviceId, headers, additionalParams = {}, resource }) {
|
|
29
|
+
function buildAuthorizationCodeRequest({ code, codeVerifier, redirectURI, options, deviceId, headers, additionalParams = {}, resource }) {
|
|
37
30
|
const body = new URLSearchParams();
|
|
38
31
|
const requestHeaders = {
|
|
39
32
|
"content-type": "application/x-www-form-urlencoded",
|
|
@@ -48,26 +41,20 @@ function createAuthorizationCodeRequest({ code, codeVerifier, redirectURI, optio
|
|
|
48
41
|
body.set("redirect_uri", options.redirectURI || redirectURI);
|
|
49
42
|
if (resource) if (typeof resource === "string") body.append("resource", resource);
|
|
50
43
|
else for (const _resource of resource) body.append("resource", _resource);
|
|
51
|
-
const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
|
|
52
|
-
if (authentication === "basic") requestHeaders["authorization"] = `Basic ${base64.encode(`${primaryClientId}:${options.clientSecret ?? ""}`)}`;
|
|
53
|
-
else {
|
|
54
|
-
body.set("client_id", primaryClientId);
|
|
55
|
-
if (authentication !== "private_key_jwt" && options.clientSecret) body.set("client_secret", options.clientSecret);
|
|
56
|
-
}
|
|
57
44
|
for (const [key, value] of Object.entries(additionalParams)) if (!body.has(key)) body.append(key, value);
|
|
58
45
|
return {
|
|
59
46
|
body,
|
|
60
47
|
headers: requestHeaders
|
|
61
48
|
};
|
|
62
49
|
}
|
|
63
|
-
async function validateAuthorizationCode({ code, codeVerifier, redirectURI, options, tokenEndpoint, authentication,
|
|
50
|
+
async function validateAuthorizationCode({ code, codeVerifier, redirectURI, options, tokenEndpoint, authentication, tokenEndpointAuth, deviceId, headers, additionalParams = {}, resource }) {
|
|
64
51
|
const { body, headers: requestHeaders } = await authorizationCodeRequest({
|
|
65
52
|
code,
|
|
66
53
|
codeVerifier,
|
|
67
54
|
redirectURI,
|
|
68
55
|
options,
|
|
69
56
|
authentication,
|
|
70
|
-
|
|
57
|
+
tokenEndpointAuth,
|
|
71
58
|
tokenEndpoint,
|
|
72
59
|
deviceId,
|
|
73
60
|
headers,
|
|
@@ -89,4 +76,4 @@ async function validateToken(token, jwksEndpoint, options) {
|
|
|
89
76
|
});
|
|
90
77
|
}
|
|
91
78
|
//#endregion
|
|
92
|
-
export { authorizationCodeRequest,
|
|
79
|
+
export { authorizationCodeRequest, validateAuthorizationCode, validateToken };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { UpstreamProvider } from "./oauth-provider.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/oauth2/verify-id-token.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Whether a provider can verify a client-submitted id_token.
|
|
6
|
+
*
|
|
7
|
+
* A provider supports id_token sign-in when it declares an {@link UpstreamProvider.idToken}
|
|
8
|
+
* verification config, or when the integrator supplies a `verifyIdToken` override on the
|
|
9
|
+
* provider options. A provider whose options set `disableIdTokenSignIn`, or that declares
|
|
10
|
+
* neither, rejects the client id_token sign-in path with `ID_TOKEN_NOT_SUPPORTED`.
|
|
11
|
+
*/
|
|
12
|
+
declare function supportsIdTokenSignIn(provider: UpstreamProvider<any, any>): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Verify a client-submitted id_token against a provider's verification config.
|
|
15
|
+
*
|
|
16
|
+
* This is the single id_token verifier for every social provider. Providers no longer
|
|
17
|
+
* implement their own boolean `verifyIdToken`; they declare an {@link UpstreamProvider.idToken}
|
|
18
|
+
* config and this function performs the cryptographic check. The contract is fail-closed: a
|
|
19
|
+
* provider without a config (and without an integrator `verifyIdToken` override) returns
|
|
20
|
+
* `false`, so a forged token can never be accepted by omission.
|
|
21
|
+
*
|
|
22
|
+
* @returns `true` only when the token is authentic for the provider.
|
|
23
|
+
*/
|
|
24
|
+
declare function verifyProviderIdToken(provider: UpstreamProvider<any, any>, token: string, nonce?: string): Promise<boolean>;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { supportsIdTokenSignIn, verifyProviderIdToken };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { decodeProtectedHeader, jwtVerify } from "jose";
|
|
2
|
+
//#region src/oauth2/verify-id-token.ts
|
|
3
|
+
async function sha256Hex(value) {
|
|
4
|
+
const data = new TextEncoder().encode(value);
|
|
5
|
+
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
6
|
+
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
7
|
+
}
|
|
8
|
+
async function nonceMatches(claimNonce, nonce, comparison = "exact") {
|
|
9
|
+
if (typeof claimNonce !== "string") return false;
|
|
10
|
+
if (claimNonce === nonce) return true;
|
|
11
|
+
if (comparison === "exact-or-sha256") return claimNonce === await sha256Hex(nonce);
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Whether a provider can verify a client-submitted id_token.
|
|
16
|
+
*
|
|
17
|
+
* A provider supports id_token sign-in when it declares an {@link UpstreamProvider.idToken}
|
|
18
|
+
* verification config, or when the integrator supplies a `verifyIdToken` override on the
|
|
19
|
+
* provider options. A provider whose options set `disableIdTokenSignIn`, or that declares
|
|
20
|
+
* neither, rejects the client id_token sign-in path with `ID_TOKEN_NOT_SUPPORTED`.
|
|
21
|
+
*/
|
|
22
|
+
function supportsIdTokenSignIn(provider) {
|
|
23
|
+
const options = provider.options ?? {};
|
|
24
|
+
if (options.disableIdTokenSignIn) return false;
|
|
25
|
+
return Boolean(provider.idToken || options.verifyIdToken);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Verify a client-submitted id_token against a provider's verification config.
|
|
29
|
+
*
|
|
30
|
+
* This is the single id_token verifier for every social provider. Providers no longer
|
|
31
|
+
* implement their own boolean `verifyIdToken`; they declare an {@link UpstreamProvider.idToken}
|
|
32
|
+
* config and this function performs the cryptographic check. The contract is fail-closed: a
|
|
33
|
+
* provider without a config (and without an integrator `verifyIdToken` override) returns
|
|
34
|
+
* `false`, so a forged token can never be accepted by omission.
|
|
35
|
+
*
|
|
36
|
+
* @returns `true` only when the token is authentic for the provider.
|
|
37
|
+
*/
|
|
38
|
+
async function verifyProviderIdToken(provider, token, nonce) {
|
|
39
|
+
const options = provider.options ?? {};
|
|
40
|
+
if (options.disableIdTokenSignIn) return false;
|
|
41
|
+
try {
|
|
42
|
+
if (options.verifyIdToken) return await options.verifyIdToken(token, nonce);
|
|
43
|
+
const config = provider.idToken;
|
|
44
|
+
if (!config) return false;
|
|
45
|
+
if ("verify" in config) return await config.verify(token, nonce);
|
|
46
|
+
if (token.split(".").length !== 3) return config.allowOpaqueToken === true;
|
|
47
|
+
const { alg } = decodeProtectedHeader(token);
|
|
48
|
+
const { payload } = await jwtVerify(token, config.jwks, {
|
|
49
|
+
issuer: config.issuer,
|
|
50
|
+
audience: config.audience,
|
|
51
|
+
algorithms: config.algorithms ?? (alg ? [alg] : void 0),
|
|
52
|
+
maxTokenAge: config.maxTokenAge
|
|
53
|
+
});
|
|
54
|
+
if (nonce && !await nonceMatches(payload.nonce, nonce, config.nonceComparison)) return false;
|
|
55
|
+
if (config.verifyClaims && !config.verifyClaims(payload)) return false;
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { supportsIdTokenSignIn, verifyProviderIdToken };
|
package/dist/oauth2/verify.d.mts
CHANGED
|
@@ -14,6 +14,20 @@ interface VerifyAccessTokenRemote {
|
|
|
14
14
|
* is also still active.
|
|
15
15
|
*/
|
|
16
16
|
force?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Accept introspection responses that omit the `aud` claim even when a
|
|
19
|
+
* required `audience` is configured in `verifyOptions`.
|
|
20
|
+
*
|
|
21
|
+
* By default verification fails closed: if you configure an `audience` and
|
|
22
|
+
* the introspection response has no `aud` (or a mismatching one), the token
|
|
23
|
+
* is rejected. Some authorization servers legitimately omit `aud` from
|
|
24
|
+
* introspection responses (it is OPTIONAL per RFC 7662 §2.2); only enable
|
|
25
|
+
* this if you trust the issuer to bind the token to this resource through
|
|
26
|
+
* another mechanism, as it skips the audience check in that case.
|
|
27
|
+
*
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
allowMissingAudience?: boolean;
|
|
17
31
|
}
|
|
18
32
|
/**
|
|
19
33
|
* Performs local verification of an access token for your APIs.
|
package/dist/oauth2/verify.mjs
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import { logger } from "../env/logger.mjs";
|
|
2
2
|
import { APIError } from "better-call";
|
|
3
|
-
import { UnsecuredJWT, createLocalJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
|
|
3
|
+
import { UnsecuredJWT, createLocalJWKSet, decodeProtectedHeader, errors, jwtVerify } from "jose";
|
|
4
4
|
import { betterFetch } from "@better-fetch/fetch";
|
|
5
5
|
//#region src/oauth2/verify.ts
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const joseInfrastructureErrorCodes = new Set([
|
|
7
|
+
errors.JWKSTimeout.code,
|
|
8
|
+
errors.JWKSInvalid.code,
|
|
9
|
+
errors.JWKSMultipleMatchingKeys.code
|
|
10
|
+
]);
|
|
11
|
+
function isJoseInfrastructureError(error) {
|
|
12
|
+
return joseInfrastructureErrorCodes.has(error.code);
|
|
13
|
+
}
|
|
14
|
+
const jwksCache = /* @__PURE__ */ new Map();
|
|
15
|
+
/**
|
|
16
|
+
* How long a cached JWKS is trusted before it is refetched
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
const JWKS_CACHE_TTL_MS = 300 * 1e3;
|
|
8
21
|
/**
|
|
9
22
|
* Performs local verification of an access token for your APIs.
|
|
10
23
|
*
|
|
@@ -28,15 +41,25 @@ async function getJwks(token, opts) {
|
|
|
28
41
|
if (error instanceof Error) throw error;
|
|
29
42
|
throw new Error(error);
|
|
30
43
|
}
|
|
31
|
-
if (!jwtHeaders.kid) throw new
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
if (!jwtHeaders.kid) throw new APIError("UNAUTHORIZED", { message: "invalid access token" });
|
|
45
|
+
const kid = jwtHeaders.kid;
|
|
46
|
+
const cacheKey = opts.jwksFetch;
|
|
47
|
+
const cached = jwksCache.get(cacheKey);
|
|
48
|
+
const isFresh = cached ? Date.now() - cached.fetchedAt < JWKS_CACHE_TTL_MS : false;
|
|
49
|
+
const hasKid = cached?.jwks.keys.some((jwk) => jwk.kid === kid) ?? false;
|
|
50
|
+
if (!cached || !isFresh || !hasKid) {
|
|
51
|
+
const jwks = typeof opts.jwksFetch === "string" ? await betterFetch(opts.jwksFetch, { headers: { Accept: "application/json" } }).then(async (res) => {
|
|
34
52
|
if (res.error) throw new Error(`Jwks failed: ${res.error.message ?? res.error.statusText}`);
|
|
35
53
|
return res.data;
|
|
36
54
|
}) : await opts.jwksFetch();
|
|
37
55
|
if (!jwks) throw new Error("No jwks found");
|
|
56
|
+
jwksCache.set(cacheKey, {
|
|
57
|
+
jwks,
|
|
58
|
+
fetchedAt: Date.now()
|
|
59
|
+
});
|
|
60
|
+
return jwks;
|
|
38
61
|
}
|
|
39
|
-
return jwks;
|
|
62
|
+
return cached.jwks;
|
|
40
63
|
}
|
|
41
64
|
/**
|
|
42
65
|
* Performs local verification of an access token for your API.
|
|
@@ -51,9 +74,11 @@ async function verifyAccessToken(token, opts) {
|
|
|
51
74
|
verifyOptions: opts.verifyOptions
|
|
52
75
|
});
|
|
53
76
|
} catch (error) {
|
|
54
|
-
if (error instanceof Error) if (error.name === "TypeError" || error.name === "JWSInvalid") {} else if (error
|
|
55
|
-
else if (error
|
|
56
|
-
|
|
77
|
+
if (error instanceof Error) if (error.name === "TypeError" || error.name === "JWSInvalid") {} else if (error instanceof errors.JWTExpired) throw new APIError("UNAUTHORIZED", { message: "token expired" });
|
|
78
|
+
else if (error instanceof errors.JOSEError) {
|
|
79
|
+
if (isJoseInfrastructureError(error)) throw error;
|
|
80
|
+
throw new APIError("UNAUTHORIZED", { message: "invalid access token" });
|
|
81
|
+
} else throw error;
|
|
57
82
|
else throw new Error(error);
|
|
58
83
|
}
|
|
59
84
|
if (opts?.remoteVerify) {
|
|
@@ -75,8 +100,9 @@ async function verifyAccessToken(token, opts) {
|
|
|
75
100
|
if (!introspect.active) throw new APIError("UNAUTHORIZED", { message: "token inactive" });
|
|
76
101
|
try {
|
|
77
102
|
const unsecuredJwt = new UnsecuredJWT(introspect).encode();
|
|
78
|
-
const { audience: _audience, ...
|
|
79
|
-
|
|
103
|
+
const { audience: _audience, ...verifyOptionsNoAudience } = opts.verifyOptions;
|
|
104
|
+
const skipAudience = !introspect.aud && opts.remoteVerify.allowMissingAudience === true;
|
|
105
|
+
payload = UnsecuredJWT.decode(unsecuredJwt, skipAudience ? verifyOptionsNoAudience : opts.verifyOptions).payload;
|
|
80
106
|
} catch (error) {
|
|
81
107
|
throw new Error(error);
|
|
82
108
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
|
|
2
|
+
import * as jose from "jose";
|
|
3
|
+
|
|
2
4
|
//#region src/social-providers/apple.d.ts
|
|
3
5
|
interface AppleProfile {
|
|
4
6
|
/**
|
|
@@ -67,10 +69,12 @@ interface AppleOptions extends ProviderOptions<AppleProfile> {
|
|
|
67
69
|
declare const apple: (options: AppleOptions) => {
|
|
68
70
|
id: "apple";
|
|
69
71
|
name: string;
|
|
72
|
+
callbackPath: string;
|
|
70
73
|
createAuthorizationURL({
|
|
71
74
|
state,
|
|
72
75
|
scopes,
|
|
73
|
-
redirectURI
|
|
76
|
+
redirectURI,
|
|
77
|
+
additionalParams
|
|
74
78
|
}: {
|
|
75
79
|
state: string;
|
|
76
80
|
codeVerifier: string;
|
|
@@ -78,7 +82,11 @@ declare const apple: (options: AppleOptions) => {
|
|
|
78
82
|
redirectURI: string;
|
|
79
83
|
display?: string | undefined;
|
|
80
84
|
loginHint?: string | undefined;
|
|
81
|
-
|
|
85
|
+
additionalParams?: Record<string, string> | undefined;
|
|
86
|
+
}): Promise<{
|
|
87
|
+
url: URL;
|
|
88
|
+
requestedScopes: string[];
|
|
89
|
+
}>;
|
|
82
90
|
validateAuthorizationCode: ({
|
|
83
91
|
code,
|
|
84
92
|
codeVerifier,
|
|
@@ -89,27 +97,17 @@ declare const apple: (options: AppleOptions) => {
|
|
|
89
97
|
codeVerifier?: string | undefined;
|
|
90
98
|
deviceId?: string | undefined;
|
|
91
99
|
}) => Promise<OAuth2Tokens>;
|
|
92
|
-
|
|
100
|
+
idToken: {
|
|
101
|
+
jwks: (header: jose.JWTHeaderParameters) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
|
|
102
|
+
issuer: string;
|
|
103
|
+
audience: string | string[];
|
|
104
|
+
maxTokenAge: string;
|
|
105
|
+
nonceComparison: "exact-or-sha256";
|
|
106
|
+
};
|
|
93
107
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
94
108
|
getUserInfo(token: OAuth2Tokens & {
|
|
95
109
|
user?: {
|
|
96
|
-
name
|
|
97
|
-
* An Integer value that indicates whether the user appears to be a real
|
|
98
|
-
* person. Use the value of this claim to mitigate fraud. The possible
|
|
99
|
-
* values are: 0 (or Unsupported), 1 (or Unknown), 2 (or LikelyReal). For
|
|
100
|
-
* more information, see ASUserDetectionStatus. This claim is present only
|
|
101
|
-
* in iOS 14 and later, macOS 11 and later, watchOS 7 and later, tvOS 14
|
|
102
|
-
* and later. The claim isn’t present or supported for web-based apps.
|
|
103
|
-
*/?
|
|
104
|
-
/**
|
|
105
|
-
* An Integer value that indicates whether the user appears to be a real
|
|
106
|
-
* person. Use the value of this claim to mitigate fraud. The possible
|
|
107
|
-
* values are: 0 (or Unsupported), 1 (or Unknown), 2 (or LikelyReal). For
|
|
108
|
-
* more information, see ASUserDetectionStatus. This claim is present only
|
|
109
|
-
* in iOS 14 and later, macOS 11 and later, watchOS 7 and later, tvOS 14
|
|
110
|
-
* and later. The claim isn’t present or supported for web-based apps.
|
|
111
|
-
*/
|
|
112
|
-
: {
|
|
110
|
+
name?: {
|
|
113
111
|
firstName?: string;
|
|
114
112
|
lastName?: string;
|
|
115
113
|
};
|
|
@@ -1,34 +1,35 @@
|
|
|
1
1
|
import { APIError, BetterAuthError } from "../error/index.mjs";
|
|
2
2
|
import { logger } from "../env/logger.mjs";
|
|
3
|
+
import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
|
|
3
4
|
import { getPrimaryClientId } from "../oauth2/utils.mjs";
|
|
4
5
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
5
6
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
6
7
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
7
|
-
import { decodeJwt,
|
|
8
|
+
import { decodeJwt, importJWK } from "jose";
|
|
8
9
|
import { betterFetch } from "@better-fetch/fetch";
|
|
9
10
|
//#region src/social-providers/apple.ts
|
|
11
|
+
const APPLE_DEFAULT_SCOPES = ["email", "name"];
|
|
10
12
|
const apple = (options) => {
|
|
11
13
|
const tokenEndpoint = "https://appleid.apple.com/auth/token";
|
|
12
14
|
return {
|
|
13
15
|
id: "apple",
|
|
14
16
|
name: "Apple",
|
|
15
|
-
|
|
17
|
+
callbackPath: "/callback/apple",
|
|
18
|
+
async createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
|
|
16
19
|
if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
|
|
17
20
|
logger.error("Client ID and client secret are required for Apple. Make sure to provide them in the options.");
|
|
18
21
|
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
19
22
|
}
|
|
20
|
-
|
|
21
|
-
if (options.scope) _scope.push(...options.scope);
|
|
22
|
-
if (scopes) _scope.push(...scopes);
|
|
23
|
-
return await createAuthorizationURL({
|
|
23
|
+
return createAuthorizationURL({
|
|
24
24
|
id: "apple",
|
|
25
25
|
options,
|
|
26
26
|
authorizationEndpoint: "https://appleid.apple.com/auth/authorize",
|
|
27
|
-
scopes:
|
|
27
|
+
scopes: resolveRequestedScopes(options, APPLE_DEFAULT_SCOPES, scopes),
|
|
28
28
|
state,
|
|
29
29
|
redirectURI,
|
|
30
30
|
responseMode: "form_post",
|
|
31
|
-
responseType: "code id_token"
|
|
31
|
+
responseType: "code id_token",
|
|
32
|
+
additionalParams
|
|
32
33
|
});
|
|
33
34
|
},
|
|
34
35
|
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
@@ -40,26 +41,12 @@ const apple = (options) => {
|
|
|
40
41
|
tokenEndpoint
|
|
41
42
|
});
|
|
42
43
|
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const { payload: jwtClaims } = await jwtVerify(token, await getApplePublicKey(kid), {
|
|
50
|
-
algorithms: [jwtAlg],
|
|
51
|
-
issuer: "https://appleid.apple.com",
|
|
52
|
-
audience: options.audience && options.audience.length ? options.audience : options.appBundleIdentifier ? options.appBundleIdentifier : options.clientId,
|
|
53
|
-
maxTokenAge: "1h"
|
|
54
|
-
});
|
|
55
|
-
["email_verified", "is_private_email"].forEach((field) => {
|
|
56
|
-
if (jwtClaims[field] !== void 0) jwtClaims[field] = Boolean(jwtClaims[field]);
|
|
57
|
-
});
|
|
58
|
-
if (nonce && jwtClaims.nonce !== nonce) return false;
|
|
59
|
-
return !!jwtClaims;
|
|
60
|
-
} catch {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
44
|
+
idToken: {
|
|
45
|
+
jwks: (header) => getApplePublicKey(header.kid),
|
|
46
|
+
issuer: "https://appleid.apple.com",
|
|
47
|
+
audience: options.audience && options.audience.length ? options.audience : options.appBundleIdentifier ? options.appBundleIdentifier : options.clientId,
|
|
48
|
+
maxTokenAge: "1h",
|
|
49
|
+
nonceComparison: "exact-or-sha256"
|
|
63
50
|
},
|
|
64
51
|
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
65
52
|
return refreshAccessToken({
|
|
@@ -21,11 +21,13 @@ interface AtlassianOptions extends ProviderOptions<AtlassianProfile> {
|
|
|
21
21
|
declare const atlassian: (options: AtlassianOptions) => {
|
|
22
22
|
id: "atlassian";
|
|
23
23
|
name: string;
|
|
24
|
+
callbackPath: string;
|
|
24
25
|
createAuthorizationURL({
|
|
25
26
|
state,
|
|
26
27
|
scopes,
|
|
27
28
|
codeVerifier,
|
|
28
|
-
redirectURI
|
|
29
|
+
redirectURI,
|
|
30
|
+
additionalParams
|
|
29
31
|
}: {
|
|
30
32
|
state: string;
|
|
31
33
|
codeVerifier: string;
|
|
@@ -33,7 +35,11 @@ declare const atlassian: (options: AtlassianOptions) => {
|
|
|
33
35
|
redirectURI: string;
|
|
34
36
|
display?: string | undefined;
|
|
35
37
|
loginHint?: string | undefined;
|
|
36
|
-
|
|
38
|
+
additionalParams?: Record<string, string> | undefined;
|
|
39
|
+
}): Promise<{
|
|
40
|
+
url: URL;
|
|
41
|
+
requestedScopes: string[];
|
|
42
|
+
}>;
|
|
37
43
|
validateAuthorizationCode: ({
|
|
38
44
|
code,
|
|
39
45
|
codeVerifier,
|
|
@@ -1,33 +1,36 @@
|
|
|
1
1
|
import { BetterAuthError } from "../error/index.mjs";
|
|
2
2
|
import { logger } from "../env/logger.mjs";
|
|
3
|
+
import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
|
|
3
4
|
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
4
5
|
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
5
6
|
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
6
7
|
import { betterFetch } from "@better-fetch/fetch";
|
|
7
8
|
//#region src/social-providers/atlassian.ts
|
|
9
|
+
const ATLASSIAN_DEFAULT_SCOPES = ["read:jira-user", "offline_access"];
|
|
8
10
|
const atlassian = (options) => {
|
|
9
11
|
const tokenEndpoint = "https://auth.atlassian.com/oauth/token";
|
|
10
12
|
return {
|
|
11
13
|
id: "atlassian",
|
|
12
14
|
name: "Atlassian",
|
|
13
|
-
|
|
15
|
+
callbackPath: "/callback/atlassian",
|
|
16
|
+
async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, additionalParams }) {
|
|
14
17
|
if (!options.clientId || !options.clientSecret) {
|
|
15
18
|
logger.error("Client Id and Secret are required for Atlassian");
|
|
16
19
|
throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
|
|
17
20
|
}
|
|
18
21
|
if (!codeVerifier) throw new BetterAuthError("codeVerifier is required for Atlassian");
|
|
19
|
-
const _scopes = options.disableDefaultScope ? [] : ["read:jira-user", "offline_access"];
|
|
20
|
-
if (options.scope) _scopes.push(...options.scope);
|
|
21
|
-
if (scopes) _scopes.push(...scopes);
|
|
22
22
|
return createAuthorizationURL({
|
|
23
23
|
id: "atlassian",
|
|
24
24
|
options,
|
|
25
25
|
authorizationEndpoint: "https://auth.atlassian.com/authorize",
|
|
26
|
-
scopes:
|
|
26
|
+
scopes: resolveRequestedScopes(options, ATLASSIAN_DEFAULT_SCOPES, scopes),
|
|
27
27
|
state,
|
|
28
28
|
codeVerifier,
|
|
29
29
|
redirectURI,
|
|
30
|
-
additionalParams: {
|
|
30
|
+
additionalParams: {
|
|
31
|
+
...additionalParams ?? {},
|
|
32
|
+
audience: "api.atlassian.com"
|
|
33
|
+
},
|
|
31
34
|
prompt: options.prompt
|
|
32
35
|
});
|
|
33
36
|
},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
|
|
2
|
+
import * as jose from "jose";
|
|
3
|
+
|
|
2
4
|
//#region src/social-providers/cognito.d.ts
|
|
3
5
|
interface CognitoProfile {
|
|
4
6
|
sub: string;
|
|
@@ -30,15 +32,30 @@ interface CognitoOptions extends ProviderOptions<CognitoProfile> {
|
|
|
30
32
|
region: string;
|
|
31
33
|
userPoolId: string;
|
|
32
34
|
requireClientSecret?: boolean | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Skip the Cognito hosted-UI identity-provider picker by preselecting an
|
|
37
|
+
* IdP (maps to the `identity_provider` query parameter on the authorize
|
|
38
|
+
* request). Accepts `"COGNITO"`, a SAML/OIDC provider name configured on
|
|
39
|
+
* the User Pool, or one of the social providers (`"Google"`, `"Facebook"`,
|
|
40
|
+
* `"LoginWithAmazon"`, `"SignInWithApple"`).
|
|
41
|
+
*
|
|
42
|
+
* Per-request overrides via `signIn.social({ additionalParams: { identity_provider } })`
|
|
43
|
+
* take precedence over this value.
|
|
44
|
+
*
|
|
45
|
+
* @see https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html
|
|
46
|
+
*/
|
|
47
|
+
identityProvider?: string | undefined;
|
|
33
48
|
}
|
|
34
49
|
declare const cognito: (options: CognitoOptions) => {
|
|
35
50
|
id: "cognito";
|
|
36
51
|
name: string;
|
|
52
|
+
callbackPath: string;
|
|
37
53
|
createAuthorizationURL({
|
|
38
54
|
state,
|
|
39
55
|
scopes,
|
|
40
56
|
codeVerifier,
|
|
41
|
-
redirectURI
|
|
57
|
+
redirectURI,
|
|
58
|
+
additionalParams
|
|
42
59
|
}: {
|
|
43
60
|
state: string;
|
|
44
61
|
codeVerifier: string;
|
|
@@ -46,7 +63,11 @@ declare const cognito: (options: CognitoOptions) => {
|
|
|
46
63
|
redirectURI: string;
|
|
47
64
|
display?: string | undefined;
|
|
48
65
|
loginHint?: string | undefined;
|
|
49
|
-
|
|
66
|
+
additionalParams?: Record<string, string> | undefined;
|
|
67
|
+
}): Promise<{
|
|
68
|
+
url: URL;
|
|
69
|
+
requestedScopes: string[];
|
|
70
|
+
}>;
|
|
50
71
|
validateAuthorizationCode: ({
|
|
51
72
|
code,
|
|
52
73
|
codeVerifier,
|
|
@@ -58,7 +79,12 @@ declare const cognito: (options: CognitoOptions) => {
|
|
|
58
79
|
deviceId?: string | undefined;
|
|
59
80
|
}) => Promise<OAuth2Tokens>;
|
|
60
81
|
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
61
|
-
|
|
82
|
+
idToken: {
|
|
83
|
+
jwks: (header: jose.JWTHeaderParameters) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
|
|
84
|
+
issuer: string;
|
|
85
|
+
audience: string | string[];
|
|
86
|
+
maxTokenAge: string;
|
|
87
|
+
};
|
|
62
88
|
getUserInfo(token: OAuth2Tokens & {
|
|
63
89
|
user?: {
|
|
64
90
|
name?: {
|