better-auth 1.1.18-beta.2 → 1.1.18

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 (33) hide show
  1. package/dist/{chunk-2VHVLRC7.cjs → chunk-5JET74FS.cjs} +50 -1
  2. package/dist/{chunk-CFERT2JN.js → chunk-HA5MENQC.js} +110 -2
  3. package/dist/{chunk-ZQFLCL7G.js → chunk-KFSLYLGZ.js} +50 -1
  4. package/dist/{chunk-FS4Q5N2E.js → chunk-PB7U7ZCO.js} +57 -2
  5. package/dist/{chunk-7TKSNJYH.js → chunk-RPETDYVK.js} +23 -0
  6. package/dist/{chunk-G4LNWL5L.cjs → chunk-TSWYQ5PN.cjs} +57 -2
  7. package/dist/{chunk-OLYRJHRO.cjs → chunk-W2IU5LNT.cjs} +23 -0
  8. package/dist/{chunk-KM2MFBY2.cjs → chunk-XW6BECDW.cjs} +109 -1
  9. package/dist/client/plugins.d.cts +1 -1
  10. package/dist/client/plugins.d.ts +1 -1
  11. package/dist/{index-BAdTrrkO.d.ts → index-Cqgianwx.d.ts} +107 -0
  12. package/dist/{index-CvVfvvKF.d.cts → index-D2rGzLac.d.cts} +107 -0
  13. package/dist/plugins/admin.cjs +2 -2
  14. package/dist/plugins/admin.d.cts +65 -0
  15. package/dist/plugins/admin.d.ts +65 -0
  16. package/dist/plugins/admin.js +1 -1
  17. package/dist/plugins/generic-oauth.cjs +2 -2
  18. package/dist/plugins/generic-oauth.d.cts +82 -0
  19. package/dist/plugins/generic-oauth.d.ts +82 -0
  20. package/dist/plugins/generic-oauth.js +1 -1
  21. package/dist/plugins/organization.cjs +2 -2
  22. package/dist/plugins/organization.d.cts +1 -1
  23. package/dist/plugins/organization.d.ts +1 -1
  24. package/dist/plugins/organization.js +1 -1
  25. package/dist/plugins/username.cjs +2 -2
  26. package/dist/plugins/username.d.cts +18 -0
  27. package/dist/plugins/username.d.ts +18 -0
  28. package/dist/plugins/username.js +1 -1
  29. package/dist/plugins.cjs +8 -8
  30. package/dist/plugins.d.cts +1 -1
  31. package/dist/plugins.d.ts +1 -1
  32. package/dist/plugins.js +4 -4
  33. package/package.json +1 -1
@@ -1351,6 +1351,54 @@ var getActiveMember = chunkCQNPMJJ3_cjs.createAuthEndpoint(
1351
1351
  return ctx.json(member);
1352
1352
  }
1353
1353
  );
1354
+ var leaveOrganization = chunkCQNPMJJ3_cjs.createAuthEndpoint(
1355
+ "/organization/leave",
1356
+ {
1357
+ method: "POST",
1358
+ body: zod.z.object({
1359
+ organizationId: zod.z.string()
1360
+ }),
1361
+ use: [chunkCQNPMJJ3_cjs.sessionMiddleware, orgMiddleware]
1362
+ },
1363
+ async (ctx) => {
1364
+ const session = ctx.context.session;
1365
+ const adapter = getOrgAdapter(ctx.context);
1366
+ const member = await adapter.findMemberByOrgId({
1367
+ userId: session.user.id,
1368
+ organizationId: ctx.body.organizationId
1369
+ });
1370
+ if (!member) {
1371
+ throw new betterCall.APIError("BAD_REQUEST", {
1372
+ message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND
1373
+ });
1374
+ }
1375
+ const isOwnerLeaving = member.role === (ctx.context.orgOptions?.creatorRole || "owner");
1376
+ if (isOwnerLeaving) {
1377
+ const members = await ctx.context.adapter.findMany({
1378
+ model: "member",
1379
+ where: [
1380
+ {
1381
+ field: "organizationId",
1382
+ value: ctx.body.organizationId
1383
+ }
1384
+ ]
1385
+ });
1386
+ const owners = members.filter(
1387
+ (member2) => member2.role === (ctx.context.orgOptions?.creatorRole || "owner")
1388
+ );
1389
+ if (owners.length <= 1) {
1390
+ throw new betterCall.APIError("BAD_REQUEST", {
1391
+ message: ORGANIZATION_ERROR_CODES.YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER
1392
+ });
1393
+ }
1394
+ }
1395
+ await adapter.deleteMember(member.id);
1396
+ if (session.session.activeOrganizationId === ctx.body.organizationId) {
1397
+ await adapter.setActiveOrganization(session.session.token, null);
1398
+ }
1399
+ return ctx.json(member);
1400
+ }
1401
+ );
1354
1402
  var createOrganization = chunkCQNPMJJ3_cjs.createAuthEndpoint(
1355
1403
  "/organization/create",
1356
1404
  {
@@ -1860,7 +1908,8 @@ var organization = (options) => {
1860
1908
  addMember: addMember(),
1861
1909
  removeMember,
1862
1910
  updateMemberRole: updateMemberRole(),
1863
- getActiveMember
1911
+ getActiveMember,
1912
+ leaveOrganization
1864
1913
  };
1865
1914
  const roles = {
1866
1915
  ...chunk2X5G64P2_cjs.defaultRoles,
@@ -1,4 +1,4 @@
1
- import { createAuthEndpoint, handleOAuthUserInfo } from './chunk-KALC2G3Y.js';
1
+ import { createAuthEndpoint, handleOAuthUserInfo, sessionMiddleware, BASE_ERROR_CODES } from './chunk-KALC2G3Y.js';
2
2
  import { createAuthorizationURL, validateAuthorizationCode } from './chunk-GTQM7JU7.js';
3
3
  import { setSessionCookie } from './chunk-QQGZ3XGI.js';
4
4
  import { generateState, parseState } from './chunk-NPO64SVN.js';
@@ -306,7 +306,7 @@ var genericOAuth = (options) => {
306
306
  }
307
307
  let tokens = void 0;
308
308
  const parsedState = await parseState(ctx);
309
- const { callbackURL, codeVerifier, errorURL, newUserURL } = parsedState;
309
+ const { callbackURL, codeVerifier, errorURL, newUserURL, link } = parsedState;
310
310
  const code = ctx.query.code;
311
311
  let finalTokenUrl = provider.tokenUrl;
312
312
  let finalUserInfoUrl = provider.userInfoUrl;
@@ -358,6 +358,27 @@ var genericOAuth = (options) => {
358
358
  );
359
359
  }
360
360
  const mapUser = provider.mapProfileToUser ? await provider.mapProfileToUser(userInfo) : null;
361
+ if (link) {
362
+ if (link.email !== userInfo.email.toLowerCase()) {
363
+ return redirectOnError("email_doesn't_match");
364
+ }
365
+ const newAccount = await ctx.context.internalAdapter.createAccount({
366
+ userId: link.userId,
367
+ providerId: provider.providerId,
368
+ accountId: userInfo.id
369
+ });
370
+ if (!newAccount) {
371
+ return redirectOnError("unable_to_link_account");
372
+ }
373
+ let toRedirectTo2;
374
+ try {
375
+ const url = callbackURL;
376
+ toRedirectTo2 = url.toString();
377
+ } catch {
378
+ toRedirectTo2 = callbackURL;
379
+ }
380
+ throw ctx.redirect(toRedirectTo2);
381
+ }
361
382
  const result = await handleOAuthUserInfo(ctx, {
362
383
  userInfo: {
363
384
  ...userInfo,
@@ -392,6 +413,93 @@ var genericOAuth = (options) => {
392
413
  }
393
414
  throw ctx.redirect(toRedirectTo);
394
415
  }
416
+ ),
417
+ oAuth2LinkAccount: createAuthEndpoint(
418
+ "/oauth2/link",
419
+ {
420
+ method: "POST",
421
+ body: z.object({
422
+ providerId: z.string(),
423
+ callbackURL: z.string()
424
+ }),
425
+ use: [sessionMiddleware]
426
+ },
427
+ async (c) => {
428
+ const session = c.context.session;
429
+ const account = await c.context.internalAdapter.findAccounts(
430
+ session.user.id
431
+ );
432
+ const existingAccount = account.find(
433
+ (a) => a.providerId === c.body.providerId
434
+ );
435
+ if (existingAccount) {
436
+ throw new APIError("BAD_REQUEST", {
437
+ message: BASE_ERROR_CODES.SOCIAL_ACCOUNT_ALREADY_LINKED
438
+ });
439
+ }
440
+ const provider = options.config.find(
441
+ (p) => p.providerId === c.body.providerId
442
+ );
443
+ if (!provider) {
444
+ throw new APIError("NOT_FOUND", {
445
+ message: BASE_ERROR_CODES.PROVIDER_NOT_FOUND
446
+ });
447
+ }
448
+ const {
449
+ providerId,
450
+ clientId,
451
+ clientSecret,
452
+ redirectURI,
453
+ authorizationUrl,
454
+ discoveryUrl,
455
+ pkce,
456
+ scopes
457
+ } = provider;
458
+ let finalAuthUrl = authorizationUrl;
459
+ if (!finalAuthUrl) {
460
+ if (!discoveryUrl) {
461
+ throw new APIError("BAD_REQUEST", {
462
+ message: ERROR_CODES.INVALID_OAUTH_CONFIGURATION
463
+ });
464
+ }
465
+ const discovery = await betterFetch(discoveryUrl, {
466
+ onError(context) {
467
+ c.context.logger.error(context.error.message, context.error, {
468
+ discoveryUrl
469
+ });
470
+ }
471
+ });
472
+ if (discovery.data) {
473
+ finalAuthUrl = discovery.data.authorization_endpoint;
474
+ }
475
+ }
476
+ if (!finalAuthUrl) {
477
+ throw new APIError("BAD_REQUEST", {
478
+ message: ERROR_CODES.INVALID_OAUTH_CONFIGURATION
479
+ });
480
+ }
481
+ const state = await generateState(c, {
482
+ userId: session.user.id,
483
+ email: session.user.email
484
+ });
485
+ const url = await createAuthorizationURL({
486
+ id: providerId,
487
+ options: {
488
+ clientId,
489
+ clientSecret,
490
+ redirectURI: redirectURI || `${c.context.baseURL}/oauth2/callback`
491
+ },
492
+ authorizationEndpoint: finalAuthUrl,
493
+ state: state.state,
494
+ codeVerifier: pkce ? state.codeVerifier : void 0,
495
+ scopes: scopes || [],
496
+ redirectURI: `${c.context.baseURL}/oauth2/callback/${providerId}`
497
+ });
498
+ return c.json({
499
+ url: url.toString(),
500
+ redirect: true
501
+ });
502
+ }
395
503
  )
396
504
  },
397
505
  $ERROR_CODES: ERROR_CODES
@@ -1349,6 +1349,54 @@ var getActiveMember = createAuthEndpoint(
1349
1349
  return ctx.json(member);
1350
1350
  }
1351
1351
  );
1352
+ var leaveOrganization = createAuthEndpoint(
1353
+ "/organization/leave",
1354
+ {
1355
+ method: "POST",
1356
+ body: z.object({
1357
+ organizationId: z.string()
1358
+ }),
1359
+ use: [sessionMiddleware, orgMiddleware]
1360
+ },
1361
+ async (ctx) => {
1362
+ const session = ctx.context.session;
1363
+ const adapter = getOrgAdapter(ctx.context);
1364
+ const member = await adapter.findMemberByOrgId({
1365
+ userId: session.user.id,
1366
+ organizationId: ctx.body.organizationId
1367
+ });
1368
+ if (!member) {
1369
+ throw new APIError("BAD_REQUEST", {
1370
+ message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND
1371
+ });
1372
+ }
1373
+ const isOwnerLeaving = member.role === (ctx.context.orgOptions?.creatorRole || "owner");
1374
+ if (isOwnerLeaving) {
1375
+ const members = await ctx.context.adapter.findMany({
1376
+ model: "member",
1377
+ where: [
1378
+ {
1379
+ field: "organizationId",
1380
+ value: ctx.body.organizationId
1381
+ }
1382
+ ]
1383
+ });
1384
+ const owners = members.filter(
1385
+ (member2) => member2.role === (ctx.context.orgOptions?.creatorRole || "owner")
1386
+ );
1387
+ if (owners.length <= 1) {
1388
+ throw new APIError("BAD_REQUEST", {
1389
+ message: ORGANIZATION_ERROR_CODES.YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER
1390
+ });
1391
+ }
1392
+ }
1393
+ await adapter.deleteMember(member.id);
1394
+ if (session.session.activeOrganizationId === ctx.body.organizationId) {
1395
+ await adapter.setActiveOrganization(session.session.token, null);
1396
+ }
1397
+ return ctx.json(member);
1398
+ }
1399
+ );
1352
1400
  var createOrganization = createAuthEndpoint(
1353
1401
  "/organization/create",
1354
1402
  {
@@ -1858,7 +1906,8 @@ var organization = (options) => {
1858
1906
  addMember: addMember(),
1859
1907
  removeMember,
1860
1908
  updateMemberRole: updateMemberRole(),
1861
- getActiveMember
1909
+ getActiveMember,
1910
+ leaveOrganization
1862
1911
  };
1863
1912
  const roles = {
1864
1913
  ...defaultRoles,
@@ -26,12 +26,18 @@ var schema = {
26
26
  };
27
27
 
28
28
  // src/plugins/username/index.ts
29
+ function defaultUsernameValidator(username2) {
30
+ return /^[a-zA-Z0-9_]+$/.test(username2);
31
+ }
29
32
  var username = (options) => {
30
33
  const ERROR_CODES = {
31
34
  INVALID_USERNAME_OR_PASSWORD: "invalid username or password",
32
35
  EMAIL_NOT_VERIFIED: "email not verified",
33
36
  UNEXPECTED_ERROR: "unexpected error",
34
- USERNAME_IS_ALREADY_TAKEN: "username is already taken. please try another."
37
+ USERNAME_IS_ALREADY_TAKEN: "username is already taken. please try another.",
38
+ USERNAME_TOO_SHORT: "username is too short",
39
+ USERNAME_TOO_LONG: "username is too long",
40
+ INVALID_USERNAME: "username is invalid"
35
41
  };
36
42
  return {
37
43
  id: "username",
@@ -79,6 +85,36 @@ var username = (options) => {
79
85
  }
80
86
  },
81
87
  async (ctx) => {
88
+ if (!ctx.body.username || !ctx.body.password) {
89
+ ctx.context.logger.error("Username or password not found");
90
+ throw new APIError("UNAUTHORIZED", {
91
+ message: ERROR_CODES.INVALID_USERNAME_OR_PASSWORD
92
+ });
93
+ }
94
+ const minUsernameLength = options?.minUsernameLength || 3;
95
+ const maxUsernameLength = options?.maxUsernameLength || 30;
96
+ if (ctx.body.username.length < minUsernameLength) {
97
+ ctx.context.logger.error("Username too short", {
98
+ username: ctx.body.username
99
+ });
100
+ throw new APIError("UNPROCESSABLE_ENTITY", {
101
+ message: ERROR_CODES.USERNAME_TOO_SHORT
102
+ });
103
+ }
104
+ if (ctx.body.username.length > maxUsernameLength) {
105
+ ctx.context.logger.error("Username too long", {
106
+ username: ctx.body.username
107
+ });
108
+ throw new APIError("UNPROCESSABLE_ENTITY", {
109
+ message: ERROR_CODES.USERNAME_TOO_LONG
110
+ });
111
+ }
112
+ const validator = options?.usernameValidator || defaultUsernameValidator;
113
+ if (!validator(ctx.body.username)) {
114
+ throw new APIError("UNPROCESSABLE_ENTITY", {
115
+ message: ERROR_CODES.INVALID_USERNAME
116
+ });
117
+ }
82
118
  const user = await ctx.context.adapter.findOne({
83
119
  model: "user",
84
120
  where: [
@@ -175,11 +211,30 @@ var username = (options) => {
175
211
  before: [
176
212
  {
177
213
  matcher(context) {
178
- return context.path === "/sign-up/email";
214
+ return context.path === "/sign-up/email" || context.path === "/update-user";
179
215
  },
180
216
  async handler(ctx) {
181
217
  const username2 = ctx.body.username;
182
218
  if (username2) {
219
+ const minUsernameLength = options?.minUsernameLength || 3;
220
+ const maxUsernameLength = options?.maxUsernameLength || 30;
221
+ if (username2.length < minUsernameLength) {
222
+ throw new APIError("UNPROCESSABLE_ENTITY", {
223
+ message: ERROR_CODES.USERNAME_TOO_SHORT
224
+ });
225
+ }
226
+ if (username2.length > maxUsernameLength) {
227
+ throw new APIError("UNPROCESSABLE_ENTITY", {
228
+ message: ERROR_CODES.USERNAME_TOO_LONG
229
+ });
230
+ }
231
+ const validator = options?.usernameValidator || defaultUsernameValidator;
232
+ const valid = await validator(username2);
233
+ if (!valid) {
234
+ throw new APIError("UNPROCESSABLE_ENTITY", {
235
+ message: ERROR_CODES.INVALID_USERNAME
236
+ });
237
+ }
183
238
  const user = await ctx.context.adapter.findOne({
184
239
  model: "user",
185
240
  where: [
@@ -769,6 +769,29 @@ var admin = (options) => {
769
769
  success: true
770
770
  });
771
771
  }
772
+ ),
773
+ setUserPassword: createAuthEndpoint(
774
+ "/admin/set-user-password",
775
+ {
776
+ method: "POST",
777
+ body: z.object({
778
+ newPassword: z.string(),
779
+ userId: z.string()
780
+ }),
781
+ use: [adminMiddleware]
782
+ },
783
+ async (ctx) => {
784
+ const hashedPassword = await ctx.context.password.hash(
785
+ ctx.body.newPassword
786
+ );
787
+ await ctx.context.internalAdapter.updatePassword(
788
+ ctx.body.userId,
789
+ hashedPassword
790
+ );
791
+ return ctx.json({
792
+ status: true
793
+ });
794
+ }
772
795
  )
773
796
  },
774
797
  $ERROR_CODES: ERROR_CODES,
@@ -28,12 +28,18 @@ var schema = {
28
28
  };
29
29
 
30
30
  // src/plugins/username/index.ts
31
+ function defaultUsernameValidator(username2) {
32
+ return /^[a-zA-Z0-9_]+$/.test(username2);
33
+ }
31
34
  var username = (options) => {
32
35
  const ERROR_CODES = {
33
36
  INVALID_USERNAME_OR_PASSWORD: "invalid username or password",
34
37
  EMAIL_NOT_VERIFIED: "email not verified",
35
38
  UNEXPECTED_ERROR: "unexpected error",
36
- USERNAME_IS_ALREADY_TAKEN: "username is already taken. please try another."
39
+ USERNAME_IS_ALREADY_TAKEN: "username is already taken. please try another.",
40
+ USERNAME_TOO_SHORT: "username is too short",
41
+ USERNAME_TOO_LONG: "username is too long",
42
+ INVALID_USERNAME: "username is invalid"
37
43
  };
38
44
  return {
39
45
  id: "username",
@@ -81,6 +87,36 @@ var username = (options) => {
81
87
  }
82
88
  },
83
89
  async (ctx) => {
90
+ if (!ctx.body.username || !ctx.body.password) {
91
+ ctx.context.logger.error("Username or password not found");
92
+ throw new betterCall.APIError("UNAUTHORIZED", {
93
+ message: ERROR_CODES.INVALID_USERNAME_OR_PASSWORD
94
+ });
95
+ }
96
+ const minUsernameLength = options?.minUsernameLength || 3;
97
+ const maxUsernameLength = options?.maxUsernameLength || 30;
98
+ if (ctx.body.username.length < minUsernameLength) {
99
+ ctx.context.logger.error("Username too short", {
100
+ username: ctx.body.username
101
+ });
102
+ throw new betterCall.APIError("UNPROCESSABLE_ENTITY", {
103
+ message: ERROR_CODES.USERNAME_TOO_SHORT
104
+ });
105
+ }
106
+ if (ctx.body.username.length > maxUsernameLength) {
107
+ ctx.context.logger.error("Username too long", {
108
+ username: ctx.body.username
109
+ });
110
+ throw new betterCall.APIError("UNPROCESSABLE_ENTITY", {
111
+ message: ERROR_CODES.USERNAME_TOO_LONG
112
+ });
113
+ }
114
+ const validator = options?.usernameValidator || defaultUsernameValidator;
115
+ if (!validator(ctx.body.username)) {
116
+ throw new betterCall.APIError("UNPROCESSABLE_ENTITY", {
117
+ message: ERROR_CODES.INVALID_USERNAME
118
+ });
119
+ }
84
120
  const user = await ctx.context.adapter.findOne({
85
121
  model: "user",
86
122
  where: [
@@ -177,11 +213,30 @@ var username = (options) => {
177
213
  before: [
178
214
  {
179
215
  matcher(context) {
180
- return context.path === "/sign-up/email";
216
+ return context.path === "/sign-up/email" || context.path === "/update-user";
181
217
  },
182
218
  async handler(ctx) {
183
219
  const username2 = ctx.body.username;
184
220
  if (username2) {
221
+ const minUsernameLength = options?.minUsernameLength || 3;
222
+ const maxUsernameLength = options?.maxUsernameLength || 30;
223
+ if (username2.length < minUsernameLength) {
224
+ throw new betterCall.APIError("UNPROCESSABLE_ENTITY", {
225
+ message: ERROR_CODES.USERNAME_TOO_SHORT
226
+ });
227
+ }
228
+ if (username2.length > maxUsernameLength) {
229
+ throw new betterCall.APIError("UNPROCESSABLE_ENTITY", {
230
+ message: ERROR_CODES.USERNAME_TOO_LONG
231
+ });
232
+ }
233
+ const validator = options?.usernameValidator || defaultUsernameValidator;
234
+ const valid = await validator(username2);
235
+ if (!valid) {
236
+ throw new betterCall.APIError("UNPROCESSABLE_ENTITY", {
237
+ message: ERROR_CODES.INVALID_USERNAME
238
+ });
239
+ }
185
240
  const user = await ctx.context.adapter.findOne({
186
241
  model: "user",
187
242
  where: [
@@ -771,6 +771,29 @@ var admin = (options) => {
771
771
  success: true
772
772
  });
773
773
  }
774
+ ),
775
+ setUserPassword: chunkCQNPMJJ3_cjs.createAuthEndpoint(
776
+ "/admin/set-user-password",
777
+ {
778
+ method: "POST",
779
+ body: zod.z.object({
780
+ newPassword: zod.z.string(),
781
+ userId: zod.z.string()
782
+ }),
783
+ use: [adminMiddleware]
784
+ },
785
+ async (ctx) => {
786
+ const hashedPassword = await ctx.context.password.hash(
787
+ ctx.body.newPassword
788
+ );
789
+ await ctx.context.internalAdapter.updatePassword(
790
+ ctx.body.userId,
791
+ hashedPassword
792
+ );
793
+ return ctx.json({
794
+ status: true
795
+ });
796
+ }
774
797
  )
775
798
  },
776
799
  $ERROR_CODES: ERROR_CODES,
@@ -308,7 +308,7 @@ var genericOAuth = (options) => {
308
308
  }
309
309
  let tokens = void 0;
310
310
  const parsedState = await chunk5E75URIA_cjs.parseState(ctx);
311
- const { callbackURL, codeVerifier, errorURL, newUserURL } = parsedState;
311
+ const { callbackURL, codeVerifier, errorURL, newUserURL, link } = parsedState;
312
312
  const code = ctx.query.code;
313
313
  let finalTokenUrl = provider.tokenUrl;
314
314
  let finalUserInfoUrl = provider.userInfoUrl;
@@ -360,6 +360,27 @@ var genericOAuth = (options) => {
360
360
  );
361
361
  }
362
362
  const mapUser = provider.mapProfileToUser ? await provider.mapProfileToUser(userInfo) : null;
363
+ if (link) {
364
+ if (link.email !== userInfo.email.toLowerCase()) {
365
+ return redirectOnError("email_doesn't_match");
366
+ }
367
+ const newAccount = await ctx.context.internalAdapter.createAccount({
368
+ userId: link.userId,
369
+ providerId: provider.providerId,
370
+ accountId: userInfo.id
371
+ });
372
+ if (!newAccount) {
373
+ return redirectOnError("unable_to_link_account");
374
+ }
375
+ let toRedirectTo2;
376
+ try {
377
+ const url = callbackURL;
378
+ toRedirectTo2 = url.toString();
379
+ } catch {
380
+ toRedirectTo2 = callbackURL;
381
+ }
382
+ throw ctx.redirect(toRedirectTo2);
383
+ }
363
384
  const result = await chunkCQNPMJJ3_cjs.handleOAuthUserInfo(ctx, {
364
385
  userInfo: {
365
386
  ...userInfo,
@@ -394,6 +415,93 @@ var genericOAuth = (options) => {
394
415
  }
395
416
  throw ctx.redirect(toRedirectTo);
396
417
  }
418
+ ),
419
+ oAuth2LinkAccount: chunkCQNPMJJ3_cjs.createAuthEndpoint(
420
+ "/oauth2/link",
421
+ {
422
+ method: "POST",
423
+ body: zod.z.object({
424
+ providerId: zod.z.string(),
425
+ callbackURL: zod.z.string()
426
+ }),
427
+ use: [chunkCQNPMJJ3_cjs.sessionMiddleware]
428
+ },
429
+ async (c) => {
430
+ const session = c.context.session;
431
+ const account = await c.context.internalAdapter.findAccounts(
432
+ session.user.id
433
+ );
434
+ const existingAccount = account.find(
435
+ (a) => a.providerId === c.body.providerId
436
+ );
437
+ if (existingAccount) {
438
+ throw new betterCall.APIError("BAD_REQUEST", {
439
+ message: chunkCQNPMJJ3_cjs.BASE_ERROR_CODES.SOCIAL_ACCOUNT_ALREADY_LINKED
440
+ });
441
+ }
442
+ const provider = options.config.find(
443
+ (p) => p.providerId === c.body.providerId
444
+ );
445
+ if (!provider) {
446
+ throw new betterCall.APIError("NOT_FOUND", {
447
+ message: chunkCQNPMJJ3_cjs.BASE_ERROR_CODES.PROVIDER_NOT_FOUND
448
+ });
449
+ }
450
+ const {
451
+ providerId,
452
+ clientId,
453
+ clientSecret,
454
+ redirectURI,
455
+ authorizationUrl,
456
+ discoveryUrl,
457
+ pkce,
458
+ scopes
459
+ } = provider;
460
+ let finalAuthUrl = authorizationUrl;
461
+ if (!finalAuthUrl) {
462
+ if (!discoveryUrl) {
463
+ throw new betterCall.APIError("BAD_REQUEST", {
464
+ message: ERROR_CODES.INVALID_OAUTH_CONFIGURATION
465
+ });
466
+ }
467
+ const discovery = await fetch.betterFetch(discoveryUrl, {
468
+ onError(context) {
469
+ c.context.logger.error(context.error.message, context.error, {
470
+ discoveryUrl
471
+ });
472
+ }
473
+ });
474
+ if (discovery.data) {
475
+ finalAuthUrl = discovery.data.authorization_endpoint;
476
+ }
477
+ }
478
+ if (!finalAuthUrl) {
479
+ throw new betterCall.APIError("BAD_REQUEST", {
480
+ message: ERROR_CODES.INVALID_OAUTH_CONFIGURATION
481
+ });
482
+ }
483
+ const state = await chunk5E75URIA_cjs.generateState(c, {
484
+ userId: session.user.id,
485
+ email: session.user.email
486
+ });
487
+ const url = await chunkV6RGXSG4_cjs.createAuthorizationURL({
488
+ id: providerId,
489
+ options: {
490
+ clientId,
491
+ clientSecret,
492
+ redirectURI: redirectURI || `${c.context.baseURL}/oauth2/callback`
493
+ },
494
+ authorizationEndpoint: finalAuthUrl,
495
+ state: state.state,
496
+ codeVerifier: pkce ? state.codeVerifier : void 0,
497
+ scopes: scopes || [],
498
+ redirectURI: `${c.context.baseURL}/oauth2/callback/${providerId}`
499
+ });
500
+ return c.json({
501
+ url: url.toString(),
502
+ redirect: true
503
+ });
504
+ }
397
505
  )
398
506
  },
399
507
  $ERROR_CODES: ERROR_CODES
@@ -2,7 +2,7 @@ import * as nanostores from 'nanostores';
2
2
  import { AccessControl, StatementsPrimitive, Role } from '../plugins/access.cjs';
3
3
  import * as _better_fetch_fetch from '@better-fetch/fetch';
4
4
  import { BetterFetchOption } from '@better-fetch/fetch';
5
- import { o as organization, a as Organization, M as Member, I as Invitation } from '../index-CvVfvvKF.cjs';
5
+ import { o as organization, a as Organization, M as Member, I as Invitation } from '../index-D2rGzLac.cjs';
6
6
  import { b as Prettify } from '../helper-Bi8FQwDD.cjs';
7
7
  import { username } from '../plugins/username.cjs';
8
8
  export { getPasskeyActions, passkeyClient } from '../plugins/passkey.cjs';
@@ -2,7 +2,7 @@ import * as nanostores from 'nanostores';
2
2
  import { AccessControl, StatementsPrimitive, Role } from '../plugins/access.js';
3
3
  import * as _better_fetch_fetch from '@better-fetch/fetch';
4
4
  import { BetterFetchOption } from '@better-fetch/fetch';
5
- import { o as organization, a as Organization, M as Member, I as Invitation } from '../index-BAdTrrkO.js';
5
+ import { o as organization, a as Organization, M as Member, I as Invitation } from '../index-Cqgianwx.js';
6
6
  import { b as Prettify } from '../helper-Bi8FQwDD.js';
7
7
  import { username } from '../plugins/username.js';
8
8
  export { getPasskeyActions, passkeyClient } from '../plugins/passkey.js';