@gatwech/core 0.0.11 → 1.0.0-beta.19

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 (134) hide show
  1. package/dist/api/index.d.mts +2 -2
  2. package/dist/context/index.d.mts +2 -2
  3. package/dist/db/adapter/index.d.mts +2 -2
  4. package/dist/db/index.d.mts +2 -2
  5. package/dist/env/index.d.mts +1 -1
  6. package/dist/env/index.mjs +1 -1
  7. package/dist/{env-JMSMESs1.mjs → env-ubiqEEZm.mjs} +10 -9
  8. package/dist/error/index.mjs +2 -2
  9. package/dist/{error-41YsFQ8b.mjs → error-DKIIiFmH.mjs} +1 -1
  10. package/dist/{index-DLP-K0uY.d.mts → index-CNtOUpmF.d.mts} +4 -3
  11. package/dist/index-Df0KhQRf.d.mts +3002 -0
  12. package/dist/index.d.mts +3 -3
  13. package/dist/oauth2/index.d.mts +2 -2
  14. package/dist/social-providers/index.d.mts +3 -3
  15. package/dist/social-providers/index.mjs +73 -2414
  16. package/dist/src/api/index.d.ts +119 -0
  17. package/dist/src/async_hooks/index.d.ts +6 -0
  18. package/dist/src/context/endpoint-context.d.ts +14 -0
  19. package/dist/src/context/index.d.ts +3 -0
  20. package/dist/src/context/request-state.d.ts +13 -0
  21. package/dist/src/context/transaction.d.ts +11 -0
  22. package/dist/src/db/adapter/index.d.ts +401 -0
  23. package/{src/db/index.ts → dist/src/db/index.d.ts} +2 -18
  24. package/dist/src/db/plugin.d.ts +10 -0
  25. package/dist/src/db/schema/account.d.ts +22 -0
  26. package/dist/src/db/schema/rate-limit.d.ts +10 -0
  27. package/dist/src/db/schema/session.d.ts +17 -0
  28. package/dist/src/db/schema/shared.d.ts +6 -0
  29. package/dist/src/db/schema/user.d.ts +16 -0
  30. package/{src/db/schema/verification.ts → dist/src/db/schema/verification.d.ts} +8 -8
  31. package/dist/src/db/type.d.ts +159 -0
  32. package/dist/src/env/color-depth.d.ts +1 -0
  33. package/dist/src/env/env-impl.d.ts +29 -0
  34. package/dist/src/env/index.d.ts +3 -0
  35. package/dist/src/env/logger.d.ts +52 -0
  36. package/dist/src/error/codes.d.ts +27 -0
  37. package/dist/src/error/index.d.ts +4 -0
  38. package/dist/src/oauth2/client-credentials-token.d.ts +21 -0
  39. package/dist/src/oauth2/create-authorization-url.d.ts +21 -0
  40. package/dist/src/oauth2/index.d.ts +6 -0
  41. package/dist/src/oauth2/oauth-provider.d.ts +178 -0
  42. package/dist/src/oauth2/refresh-access-token.d.ts +20 -0
  43. package/dist/src/oauth2/utils.d.ts +3 -0
  44. package/dist/src/oauth2/validate-authorization-code.d.ts +28 -0
  45. package/dist/src/social-providers/github.d.ts +89 -0
  46. package/dist/src/social-providers/google.d.ts +82 -0
  47. package/dist/src/social-providers/index.d.ts +93 -0
  48. package/dist/src/types/context.d.ts +177 -0
  49. package/dist/src/types/cookie.d.ts +15 -0
  50. package/dist/src/types/helper.d.ts +9 -0
  51. package/dist/src/types/index.d.ts +7 -0
  52. package/dist/src/types/init-options.d.ts +1140 -0
  53. package/dist/src/types/plugin-client.d.ts +88 -0
  54. package/dist/src/types/plugin.d.ts +120 -0
  55. package/dist/src/utils/error-codes.d.ts +9 -0
  56. package/dist/utils/index.mjs +1 -1
  57. package/package.json +11 -5
  58. package/.turbo/turbo-build.log +0 -42
  59. package/dist/index-DjrJweYn.d.mts +0 -7312
  60. package/src/api/index.ts +0 -57
  61. package/src/async_hooks/index.ts +0 -35
  62. package/src/context/endpoint-context.ts +0 -48
  63. package/src/context/index.ts +0 -21
  64. package/src/context/request-state.test.ts +0 -346
  65. package/src/context/request-state.ts +0 -75
  66. package/src/context/transaction.ts +0 -72
  67. package/src/db/adapter/index.ts +0 -466
  68. package/src/db/plugin.ts +0 -11
  69. package/src/db/schema/account.ts +0 -34
  70. package/src/db/schema/rate-limit.ts +0 -21
  71. package/src/db/schema/session.ts +0 -17
  72. package/src/db/schema/shared.ts +0 -7
  73. package/src/db/schema/user.ts +0 -16
  74. package/src/db/type.ts +0 -180
  75. package/src/env/color-depth.ts +0 -172
  76. package/src/env/env-impl.ts +0 -124
  77. package/src/env/index.ts +0 -23
  78. package/src/env/logger.test.ts +0 -33
  79. package/src/env/logger.ts +0 -145
  80. package/src/error/codes.ts +0 -31
  81. package/src/error/index.ts +0 -11
  82. package/src/oauth2/client-credentials-token.ts +0 -102
  83. package/src/oauth2/create-authorization-url.ts +0 -85
  84. package/src/oauth2/index.ts +0 -21
  85. package/src/oauth2/oauth-provider.ts +0 -211
  86. package/src/oauth2/refresh-access-token.ts +0 -124
  87. package/src/oauth2/utils.ts +0 -36
  88. package/src/oauth2/validate-authorization-code.ts +0 -154
  89. package/src/social-providers/apple.ts +0 -213
  90. package/src/social-providers/atlassian.ts +0 -132
  91. package/src/social-providers/cognito.ts +0 -269
  92. package/src/social-providers/discord.ts +0 -169
  93. package/src/social-providers/dropbox.ts +0 -112
  94. package/src/social-providers/facebook.ts +0 -204
  95. package/src/social-providers/figma.ts +0 -115
  96. package/src/social-providers/github.ts +0 -154
  97. package/src/social-providers/gitlab.ts +0 -152
  98. package/src/social-providers/google.ts +0 -171
  99. package/src/social-providers/huggingface.ts +0 -118
  100. package/src/social-providers/index.ts +0 -124
  101. package/src/social-providers/kakao.ts +0 -178
  102. package/src/social-providers/kick.ts +0 -92
  103. package/src/social-providers/line.ts +0 -169
  104. package/src/social-providers/linear.ts +0 -120
  105. package/src/social-providers/linkedin.ts +0 -110
  106. package/src/social-providers/microsoft-entra-id.ts +0 -245
  107. package/src/social-providers/naver.ts +0 -112
  108. package/src/social-providers/notion.ts +0 -108
  109. package/src/social-providers/paybin.ts +0 -122
  110. package/src/social-providers/paypal.ts +0 -263
  111. package/src/social-providers/polar.ts +0 -107
  112. package/src/social-providers/reddit.ts +0 -122
  113. package/src/social-providers/roblox.ts +0 -110
  114. package/src/social-providers/salesforce.ts +0 -159
  115. package/src/social-providers/slack.ts +0 -111
  116. package/src/social-providers/spotify.ts +0 -93
  117. package/src/social-providers/tiktok.ts +0 -210
  118. package/src/social-providers/twitch.ts +0 -111
  119. package/src/social-providers/twitter.ts +0 -198
  120. package/src/social-providers/vk.ts +0 -126
  121. package/src/social-providers/zoom.ts +0 -217
  122. package/src/types/context.ts +0 -269
  123. package/src/types/cookie.ts +0 -7
  124. package/src/types/helper.ts +0 -6
  125. package/src/types/index.ts +0 -21
  126. package/src/types/init-options.ts +0 -1346
  127. package/src/types/plugin-client.ts +0 -100
  128. package/src/types/plugin.ts +0 -158
  129. package/src/utils/error-codes.ts +0 -51
  130. package/tsconfig.json +0 -7
  131. package/tsdown.config.ts +0 -20
  132. /package/{src/index.ts → dist/src/index.d.ts} +0 -0
  133. /package/{src/utils/index.ts → dist/src/utils/index.d.ts} +0 -0
  134. /package/dist/{utils-BFQCAfNW.mjs → utils-CTOwq_Nj.mjs} +0 -0
@@ -1,154 +0,0 @@
1
- import { base64 } from "@gatwech/utils/base64";
2
- import { betterFetch } from "@better-fetch/fetch";
3
- import { jwtVerify } from "jose";
4
- import type { ProviderOptions } from "./index";
5
- import { getOAuth2Tokens } from "./index";
6
-
7
- export function createAuthorizationCodeRequest({
8
- code,
9
- codeVerifier,
10
- redirectURI,
11
- options,
12
- authentication,
13
- deviceId,
14
- headers,
15
- additionalParams = {},
16
- resource,
17
- }: {
18
- code: string;
19
- redirectURI: string;
20
- options: Partial<ProviderOptions>;
21
- codeVerifier?: string | undefined;
22
- deviceId?: string | undefined;
23
- authentication?: ("basic" | "post") | undefined;
24
- headers?: Record<string, string> | undefined;
25
- additionalParams?: Record<string, string> | undefined;
26
- resource?: (string | string[]) | undefined;
27
- }) {
28
- const body = new URLSearchParams();
29
- const requestHeaders: Record<string, any> = {
30
- "content-type": "application/x-www-form-urlencoded",
31
- accept: "application/json",
32
- ...headers,
33
- };
34
- body.set("grant_type", "authorization_code");
35
- body.set("code", code);
36
- codeVerifier && body.set("code_verifier", codeVerifier);
37
- options.clientKey && body.set("client_key", options.clientKey);
38
- deviceId && body.set("device_id", deviceId);
39
- body.set("redirect_uri", options.redirectURI || redirectURI);
40
- if (resource) {
41
- if (typeof resource === "string") {
42
- body.append("resource", resource);
43
- } else {
44
- for (const _resource of resource) {
45
- body.append("resource", _resource);
46
- }
47
- }
48
- }
49
- // Use standard Base64 encoding for HTTP Basic Auth (OAuth2 spec, RFC 7617)
50
- // Fixes compatibility with providers like Notion, Twitter, etc.
51
- if (authentication === "basic") {
52
- const primaryClientId = Array.isArray(options.clientId)
53
- ? options.clientId[0]
54
- : options.clientId;
55
- const encodedCredentials = base64.encode(
56
- `${primaryClientId}:${options.clientSecret ?? ""}`
57
- );
58
- requestHeaders["authorization"] = `Basic ${encodedCredentials}`;
59
- } else {
60
- const primaryClientId = Array.isArray(options.clientId)
61
- ? options.clientId[0]
62
- : options.clientId;
63
- body.set("client_id", primaryClientId);
64
- if (options.clientSecret) {
65
- body.set("client_secret", options.clientSecret);
66
- }
67
- }
68
-
69
- for (const [key, value] of Object.entries(additionalParams)) {
70
- if (!body.has(key)) body.append(key, value);
71
- }
72
-
73
- return {
74
- body,
75
- headers: requestHeaders,
76
- };
77
- }
78
-
79
- export async function validateAuthorizationCode({
80
- code,
81
- codeVerifier,
82
- redirectURI,
83
- options,
84
- tokenEndpoint,
85
- authentication,
86
- deviceId,
87
- headers,
88
- additionalParams = {},
89
- resource,
90
- }: {
91
- code: string;
92
- redirectURI: string;
93
- options: Partial<ProviderOptions>;
94
- codeVerifier?: string | undefined;
95
- deviceId?: string | undefined;
96
- tokenEndpoint: string;
97
- authentication?: ("basic" | "post") | undefined;
98
- headers?: Record<string, string> | undefined;
99
- additionalParams?: Record<string, string> | undefined;
100
- resource?: (string | string[]) | undefined;
101
- }) {
102
- const { body, headers: requestHeaders } = createAuthorizationCodeRequest({
103
- code,
104
- codeVerifier,
105
- redirectURI,
106
- options,
107
- authentication,
108
- deviceId,
109
- headers,
110
- additionalParams,
111
- resource,
112
- });
113
-
114
- const { data, error } = await betterFetch<object>(tokenEndpoint, {
115
- method: "POST",
116
- body: body,
117
- headers: requestHeaders,
118
- });
119
-
120
- if (error) {
121
- throw error;
122
- }
123
- const tokens = getOAuth2Tokens(data);
124
- return tokens;
125
- }
126
-
127
- export async function validateToken(token: string, jwksEndpoint: string) {
128
- const { data, error } = await betterFetch<{
129
- keys: {
130
- kid: string;
131
- kty: string;
132
- use: string;
133
- n: string;
134
- e: string;
135
- x5c: string[];
136
- }[];
137
- }>(jwksEndpoint, {
138
- method: "GET",
139
- headers: {
140
- accept: "application/json",
141
- },
142
- });
143
- if (error) {
144
- throw error;
145
- }
146
- const keys = data["keys"];
147
- const header = JSON.parse(atob(token.split(".")[0]!));
148
- const key = keys.find((key) => key.kid === header.kid);
149
- if (!key) {
150
- throw new Error("Key not found");
151
- }
152
- const verified = await jwtVerify(token, key);
153
- return verified;
154
- }
@@ -1,213 +0,0 @@
1
- import { betterFetch } from "@better-fetch/fetch";
2
- import { APIError } from "better-call";
3
- import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
4
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
5
- import {
6
- createAuthorizationURL,
7
- refreshAccessToken,
8
- validateAuthorizationCode,
9
- } from "../oauth2";
10
- export interface AppleProfile {
11
- /**
12
- * The subject registered claim identifies the principal that’s the subject
13
- * of the identity token. Because this token is for your app, the value is
14
- * the unique identifier for the user.
15
- */
16
- sub: string;
17
- /**
18
- * A String value representing the user's email address.
19
- * The email address is either the user's real email address or the proxy
20
- * address, depending on their status private email relay service.
21
- */
22
- email: string;
23
- /**
24
- * A string or Boolean value that indicates whether the service verifies
25
- * the email. The value can either be a string ("true" or "false") or a
26
- * Boolean (true or false). The system may not verify email addresses for
27
- * Sign in with Apple at Work & School users, and this claim is "false" or
28
- * false for those users.
29
- */
30
- email_verified: true | "true";
31
- /**
32
- * A string or Boolean value that indicates whether the email that the user
33
- * shares is the proxy address. The value can either be a string ("true" or
34
- * "false") or a Boolean (true or false).
35
- */
36
- is_private_email: boolean;
37
- /**
38
- * An Integer value that indicates whether the user appears to be a real
39
- * person. Use the value of this claim to mitigate fraud. The possible
40
- * values are: 0 (or Unsupported), 1 (or Unknown), 2 (or LikelyReal). For
41
- * more information, see ASUserDetectionStatus. This claim is present only
42
- * in iOS 14 and later, macOS 11 and later, watchOS 7 and later, tvOS 14
43
- * and later. The claim isn’t present or supported for web-based apps.
44
- */
45
- real_user_status: number;
46
- /**
47
- * The user’s full name in the format provided during the authorization
48
- * process.
49
- */
50
- name: string;
51
- /**
52
- * The URL to the user's profile picture.
53
- */
54
- picture: string;
55
- user?: AppleNonConformUser | undefined;
56
- }
57
-
58
- /**
59
- * This is the shape of the `user` query parameter that Apple sends the first
60
- * time the user consents to the app.
61
- * @see https://developer.apple.com/documentation/signinwithapplerestapi/request-an-authorization-to-the-sign-in-with-apple-server./
62
- */
63
- export interface AppleNonConformUser {
64
- name: {
65
- firstName: string;
66
- lastName: string;
67
- };
68
- email: string;
69
- }
70
-
71
- export interface AppleOptions extends ProviderOptions<AppleProfile> {
72
- clientId: string;
73
- appBundleIdentifier?: string | undefined;
74
- audience?: (string | string[]) | undefined;
75
- }
76
-
77
- export const apple = (options: AppleOptions) => {
78
- const tokenEndpoint = "https://appleid.apple.com/auth/token";
79
- return {
80
- id: "apple",
81
- name: "Apple",
82
- async createAuthorizationURL({ state, scopes, redirectURI }) {
83
- const _scope = options.disableDefaultScope ? [] : ["email", "name"];
84
- if (options.scope) _scope.push(...options.scope);
85
- if (scopes) _scope.push(...scopes);
86
- const url = await createAuthorizationURL({
87
- id: "apple",
88
- options,
89
- authorizationEndpoint: "https://appleid.apple.com/auth/authorize",
90
- scopes: _scope,
91
- state,
92
- redirectURI,
93
- responseMode: "form_post",
94
- responseType: "code id_token",
95
- });
96
- return url;
97
- },
98
- validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
99
- return validateAuthorizationCode({
100
- code,
101
- codeVerifier,
102
- redirectURI,
103
- options,
104
- tokenEndpoint,
105
- });
106
- },
107
- async verifyIdToken(token, nonce) {
108
- if (options.disableIdTokenSignIn) {
109
- return false;
110
- }
111
- if (options.verifyIdToken) {
112
- return options.verifyIdToken(token, nonce);
113
- }
114
- const decodedHeader = decodeProtectedHeader(token);
115
- const { kid, alg: jwtAlg } = decodedHeader;
116
- if (!kid || !jwtAlg) return false;
117
- const publicKey = await getApplePublicKey(kid);
118
- const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
119
- algorithms: [jwtAlg],
120
- issuer: "https://appleid.apple.com",
121
- audience:
122
- options.audience && options.audience.length
123
- ? options.audience
124
- : options.appBundleIdentifier
125
- ? options.appBundleIdentifier
126
- : options.clientId,
127
- maxTokenAge: "1h",
128
- });
129
- ["email_verified", "is_private_email"].forEach((field) => {
130
- if (jwtClaims[field] !== undefined) {
131
- jwtClaims[field] = Boolean(jwtClaims[field]);
132
- }
133
- });
134
- if (nonce && jwtClaims.nonce !== nonce) {
135
- return false;
136
- }
137
- return !!jwtClaims;
138
- },
139
- refreshAccessToken: options.refreshAccessToken
140
- ? options.refreshAccessToken
141
- : async (refreshToken) => {
142
- return refreshAccessToken({
143
- refreshToken,
144
- options: {
145
- clientId: options.clientId,
146
- clientKey: options.clientKey,
147
- clientSecret: options.clientSecret,
148
- },
149
- tokenEndpoint: "https://appleid.apple.com/auth/token",
150
- });
151
- },
152
- async getUserInfo(token) {
153
- if (options.getUserInfo) {
154
- return options.getUserInfo(token);
155
- }
156
- if (!token.idToken) {
157
- return null;
158
- }
159
- const profile = decodeJwt<AppleProfile>(token.idToken);
160
- if (!profile) {
161
- return null;
162
- }
163
- const name = token.user
164
- ? `${token.user.name?.firstName} ${token.user.name?.lastName}`
165
- : profile.name || profile.email;
166
- const emailVerified =
167
- typeof profile.email_verified === "boolean"
168
- ? profile.email_verified
169
- : profile.email_verified === "true";
170
- const enrichedProfile = {
171
- ...profile,
172
- name,
173
- };
174
- const userMap = await options.mapProfileToUser?.(enrichedProfile);
175
- return {
176
- user: {
177
- id: profile.sub,
178
- name: enrichedProfile.name,
179
- emailVerified: emailVerified,
180
- email: profile.email,
181
- ...userMap,
182
- },
183
- data: enrichedProfile,
184
- };
185
- },
186
- options,
187
- } satisfies OAuthProvider<AppleProfile>;
188
- };
189
-
190
- export const getApplePublicKey = async (kid: string) => {
191
- const APPLE_BASE_URL = "https://appleid.apple.com";
192
- const JWKS_APPLE_URI = "/auth/keys";
193
- const { data } = await betterFetch<{
194
- keys: Array<{
195
- kid: string;
196
- alg: string;
197
- kty: string;
198
- use: string;
199
- n: string;
200
- e: string;
201
- }>;
202
- }>(`${APPLE_BASE_URL}${JWKS_APPLE_URI}`);
203
- if (!data?.keys) {
204
- throw new APIError("BAD_REQUEST", {
205
- message: "Keys not found",
206
- });
207
- }
208
- const jwk = data.keys.find((key) => key.kid === kid);
209
- if (!jwk) {
210
- throw new Error(`JWK with kid ${kid} not found`);
211
- }
212
- return await importJWK(jwk, jwk.alg);
213
- };
@@ -1,132 +0,0 @@
1
- import { betterFetch } from "@better-fetch/fetch";
2
- import { logger } from "../env";
3
- import { PayvoAuthError } from "../error";
4
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
5
- import {
6
- createAuthorizationURL,
7
- refreshAccessToken,
8
- validateAuthorizationCode,
9
- } from "../oauth2";
10
-
11
- export interface AtlassianProfile {
12
- account_type?: string | undefined;
13
- account_id: string;
14
- email?: string | undefined;
15
- name: string;
16
- picture?: string | undefined;
17
- nickname?: string | undefined;
18
- locale?: string | undefined;
19
- extended_profile?:
20
- | {
21
- job_title?: string;
22
- organization?: string;
23
- department?: string;
24
- location?: string;
25
- }
26
- | undefined;
27
- }
28
- export interface AtlassianOptions extends ProviderOptions<AtlassianProfile> {
29
- clientId: string;
30
- }
31
-
32
- export const atlassian = (options: AtlassianOptions) => {
33
- return {
34
- id: "atlassian",
35
- name: "Atlassian",
36
-
37
- async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
38
- if (!options.clientId || !options.clientSecret) {
39
- logger.error("Client Id and Secret are required for Atlassian");
40
- throw new PayvoAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
41
- }
42
- if (!codeVerifier) {
43
- throw new PayvoAuthError("codeVerifier is required for Atlassian");
44
- }
45
-
46
- const _scopes = options.disableDefaultScope
47
- ? []
48
- : ["read:jira-user", "offline_access"];
49
- if (options.scope) _scopes.push(...options.scope);
50
- if (scopes) _scopes.push(...scopes);
51
-
52
- return createAuthorizationURL({
53
- id: "atlassian",
54
- options,
55
- authorizationEndpoint: "https://auth.atlassian.com/authorize",
56
- scopes: _scopes,
57
- state,
58
- codeVerifier,
59
- redirectURI,
60
- additionalParams: {
61
- audience: "api.atlassian.com",
62
- },
63
- prompt: options.prompt,
64
- });
65
- },
66
-
67
- validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
68
- return validateAuthorizationCode({
69
- code,
70
- codeVerifier,
71
- redirectURI,
72
- options,
73
- tokenEndpoint: "https://auth.atlassian.com/oauth/token",
74
- });
75
- },
76
-
77
- refreshAccessToken: options.refreshAccessToken
78
- ? options.refreshAccessToken
79
- : async (refreshToken) => {
80
- return refreshAccessToken({
81
- refreshToken,
82
- options: {
83
- clientId: options.clientId,
84
- clientSecret: options.clientSecret,
85
- },
86
- tokenEndpoint: "https://auth.atlassian.com/oauth/token",
87
- });
88
- },
89
-
90
- async getUserInfo(token) {
91
- if (options.getUserInfo) {
92
- return options.getUserInfo(token);
93
- }
94
-
95
- if (!token.accessToken) {
96
- return null;
97
- }
98
-
99
- try {
100
- const { data: profile } = await betterFetch<{
101
- account_id: string;
102
- name: string;
103
- email?: string | undefined;
104
- picture?: string | undefined;
105
- }>("https://api.atlassian.com/me", {
106
- headers: { Authorization: `Bearer ${token.accessToken}` },
107
- });
108
-
109
- if (!profile) return null;
110
-
111
- const userMap = await options.mapProfileToUser?.(profile);
112
-
113
- return {
114
- user: {
115
- id: profile.account_id,
116
- name: profile.name,
117
- email: profile.email,
118
- image: profile.picture,
119
- emailVerified: false,
120
- ...userMap,
121
- },
122
- data: profile,
123
- };
124
- } catch (error) {
125
- logger.error("Failed to fetch user info from Figma:", error);
126
- return null;
127
- }
128
- },
129
-
130
- options,
131
- } satisfies OAuthProvider<AtlassianProfile>;
132
- };