@better-auth/core 1.5.0-beta.3 → 1.5.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/.turbo/turbo-build.log +170 -37
- package/dist/api/index.d.mts +188 -1
- package/dist/api/index.mjs +2 -1
- package/dist/context/endpoint-context.d.mts +19 -0
- package/dist/context/endpoint-context.mjs +27 -0
- package/dist/context/index.d.mts +3 -52
- package/dist/context/index.mjs +22 -1
- package/dist/context/request-state.d.mts +27 -0
- package/dist/context/request-state.mjs +45 -0
- package/dist/context/transaction.d.mts +16 -0
- package/dist/context/transaction.mjs +48 -0
- package/dist/db/adapter/factory.d.mts +27 -0
- package/dist/db/adapter/factory.mjs +738 -0
- package/dist/db/adapter/get-default-field-name.d.mts +18 -0
- package/dist/db/adapter/get-default-field-name.mjs +38 -0
- package/dist/db/adapter/get-default-model-name.d.mts +12 -0
- package/dist/db/adapter/get-default-model-name.mjs +32 -0
- package/dist/db/adapter/get-field-attributes.d.mts +29 -0
- package/dist/db/adapter/get-field-attributes.mjs +39 -0
- package/dist/db/adapter/get-field-name.d.mts +18 -0
- package/dist/db/adapter/get-field-name.mjs +33 -0
- package/dist/db/adapter/get-id-field.d.mts +39 -0
- package/dist/db/adapter/get-id-field.mjs +67 -0
- package/dist/db/adapter/get-model-name.d.mts +12 -0
- package/dist/db/adapter/get-model-name.mjs +23 -0
- package/dist/db/adapter/index.d.mts +513 -1
- package/dist/db/adapter/index.mjs +8 -970
- package/dist/db/adapter/types.d.mts +139 -0
- package/dist/db/adapter/utils.d.mts +7 -0
- package/dist/db/adapter/utils.mjs +38 -0
- package/dist/db/get-tables.d.mts +8 -0
- package/dist/{get-tables-CMc_Emww.mjs → db/get-tables.mjs} +1 -1
- package/dist/db/index.d.mts +10 -2
- package/dist/db/index.mjs +7 -60
- package/dist/db/plugin.d.mts +12 -0
- package/dist/db/schema/account.d.mts +26 -0
- package/dist/db/schema/account.mjs +19 -0
- package/dist/db/schema/rate-limit.d.mts +14 -0
- package/dist/db/schema/rate-limit.mjs +11 -0
- package/dist/db/schema/session.d.mts +21 -0
- package/dist/db/schema/session.mjs +14 -0
- package/dist/db/schema/shared.d.mts +10 -0
- package/dist/db/schema/shared.mjs +11 -0
- package/dist/db/schema/user.d.mts +20 -0
- package/dist/db/schema/user.mjs +13 -0
- package/dist/db/schema/verification.d.mts +19 -0
- package/dist/db/schema/verification.mjs +12 -0
- package/dist/db/type.d.mts +143 -0
- package/dist/env/color-depth.d.mts +4 -0
- package/dist/env/color-depth.mjs +88 -0
- package/dist/env/env-impl.d.mts +32 -0
- package/dist/env/env-impl.mjs +82 -0
- package/dist/env/index.d.mts +4 -2
- package/dist/env/index.mjs +3 -1
- package/dist/{index-BRBu0-5h.d.mts → env/logger.d.mts} +1 -35
- package/dist/env/logger.mjs +81 -0
- package/dist/error/codes.d.mts +186 -0
- package/dist/{error-GNtLPYaS.mjs → error/codes.mjs} +2 -29
- package/dist/error/index.d.mts +1 -185
- package/dist/error/index.mjs +28 -3
- package/dist/index.d.mts +7 -1
- package/dist/oauth2/client-credentials-token.d.mts +36 -0
- package/dist/oauth2/client-credentials-token.mjs +54 -0
- package/dist/oauth2/create-authorization-url.d.mts +45 -0
- package/dist/oauth2/create-authorization-url.mjs +42 -0
- package/dist/oauth2/index.d.mts +8 -2
- package/dist/oauth2/index.mjs +6 -2
- package/dist/oauth2/oauth-provider.d.mts +194 -0
- package/dist/oauth2/refresh-access-token.d.mts +36 -0
- package/dist/oauth2/refresh-access-token.mjs +58 -0
- package/dist/oauth2/utils.d.mts +7 -0
- package/dist/oauth2/utils.mjs +27 -0
- package/dist/oauth2/validate-authorization-code.d.mts +55 -0
- package/dist/oauth2/validate-authorization-code.mjs +71 -0
- package/dist/oauth2/verify.d.mts +49 -0
- package/dist/oauth2/verify.mjs +95 -0
- package/dist/social-providers/apple.d.mts +119 -0
- package/dist/social-providers/apple.mjs +102 -0
- package/dist/social-providers/atlassian.d.mts +72 -0
- package/dist/social-providers/atlassian.mjs +83 -0
- package/dist/social-providers/cognito.d.mts +87 -0
- package/dist/social-providers/cognito.mjs +165 -0
- package/dist/social-providers/discord.d.mts +126 -0
- package/dist/social-providers/discord.mjs +64 -0
- package/dist/social-providers/dropbox.d.mts +71 -0
- package/dist/social-providers/dropbox.mjs +75 -0
- package/dist/social-providers/facebook.d.mts +81 -0
- package/dist/social-providers/facebook.mjs +120 -0
- package/dist/social-providers/figma.d.mts +63 -0
- package/dist/social-providers/figma.mjs +84 -0
- package/dist/social-providers/github.d.mts +104 -0
- package/dist/social-providers/github.mjs +80 -0
- package/dist/social-providers/gitlab.d.mts +125 -0
- package/dist/social-providers/gitlab.mjs +82 -0
- package/dist/social-providers/google.d.mts +99 -0
- package/dist/social-providers/google.mjs +108 -0
- package/dist/social-providers/huggingface.d.mts +85 -0
- package/dist/social-providers/huggingface.mjs +75 -0
- package/dist/social-providers/index.d.mts +1723 -1
- package/dist/social-providers/index.mjs +33 -2569
- package/dist/social-providers/kakao.d.mts +163 -0
- package/dist/social-providers/kakao.mjs +72 -0
- package/dist/social-providers/kick.d.mts +75 -0
- package/dist/social-providers/kick.mjs +71 -0
- package/dist/social-providers/line.d.mts +107 -0
- package/dist/social-providers/line.mjs +113 -0
- package/dist/social-providers/linear.d.mts +70 -0
- package/dist/social-providers/linear.mjs +88 -0
- package/dist/social-providers/linkedin.d.mts +69 -0
- package/dist/social-providers/linkedin.mjs +76 -0
- package/dist/social-providers/microsoft-entra-id.d.mts +174 -0
- package/dist/social-providers/microsoft-entra-id.mjs +106 -0
- package/dist/social-providers/naver.d.mts +104 -0
- package/dist/social-providers/naver.mjs +67 -0
- package/dist/social-providers/notion.d.mts +66 -0
- package/dist/social-providers/notion.mjs +75 -0
- package/dist/social-providers/paybin.d.mts +73 -0
- package/dist/social-providers/paybin.mjs +85 -0
- package/dist/social-providers/paypal.d.mts +131 -0
- package/dist/social-providers/paypal.mjs +144 -0
- package/dist/social-providers/polar.d.mts +76 -0
- package/dist/social-providers/polar.mjs +73 -0
- package/dist/social-providers/reddit.d.mts +64 -0
- package/dist/social-providers/reddit.mjs +83 -0
- package/dist/social-providers/roblox.d.mts +72 -0
- package/dist/social-providers/roblox.mjs +59 -0
- package/dist/social-providers/salesforce.d.mts +81 -0
- package/dist/social-providers/salesforce.mjs +91 -0
- package/dist/social-providers/slack.d.mts +85 -0
- package/dist/social-providers/slack.mjs +68 -0
- package/dist/social-providers/spotify.d.mts +65 -0
- package/dist/social-providers/spotify.mjs +71 -0
- package/dist/social-providers/tiktok.d.mts +171 -0
- package/dist/social-providers/tiktok.mjs +62 -0
- package/dist/social-providers/twitch.d.mts +81 -0
- package/dist/social-providers/twitch.mjs +78 -0
- package/dist/social-providers/twitter.d.mts +140 -0
- package/dist/social-providers/twitter.mjs +87 -0
- package/dist/social-providers/vercel.d.mts +64 -0
- package/dist/social-providers/vercel.mjs +61 -0
- package/dist/social-providers/vk.d.mts +72 -0
- package/dist/social-providers/vk.mjs +83 -0
- package/dist/social-providers/zoom.d.mts +173 -0
- package/dist/social-providers/zoom.mjs +72 -0
- package/dist/types/context.d.mts +246 -0
- package/dist/types/cookie.d.mts +23 -0
- package/dist/types/helper.d.mts +8 -0
- package/dist/types/index.d.mts +8 -0
- package/dist/types/init-options.d.mts +1266 -0
- package/dist/types/plugin-client.d.mts +110 -0
- package/dist/types/plugin.d.mts +124 -0
- package/dist/utils/deprecate.d.mts +10 -0
- package/dist/utils/deprecate.mjs +17 -0
- package/dist/utils/{index.d.mts → error-codes.d.mts} +1 -19
- package/dist/utils/error-codes.mjs +11 -0
- package/dist/utils/id.d.mts +4 -0
- package/dist/utils/id.mjs +9 -0
- package/dist/utils/json.d.mts +4 -0
- package/dist/utils/json.mjs +25 -0
- package/dist/utils/string.d.mts +4 -0
- package/dist/utils/string.mjs +7 -0
- package/package.json +10 -7
- package/src/db/adapter/get-id-field.ts +1 -1
- package/src/error/codes.ts +1 -1
- package/src/oauth2/create-authorization-url.ts +1 -1
- package/src/oauth2/oauth-provider.ts +6 -0
- package/src/types/init-options.ts +19 -4
- package/tsdown.config.ts +3 -1
- package/dist/context-BBNwughv.mjs +0 -133
- package/dist/env-DbssmzoK.mjs +0 -245
- package/dist/index-CGr4Qrv8.d.mts +0 -8039
- package/dist/oauth2-BjWM15hm.mjs +0 -326
- package/dist/utils/index.mjs +0 -4
- package/dist/utils-puAL36Bz.mjs +0 -63
- package/src/utils/index.ts +0 -5
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { base64 } from "@better-auth/utils/base64";
|
|
2
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
3
|
+
|
|
4
|
+
//#region src/oauth2/refresh-access-token.ts
|
|
5
|
+
function createRefreshAccessTokenRequest({ refreshToken, options, authentication, extraParams, resource }) {
|
|
6
|
+
const body = new URLSearchParams();
|
|
7
|
+
const headers = {
|
|
8
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
9
|
+
accept: "application/json"
|
|
10
|
+
};
|
|
11
|
+
body.set("grant_type", "refresh_token");
|
|
12
|
+
body.set("refresh_token", refreshToken);
|
|
13
|
+
if (authentication === "basic") {
|
|
14
|
+
const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
|
|
15
|
+
if (primaryClientId) headers["authorization"] = "Basic " + base64.encode(`${primaryClientId}:${options.clientSecret ?? ""}`);
|
|
16
|
+
else headers["authorization"] = "Basic " + base64.encode(`:${options.clientSecret ?? ""}`);
|
|
17
|
+
} else {
|
|
18
|
+
const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
|
|
19
|
+
body.set("client_id", primaryClientId);
|
|
20
|
+
if (options.clientSecret) body.set("client_secret", options.clientSecret);
|
|
21
|
+
}
|
|
22
|
+
if (resource) if (typeof resource === "string") body.append("resource", resource);
|
|
23
|
+
else for (const _resource of resource) body.append("resource", _resource);
|
|
24
|
+
if (extraParams) for (const [key, value] of Object.entries(extraParams)) body.set(key, value);
|
|
25
|
+
return {
|
|
26
|
+
body,
|
|
27
|
+
headers
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async function refreshAccessToken({ refreshToken, options, tokenEndpoint, authentication, extraParams }) {
|
|
31
|
+
const { body, headers } = createRefreshAccessTokenRequest({
|
|
32
|
+
refreshToken,
|
|
33
|
+
options,
|
|
34
|
+
authentication,
|
|
35
|
+
extraParams
|
|
36
|
+
});
|
|
37
|
+
const { data, error } = await betterFetch(tokenEndpoint, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
body,
|
|
40
|
+
headers
|
|
41
|
+
});
|
|
42
|
+
if (error) throw error;
|
|
43
|
+
const tokens = {
|
|
44
|
+
accessToken: data.access_token,
|
|
45
|
+
refreshToken: data.refresh_token,
|
|
46
|
+
tokenType: data.token_type,
|
|
47
|
+
scopes: data.scope?.split(" "),
|
|
48
|
+
idToken: data.id_token
|
|
49
|
+
};
|
|
50
|
+
if (data.expires_in) {
|
|
51
|
+
const now = /* @__PURE__ */ new Date();
|
|
52
|
+
tokens.accessTokenExpiresAt = new Date(now.getTime() + data.expires_in * 1e3);
|
|
53
|
+
}
|
|
54
|
+
return tokens;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
export { createRefreshAccessTokenRequest, refreshAccessToken };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { OAuth2Tokens } from "./oauth-provider.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/oauth2/utils.d.ts
|
|
4
|
+
declare function getOAuth2Tokens(data: Record<string, any>): OAuth2Tokens;
|
|
5
|
+
declare function generateCodeChallenge(codeVerifier: string): Promise<string>;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { generateCodeChallenge, getOAuth2Tokens };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { base64Url } from "@better-auth/utils/base64";
|
|
2
|
+
|
|
3
|
+
//#region src/oauth2/utils.ts
|
|
4
|
+
function getOAuth2Tokens(data) {
|
|
5
|
+
const getDate = (seconds) => {
|
|
6
|
+
const now = /* @__PURE__ */ new Date();
|
|
7
|
+
return new Date(now.getTime() + seconds * 1e3);
|
|
8
|
+
};
|
|
9
|
+
return {
|
|
10
|
+
tokenType: data.token_type,
|
|
11
|
+
accessToken: data.access_token,
|
|
12
|
+
refreshToken: data.refresh_token,
|
|
13
|
+
accessTokenExpiresAt: data.expires_in ? getDate(data.expires_in) : void 0,
|
|
14
|
+
refreshTokenExpiresAt: data.refresh_token_expires_in ? getDate(data.refresh_token_expires_in) : void 0,
|
|
15
|
+
scopes: data?.scope ? typeof data.scope === "string" ? data.scope.split(" ") : data.scope : [],
|
|
16
|
+
idToken: data.id_token,
|
|
17
|
+
raw: data
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async function generateCodeChallenge(codeVerifier) {
|
|
21
|
+
const data = new TextEncoder().encode(codeVerifier);
|
|
22
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
23
|
+
return base64Url.encode(new Uint8Array(hash), { padding: false });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { generateCodeChallenge, getOAuth2Tokens };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { OAuth2Tokens, ProviderOptions } from "./oauth-provider.mjs";
|
|
2
|
+
import "./index.mjs";
|
|
3
|
+
import * as jose0 from "jose";
|
|
4
|
+
|
|
5
|
+
//#region src/oauth2/validate-authorization-code.d.ts
|
|
6
|
+
declare function createAuthorizationCodeRequest({
|
|
7
|
+
code,
|
|
8
|
+
codeVerifier,
|
|
9
|
+
redirectURI,
|
|
10
|
+
options,
|
|
11
|
+
authentication,
|
|
12
|
+
deviceId,
|
|
13
|
+
headers,
|
|
14
|
+
additionalParams,
|
|
15
|
+
resource
|
|
16
|
+
}: {
|
|
17
|
+
code: string;
|
|
18
|
+
redirectURI: string;
|
|
19
|
+
options: Partial<ProviderOptions>;
|
|
20
|
+
codeVerifier?: string | undefined;
|
|
21
|
+
deviceId?: string | undefined;
|
|
22
|
+
authentication?: ("basic" | "post") | undefined;
|
|
23
|
+
headers?: Record<string, string> | undefined;
|
|
24
|
+
additionalParams?: Record<string, string> | undefined;
|
|
25
|
+
resource?: (string | string[]) | undefined;
|
|
26
|
+
}): {
|
|
27
|
+
body: URLSearchParams;
|
|
28
|
+
headers: Record<string, any>;
|
|
29
|
+
};
|
|
30
|
+
declare function validateAuthorizationCode({
|
|
31
|
+
code,
|
|
32
|
+
codeVerifier,
|
|
33
|
+
redirectURI,
|
|
34
|
+
options,
|
|
35
|
+
tokenEndpoint,
|
|
36
|
+
authentication,
|
|
37
|
+
deviceId,
|
|
38
|
+
headers,
|
|
39
|
+
additionalParams,
|
|
40
|
+
resource
|
|
41
|
+
}: {
|
|
42
|
+
code: string;
|
|
43
|
+
redirectURI: string;
|
|
44
|
+
options: Partial<ProviderOptions>;
|
|
45
|
+
codeVerifier?: string | undefined;
|
|
46
|
+
deviceId?: string | undefined;
|
|
47
|
+
tokenEndpoint: string;
|
|
48
|
+
authentication?: ("basic" | "post") | undefined;
|
|
49
|
+
headers?: Record<string, string> | undefined;
|
|
50
|
+
additionalParams?: Record<string, string> | undefined;
|
|
51
|
+
resource?: (string | string[]) | undefined;
|
|
52
|
+
}): Promise<OAuth2Tokens>;
|
|
53
|
+
declare function validateToken(token: string, jwksEndpoint: string): Promise<jose0.JWTVerifyResult<jose0.JWTPayload>>;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { createAuthorizationCodeRequest, validateAuthorizationCode, validateToken };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { getOAuth2Tokens } from "./utils.mjs";
|
|
2
|
+
import "./index.mjs";
|
|
3
|
+
import { base64 } from "@better-auth/utils/base64";
|
|
4
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
5
|
+
import { jwtVerify } from "jose";
|
|
6
|
+
|
|
7
|
+
//#region src/oauth2/validate-authorization-code.ts
|
|
8
|
+
function createAuthorizationCodeRequest({ code, codeVerifier, redirectURI, options, authentication, deviceId, headers, additionalParams = {}, resource }) {
|
|
9
|
+
const body = new URLSearchParams();
|
|
10
|
+
const requestHeaders = {
|
|
11
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
12
|
+
accept: "application/json",
|
|
13
|
+
...headers
|
|
14
|
+
};
|
|
15
|
+
body.set("grant_type", "authorization_code");
|
|
16
|
+
body.set("code", code);
|
|
17
|
+
codeVerifier && body.set("code_verifier", codeVerifier);
|
|
18
|
+
options.clientKey && body.set("client_key", options.clientKey);
|
|
19
|
+
deviceId && body.set("device_id", deviceId);
|
|
20
|
+
body.set("redirect_uri", options.redirectURI || redirectURI);
|
|
21
|
+
if (resource) if (typeof resource === "string") body.append("resource", resource);
|
|
22
|
+
else for (const _resource of resource) body.append("resource", _resource);
|
|
23
|
+
if (authentication === "basic") {
|
|
24
|
+
const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
|
|
25
|
+
requestHeaders["authorization"] = `Basic ${base64.encode(`${primaryClientId}:${options.clientSecret ?? ""}`)}`;
|
|
26
|
+
} else {
|
|
27
|
+
const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
|
|
28
|
+
body.set("client_id", primaryClientId);
|
|
29
|
+
if (options.clientSecret) body.set("client_secret", options.clientSecret);
|
|
30
|
+
}
|
|
31
|
+
for (const [key, value] of Object.entries(additionalParams)) if (!body.has(key)) body.append(key, value);
|
|
32
|
+
return {
|
|
33
|
+
body,
|
|
34
|
+
headers: requestHeaders
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async function validateAuthorizationCode({ code, codeVerifier, redirectURI, options, tokenEndpoint, authentication, deviceId, headers, additionalParams = {}, resource }) {
|
|
38
|
+
const { body, headers: requestHeaders } = createAuthorizationCodeRequest({
|
|
39
|
+
code,
|
|
40
|
+
codeVerifier,
|
|
41
|
+
redirectURI,
|
|
42
|
+
options,
|
|
43
|
+
authentication,
|
|
44
|
+
deviceId,
|
|
45
|
+
headers,
|
|
46
|
+
additionalParams,
|
|
47
|
+
resource
|
|
48
|
+
});
|
|
49
|
+
const { data, error } = await betterFetch(tokenEndpoint, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
body,
|
|
52
|
+
headers: requestHeaders
|
|
53
|
+
});
|
|
54
|
+
if (error) throw error;
|
|
55
|
+
return getOAuth2Tokens(data);
|
|
56
|
+
}
|
|
57
|
+
async function validateToken(token, jwksEndpoint) {
|
|
58
|
+
const { data, error } = await betterFetch(jwksEndpoint, {
|
|
59
|
+
method: "GET",
|
|
60
|
+
headers: { accept: "application/json" }
|
|
61
|
+
});
|
|
62
|
+
if (error) throw error;
|
|
63
|
+
const keys = data["keys"];
|
|
64
|
+
const header = JSON.parse(atob(token.split(".")[0]));
|
|
65
|
+
const key = keys.find((key$1) => key$1.kid === header.kid);
|
|
66
|
+
if (!key) throw new Error("Key not found");
|
|
67
|
+
return await jwtVerify(token, key);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
export { createAuthorizationCodeRequest, validateAuthorizationCode, validateToken };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { JSONWebKeySet, JWTPayload, JWTVerifyOptions } from "jose";
|
|
2
|
+
|
|
3
|
+
//#region src/oauth2/verify.d.ts
|
|
4
|
+
interface VerifyAccessTokenRemote {
|
|
5
|
+
/** Full url of the introspect endpoint. Should end with `/oauth2/introspect` */
|
|
6
|
+
introspectUrl: string;
|
|
7
|
+
/** Client Secret */
|
|
8
|
+
clientId: string;
|
|
9
|
+
/** Client Secret */
|
|
10
|
+
clientSecret: string;
|
|
11
|
+
/**
|
|
12
|
+
* Forces remote verification of a token.
|
|
13
|
+
* This ensures attached session (if applicable)
|
|
14
|
+
* is also still active.
|
|
15
|
+
*/
|
|
16
|
+
force?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Performs local verification of an access token for your APIs.
|
|
20
|
+
*
|
|
21
|
+
* Can also be configured for remote verification.
|
|
22
|
+
*/
|
|
23
|
+
declare function verifyJwsAccessToken(token: string, opts: {
|
|
24
|
+
/** Jwks url or promise of a Jwks */
|
|
25
|
+
jwksFetch: string | (() => Promise<JSONWebKeySet | undefined>);
|
|
26
|
+
/** Verify options */
|
|
27
|
+
verifyOptions: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience" | "issuer">>;
|
|
28
|
+
}): Promise<JWTPayload>;
|
|
29
|
+
declare function getJwks(token: string, opts: {
|
|
30
|
+
/** Jwks url or promise of a Jwks */
|
|
31
|
+
jwksFetch: string | (() => Promise<JSONWebKeySet | undefined>);
|
|
32
|
+
}): Promise<JSONWebKeySet>;
|
|
33
|
+
/**
|
|
34
|
+
* Performs local verification of an access token for your API.
|
|
35
|
+
*
|
|
36
|
+
* Can also be configured for remote verification.
|
|
37
|
+
*/
|
|
38
|
+
declare function verifyAccessToken(token: string, opts: {
|
|
39
|
+
/** Verify options */
|
|
40
|
+
verifyOptions: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience" | "issuer">>;
|
|
41
|
+
/** Scopes to additionally verify. Token must include all but not exact. */
|
|
42
|
+
scopes?: string[];
|
|
43
|
+
/** Required to verify access token locally */
|
|
44
|
+
jwksUrl?: string;
|
|
45
|
+
/** If provided, can verify a token remotely */
|
|
46
|
+
remoteVerify?: VerifyAccessTokenRemote;
|
|
47
|
+
}): Promise<JWTPayload>;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { getJwks, verifyAccessToken, verifyJwsAccessToken };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { logger } from "../env/logger.mjs";
|
|
2
|
+
import "../env/index.mjs";
|
|
3
|
+
import { APIError } from "better-call";
|
|
4
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
5
|
+
import { UnsecuredJWT, createLocalJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
|
|
6
|
+
|
|
7
|
+
//#region src/oauth2/verify.ts
|
|
8
|
+
/** Last fetched jwks used locally in getJwks @internal */
|
|
9
|
+
let jwks;
|
|
10
|
+
/**
|
|
11
|
+
* Performs local verification of an access token for your APIs.
|
|
12
|
+
*
|
|
13
|
+
* Can also be configured for remote verification.
|
|
14
|
+
*/
|
|
15
|
+
async function verifyJwsAccessToken(token, opts) {
|
|
16
|
+
try {
|
|
17
|
+
const jwt = await jwtVerify(token, createLocalJWKSet(await getJwks(token, opts)), opts.verifyOptions);
|
|
18
|
+
if (jwt.payload.azp) jwt.payload.client_id = jwt.payload.azp;
|
|
19
|
+
return jwt.payload;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error instanceof Error) throw error;
|
|
22
|
+
throw new Error(error);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function getJwks(token, opts) {
|
|
26
|
+
let jwtHeaders;
|
|
27
|
+
try {
|
|
28
|
+
jwtHeaders = decodeProtectedHeader(token);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error instanceof Error) throw error;
|
|
31
|
+
throw new Error(error);
|
|
32
|
+
}
|
|
33
|
+
if (!jwtHeaders.kid) throw new Error("Missing jwt kid");
|
|
34
|
+
if (!jwks || !jwks.keys.find((jwk) => jwk.kid === jwtHeaders.kid)) {
|
|
35
|
+
jwks = typeof opts.jwksFetch === "string" ? await betterFetch(opts.jwksFetch, { headers: { Accept: "application/json" } }).then(async (res) => {
|
|
36
|
+
if (res.error) throw new Error(`Jwks failed: ${res.error.message ?? res.error.statusText}`);
|
|
37
|
+
return res.data;
|
|
38
|
+
}) : await opts.jwksFetch();
|
|
39
|
+
if (!jwks) throw new Error("No jwks found");
|
|
40
|
+
}
|
|
41
|
+
return jwks;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Performs local verification of an access token for your API.
|
|
45
|
+
*
|
|
46
|
+
* Can also be configured for remote verification.
|
|
47
|
+
*/
|
|
48
|
+
async function verifyAccessToken(token, opts) {
|
|
49
|
+
let payload;
|
|
50
|
+
if (opts.jwksUrl && !opts?.remoteVerify?.force) try {
|
|
51
|
+
payload = await verifyJwsAccessToken(token, {
|
|
52
|
+
jwksFetch: opts.jwksUrl,
|
|
53
|
+
verifyOptions: opts.verifyOptions
|
|
54
|
+
});
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error instanceof Error) if (error.name === "TypeError" || error.name === "JWSInvalid") {} else if (error.name === "JWTExpired") throw new APIError("UNAUTHORIZED", { message: "token expired" });
|
|
57
|
+
else if (error.name === "JWTInvalid") throw new APIError("UNAUTHORIZED", { message: "token invalid" });
|
|
58
|
+
else throw error;
|
|
59
|
+
else throw new Error(error);
|
|
60
|
+
}
|
|
61
|
+
if (opts?.remoteVerify) {
|
|
62
|
+
const { data: introspect, error: introspectError } = await betterFetch(opts.remoteVerify.introspectUrl, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: {
|
|
65
|
+
Accept: "application/json",
|
|
66
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
67
|
+
},
|
|
68
|
+
body: new URLSearchParams({
|
|
69
|
+
client_id: opts.remoteVerify.clientId,
|
|
70
|
+
client_secret: opts.remoteVerify.clientSecret,
|
|
71
|
+
token,
|
|
72
|
+
token_type_hint: "access_token"
|
|
73
|
+
}).toString()
|
|
74
|
+
});
|
|
75
|
+
if (introspectError) logger.error(`Introspection failed: ${introspectError.message ?? introspectError.statusText}`);
|
|
76
|
+
if (!introspect) throw new APIError("INTERNAL_SERVER_ERROR", { message: "introspection failed" });
|
|
77
|
+
if (!introspect.active) throw new APIError("UNAUTHORIZED", { message: "token inactive" });
|
|
78
|
+
try {
|
|
79
|
+
const unsecuredJwt = new UnsecuredJWT(introspect).encode();
|
|
80
|
+
const { audience: _audience, ...verifyOptions } = opts.verifyOptions;
|
|
81
|
+
payload = (introspect.aud ? UnsecuredJWT.decode(unsecuredJwt, opts.verifyOptions) : UnsecuredJWT.decode(unsecuredJwt, verifyOptions)).payload;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
throw new Error(error);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!payload) throw new APIError("UNAUTHORIZED", { message: `no token payload` });
|
|
87
|
+
if (opts.scopes) {
|
|
88
|
+
const validScopes = new Set(payload.scope?.split(" "));
|
|
89
|
+
for (const sc of opts.scopes) if (!validScopes.has(sc)) throw new APIError("FORBIDDEN", { message: `invalid scope ${sc}` });
|
|
90
|
+
}
|
|
91
|
+
return payload;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//#endregion
|
|
95
|
+
export { getJwks, verifyAccessToken, verifyJwsAccessToken };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
|
|
2
|
+
import "../oauth2/index.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/social-providers/apple.d.ts
|
|
5
|
+
interface AppleProfile {
|
|
6
|
+
/**
|
|
7
|
+
* The subject registered claim identifies the principal that’s the subject
|
|
8
|
+
* of the identity token. Because this token is for your app, the value is
|
|
9
|
+
* the unique identifier for the user.
|
|
10
|
+
*/
|
|
11
|
+
sub: string;
|
|
12
|
+
/**
|
|
13
|
+
* A String value representing the user's email address.
|
|
14
|
+
* The email address is either the user's real email address or the proxy
|
|
15
|
+
* address, depending on their status private email relay service.
|
|
16
|
+
*/
|
|
17
|
+
email: string;
|
|
18
|
+
/**
|
|
19
|
+
* A string or Boolean value that indicates whether the service verifies
|
|
20
|
+
* the email. The value can either be a string ("true" or "false") or a
|
|
21
|
+
* Boolean (true or false). The system may not verify email addresses for
|
|
22
|
+
* Sign in with Apple at Work & School users, and this claim is "false" or
|
|
23
|
+
* false for those users.
|
|
24
|
+
*/
|
|
25
|
+
email_verified: true | "true";
|
|
26
|
+
/**
|
|
27
|
+
* A string or Boolean value that indicates whether the email that the user
|
|
28
|
+
* shares is the proxy address. The value can either be a string ("true" or
|
|
29
|
+
* "false") or a Boolean (true or false).
|
|
30
|
+
*/
|
|
31
|
+
is_private_email: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* An Integer value that indicates whether the user appears to be a real
|
|
34
|
+
* person. Use the value of this claim to mitigate fraud. The possible
|
|
35
|
+
* values are: 0 (or Unsupported), 1 (or Unknown), 2 (or LikelyReal). For
|
|
36
|
+
* more information, see ASUserDetectionStatus. This claim is present only
|
|
37
|
+
* in iOS 14 and later, macOS 11 and later, watchOS 7 and later, tvOS 14
|
|
38
|
+
* and later. The claim isn’t present or supported for web-based apps.
|
|
39
|
+
*/
|
|
40
|
+
real_user_status: number;
|
|
41
|
+
/**
|
|
42
|
+
* The user’s full name in the format provided during the authorization
|
|
43
|
+
* process.
|
|
44
|
+
*/
|
|
45
|
+
name: string;
|
|
46
|
+
/**
|
|
47
|
+
* The URL to the user's profile picture.
|
|
48
|
+
*/
|
|
49
|
+
picture: string;
|
|
50
|
+
user?: AppleNonConformUser | undefined;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* This is the shape of the `user` query parameter that Apple sends the first
|
|
54
|
+
* time the user consents to the app.
|
|
55
|
+
* @see https://developer.apple.com/documentation/signinwithapplerestapi/request-an-authorization-to-the-sign-in-with-apple-server./
|
|
56
|
+
*/
|
|
57
|
+
interface AppleNonConformUser {
|
|
58
|
+
name: {
|
|
59
|
+
firstName: string;
|
|
60
|
+
lastName: string;
|
|
61
|
+
};
|
|
62
|
+
email: string;
|
|
63
|
+
}
|
|
64
|
+
interface AppleOptions extends ProviderOptions<AppleProfile> {
|
|
65
|
+
clientId: string;
|
|
66
|
+
appBundleIdentifier?: string | undefined;
|
|
67
|
+
audience?: (string | string[]) | undefined;
|
|
68
|
+
}
|
|
69
|
+
declare const apple: (options: AppleOptions) => {
|
|
70
|
+
id: "apple";
|
|
71
|
+
name: string;
|
|
72
|
+
createAuthorizationURL({
|
|
73
|
+
state,
|
|
74
|
+
scopes,
|
|
75
|
+
redirectURI
|
|
76
|
+
}: {
|
|
77
|
+
state: string;
|
|
78
|
+
codeVerifier: string;
|
|
79
|
+
scopes?: string[] | undefined;
|
|
80
|
+
redirectURI: string;
|
|
81
|
+
display?: string | undefined;
|
|
82
|
+
loginHint?: string | undefined;
|
|
83
|
+
}): Promise<URL>;
|
|
84
|
+
validateAuthorizationCode: ({
|
|
85
|
+
code,
|
|
86
|
+
codeVerifier,
|
|
87
|
+
redirectURI
|
|
88
|
+
}: {
|
|
89
|
+
code: string;
|
|
90
|
+
redirectURI: string;
|
|
91
|
+
codeVerifier?: string | undefined;
|
|
92
|
+
deviceId?: string | undefined;
|
|
93
|
+
}) => Promise<OAuth2Tokens>;
|
|
94
|
+
verifyIdToken(token: string, nonce: string | undefined): Promise<boolean>;
|
|
95
|
+
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
96
|
+
getUserInfo(token: OAuth2Tokens & {
|
|
97
|
+
user?: {
|
|
98
|
+
name?: {
|
|
99
|
+
firstName?: string;
|
|
100
|
+
lastName?: string;
|
|
101
|
+
};
|
|
102
|
+
email?: string;
|
|
103
|
+
} | undefined;
|
|
104
|
+
}): Promise<{
|
|
105
|
+
user: {
|
|
106
|
+
id: string;
|
|
107
|
+
name?: string;
|
|
108
|
+
email?: string | null;
|
|
109
|
+
image?: string;
|
|
110
|
+
emailVerified: boolean;
|
|
111
|
+
[key: string]: any;
|
|
112
|
+
};
|
|
113
|
+
data: any;
|
|
114
|
+
} | null>;
|
|
115
|
+
options: AppleOptions;
|
|
116
|
+
};
|
|
117
|
+
declare const getApplePublicKey: (kid: string) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
|
|
118
|
+
//#endregion
|
|
119
|
+
export { AppleNonConformUser, AppleOptions, AppleProfile, apple, getApplePublicKey };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { APIError } from "../error/index.mjs";
|
|
2
|
+
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
3
|
+
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
4
|
+
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
5
|
+
import "../oauth2/index.mjs";
|
|
6
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
7
|
+
import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
|
|
8
|
+
|
|
9
|
+
//#region src/social-providers/apple.ts
|
|
10
|
+
const apple = (options) => {
|
|
11
|
+
const tokenEndpoint = "https://appleid.apple.com/auth/token";
|
|
12
|
+
return {
|
|
13
|
+
id: "apple",
|
|
14
|
+
name: "Apple",
|
|
15
|
+
async createAuthorizationURL({ state, scopes, redirectURI }) {
|
|
16
|
+
const _scope = options.disableDefaultScope ? [] : ["email", "name"];
|
|
17
|
+
if (options.scope) _scope.push(...options.scope);
|
|
18
|
+
if (scopes) _scope.push(...scopes);
|
|
19
|
+
return await createAuthorizationURL({
|
|
20
|
+
id: "apple",
|
|
21
|
+
options,
|
|
22
|
+
authorizationEndpoint: "https://appleid.apple.com/auth/authorize",
|
|
23
|
+
scopes: _scope,
|
|
24
|
+
state,
|
|
25
|
+
redirectURI,
|
|
26
|
+
responseMode: "form_post",
|
|
27
|
+
responseType: "code id_token"
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
31
|
+
return validateAuthorizationCode({
|
|
32
|
+
code,
|
|
33
|
+
codeVerifier,
|
|
34
|
+
redirectURI,
|
|
35
|
+
options,
|
|
36
|
+
tokenEndpoint
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
async verifyIdToken(token, nonce) {
|
|
40
|
+
if (options.disableIdTokenSignIn) return false;
|
|
41
|
+
if (options.verifyIdToken) return options.verifyIdToken(token, nonce);
|
|
42
|
+
const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
|
|
43
|
+
if (!kid || !jwtAlg) return false;
|
|
44
|
+
const { payload: jwtClaims } = await jwtVerify(token, await getApplePublicKey(kid), {
|
|
45
|
+
algorithms: [jwtAlg],
|
|
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
|
+
});
|
|
50
|
+
["email_verified", "is_private_email"].forEach((field) => {
|
|
51
|
+
if (jwtClaims[field] !== void 0) jwtClaims[field] = Boolean(jwtClaims[field]);
|
|
52
|
+
});
|
|
53
|
+
if (nonce && jwtClaims.nonce !== nonce) return false;
|
|
54
|
+
return !!jwtClaims;
|
|
55
|
+
},
|
|
56
|
+
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
57
|
+
return refreshAccessToken({
|
|
58
|
+
refreshToken,
|
|
59
|
+
options: {
|
|
60
|
+
clientId: options.clientId,
|
|
61
|
+
clientKey: options.clientKey,
|
|
62
|
+
clientSecret: options.clientSecret
|
|
63
|
+
},
|
|
64
|
+
tokenEndpoint: "https://appleid.apple.com/auth/token"
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
async getUserInfo(token) {
|
|
68
|
+
if (options.getUserInfo) return options.getUserInfo(token);
|
|
69
|
+
if (!token.idToken) return null;
|
|
70
|
+
const profile = decodeJwt(token.idToken);
|
|
71
|
+
if (!profile) return null;
|
|
72
|
+
const name = token.user ? `${token.user.name?.firstName} ${token.user.name?.lastName}` : profile.name || profile.email;
|
|
73
|
+
const emailVerified = typeof profile.email_verified === "boolean" ? profile.email_verified : profile.email_verified === "true";
|
|
74
|
+
const enrichedProfile = {
|
|
75
|
+
...profile,
|
|
76
|
+
name
|
|
77
|
+
};
|
|
78
|
+
const userMap = await options.mapProfileToUser?.(enrichedProfile);
|
|
79
|
+
return {
|
|
80
|
+
user: {
|
|
81
|
+
id: profile.sub,
|
|
82
|
+
name: enrichedProfile.name,
|
|
83
|
+
emailVerified,
|
|
84
|
+
email: profile.email,
|
|
85
|
+
...userMap
|
|
86
|
+
},
|
|
87
|
+
data: enrichedProfile
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
options
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
const getApplePublicKey = async (kid) => {
|
|
94
|
+
const { data } = await betterFetch(`https://appleid.apple.com/auth/keys`);
|
|
95
|
+
if (!data?.keys) throw new APIError("BAD_REQUEST", { message: "Keys not found" });
|
|
96
|
+
const jwk = data.keys.find((key) => key.kid === kid);
|
|
97
|
+
if (!jwk) throw new Error(`JWK with kid ${kid} not found`);
|
|
98
|
+
return await importJWK(jwk, jwk.alg);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
export { apple, getApplePublicKey };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
|
|
2
|
+
import "../oauth2/index.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/social-providers/atlassian.d.ts
|
|
5
|
+
interface AtlassianProfile {
|
|
6
|
+
account_type?: string | undefined;
|
|
7
|
+
account_id: string;
|
|
8
|
+
email?: string | undefined;
|
|
9
|
+
name: string;
|
|
10
|
+
picture?: string | undefined;
|
|
11
|
+
nickname?: string | undefined;
|
|
12
|
+
locale?: string | undefined;
|
|
13
|
+
extended_profile?: {
|
|
14
|
+
job_title?: string;
|
|
15
|
+
organization?: string;
|
|
16
|
+
department?: string;
|
|
17
|
+
location?: string;
|
|
18
|
+
} | undefined;
|
|
19
|
+
}
|
|
20
|
+
interface AtlassianOptions extends ProviderOptions<AtlassianProfile> {
|
|
21
|
+
clientId: string;
|
|
22
|
+
}
|
|
23
|
+
declare const atlassian: (options: AtlassianOptions) => {
|
|
24
|
+
id: "atlassian";
|
|
25
|
+
name: string;
|
|
26
|
+
createAuthorizationURL({
|
|
27
|
+
state,
|
|
28
|
+
scopes,
|
|
29
|
+
codeVerifier,
|
|
30
|
+
redirectURI
|
|
31
|
+
}: {
|
|
32
|
+
state: string;
|
|
33
|
+
codeVerifier: string;
|
|
34
|
+
scopes?: string[] | undefined;
|
|
35
|
+
redirectURI: string;
|
|
36
|
+
display?: string | undefined;
|
|
37
|
+
loginHint?: string | undefined;
|
|
38
|
+
}): Promise<URL>;
|
|
39
|
+
validateAuthorizationCode: ({
|
|
40
|
+
code,
|
|
41
|
+
codeVerifier,
|
|
42
|
+
redirectURI
|
|
43
|
+
}: {
|
|
44
|
+
code: string;
|
|
45
|
+
redirectURI: string;
|
|
46
|
+
codeVerifier?: string | undefined;
|
|
47
|
+
deviceId?: string | undefined;
|
|
48
|
+
}) => Promise<OAuth2Tokens>;
|
|
49
|
+
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
50
|
+
getUserInfo(token: OAuth2Tokens & {
|
|
51
|
+
user?: {
|
|
52
|
+
name?: {
|
|
53
|
+
firstName?: string;
|
|
54
|
+
lastName?: string;
|
|
55
|
+
};
|
|
56
|
+
email?: string;
|
|
57
|
+
} | undefined;
|
|
58
|
+
}): Promise<{
|
|
59
|
+
user: {
|
|
60
|
+
id: string;
|
|
61
|
+
name?: string;
|
|
62
|
+
email?: string | null;
|
|
63
|
+
image?: string;
|
|
64
|
+
emailVerified: boolean;
|
|
65
|
+
[key: string]: any;
|
|
66
|
+
};
|
|
67
|
+
data: any;
|
|
68
|
+
} | null>;
|
|
69
|
+
options: AtlassianOptions;
|
|
70
|
+
};
|
|
71
|
+
//#endregion
|
|
72
|
+
export { AtlassianOptions, AtlassianProfile, atlassian };
|