@better-auth/core 1.5.0-beta.13 → 1.5.0-beta.15
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/context/global.mjs +1 -1
- package/dist/db/adapter/factory.mjs +11 -1
- package/dist/db/adapter/factory.mjs.map +1 -1
- package/dist/db/adapter/index.d.mts +4 -2
- package/dist/db/adapter/index.mjs +18 -1
- package/dist/db/adapter/index.mjs.map +1 -0
- package/dist/oauth2/refresh-access-token.mjs +4 -0
- package/dist/oauth2/refresh-access-token.mjs.map +1 -1
- package/dist/oauth2/validate-authorization-code.d.mts +2 -2
- package/dist/social-providers/apple.mjs +2 -2
- package/dist/social-providers/apple.mjs.map +1 -1
- package/dist/social-providers/cognito.mjs +2 -2
- package/dist/social-providers/cognito.mjs.map +1 -1
- package/dist/social-providers/github.mjs +1 -1
- package/dist/social-providers/github.mjs.map +1 -1
- package/dist/social-providers/gitlab.mjs +1 -1
- package/dist/social-providers/gitlab.mjs.map +1 -1
- package/dist/social-providers/huggingface.mjs +1 -1
- package/dist/social-providers/huggingface.mjs.map +1 -1
- package/dist/social-providers/index.d.mts +51 -2
- package/dist/social-providers/index.mjs +3 -1
- package/dist/social-providers/index.mjs.map +1 -1
- package/dist/social-providers/kakao.d.mts +1 -1
- package/dist/social-providers/kakao.mjs +1 -1
- package/dist/social-providers/kakao.mjs.map +1 -1
- package/dist/social-providers/line.mjs +1 -1
- package/dist/social-providers/line.mjs.map +1 -1
- package/dist/social-providers/naver.mjs +1 -1
- package/dist/social-providers/naver.mjs.map +1 -1
- package/dist/social-providers/notion.mjs +1 -1
- package/dist/social-providers/notion.mjs.map +1 -1
- package/dist/social-providers/paybin.mjs +1 -1
- package/dist/social-providers/paybin.mjs.map +1 -1
- package/dist/social-providers/polar.mjs +1 -1
- package/dist/social-providers/polar.mjs.map +1 -1
- package/dist/social-providers/railway.d.mts +68 -0
- package/dist/social-providers/railway.mjs +78 -0
- package/dist/social-providers/railway.mjs.map +1 -0
- package/dist/social-providers/tiktok.mjs +1 -1
- package/dist/social-providers/tiktok.mjs.map +1 -1
- package/dist/social-providers/vercel.mjs +1 -1
- package/dist/social-providers/vercel.mjs.map +1 -1
- package/dist/types/plugin.d.mts +1 -1
- package/package.json +8 -4
- package/src/db/adapter/factory.ts +22 -2
- package/src/db/adapter/index.ts +17 -15
- package/src/oauth2/refresh-access-token.test.ts +90 -0
- package/src/oauth2/refresh-access-token.ts +8 -0
- package/src/oauth2/validate-token.test.ts +1 -13
- package/src/social-providers/apple.ts +3 -3
- package/src/social-providers/cognito.ts +6 -5
- package/src/social-providers/github.ts +1 -1
- package/src/social-providers/gitlab.ts +1 -1
- package/src/social-providers/huggingface.ts +1 -1
- package/src/social-providers/index.ts +3 -0
- package/src/social-providers/kakao.ts +1 -1
- package/src/social-providers/line.ts +1 -1
- package/src/social-providers/naver.ts +1 -1
- package/src/social-providers/notion.ts +1 -1
- package/src/social-providers/paybin.ts +1 -5
- package/src/social-providers/polar.ts +1 -1
- package/src/social-providers/railway.ts +100 -0
- package/src/social-providers/tiktok.ts +2 -1
- package/src/social-providers/vercel.ts +1 -1
- package/src/types/plugin.ts +2 -1
- package/src/utils/deprecate.test.ts +0 -1
- package/.turbo/turbo-build.log +0 -265
- package/tsconfig.json +0 -7
- package/tsdown.config.ts +0 -35
- package/vitest.config.ts +0 -3
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
|
|
2
|
+
import "../oauth2/index.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/social-providers/railway.d.ts
|
|
5
|
+
interface RailwayProfile {
|
|
6
|
+
/** The user's unique ID (OAuth `sub` claim). */
|
|
7
|
+
sub: string;
|
|
8
|
+
/** The user's email address. */
|
|
9
|
+
email: string;
|
|
10
|
+
/** The user's display name. */
|
|
11
|
+
name: string;
|
|
12
|
+
/** URL of the user's profile picture. */
|
|
13
|
+
picture: string;
|
|
14
|
+
}
|
|
15
|
+
interface RailwayOptions extends ProviderOptions<RailwayProfile> {
|
|
16
|
+
clientId: string;
|
|
17
|
+
}
|
|
18
|
+
declare const railway: (options: RailwayOptions) => {
|
|
19
|
+
id: "railway";
|
|
20
|
+
name: string;
|
|
21
|
+
createAuthorizationURL({
|
|
22
|
+
state,
|
|
23
|
+
scopes,
|
|
24
|
+
codeVerifier,
|
|
25
|
+
redirectURI
|
|
26
|
+
}: {
|
|
27
|
+
state: string;
|
|
28
|
+
codeVerifier: string;
|
|
29
|
+
scopes?: string[] | undefined;
|
|
30
|
+
redirectURI: string;
|
|
31
|
+
display?: string | undefined;
|
|
32
|
+
loginHint?: string | undefined;
|
|
33
|
+
}): Promise<URL>;
|
|
34
|
+
validateAuthorizationCode: ({
|
|
35
|
+
code,
|
|
36
|
+
codeVerifier,
|
|
37
|
+
redirectURI
|
|
38
|
+
}: {
|
|
39
|
+
code: string;
|
|
40
|
+
redirectURI: string;
|
|
41
|
+
codeVerifier?: string | undefined;
|
|
42
|
+
deviceId?: string | undefined;
|
|
43
|
+
}) => Promise<OAuth2Tokens>;
|
|
44
|
+
refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
|
|
45
|
+
getUserInfo(token: OAuth2Tokens & {
|
|
46
|
+
user?: {
|
|
47
|
+
name?: {
|
|
48
|
+
firstName?: string;
|
|
49
|
+
lastName?: string;
|
|
50
|
+
};
|
|
51
|
+
email?: string;
|
|
52
|
+
} | undefined;
|
|
53
|
+
}): Promise<{
|
|
54
|
+
user: {
|
|
55
|
+
id: string;
|
|
56
|
+
name?: string;
|
|
57
|
+
email?: string | null;
|
|
58
|
+
image?: string;
|
|
59
|
+
emailVerified: boolean;
|
|
60
|
+
[key: string]: any;
|
|
61
|
+
};
|
|
62
|
+
data: any;
|
|
63
|
+
} | null>;
|
|
64
|
+
options: RailwayOptions;
|
|
65
|
+
};
|
|
66
|
+
//#endregion
|
|
67
|
+
export { RailwayOptions, RailwayProfile, railway };
|
|
68
|
+
//# sourceMappingURL=railway.d.mts.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
|
|
2
|
+
import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
|
|
3
|
+
import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
|
|
4
|
+
import "../oauth2/index.mjs";
|
|
5
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
6
|
+
|
|
7
|
+
//#region src/social-providers/railway.ts
|
|
8
|
+
const authorizationEndpoint = "https://backboard.railway.com/oauth/auth";
|
|
9
|
+
const tokenEndpoint = "https://backboard.railway.com/oauth/token";
|
|
10
|
+
const userinfoEndpoint = "https://backboard.railway.com/oauth/me";
|
|
11
|
+
const railway = (options) => {
|
|
12
|
+
return {
|
|
13
|
+
id: "railway",
|
|
14
|
+
name: "Railway",
|
|
15
|
+
createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
|
|
16
|
+
const _scopes = options.disableDefaultScope ? [] : [
|
|
17
|
+
"openid",
|
|
18
|
+
"email",
|
|
19
|
+
"profile"
|
|
20
|
+
];
|
|
21
|
+
if (options.scope) _scopes.push(...options.scope);
|
|
22
|
+
if (scopes) _scopes.push(...scopes);
|
|
23
|
+
return createAuthorizationURL({
|
|
24
|
+
id: "railway",
|
|
25
|
+
options,
|
|
26
|
+
authorizationEndpoint,
|
|
27
|
+
scopes: _scopes,
|
|
28
|
+
state,
|
|
29
|
+
codeVerifier,
|
|
30
|
+
redirectURI
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
|
34
|
+
return validateAuthorizationCode({
|
|
35
|
+
code,
|
|
36
|
+
codeVerifier,
|
|
37
|
+
redirectURI,
|
|
38
|
+
options,
|
|
39
|
+
tokenEndpoint,
|
|
40
|
+
authentication: "basic"
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
|
|
44
|
+
return refreshAccessToken({
|
|
45
|
+
refreshToken,
|
|
46
|
+
options: {
|
|
47
|
+
clientId: options.clientId,
|
|
48
|
+
clientKey: options.clientKey,
|
|
49
|
+
clientSecret: options.clientSecret
|
|
50
|
+
},
|
|
51
|
+
tokenEndpoint,
|
|
52
|
+
authentication: "basic"
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
async getUserInfo(token) {
|
|
56
|
+
if (options.getUserInfo) return options.getUserInfo(token);
|
|
57
|
+
const { data: profile, error } = await betterFetch(userinfoEndpoint, { headers: { authorization: `Bearer ${token.accessToken}` } });
|
|
58
|
+
if (error || !profile) return null;
|
|
59
|
+
const userMap = await options.mapProfileToUser?.(profile);
|
|
60
|
+
return {
|
|
61
|
+
user: {
|
|
62
|
+
id: profile.sub,
|
|
63
|
+
name: profile.name,
|
|
64
|
+
email: profile.email,
|
|
65
|
+
image: profile.picture,
|
|
66
|
+
emailVerified: false,
|
|
67
|
+
...userMap
|
|
68
|
+
},
|
|
69
|
+
data: profile
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
options
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
export { railway };
|
|
78
|
+
//# sourceMappingURL=railway.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"railway.mjs","names":[],"sources":["../../src/social-providers/railway.ts"],"sourcesContent":["import { betterFetch } from \"@better-fetch/fetch\";\nimport type { OAuthProvider, ProviderOptions } from \"../oauth2\";\nimport {\n\tcreateAuthorizationURL,\n\trefreshAccessToken,\n\tvalidateAuthorizationCode,\n} from \"../oauth2\";\n\nconst authorizationEndpoint = \"https://backboard.railway.com/oauth/auth\";\nconst tokenEndpoint = \"https://backboard.railway.com/oauth/token\";\nconst userinfoEndpoint = \"https://backboard.railway.com/oauth/me\";\n\nexport interface RailwayProfile {\n\t/** The user's unique ID (OAuth `sub` claim). */\n\tsub: string;\n\t/** The user's email address. */\n\temail: string;\n\t/** The user's display name. */\n\tname: string;\n\t/** URL of the user's profile picture. */\n\tpicture: string;\n}\n\nexport interface RailwayOptions extends ProviderOptions<RailwayProfile> {\n\tclientId: string;\n}\n\nexport const railway = (options: RailwayOptions) => {\n\treturn {\n\t\tid: \"railway\",\n\t\tname: \"Railway\",\n\t\tcreateAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {\n\t\t\tconst _scopes = options.disableDefaultScope\n\t\t\t\t? []\n\t\t\t\t: [\"openid\", \"email\", \"profile\"];\n\t\t\tif (options.scope) _scopes.push(...options.scope);\n\t\t\tif (scopes) _scopes.push(...scopes);\n\t\t\treturn createAuthorizationURL({\n\t\t\t\tid: \"railway\",\n\t\t\t\toptions,\n\t\t\t\tauthorizationEndpoint,\n\t\t\t\tscopes: _scopes,\n\t\t\t\tstate,\n\t\t\t\tcodeVerifier,\n\t\t\t\tredirectURI,\n\t\t\t});\n\t\t},\n\t\tvalidateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {\n\t\t\treturn validateAuthorizationCode({\n\t\t\t\tcode,\n\t\t\t\tcodeVerifier,\n\t\t\t\tredirectURI,\n\t\t\t\toptions,\n\t\t\t\ttokenEndpoint,\n\t\t\t\tauthentication: \"basic\",\n\t\t\t});\n\t\t},\n\t\trefreshAccessToken: options.refreshAccessToken\n\t\t\t? options.refreshAccessToken\n\t\t\t: async (refreshToken) => {\n\t\t\t\t\treturn refreshAccessToken({\n\t\t\t\t\t\trefreshToken,\n\t\t\t\t\t\toptions: {\n\t\t\t\t\t\t\tclientId: options.clientId,\n\t\t\t\t\t\t\tclientKey: options.clientKey,\n\t\t\t\t\t\t\tclientSecret: options.clientSecret,\n\t\t\t\t\t\t},\n\t\t\t\t\t\ttokenEndpoint,\n\t\t\t\t\t\tauthentication: \"basic\",\n\t\t\t\t\t});\n\t\t\t\t},\n\t\tasync getUserInfo(token) {\n\t\t\tif (options.getUserInfo) {\n\t\t\t\treturn options.getUserInfo(token);\n\t\t\t}\n\t\t\tconst { data: profile, error } = await betterFetch<RailwayProfile>(\n\t\t\t\tuserinfoEndpoint,\n\t\t\t\t{ headers: { authorization: `Bearer ${token.accessToken}` } },\n\t\t\t);\n\t\t\tif (error || !profile) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst userMap = await options.mapProfileToUser?.(profile);\n\t\t\t// Railway does not provide an email_verified claim.\n\t\t\t// We default to false for security consistency.\n\t\t\treturn {\n\t\t\t\tuser: {\n\t\t\t\t\tid: profile.sub,\n\t\t\t\t\tname: profile.name,\n\t\t\t\t\temail: profile.email,\n\t\t\t\t\timage: profile.picture,\n\t\t\t\t\temailVerified: false,\n\t\t\t\t\t...userMap,\n\t\t\t\t},\n\t\t\t\tdata: profile,\n\t\t\t};\n\t\t},\n\t\toptions,\n\t} satisfies OAuthProvider<RailwayProfile>;\n};\n"],"mappings":";;;;;;;AAQA,MAAM,wBAAwB;AAC9B,MAAM,gBAAgB;AACtB,MAAM,mBAAmB;AAiBzB,MAAa,WAAW,YAA4B;AACnD,QAAO;EACN,IAAI;EACJ,MAAM;EACN,uBAAuB,EAAE,OAAO,QAAQ,cAAc,eAAe;GACpE,MAAM,UAAU,QAAQ,sBACrB,EAAE,GACF;IAAC;IAAU;IAAS;IAAU;AACjC,OAAI,QAAQ,MAAO,SAAQ,KAAK,GAAG,QAAQ,MAAM;AACjD,OAAI,OAAQ,SAAQ,KAAK,GAAG,OAAO;AACnC,UAAO,uBAAuB;IAC7B,IAAI;IACJ;IACA;IACA,QAAQ;IACR;IACA;IACA;IACA,CAAC;;EAEH,2BAA2B,OAAO,EAAE,MAAM,cAAc,kBAAkB;AACzE,UAAO,0BAA0B;IAChC;IACA;IACA;IACA;IACA;IACA,gBAAgB;IAChB,CAAC;;EAEH,oBAAoB,QAAQ,qBACzB,QAAQ,qBACR,OAAO,iBAAiB;AACxB,UAAO,mBAAmB;IACzB;IACA,SAAS;KACR,UAAU,QAAQ;KAClB,WAAW,QAAQ;KACnB,cAAc,QAAQ;KACtB;IACD;IACA,gBAAgB;IAChB,CAAC;;EAEL,MAAM,YAAY,OAAO;AACxB,OAAI,QAAQ,YACX,QAAO,QAAQ,YAAY,MAAM;GAElC,MAAM,EAAE,MAAM,SAAS,UAAU,MAAM,YACtC,kBACA,EAAE,SAAS,EAAE,eAAe,UAAU,MAAM,eAAe,EAAE,CAC7D;AACD,OAAI,SAAS,CAAC,QACb,QAAO;GAER,MAAM,UAAU,MAAM,QAAQ,mBAAmB,QAAQ;AAGzD,UAAO;IACN,MAAM;KACL,IAAI,QAAQ;KACZ,MAAM,QAAQ;KACd,OAAO,QAAQ;KACf,OAAO,QAAQ;KACf,eAAe;KACf,GAAG;KACH;IACD,MAAM;IACN;;EAEF;EACA"}
|
|
@@ -47,7 +47,7 @@ const tiktok = (options) => {
|
|
|
47
47
|
user: {
|
|
48
48
|
email: profile.data.user.email || profile.data.user.username,
|
|
49
49
|
id: profile.data.user.open_id,
|
|
50
|
-
name: profile.data.user.display_name || profile.data.user.username,
|
|
50
|
+
name: profile.data.user.display_name || profile.data.user.username || "",
|
|
51
51
|
image: profile.data.user.avatar_large_url,
|
|
52
52
|
emailVerified: false
|
|
53
53
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tiktok.mjs","names":[],"sources":["../../src/social-providers/tiktok.ts"],"sourcesContent":["import { betterFetch } from \"@better-fetch/fetch\";\nimport type { OAuthProvider, ProviderOptions } from \"../oauth2\";\nimport { refreshAccessToken, validateAuthorizationCode } from \"../oauth2\";\n\n/**\n * [More info](https://developers.tiktok.com/doc/tiktok-api-v2-get-user-info/)\n */\nexport interface TiktokProfile extends Record<string, any> {\n\tdata: {\n\t\tuser: {\n\t\t\t/**\n\t\t\t * The unique identification of the user in the current application.Open id\n\t\t\t * for the client.\n\t\t\t *\n\t\t\t * To return this field, add `fields=open_id` in the user profile request's query parameter.\n\t\t\t */\n\t\t\topen_id: string;\n\t\t\t/**\n\t\t\t * The unique identification of the user across different apps for the same developer.\n\t\t\t * For example, if a partner has X number of clients,\n\t\t\t * it will get X number of open_id for the same TikTok user,\n\t\t\t * but one persistent union_id for the particular user.\n\t\t\t *\n\t\t\t * To return this field, add `fields=union_id` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tunion_id?: string | undefined;\n\t\t\t/**\n\t\t\t * User's profile image.\n\t\t\t *\n\t\t\t * To return this field, add `fields=avatar_url` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tavatar_url?: string | undefined;\n\t\t\t/**\n\t\t\t * User`s profile image in 100x100 size.\n\t\t\t *\n\t\t\t * To return this field, add `fields=avatar_url_100` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tavatar_url_100?: string | undefined;\n\t\t\t/**\n\t\t\t * User's profile image with higher resolution\n\t\t\t *\n\t\t\t * To return this field, add `fields=avatar_url_100` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tavatar_large_url: string;\n\t\t\t/**\n\t\t\t * User's profile name\n\t\t\t *\n\t\t\t * To return this field, add `fields=display_name` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tdisplay_name: string;\n\t\t\t/**\n\t\t\t * User's username.\n\t\t\t *\n\t\t\t * To return this field, add `fields=username` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tusername: string;\n\t\t\t/** @note Email is currently unsupported by TikTok */\n\t\t\temail?: string | undefined;\n\t\t\t/**\n\t\t\t * User's bio description if there is a valid one.\n\t\t\t *\n\t\t\t * To return this field, add `fields=bio_description` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tbio_description?: string | undefined;\n\t\t\t/**\n\t\t\t * The link to user's TikTok profile page.\n\t\t\t *\n\t\t\t * To return this field, add `fields=profile_deep_link` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tprofile_deep_link?: string | undefined;\n\t\t\t/**\n\t\t\t * Whether TikTok has provided a verified badge to the account after confirming\n\t\t\t * that it belongs to the user it represents.\n\t\t\t *\n\t\t\t * To return this field, add `fields=is_verified` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tis_verified?: boolean | undefined;\n\t\t\t/**\n\t\t\t * User's followers count.\n\t\t\t *\n\t\t\t * To return this field, add `fields=follower_count` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tfollower_count?: number | undefined;\n\t\t\t/**\n\t\t\t * The number of accounts that the user is following.\n\t\t\t *\n\t\t\t * To return this field, add `fields=following_count` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tfollowing_count?: number | undefined;\n\t\t\t/**\n\t\t\t * The total number of likes received by the user across all of their videos.\n\t\t\t *\n\t\t\t * To return this field, add `fields=likes_count` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tlikes_count?: number | undefined;\n\t\t\t/**\n\t\t\t * The total number of publicly posted videos by the user.\n\t\t\t *\n\t\t\t * To return this field, add `fields=video_count` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tvideo_count?: number | undefined;\n\t\t};\n\t};\n\terror?:\n\t\t| {\n\t\t\t\t/**\n\t\t\t\t * The error category in string.\n\t\t\t\t */\n\t\t\t\tcode?: string;\n\t\t\t\t/**\n\t\t\t\t * The error message in string.\n\t\t\t\t */\n\t\t\t\tmessage?: string;\n\t\t\t\t/**\n\t\t\t\t * The error message in string.\n\t\t\t\t */\n\t\t\t\tlog_id?: string;\n\t\t }\n\t\t| undefined;\n}\n\nexport interface TiktokOptions extends ProviderOptions {\n\t// Client ID is not used in TikTok, we delete it from the options\n\tclientId?: never | undefined;\n\tclientSecret: string;\n\tclientKey: string;\n}\n\nexport const tiktok = (options: TiktokOptions) => {\n\treturn {\n\t\tid: \"tiktok\",\n\t\tname: \"TikTok\",\n\t\tcreateAuthorizationURL({ state, scopes, redirectURI }) {\n\t\t\tconst _scopes = options.disableDefaultScope ? [] : [\"user.info.profile\"];\n\t\t\tif (options.scope) _scopes.push(...options.scope);\n\t\t\tif (scopes) _scopes.push(...scopes);\n\t\t\treturn new URL(\n\t\t\t\t`https://www.tiktok.com/v2/auth/authorize?scope=${_scopes.join(\n\t\t\t\t\t\",\",\n\t\t\t\t)}&response_type=code&client_key=${options.clientKey}&redirect_uri=${encodeURIComponent(\n\t\t\t\t\toptions.redirectURI || redirectURI,\n\t\t\t\t)}&state=${state}`,\n\t\t\t);\n\t\t},\n\n\t\tvalidateAuthorizationCode: async ({ code, redirectURI }) => {\n\t\t\treturn validateAuthorizationCode({\n\t\t\t\tcode,\n\t\t\t\tredirectURI: options.redirectURI || redirectURI,\n\t\t\t\toptions: {\n\t\t\t\t\tclientKey: options.clientKey,\n\t\t\t\t\tclientSecret: options.clientSecret,\n\t\t\t\t},\n\t\t\t\ttokenEndpoint: \"https://open.tiktokapis.com/v2/oauth/token/\",\n\t\t\t});\n\t\t},\n\t\trefreshAccessToken: options.refreshAccessToken\n\t\t\t? options.refreshAccessToken\n\t\t\t: async (refreshToken) => {\n\t\t\t\t\treturn refreshAccessToken({\n\t\t\t\t\t\trefreshToken,\n\t\t\t\t\t\toptions: {\n\t\t\t\t\t\t\tclientSecret: options.clientSecret,\n\t\t\t\t\t\t},\n\t\t\t\t\t\ttokenEndpoint: \"https://open.tiktokapis.com/v2/oauth/token/\",\n\t\t\t\t\t\tauthentication: \"post\",\n\t\t\t\t\t\textraParams: {\n\t\t\t\t\t\t\tclient_key: options.clientKey,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t},\n\t\tasync getUserInfo(token) {\n\t\t\tif (options.getUserInfo) {\n\t\t\t\treturn options.getUserInfo(token);\n\t\t\t}\n\n\t\t\tconst fields = [\n\t\t\t\t\"open_id\",\n\t\t\t\t\"avatar_large_url\",\n\t\t\t\t\"display_name\",\n\t\t\t\t\"username\",\n\t\t\t];\n\t\t\tconst { data: profile, error } = await betterFetch<TiktokProfile>(\n\t\t\t\t`https://open.tiktokapis.com/v2/user/info/?fields=${fields.join(\",\")}`,\n\t\t\t\t{\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tauthorization: `Bearer ${token.accessToken}`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tif (error) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tuser: {\n\t\t\t\t\temail: profile.data.user.email || profile.data.user.username,\n\t\t\t\t\tid: profile.data.user.open_id,\n\t\t\t\t\tname
|
|
1
|
+
{"version":3,"file":"tiktok.mjs","names":[],"sources":["../../src/social-providers/tiktok.ts"],"sourcesContent":["import { betterFetch } from \"@better-fetch/fetch\";\nimport type { OAuthProvider, ProviderOptions } from \"../oauth2\";\nimport { refreshAccessToken, validateAuthorizationCode } from \"../oauth2\";\n\n/**\n * [More info](https://developers.tiktok.com/doc/tiktok-api-v2-get-user-info/)\n */\nexport interface TiktokProfile extends Record<string, any> {\n\tdata: {\n\t\tuser: {\n\t\t\t/**\n\t\t\t * The unique identification of the user in the current application.Open id\n\t\t\t * for the client.\n\t\t\t *\n\t\t\t * To return this field, add `fields=open_id` in the user profile request's query parameter.\n\t\t\t */\n\t\t\topen_id: string;\n\t\t\t/**\n\t\t\t * The unique identification of the user across different apps for the same developer.\n\t\t\t * For example, if a partner has X number of clients,\n\t\t\t * it will get X number of open_id for the same TikTok user,\n\t\t\t * but one persistent union_id for the particular user.\n\t\t\t *\n\t\t\t * To return this field, add `fields=union_id` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tunion_id?: string | undefined;\n\t\t\t/**\n\t\t\t * User's profile image.\n\t\t\t *\n\t\t\t * To return this field, add `fields=avatar_url` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tavatar_url?: string | undefined;\n\t\t\t/**\n\t\t\t * User`s profile image in 100x100 size.\n\t\t\t *\n\t\t\t * To return this field, add `fields=avatar_url_100` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tavatar_url_100?: string | undefined;\n\t\t\t/**\n\t\t\t * User's profile image with higher resolution\n\t\t\t *\n\t\t\t * To return this field, add `fields=avatar_url_100` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tavatar_large_url: string;\n\t\t\t/**\n\t\t\t * User's profile name\n\t\t\t *\n\t\t\t * To return this field, add `fields=display_name` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tdisplay_name: string;\n\t\t\t/**\n\t\t\t * User's username.\n\t\t\t *\n\t\t\t * To return this field, add `fields=username` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tusername: string;\n\t\t\t/** @note Email is currently unsupported by TikTok */\n\t\t\temail?: string | undefined;\n\t\t\t/**\n\t\t\t * User's bio description if there is a valid one.\n\t\t\t *\n\t\t\t * To return this field, add `fields=bio_description` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tbio_description?: string | undefined;\n\t\t\t/**\n\t\t\t * The link to user's TikTok profile page.\n\t\t\t *\n\t\t\t * To return this field, add `fields=profile_deep_link` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tprofile_deep_link?: string | undefined;\n\t\t\t/**\n\t\t\t * Whether TikTok has provided a verified badge to the account after confirming\n\t\t\t * that it belongs to the user it represents.\n\t\t\t *\n\t\t\t * To return this field, add `fields=is_verified` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tis_verified?: boolean | undefined;\n\t\t\t/**\n\t\t\t * User's followers count.\n\t\t\t *\n\t\t\t * To return this field, add `fields=follower_count` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tfollower_count?: number | undefined;\n\t\t\t/**\n\t\t\t * The number of accounts that the user is following.\n\t\t\t *\n\t\t\t * To return this field, add `fields=following_count` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tfollowing_count?: number | undefined;\n\t\t\t/**\n\t\t\t * The total number of likes received by the user across all of their videos.\n\t\t\t *\n\t\t\t * To return this field, add `fields=likes_count` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tlikes_count?: number | undefined;\n\t\t\t/**\n\t\t\t * The total number of publicly posted videos by the user.\n\t\t\t *\n\t\t\t * To return this field, add `fields=video_count` in the user profile request's query parameter.\n\t\t\t */\n\t\t\tvideo_count?: number | undefined;\n\t\t};\n\t};\n\terror?:\n\t\t| {\n\t\t\t\t/**\n\t\t\t\t * The error category in string.\n\t\t\t\t */\n\t\t\t\tcode?: string;\n\t\t\t\t/**\n\t\t\t\t * The error message in string.\n\t\t\t\t */\n\t\t\t\tmessage?: string;\n\t\t\t\t/**\n\t\t\t\t * The error message in string.\n\t\t\t\t */\n\t\t\t\tlog_id?: string;\n\t\t }\n\t\t| undefined;\n}\n\nexport interface TiktokOptions extends ProviderOptions {\n\t// Client ID is not used in TikTok, we delete it from the options\n\tclientId?: never | undefined;\n\tclientSecret: string;\n\tclientKey: string;\n}\n\nexport const tiktok = (options: TiktokOptions) => {\n\treturn {\n\t\tid: \"tiktok\",\n\t\tname: \"TikTok\",\n\t\tcreateAuthorizationURL({ state, scopes, redirectURI }) {\n\t\t\tconst _scopes = options.disableDefaultScope ? [] : [\"user.info.profile\"];\n\t\t\tif (options.scope) _scopes.push(...options.scope);\n\t\t\tif (scopes) _scopes.push(...scopes);\n\t\t\treturn new URL(\n\t\t\t\t`https://www.tiktok.com/v2/auth/authorize?scope=${_scopes.join(\n\t\t\t\t\t\",\",\n\t\t\t\t)}&response_type=code&client_key=${options.clientKey}&redirect_uri=${encodeURIComponent(\n\t\t\t\t\toptions.redirectURI || redirectURI,\n\t\t\t\t)}&state=${state}`,\n\t\t\t);\n\t\t},\n\n\t\tvalidateAuthorizationCode: async ({ code, redirectURI }) => {\n\t\t\treturn validateAuthorizationCode({\n\t\t\t\tcode,\n\t\t\t\tredirectURI: options.redirectURI || redirectURI,\n\t\t\t\toptions: {\n\t\t\t\t\tclientKey: options.clientKey,\n\t\t\t\t\tclientSecret: options.clientSecret,\n\t\t\t\t},\n\t\t\t\ttokenEndpoint: \"https://open.tiktokapis.com/v2/oauth/token/\",\n\t\t\t});\n\t\t},\n\t\trefreshAccessToken: options.refreshAccessToken\n\t\t\t? options.refreshAccessToken\n\t\t\t: async (refreshToken) => {\n\t\t\t\t\treturn refreshAccessToken({\n\t\t\t\t\t\trefreshToken,\n\t\t\t\t\t\toptions: {\n\t\t\t\t\t\t\tclientSecret: options.clientSecret,\n\t\t\t\t\t\t},\n\t\t\t\t\t\ttokenEndpoint: \"https://open.tiktokapis.com/v2/oauth/token/\",\n\t\t\t\t\t\tauthentication: \"post\",\n\t\t\t\t\t\textraParams: {\n\t\t\t\t\t\t\tclient_key: options.clientKey,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t},\n\t\tasync getUserInfo(token) {\n\t\t\tif (options.getUserInfo) {\n\t\t\t\treturn options.getUserInfo(token);\n\t\t\t}\n\n\t\t\tconst fields = [\n\t\t\t\t\"open_id\",\n\t\t\t\t\"avatar_large_url\",\n\t\t\t\t\"display_name\",\n\t\t\t\t\"username\",\n\t\t\t];\n\t\t\tconst { data: profile, error } = await betterFetch<TiktokProfile>(\n\t\t\t\t`https://open.tiktokapis.com/v2/user/info/?fields=${fields.join(\",\")}`,\n\t\t\t\t{\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tauthorization: `Bearer ${token.accessToken}`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tif (error) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tuser: {\n\t\t\t\t\temail: profile.data.user.email || profile.data.user.username,\n\t\t\t\t\tid: profile.data.user.open_id,\n\t\t\t\t\tname:\n\t\t\t\t\t\tprofile.data.user.display_name || profile.data.user.username || \"\",\n\t\t\t\t\timage: profile.data.user.avatar_large_url,\n\t\t\t\t\temailVerified: false,\n\t\t\t\t},\n\t\t\t\tdata: profile,\n\t\t\t};\n\t\t},\n\t\toptions,\n\t} satisfies OAuthProvider<TiktokProfile, TiktokOptions>;\n};\n"],"mappings":";;;;;;AAgIA,MAAa,UAAU,YAA2B;AACjD,QAAO;EACN,IAAI;EACJ,MAAM;EACN,uBAAuB,EAAE,OAAO,QAAQ,eAAe;GACtD,MAAM,UAAU,QAAQ,sBAAsB,EAAE,GAAG,CAAC,oBAAoB;AACxE,OAAI,QAAQ,MAAO,SAAQ,KAAK,GAAG,QAAQ,MAAM;AACjD,OAAI,OAAQ,SAAQ,KAAK,GAAG,OAAO;AACnC,UAAO,IAAI,IACV,kDAAkD,QAAQ,KACzD,IACA,CAAC,iCAAiC,QAAQ,UAAU,gBAAgB,mBACpE,QAAQ,eAAe,YACvB,CAAC,SAAS,QACX;;EAGF,2BAA2B,OAAO,EAAE,MAAM,kBAAkB;AAC3D,UAAO,0BAA0B;IAChC;IACA,aAAa,QAAQ,eAAe;IACpC,SAAS;KACR,WAAW,QAAQ;KACnB,cAAc,QAAQ;KACtB;IACD,eAAe;IACf,CAAC;;EAEH,oBAAoB,QAAQ,qBACzB,QAAQ,qBACR,OAAO,iBAAiB;AACxB,UAAO,mBAAmB;IACzB;IACA,SAAS,EACR,cAAc,QAAQ,cACtB;IACD,eAAe;IACf,gBAAgB;IAChB,aAAa,EACZ,YAAY,QAAQ,WACpB;IACD,CAAC;;EAEL,MAAM,YAAY,OAAO;AACxB,OAAI,QAAQ,YACX,QAAO,QAAQ,YAAY,MAAM;GASlC,MAAM,EAAE,MAAM,SAAS,UAAU,MAAM,YACtC,oDAPc;IACd;IACA;IACA;IACA;IACA,CAE2D,KAAK,IAAI,IACpE,EACC,SAAS,EACR,eAAe,UAAU,MAAM,eAC/B,EACD,CACD;AAED,OAAI,MACH,QAAO;AAGR,UAAO;IACN,MAAM;KACL,OAAO,QAAQ,KAAK,KAAK,SAAS,QAAQ,KAAK,KAAK;KACpD,IAAI,QAAQ,KAAK,KAAK;KACtB,MACC,QAAQ,KAAK,KAAK,gBAAgB,QAAQ,KAAK,KAAK,YAAY;KACjE,OAAO,QAAQ,KAAK,KAAK;KACzB,eAAe;KACf;IACD,MAAM;IACN;;EAEF;EACA"}
|
|
@@ -44,7 +44,7 @@ const vercel = (options) => {
|
|
|
44
44
|
return {
|
|
45
45
|
user: {
|
|
46
46
|
id: profile.sub,
|
|
47
|
-
name: profile.name ?? profile.preferred_username,
|
|
47
|
+
name: profile.name ?? profile.preferred_username ?? "",
|
|
48
48
|
email: profile.email,
|
|
49
49
|
image: profile.picture,
|
|
50
50
|
emailVerified: profile.email_verified ?? false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vercel.mjs","names":[],"sources":["../../src/social-providers/vercel.ts"],"sourcesContent":["import { betterFetch } from \"@better-fetch/fetch\";\nimport { BetterAuthError } from \"../error\";\nimport type { OAuthProvider, ProviderOptions } from \"../oauth2\";\nimport { createAuthorizationURL, validateAuthorizationCode } from \"../oauth2\";\n\nexport interface VercelProfile {\n\tsub: string;\n\tname?: string;\n\tpreferred_username?: string;\n\temail?: string;\n\temail_verified?: boolean;\n\tpicture?: string;\n}\n\nexport interface VercelOptions extends ProviderOptions<VercelProfile> {\n\tclientId: string;\n}\n\nexport const vercel = (options: VercelOptions) => {\n\treturn {\n\t\tid: \"vercel\",\n\t\tname: \"Vercel\",\n\t\tcreateAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {\n\t\t\tif (!codeVerifier) {\n\t\t\t\tthrow new BetterAuthError(\"codeVerifier is required for Vercel\");\n\t\t\t}\n\n\t\t\tlet _scopes: string[] | undefined = undefined;\n\t\t\tif (options.scope !== undefined || scopes !== undefined) {\n\t\t\t\t_scopes = [];\n\t\t\t\tif (options.scope) _scopes.push(...options.scope);\n\t\t\t\tif (scopes) _scopes.push(...scopes);\n\t\t\t}\n\n\t\t\treturn createAuthorizationURL({\n\t\t\t\tid: \"vercel\",\n\t\t\t\toptions,\n\t\t\t\tauthorizationEndpoint: \"https://vercel.com/oauth/authorize\",\n\t\t\t\tscopes: _scopes,\n\t\t\t\tstate,\n\t\t\t\tcodeVerifier,\n\t\t\t\tredirectURI,\n\t\t\t});\n\t\t},\n\t\tvalidateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {\n\t\t\treturn validateAuthorizationCode({\n\t\t\t\tcode,\n\t\t\t\tcodeVerifier,\n\t\t\t\tredirectURI,\n\t\t\t\toptions,\n\t\t\t\ttokenEndpoint: \"https://api.vercel.com/login/oauth/token\",\n\t\t\t});\n\t\t},\n\t\tasync getUserInfo(token) {\n\t\t\tif (options.getUserInfo) {\n\t\t\t\treturn options.getUserInfo(token);\n\t\t\t}\n\n\t\t\tconst { data: profile, error } = await betterFetch<VercelProfile>(\n\t\t\t\t\"https://api.vercel.com/login/oauth/userinfo\",\n\t\t\t\t{\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAuthorization: `Bearer ${token.accessToken}`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tif (error || !profile) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst userMap = await options.mapProfileToUser?.(profile);\n\t\t\treturn {\n\t\t\t\tuser: {\n\t\t\t\t\tid: profile.sub,\n\t\t\t\t\tname: profile.name ?? profile.preferred_username,\n\t\t\t\t\temail: profile.email,\n\t\t\t\t\timage: profile.picture,\n\t\t\t\t\temailVerified: profile.email_verified ?? false,\n\t\t\t\t\t...userMap,\n\t\t\t\t},\n\t\t\t\tdata: profile,\n\t\t\t};\n\t\t},\n\t\toptions,\n\t} satisfies OAuthProvider<VercelProfile>;\n};\n"],"mappings":";;;;;;;AAkBA,MAAa,UAAU,YAA2B;AACjD,QAAO;EACN,IAAI;EACJ,MAAM;EACN,uBAAuB,EAAE,OAAO,QAAQ,cAAc,eAAe;AACpE,OAAI,CAAC,aACJ,OAAM,IAAI,gBAAgB,sCAAsC;GAGjE,IAAI,UAAgC;AACpC,OAAI,QAAQ,UAAU,UAAa,WAAW,QAAW;AACxD,cAAU,EAAE;AACZ,QAAI,QAAQ,MAAO,SAAQ,KAAK,GAAG,QAAQ,MAAM;AACjD,QAAI,OAAQ,SAAQ,KAAK,GAAG,OAAO;;AAGpC,UAAO,uBAAuB;IAC7B,IAAI;IACJ;IACA,uBAAuB;IACvB,QAAQ;IACR;IACA;IACA;IACA,CAAC;;EAEH,2BAA2B,OAAO,EAAE,MAAM,cAAc,kBAAkB;AACzE,UAAO,0BAA0B;IAChC;IACA;IACA;IACA;IACA,eAAe;IACf,CAAC;;EAEH,MAAM,YAAY,OAAO;AACxB,OAAI,QAAQ,YACX,QAAO,QAAQ,YAAY,MAAM;GAGlC,MAAM,EAAE,MAAM,SAAS,UAAU,MAAM,YACtC,+CACA,EACC,SAAS,EACR,eAAe,UAAU,MAAM,eAC/B,EACD,CACD;AAED,OAAI,SAAS,CAAC,QACb,QAAO;GAGR,MAAM,UAAU,MAAM,QAAQ,mBAAmB,QAAQ;AACzD,UAAO;IACN,MAAM;KACL,IAAI,QAAQ;KACZ,MAAM,QAAQ,QAAQ,QAAQ;
|
|
1
|
+
{"version":3,"file":"vercel.mjs","names":[],"sources":["../../src/social-providers/vercel.ts"],"sourcesContent":["import { betterFetch } from \"@better-fetch/fetch\";\nimport { BetterAuthError } from \"../error\";\nimport type { OAuthProvider, ProviderOptions } from \"../oauth2\";\nimport { createAuthorizationURL, validateAuthorizationCode } from \"../oauth2\";\n\nexport interface VercelProfile {\n\tsub: string;\n\tname?: string;\n\tpreferred_username?: string;\n\temail?: string;\n\temail_verified?: boolean;\n\tpicture?: string;\n}\n\nexport interface VercelOptions extends ProviderOptions<VercelProfile> {\n\tclientId: string;\n}\n\nexport const vercel = (options: VercelOptions) => {\n\treturn {\n\t\tid: \"vercel\",\n\t\tname: \"Vercel\",\n\t\tcreateAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {\n\t\t\tif (!codeVerifier) {\n\t\t\t\tthrow new BetterAuthError(\"codeVerifier is required for Vercel\");\n\t\t\t}\n\n\t\t\tlet _scopes: string[] | undefined = undefined;\n\t\t\tif (options.scope !== undefined || scopes !== undefined) {\n\t\t\t\t_scopes = [];\n\t\t\t\tif (options.scope) _scopes.push(...options.scope);\n\t\t\t\tif (scopes) _scopes.push(...scopes);\n\t\t\t}\n\n\t\t\treturn createAuthorizationURL({\n\t\t\t\tid: \"vercel\",\n\t\t\t\toptions,\n\t\t\t\tauthorizationEndpoint: \"https://vercel.com/oauth/authorize\",\n\t\t\t\tscopes: _scopes,\n\t\t\t\tstate,\n\t\t\t\tcodeVerifier,\n\t\t\t\tredirectURI,\n\t\t\t});\n\t\t},\n\t\tvalidateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {\n\t\t\treturn validateAuthorizationCode({\n\t\t\t\tcode,\n\t\t\t\tcodeVerifier,\n\t\t\t\tredirectURI,\n\t\t\t\toptions,\n\t\t\t\ttokenEndpoint: \"https://api.vercel.com/login/oauth/token\",\n\t\t\t});\n\t\t},\n\t\tasync getUserInfo(token) {\n\t\t\tif (options.getUserInfo) {\n\t\t\t\treturn options.getUserInfo(token);\n\t\t\t}\n\n\t\t\tconst { data: profile, error } = await betterFetch<VercelProfile>(\n\t\t\t\t\"https://api.vercel.com/login/oauth/userinfo\",\n\t\t\t\t{\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAuthorization: `Bearer ${token.accessToken}`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tif (error || !profile) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst userMap = await options.mapProfileToUser?.(profile);\n\t\t\treturn {\n\t\t\t\tuser: {\n\t\t\t\t\tid: profile.sub,\n\t\t\t\t\tname: profile.name ?? profile.preferred_username ?? \"\",\n\t\t\t\t\temail: profile.email,\n\t\t\t\t\timage: profile.picture,\n\t\t\t\t\temailVerified: profile.email_verified ?? false,\n\t\t\t\t\t...userMap,\n\t\t\t\t},\n\t\t\t\tdata: profile,\n\t\t\t};\n\t\t},\n\t\toptions,\n\t} satisfies OAuthProvider<VercelProfile>;\n};\n"],"mappings":";;;;;;;AAkBA,MAAa,UAAU,YAA2B;AACjD,QAAO;EACN,IAAI;EACJ,MAAM;EACN,uBAAuB,EAAE,OAAO,QAAQ,cAAc,eAAe;AACpE,OAAI,CAAC,aACJ,OAAM,IAAI,gBAAgB,sCAAsC;GAGjE,IAAI,UAAgC;AACpC,OAAI,QAAQ,UAAU,UAAa,WAAW,QAAW;AACxD,cAAU,EAAE;AACZ,QAAI,QAAQ,MAAO,SAAQ,KAAK,GAAG,QAAQ,MAAM;AACjD,QAAI,OAAQ,SAAQ,KAAK,GAAG,OAAO;;AAGpC,UAAO,uBAAuB;IAC7B,IAAI;IACJ;IACA,uBAAuB;IACvB,QAAQ;IACR;IACA;IACA;IACA,CAAC;;EAEH,2BAA2B,OAAO,EAAE,MAAM,cAAc,kBAAkB;AACzE,UAAO,0BAA0B;IAChC;IACA;IACA;IACA;IACA,eAAe;IACf,CAAC;;EAEH,MAAM,YAAY,OAAO;AACxB,OAAI,QAAQ,YACX,QAAO,QAAQ,YAAY,MAAM;GAGlC,MAAM,EAAE,MAAM,SAAS,UAAU,MAAM,YACtC,+CACA,EACC,SAAS,EACR,eAAe,UAAU,MAAM,eAC/B,EACD,CACD;AAED,OAAI,SAAS,CAAC,QACb,QAAO;GAGR,MAAM,UAAU,MAAM,QAAQ,mBAAmB,QAAQ;AACzD,UAAO;IACN,MAAM;KACL,IAAI,QAAQ;KACZ,MAAM,QAAQ,QAAQ,QAAQ,sBAAsB;KACpD,OAAO,QAAQ;KACf,OAAO,QAAQ;KACf,eAAe,QAAQ,kBAAkB;KACzC,GAAG;KACH;IACD,MAAM;IACN;;EAEF;EACA"}
|
package/dist/types/plugin.d.mts
CHANGED
|
@@ -31,7 +31,7 @@ type BetterAuthPlugin = BetterAuthPluginErrorCodePart & {
|
|
|
31
31
|
* You can return a new context or modify the existing context.
|
|
32
32
|
*/
|
|
33
33
|
init?: ((ctx: AuthContext) => Awaitable<{
|
|
34
|
-
context?: DeepPartial<Omit<AuthContext, "options"
|
|
34
|
+
context?: DeepPartial<Omit<AuthContext, "options">> & Record<string, unknown>;
|
|
35
35
|
options?: Partial<BetterAuthOptions>;
|
|
36
36
|
}> | void | Promise<void>) | undefined;
|
|
37
37
|
endpoints?: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/core",
|
|
3
|
-
"version": "1.5.0-beta.
|
|
3
|
+
"version": "1.5.0-beta.15",
|
|
4
4
|
"description": "The most comprehensive authentication framework for TypeScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
"url": "git+https://github.com/better-auth/better-auth.git",
|
|
9
9
|
"directory": "packages/core"
|
|
10
10
|
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"src"
|
|
14
|
+
],
|
|
11
15
|
"main": "./dist/index.mjs",
|
|
12
16
|
"module": "./dist/index.mjs",
|
|
13
17
|
"types": "./dist/index.d.mts",
|
|
@@ -117,11 +121,11 @@
|
|
|
117
121
|
"devDependencies": {
|
|
118
122
|
"@better-auth/utils": "0.3.1",
|
|
119
123
|
"@better-fetch/fetch": "1.1.21",
|
|
120
|
-
"better-call": "1.2
|
|
124
|
+
"better-call": "1.3.2",
|
|
121
125
|
"jose": "^6.1.0",
|
|
122
126
|
"kysely": "^0.28.10",
|
|
123
127
|
"nanostores": "^1.1.0",
|
|
124
|
-
"tsdown": "^0.20.
|
|
128
|
+
"tsdown": "^0.20.3"
|
|
125
129
|
},
|
|
126
130
|
"dependencies": {
|
|
127
131
|
"@standard-schema/spec": "^1.0.0",
|
|
@@ -130,7 +134,7 @@
|
|
|
130
134
|
"peerDependencies": {
|
|
131
135
|
"@better-auth/utils": "0.3.1",
|
|
132
136
|
"@better-fetch/fetch": "1.1.21",
|
|
133
|
-
"better-call": "1.2
|
|
137
|
+
"better-call": "1.3.2",
|
|
134
138
|
"jose": "^6.1.0",
|
|
135
139
|
"kysely": "^0.28.5",
|
|
136
140
|
"nanostores": "^1.0.1"
|
|
@@ -540,12 +540,32 @@ export const createAdapterFactory =
|
|
|
540
540
|
newValue = value.toISOString();
|
|
541
541
|
}
|
|
542
542
|
|
|
543
|
+
if (fieldAttr.type === "boolean" && typeof newValue === "string") {
|
|
544
|
+
newValue = newValue === "true";
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (fieldAttr.type === "number") {
|
|
548
|
+
if (typeof newValue === "string" && newValue.trim() !== "") {
|
|
549
|
+
const parsed = Number(newValue);
|
|
550
|
+
if (!Number.isNaN(parsed)) {
|
|
551
|
+
newValue = parsed;
|
|
552
|
+
}
|
|
553
|
+
} else if (Array.isArray(newValue)) {
|
|
554
|
+
const parsed = newValue.map((v) =>
|
|
555
|
+
typeof v === "string" && v.trim() !== "" ? Number(v) : NaN,
|
|
556
|
+
);
|
|
557
|
+
if (parsed.every((n) => !Number.isNaN(n))) {
|
|
558
|
+
newValue = parsed;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
543
563
|
if (
|
|
544
564
|
fieldAttr.type === "boolean" &&
|
|
545
|
-
typeof
|
|
565
|
+
typeof newValue === "boolean" &&
|
|
546
566
|
!config.supportsBooleans
|
|
547
567
|
) {
|
|
548
|
-
newValue =
|
|
568
|
+
newValue = newValue ? 1 : 0;
|
|
549
569
|
}
|
|
550
570
|
|
|
551
571
|
if (
|
package/src/db/adapter/index.ts
CHANGED
|
@@ -301,25 +301,27 @@ export interface DBAdapterFactoryConfig<
|
|
|
301
301
|
disableTransformJoin?: boolean | undefined;
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
+
export const whereOperators = [
|
|
305
|
+
"eq",
|
|
306
|
+
"ne",
|
|
307
|
+
"lt",
|
|
308
|
+
"lte",
|
|
309
|
+
"gt",
|
|
310
|
+
"gte",
|
|
311
|
+
"in",
|
|
312
|
+
"not_in",
|
|
313
|
+
"contains",
|
|
314
|
+
"starts_with",
|
|
315
|
+
"ends_with",
|
|
316
|
+
] as const;
|
|
317
|
+
|
|
318
|
+
export type WhereOperator = (typeof whereOperators)[number];
|
|
319
|
+
|
|
304
320
|
export type Where = {
|
|
305
321
|
/**
|
|
306
322
|
* @default eq
|
|
307
323
|
*/
|
|
308
|
-
operator?:
|
|
309
|
-
| (
|
|
310
|
-
| "eq"
|
|
311
|
-
| "ne"
|
|
312
|
-
| "lt"
|
|
313
|
-
| "lte"
|
|
314
|
-
| "gt"
|
|
315
|
-
| "gte"
|
|
316
|
-
| "in"
|
|
317
|
-
| "not_in"
|
|
318
|
-
| "contains"
|
|
319
|
-
| "starts_with"
|
|
320
|
-
| "ends_with"
|
|
321
|
-
)
|
|
322
|
-
| undefined;
|
|
324
|
+
operator?: WhereOperator | undefined;
|
|
323
325
|
value: string | number | boolean | string[] | number[] | Date | null;
|
|
324
326
|
field: string;
|
|
325
327
|
/**
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
vi.mock("@better-fetch/fetch", () => ({
|
|
4
|
+
betterFetch: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
8
|
+
import { refreshAccessToken } from "./refresh-access-token";
|
|
9
|
+
|
|
10
|
+
const mockedBetterFetch = vi.mocked(betterFetch);
|
|
11
|
+
|
|
12
|
+
describe("refreshAccessToken", () => {
|
|
13
|
+
it("should set accessTokenExpiresAt when expires_in is returned", async () => {
|
|
14
|
+
const now = Date.now();
|
|
15
|
+
mockedBetterFetch.mockResolvedValueOnce({
|
|
16
|
+
data: {
|
|
17
|
+
access_token: "new-access-token",
|
|
18
|
+
refresh_token: "new-refresh-token",
|
|
19
|
+
expires_in: 3600,
|
|
20
|
+
token_type: "Bearer",
|
|
21
|
+
},
|
|
22
|
+
error: null,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const tokens = await refreshAccessToken({
|
|
26
|
+
refreshToken: "old-refresh-token",
|
|
27
|
+
options: { clientId: "test-client", clientSecret: "test-secret" },
|
|
28
|
+
tokenEndpoint: "https://example.com/token",
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(tokens.accessToken).toBe("new-access-token");
|
|
32
|
+
expect(tokens.refreshToken).toBe("new-refresh-token");
|
|
33
|
+
expect(tokens.accessTokenExpiresAt).toBeInstanceOf(Date);
|
|
34
|
+
expect(tokens.accessTokenExpiresAt!.getTime()).toBeGreaterThanOrEqual(
|
|
35
|
+
now + 3600 * 1000 - 1000,
|
|
36
|
+
);
|
|
37
|
+
expect(tokens.refreshTokenExpiresAt).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @see https://github.com/better-auth/better-auth/issues/7682
|
|
42
|
+
*/
|
|
43
|
+
it("should set refreshTokenExpiresAt when refresh_token_expires_in is returned", async () => {
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
mockedBetterFetch.mockResolvedValueOnce({
|
|
46
|
+
data: {
|
|
47
|
+
access_token: "new-access-token",
|
|
48
|
+
refresh_token: "new-refresh-token",
|
|
49
|
+
expires_in: 3600,
|
|
50
|
+
refresh_token_expires_in: 86400,
|
|
51
|
+
token_type: "Bearer",
|
|
52
|
+
},
|
|
53
|
+
error: null,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const tokens = await refreshAccessToken({
|
|
57
|
+
refreshToken: "old-refresh-token",
|
|
58
|
+
options: { clientId: "test-client", clientSecret: "test-secret" },
|
|
59
|
+
tokenEndpoint: "https://example.com/token",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(tokens.accessToken).toBe("new-access-token");
|
|
63
|
+
expect(tokens.refreshToken).toBe("new-refresh-token");
|
|
64
|
+
expect(tokens.accessTokenExpiresAt).toBeInstanceOf(Date);
|
|
65
|
+
expect(tokens.refreshTokenExpiresAt).toBeInstanceOf(Date);
|
|
66
|
+
expect(tokens.refreshTokenExpiresAt!.getTime()).toBeGreaterThanOrEqual(
|
|
67
|
+
now + 86400 * 1000 - 1000,
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should not set refreshTokenExpiresAt when refresh_token_expires_in is not returned", async () => {
|
|
72
|
+
mockedBetterFetch.mockResolvedValueOnce({
|
|
73
|
+
data: {
|
|
74
|
+
access_token: "new-access-token",
|
|
75
|
+
refresh_token: "new-refresh-token",
|
|
76
|
+
expires_in: 3600,
|
|
77
|
+
token_type: "Bearer",
|
|
78
|
+
},
|
|
79
|
+
error: null,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const tokens = await refreshAccessToken({
|
|
83
|
+
refreshToken: "old-refresh-token",
|
|
84
|
+
options: { clientId: "test-client", clientSecret: "test-secret" },
|
|
85
|
+
tokenEndpoint: "https://example.com/token",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(tokens.refreshTokenExpiresAt).toBeUndefined();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -119,6 +119,7 @@ export async function refreshAccessToken({
|
|
|
119
119
|
access_token: string;
|
|
120
120
|
refresh_token?: string | undefined;
|
|
121
121
|
expires_in?: number | undefined;
|
|
122
|
+
refresh_token_expires_in?: number | undefined;
|
|
122
123
|
token_type?: string | undefined;
|
|
123
124
|
scope?: string | undefined;
|
|
124
125
|
id_token?: string | undefined;
|
|
@@ -145,5 +146,12 @@ export async function refreshAccessToken({
|
|
|
145
146
|
);
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
if (data.refresh_token_expires_in) {
|
|
150
|
+
const now = new Date();
|
|
151
|
+
tokens.refreshTokenExpiresAt = new Date(
|
|
152
|
+
now.getTime() + data.refresh_token_expires_in * 1000,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
148
156
|
return tokens;
|
|
149
157
|
}
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import type { JWK } from "jose";
|
|
2
2
|
import { exportJWK, generateKeyPair, SignJWT } from "jose";
|
|
3
|
-
import {
|
|
4
|
-
afterAll,
|
|
5
|
-
beforeAll,
|
|
6
|
-
beforeEach,
|
|
7
|
-
describe,
|
|
8
|
-
expect,
|
|
9
|
-
it,
|
|
10
|
-
vi,
|
|
11
|
-
} from "vitest";
|
|
3
|
+
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
12
4
|
import { validateToken } from "./validate-authorization-code";
|
|
13
5
|
|
|
14
6
|
describe("validateToken", () => {
|
|
@@ -24,10 +16,6 @@ describe("validateToken", () => {
|
|
|
24
16
|
globalThis.fetch = originalFetch;
|
|
25
17
|
});
|
|
26
18
|
|
|
27
|
-
beforeEach(() => {
|
|
28
|
-
mockedFetch.mockReset();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
19
|
async function createTestJWKS(alg: string, crv?: string) {
|
|
32
20
|
const { publicKey, privateKey } = await generateKeyPair(alg, {
|
|
33
21
|
crv,
|
|
@@ -158,15 +158,15 @@ export const apple = (options: AppleOptions) => {
|
|
|
158
158
|
return null;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
// TODO: "
|
|
161
|
+
// TODO: "" masking will be removed when the name field is made optional
|
|
162
162
|
let name: string;
|
|
163
163
|
if (token.user?.name) {
|
|
164
164
|
const firstName = token.user.name.firstName || "";
|
|
165
165
|
const lastName = token.user.name.lastName || "";
|
|
166
166
|
const fullName = `${firstName} ${lastName}`.trim();
|
|
167
|
-
name = fullName
|
|
167
|
+
name = fullName;
|
|
168
168
|
} else {
|
|
169
|
-
name = profile.name || "
|
|
169
|
+
name = profile.name || "";
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
const emailVerified =
|
|
@@ -178,10 +178,7 @@ export const cognito = (options: CognitoOptions) => {
|
|
|
178
178
|
return null;
|
|
179
179
|
}
|
|
180
180
|
const name =
|
|
181
|
-
profile.name ||
|
|
182
|
-
profile.given_name ||
|
|
183
|
-
profile.username ||
|
|
184
|
-
profile.email;
|
|
181
|
+
profile.name || profile.given_name || profile.username || "";
|
|
185
182
|
const enrichedProfile = {
|
|
186
183
|
...profile,
|
|
187
184
|
name,
|
|
@@ -220,7 +217,11 @@ export const cognito = (options: CognitoOptions) => {
|
|
|
220
217
|
return {
|
|
221
218
|
user: {
|
|
222
219
|
id: userInfo.sub,
|
|
223
|
-
name:
|
|
220
|
+
name:
|
|
221
|
+
userInfo.name ||
|
|
222
|
+
userInfo.given_name ||
|
|
223
|
+
userInfo.username ||
|
|
224
|
+
"",
|
|
224
225
|
email: userInfo.email,
|
|
225
226
|
image: userInfo.picture,
|
|
226
227
|
emailVerified: userInfo.email_verified,
|
|
@@ -170,7 +170,7 @@ export const github = (options: GithubOptions) => {
|
|
|
170
170
|
return {
|
|
171
171
|
user: {
|
|
172
172
|
id: profile.id,
|
|
173
|
-
name: profile.name || profile.login,
|
|
173
|
+
name: profile.name || profile.login || "",
|
|
174
174
|
email: profile.email,
|
|
175
175
|
image: profile.avatar_url,
|
|
176
176
|
emailVerified,
|
|
@@ -141,7 +141,7 @@ export const gitlab = (options: GitlabOptions) => {
|
|
|
141
141
|
return {
|
|
142
142
|
user: {
|
|
143
143
|
id: profile.id,
|
|
144
|
-
name: profile.name ?? profile.username,
|
|
144
|
+
name: profile.name ?? profile.username ?? "",
|
|
145
145
|
email: profile.email,
|
|
146
146
|
image: profile.avatar_url,
|
|
147
147
|
emailVerified: profile.email_verified ?? false,
|
|
@@ -104,7 +104,7 @@ export const huggingface = (options: HuggingFaceOptions) => {
|
|
|
104
104
|
return {
|
|
105
105
|
user: {
|
|
106
106
|
id: profile.sub,
|
|
107
|
-
name: profile.name || profile.preferred_username,
|
|
107
|
+
name: profile.name || profile.preferred_username || "",
|
|
108
108
|
email: profile.email,
|
|
109
109
|
image: profile.picture,
|
|
110
110
|
emailVerified: profile.email_verified ?? false,
|
|
@@ -22,6 +22,7 @@ import { notion } from "./notion";
|
|
|
22
22
|
import { paybin } from "./paybin";
|
|
23
23
|
import { paypal } from "./paypal";
|
|
24
24
|
import { polar } from "./polar";
|
|
25
|
+
import { railway } from "./railway";
|
|
25
26
|
import { reddit } from "./reddit";
|
|
26
27
|
import { roblox } from "./roblox";
|
|
27
28
|
import { salesforce } from "./salesforce";
|
|
@@ -67,6 +68,7 @@ export const socialProviders = {
|
|
|
67
68
|
paybin,
|
|
68
69
|
paypal,
|
|
69
70
|
polar,
|
|
71
|
+
railway,
|
|
70
72
|
vercel,
|
|
71
73
|
};
|
|
72
74
|
|
|
@@ -113,6 +115,7 @@ export * from "./notion";
|
|
|
113
115
|
export * from "./paybin";
|
|
114
116
|
export * from "./paypal";
|
|
115
117
|
export * from "./polar";
|
|
118
|
+
export * from "./railway";
|
|
116
119
|
export * from "./reddit";
|
|
117
120
|
export * from "./roblox";
|
|
118
121
|
export * from "./salesforce";
|
|
@@ -161,7 +161,7 @@ export const kakao = (options: KakaoOptions) => {
|
|
|
161
161
|
const kakaoProfile = account.profile || {};
|
|
162
162
|
const user = {
|
|
163
163
|
id: String(profile.id),
|
|
164
|
-
name: kakaoProfile.nickname || account.name ||
|
|
164
|
+
name: kakaoProfile.nickname || account.name || "",
|
|
165
165
|
email: account.email,
|
|
166
166
|
image:
|
|
167
167
|
kakaoProfile.profile_image_url || kakaoProfile.thumbnail_image_url,
|
|
@@ -147,7 +147,7 @@ export const line = (options: LineOptions) => {
|
|
|
147
147
|
const userMap = await options.mapProfileToUser?.(profile as any);
|
|
148
148
|
// ID preference order
|
|
149
149
|
const id = (profile as any).sub || (profile as any).userId;
|
|
150
|
-
const name = (profile as any).name || (profile as any).displayName;
|
|
150
|
+
const name = (profile as any).name || (profile as any).displayName || "";
|
|
151
151
|
const image =
|
|
152
152
|
(profile as any).picture || (profile as any).pictureUrl || undefined;
|
|
153
153
|
const email = (profile as any).email;
|
|
@@ -96,7 +96,7 @@ export const naver = (options: NaverOptions) => {
|
|
|
96
96
|
const res = profile.response || {};
|
|
97
97
|
const user = {
|
|
98
98
|
id: res.id,
|
|
99
|
-
name: res.name || res.nickname,
|
|
99
|
+
name: res.name || res.nickname || "",
|
|
100
100
|
email: res.email,
|
|
101
101
|
image: res.profile_image,
|
|
102
102
|
emailVerified: false,
|
|
@@ -94,7 +94,7 @@ export const notion = (options: NotionOptions) => {
|
|
|
94
94
|
return {
|
|
95
95
|
user: {
|
|
96
96
|
id: userProfile.id,
|
|
97
|
-
name: userProfile.name || "
|
|
97
|
+
name: userProfile.name || "",
|
|
98
98
|
email: userProfile.person?.email || null,
|
|
99
99
|
image: userProfile.avatar_url,
|
|
100
100
|
emailVerified: false,
|