@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.
Files changed (70) hide show
  1. package/dist/context/global.mjs +1 -1
  2. package/dist/db/adapter/factory.mjs +11 -1
  3. package/dist/db/adapter/factory.mjs.map +1 -1
  4. package/dist/db/adapter/index.d.mts +4 -2
  5. package/dist/db/adapter/index.mjs +18 -1
  6. package/dist/db/adapter/index.mjs.map +1 -0
  7. package/dist/oauth2/refresh-access-token.mjs +4 -0
  8. package/dist/oauth2/refresh-access-token.mjs.map +1 -1
  9. package/dist/oauth2/validate-authorization-code.d.mts +2 -2
  10. package/dist/social-providers/apple.mjs +2 -2
  11. package/dist/social-providers/apple.mjs.map +1 -1
  12. package/dist/social-providers/cognito.mjs +2 -2
  13. package/dist/social-providers/cognito.mjs.map +1 -1
  14. package/dist/social-providers/github.mjs +1 -1
  15. package/dist/social-providers/github.mjs.map +1 -1
  16. package/dist/social-providers/gitlab.mjs +1 -1
  17. package/dist/social-providers/gitlab.mjs.map +1 -1
  18. package/dist/social-providers/huggingface.mjs +1 -1
  19. package/dist/social-providers/huggingface.mjs.map +1 -1
  20. package/dist/social-providers/index.d.mts +51 -2
  21. package/dist/social-providers/index.mjs +3 -1
  22. package/dist/social-providers/index.mjs.map +1 -1
  23. package/dist/social-providers/kakao.d.mts +1 -1
  24. package/dist/social-providers/kakao.mjs +1 -1
  25. package/dist/social-providers/kakao.mjs.map +1 -1
  26. package/dist/social-providers/line.mjs +1 -1
  27. package/dist/social-providers/line.mjs.map +1 -1
  28. package/dist/social-providers/naver.mjs +1 -1
  29. package/dist/social-providers/naver.mjs.map +1 -1
  30. package/dist/social-providers/notion.mjs +1 -1
  31. package/dist/social-providers/notion.mjs.map +1 -1
  32. package/dist/social-providers/paybin.mjs +1 -1
  33. package/dist/social-providers/paybin.mjs.map +1 -1
  34. package/dist/social-providers/polar.mjs +1 -1
  35. package/dist/social-providers/polar.mjs.map +1 -1
  36. package/dist/social-providers/railway.d.mts +68 -0
  37. package/dist/social-providers/railway.mjs +78 -0
  38. package/dist/social-providers/railway.mjs.map +1 -0
  39. package/dist/social-providers/tiktok.mjs +1 -1
  40. package/dist/social-providers/tiktok.mjs.map +1 -1
  41. package/dist/social-providers/vercel.mjs +1 -1
  42. package/dist/social-providers/vercel.mjs.map +1 -1
  43. package/dist/types/plugin.d.mts +1 -1
  44. package/package.json +8 -4
  45. package/src/db/adapter/factory.ts +22 -2
  46. package/src/db/adapter/index.ts +17 -15
  47. package/src/oauth2/refresh-access-token.test.ts +90 -0
  48. package/src/oauth2/refresh-access-token.ts +8 -0
  49. package/src/oauth2/validate-token.test.ts +1 -13
  50. package/src/social-providers/apple.ts +3 -3
  51. package/src/social-providers/cognito.ts +6 -5
  52. package/src/social-providers/github.ts +1 -1
  53. package/src/social-providers/gitlab.ts +1 -1
  54. package/src/social-providers/huggingface.ts +1 -1
  55. package/src/social-providers/index.ts +3 -0
  56. package/src/social-providers/kakao.ts +1 -1
  57. package/src/social-providers/line.ts +1 -1
  58. package/src/social-providers/naver.ts +1 -1
  59. package/src/social-providers/notion.ts +1 -1
  60. package/src/social-providers/paybin.ts +1 -5
  61. package/src/social-providers/polar.ts +1 -1
  62. package/src/social-providers/railway.ts +100 -0
  63. package/src/social-providers/tiktok.ts +2 -1
  64. package/src/social-providers/vercel.ts +1 -1
  65. package/src/types/plugin.ts +2 -1
  66. package/src/utils/deprecate.test.ts +0 -1
  67. package/.turbo/turbo-build.log +0 -265
  68. package/tsconfig.json +0 -7
  69. package/tsdown.config.ts +0 -35
  70. 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: profile.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,MAAM,QAAQ,KAAK,KAAK,gBAAgB,QAAQ,KAAK,KAAK;KAC1D,OAAO,QAAQ,KAAK,KAAK;KACzB,eAAe;KACf;IACD,MAAM;IACN;;EAEF;EACA"}
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;KAC9B,OAAO,QAAQ;KACf,OAAO,QAAQ;KACf,eAAe,QAAQ,kBAAkB;KACzC,GAAG;KACH;IACD,MAAM;IACN;;EAEF;EACA"}
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"}
@@ -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.13",
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.1",
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.1"
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.1",
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 value === "boolean" &&
565
+ typeof newValue === "boolean" &&
546
566
  !config.supportsBooleans
547
567
  ) {
548
- newValue = value ? 1 : 0;
568
+ newValue = newValue ? 1 : 0;
549
569
  }
550
570
 
551
571
  if (
@@ -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: " " masking will be removed when the name field is made optional
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: userInfo.name || userInfo.given_name || userInfo.username,
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 || undefined,
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 || "Notion User",
97
+ name: userProfile.name || "",
98
98
  email: userProfile.person?.email || null,
99
99
  image: userProfile.avatar_url,
100
100
  emailVerified: false,