@dodopayments/better-auth 1.3.2 → 1.3.4

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 (59) hide show
  1. package/dist/chunk-52LQJCF4.js +199 -0
  2. package/dist/chunk-52LQJCF4.js.map +1 -0
  3. package/dist/chunk-FBAIEKOL.js +70 -0
  4. package/dist/chunk-FBAIEKOL.js.map +1 -0
  5. package/dist/chunk-PXI4EHZC.js +12 -0
  6. package/dist/chunk-PXI4EHZC.js.map +1 -0
  7. package/dist/chunk-QR7PLJJQ.js +13943 -0
  8. package/dist/chunk-QR7PLJJQ.js.map +1 -0
  9. package/dist/chunk-WIFOXVZ3.js +291 -0
  10. package/dist/chunk-WIFOXVZ3.js.map +1 -0
  11. package/dist/chunk-YK6XO66Y.js +84 -0
  12. package/dist/chunk-YK6XO66Y.js.map +1 -0
  13. package/dist/chunk-Z7VSWPPK.js +181 -0
  14. package/dist/chunk-Z7VSWPPK.js.map +1 -0
  15. package/dist/client.cjs +36 -0
  16. package/dist/client.cjs.map +1 -0
  17. package/dist/client.d.cts +7 -0
  18. package/dist/client.d.ts +7 -5
  19. package/dist/client.js +6 -5
  20. package/dist/client.js.map +1 -0
  21. package/dist/hooks/customer.cjs +14036 -0
  22. package/dist/hooks/customer.cjs.map +1 -0
  23. package/dist/hooks/customer.d.cts +11 -0
  24. package/dist/hooks/customer.d.ts +11 -4
  25. package/dist/hooks/customer.js +9 -62
  26. package/dist/hooks/customer.js.map +1 -0
  27. package/dist/index.cjs +14793 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +797 -0
  30. package/dist/index.d.ts +205 -196
  31. package/dist/index.js +55 -34
  32. package/dist/index.js.map +1 -0
  33. package/dist/plugins/checkout.cjs +14424 -0
  34. package/dist/plugins/checkout.cjs.map +1 -0
  35. package/dist/plugins/checkout.d.cts +6 -0
  36. package/dist/plugins/checkout.d.ts +6 -548
  37. package/dist/plugins/checkout.js +8 -170
  38. package/dist/plugins/checkout.js.map +1 -0
  39. package/dist/plugins/portal.cjs +14446 -0
  40. package/dist/plugins/portal.cjs.map +1 -0
  41. package/dist/plugins/portal.d.cts +6 -0
  42. package/dist/plugins/portal.d.ts +6 -204
  43. package/dist/plugins/portal.js +8 -171
  44. package/dist/plugins/portal.js.map +1 -0
  45. package/dist/plugins/webhooks.cjs +14046 -0
  46. package/dist/plugins/webhooks.cjs.map +1 -0
  47. package/dist/plugins/webhooks.d.cts +6 -0
  48. package/dist/plugins/webhooks.d.ts +6 -41
  49. package/dist/plugins/webhooks.js +7 -61
  50. package/dist/plugins/webhooks.js.map +1 -0
  51. package/dist/types-CbotWcHh.d.cts +841 -0
  52. package/dist/types-CbotWcHh.d.ts +841 -0
  53. package/dist/types.cjs +19 -0
  54. package/dist/types.cjs.map +1 -0
  55. package/dist/types.d.cts +6 -0
  56. package/dist/types.d.ts +6 -52
  57. package/dist/types.js +1 -1
  58. package/dist/types.js.map +1 -0
  59. package/package.json +5 -10
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../node_modules/better-auth/dist/shared/better-auth.C1Ly154X.mjs","../../../node_modules/better-auth/dist/shared/better-auth.ffWeg50w.mjs","../../../node_modules/better-auth/dist/shared/better-auth.DGaVMVAI.mjs","../../../node_modules/better-auth/dist/plugins/access/index.mjs","../../../node_modules/better-auth/dist/plugins/organization/access/index.mjs","../../../node_modules/better-auth/dist/shared/better-auth.DXqcUO8W.mjs","../../../node_modules/better-auth/dist/plugins/two-factor/index.mjs","../../../node_modules/better-auth/dist/plugins/username/index.mjs","../../../node_modules/better-auth/dist/plugins/bearer/index.mjs","../../../node_modules/better-auth/dist/plugins/magic-link/index.mjs","../../../node_modules/better-auth/dist/plugins/phone-number/index.mjs","../../../node_modules/better-auth/dist/plugins/anonymous/index.mjs","../../../node_modules/better-auth/dist/shared/better-auth.DygEm6OX.mjs","../../../node_modules/better-auth/dist/plugins/admin/access/index.mjs","../../../node_modules/better-auth/dist/plugins/generic-oauth/index.mjs","../../../node_modules/better-auth/dist/shared/better-auth.CGrHn1Ih.mjs","../../../node_modules/better-auth/dist/plugins/jwt/index.mjs","../../../node_modules/better-auth/dist/plugins/multi-session/index.mjs","../../../node_modules/better-auth/dist/plugins/email-otp/index.mjs","../../../node_modules/better-auth/dist/plugins/one-tap/index.mjs","../../../node_modules/better-auth/dist/plugins/oauth-proxy/index.mjs","../../../node_modules/better-auth/dist/plugins/custom-session/index.mjs","../../../node_modules/better-auth/dist/plugins/open-api/index.mjs","../../../node_modules/better-auth/dist/shared/better-auth.CvxACSRz.mjs","../../../node_modules/better-auth/dist/plugins/captcha/index.mjs","../../../node_modules/better-auth/dist/shared/better-auth.Bqt8-7ls.mjs","../../../node_modules/better-auth/dist/plugins/haveibeenpwned/index.mjs","../../../node_modules/better-auth/dist/plugins/index.mjs"],"sourcesContent":["import { APIError } from 'better-call';\nimport * as z from 'zod/v4';\nimport { j as createAuthEndpoint, k as getSessionFromCtx, l as sessionMiddleware, B as BASE_ERROR_CODES, a2 as requestOnlySessionMiddleware } from './better-auth.D4HhkCZJ.mjs';\nimport './better-auth.8zoxzg-F.mjs';\nimport '@better-auth/utils/base64';\nimport '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport './better-auth.n2KFGwjY.mjs';\nimport { g as getDate } from './better-auth.CW6D9eSx.mjs';\nimport { B as BetterAuthError } from './better-auth.DdzSJf-n.mjs';\nimport { p as parseJSON } from './better-auth.ffWeg50w.mjs';\nimport { o as orgMiddleware, a as orgSessionMiddleware, t as teamSchema } from './better-auth.DGaVMVAI.mjs';\nimport { setSessionCookie } from '../cookies/index.mjs';\nimport './better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport { h as hasPermission } from './better-auth.Dt0CvI2z.mjs';\nimport { t as toZodSchema } from './better-auth.DXqcUO8W.mjs';\nimport '@better-auth/utils/random';\nimport '@better-auth/utils/hash';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport 'jose';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport './better-auth.B4Qoxdgc.mjs';\nimport 'kysely';\nimport { defaultRoles } from '../plugins/organization/access/index.mjs';\n\nconst shimContext = (originalObject, newContext) => {\n const shimmedObj = {};\n for (const [key, value] of Object.entries(originalObject)) {\n shimmedObj[key] = (ctx) => {\n return value({\n ...ctx,\n context: {\n ...newContext,\n ...ctx.context\n }\n });\n };\n shimmedObj[key].path = value.path;\n shimmedObj[key].method = value.method;\n shimmedObj[key].options = value.options;\n shimmedObj[key].headers = value.headers;\n }\n return shimmedObj;\n};\n\nconst getOrgAdapter = (context, options) => {\n const adapter = context.adapter;\n return {\n findOrganizationBySlug: async (slug) => {\n const organization = await adapter.findOne({\n model: \"organization\",\n where: [\n {\n field: \"slug\",\n value: slug\n }\n ]\n });\n return organization;\n },\n createOrganization: async (data) => {\n const organization = await adapter.create({\n model: \"organization\",\n data: {\n ...data.organization,\n metadata: data.organization.metadata ? JSON.stringify(data.organization.metadata) : void 0\n }\n });\n return {\n ...organization,\n metadata: organization.metadata && typeof organization.metadata === \"string\" ? JSON.parse(organization.metadata) : void 0\n };\n },\n findMemberByEmail: async (data) => {\n const user = await adapter.findOne({\n model: \"user\",\n where: [\n {\n field: \"email\",\n value: data.email\n }\n ]\n });\n if (!user) {\n return null;\n }\n const member = await adapter.findOne({\n model: \"member\",\n where: [\n {\n field: \"organizationId\",\n value: data.organizationId\n },\n {\n field: \"userId\",\n value: user.id\n }\n ]\n });\n if (!member) {\n return null;\n }\n return {\n ...member,\n user: {\n id: user.id,\n name: user.name,\n email: user.email,\n image: user.image\n }\n };\n },\n listMembers: async (data) => {\n const members = await adapter.findMany({\n model: \"member\",\n where: [\n {\n field: \"organizationId\",\n value: data.organizationId\n }\n ],\n limit: options?.membershipLimit || 100\n });\n return members;\n },\n findMemberByOrgId: async (data) => {\n const [member, user] = await Promise.all([\n await adapter.findOne({\n model: \"member\",\n where: [\n {\n field: \"userId\",\n value: data.userId\n },\n {\n field: \"organizationId\",\n value: data.organizationId\n }\n ]\n }),\n await adapter.findOne({\n model: \"user\",\n where: [\n {\n field: \"id\",\n value: data.userId\n }\n ]\n })\n ]);\n if (!user || !member) {\n return null;\n }\n return {\n ...member,\n user: {\n id: user.id,\n name: user.name,\n email: user.email,\n image: user.image\n }\n };\n },\n findMemberById: async (memberId) => {\n const member = await adapter.findOne({\n model: \"member\",\n where: [\n {\n field: \"id\",\n value: memberId\n }\n ]\n });\n if (!member) {\n return null;\n }\n const user = await adapter.findOne({\n model: \"user\",\n where: [\n {\n field: \"id\",\n value: member.userId\n }\n ]\n });\n if (!user) {\n return null;\n }\n return {\n ...member,\n user: {\n id: user.id,\n name: user.name,\n email: user.email,\n image: user.image\n }\n };\n },\n createMember: async (data) => {\n const member = await adapter.create({\n model: \"member\",\n data: {\n ...data,\n createdAt: /* @__PURE__ */ new Date()\n }\n });\n return member;\n },\n updateMember: async (memberId, role) => {\n const member = await adapter.update({\n model: \"member\",\n where: [\n {\n field: \"id\",\n value: memberId\n }\n ],\n update: {\n role\n }\n });\n return member;\n },\n deleteMember: async (memberId) => {\n const member = await adapter.delete({\n model: \"member\",\n where: [\n {\n field: \"id\",\n value: memberId\n }\n ]\n });\n return member;\n },\n updateOrganization: async (organizationId, data) => {\n const organization = await adapter.update({\n model: \"organization\",\n where: [\n {\n field: \"id\",\n value: organizationId\n }\n ],\n update: {\n ...data,\n metadata: typeof data.metadata === \"object\" ? JSON.stringify(data.metadata) : data.metadata\n }\n });\n if (!organization) {\n return null;\n }\n return {\n ...organization,\n metadata: organization.metadata ? parseJSON(organization.metadata) : void 0\n };\n },\n deleteOrganization: async (organizationId) => {\n await adapter.delete({\n model: \"member\",\n where: [\n {\n field: \"organizationId\",\n value: organizationId\n }\n ]\n });\n await adapter.delete({\n model: \"invitation\",\n where: [\n {\n field: \"organizationId\",\n value: organizationId\n }\n ]\n });\n await adapter.delete({\n model: \"organization\",\n where: [\n {\n field: \"id\",\n value: organizationId\n }\n ]\n });\n return organizationId;\n },\n setActiveOrganization: async (sessionToken, organizationId) => {\n const session = await context.internalAdapter.updateSession(\n sessionToken,\n {\n activeOrganizationId: organizationId\n }\n );\n return session;\n },\n findOrganizationById: async (organizationId) => {\n const organization = await adapter.findOne({\n model: \"organization\",\n where: [\n {\n field: \"id\",\n value: organizationId\n }\n ]\n });\n return organization;\n },\n checkMembership: async ({\n userId,\n organizationId\n }) => {\n const member = await adapter.findOne({\n model: \"member\",\n where: [\n {\n field: \"userId\",\n value: userId\n },\n {\n field: \"organizationId\",\n value: organizationId\n }\n ]\n });\n return member;\n },\n /**\n * @requires db\n */\n findFullOrganization: async ({\n organizationId,\n isSlug,\n includeTeams\n }) => {\n const org = await adapter.findOne({\n model: \"organization\",\n where: [{ field: isSlug ? \"slug\" : \"id\", value: organizationId }]\n });\n if (!org) {\n return null;\n }\n const [invitations, members, teams] = await Promise.all([\n adapter.findMany({\n model: \"invitation\",\n where: [{ field: \"organizationId\", value: org.id }]\n }),\n adapter.findMany({\n model: \"member\",\n where: [{ field: \"organizationId\", value: org.id }],\n limit: options?.membershipLimit || 100\n }),\n includeTeams ? adapter.findMany({\n model: \"team\",\n where: [{ field: \"organizationId\", value: org.id }]\n }) : null\n ]);\n if (!org) return null;\n const userIds = members.map((member) => member.userId);\n const users = userIds.length > 0 ? await adapter.findMany({\n model: \"user\",\n where: [{ field: \"id\", value: userIds, operator: \"in\" }],\n limit: options?.membershipLimit || 100\n }) : [];\n const userMap = new Map(users.map((user) => [user.id, user]));\n const membersWithUsers = members.map((member) => {\n const user = userMap.get(member.userId);\n if (!user) {\n throw new BetterAuthError(\n \"Unexpected error: User not found for member\"\n );\n }\n return {\n ...member,\n user: {\n id: user.id,\n name: user.name,\n email: user.email,\n image: user.image\n }\n };\n });\n return {\n ...org,\n invitations,\n members: membersWithUsers,\n teams\n };\n },\n listOrganizations: async (userId) => {\n const members = await adapter.findMany({\n model: \"member\",\n where: [\n {\n field: \"userId\",\n value: userId\n }\n ]\n });\n if (!members || members.length === 0) {\n return [];\n }\n const organizationIds = members.map((member) => member.organizationId);\n const organizations = await adapter.findMany({\n model: \"organization\",\n where: [\n {\n field: \"id\",\n value: organizationIds,\n operator: \"in\"\n }\n ]\n });\n return organizations;\n },\n createTeam: async (data) => {\n const team = await adapter.create({\n model: \"team\",\n data\n });\n return team;\n },\n findTeamById: async ({\n teamId,\n organizationId,\n includeTeamMembers\n }) => {\n const team = await adapter.findOne({\n model: \"team\",\n where: [\n {\n field: \"id\",\n value: teamId\n },\n ...organizationId ? [\n {\n field: \"organizationId\",\n value: organizationId\n }\n ] : []\n ]\n });\n if (!team) {\n return null;\n }\n let members = [];\n if (includeTeamMembers) {\n members = await adapter.findMany({\n model: \"teamMember\",\n where: [\n {\n field: \"teamId\",\n value: teamId\n }\n ],\n limit: options?.membershipLimit || 100\n });\n return {\n ...team,\n members\n };\n }\n return team;\n },\n updateTeam: async (teamId, data) => {\n if (\"id\" in data) data.id = void 0;\n const team = await adapter.update({\n model: \"team\",\n where: [\n {\n field: \"id\",\n value: teamId\n }\n ],\n update: {\n ...data\n }\n });\n return team;\n },\n deleteTeam: async (teamId) => {\n const team = await adapter.delete({\n model: \"team\",\n where: [\n {\n field: \"id\",\n value: teamId\n }\n ]\n });\n return team;\n },\n listTeams: async (organizationId) => {\n const teams = await adapter.findMany({\n model: \"team\",\n where: [\n {\n field: \"organizationId\",\n value: organizationId\n }\n ]\n });\n return teams;\n },\n createTeamInvitation: async ({\n email,\n role,\n teamId,\n organizationId,\n inviterId,\n expiresIn = 1e3 * 60 * 60 * 48\n // Default expiration: 48 hours\n }) => {\n const expiresAt = getDate(expiresIn);\n const invitation = await adapter.create({\n model: \"invitation\",\n data: {\n email,\n role,\n organizationId,\n teamId,\n inviterId,\n status: \"pending\",\n expiresAt\n }\n });\n return invitation;\n },\n setActiveTeam: async (sessionToken, teamId) => {\n const session = await context.internalAdapter.updateSession(\n sessionToken,\n {\n activeTeamId: teamId\n }\n );\n return session;\n },\n listTeamMembers: async (data) => {\n const members = await adapter.findMany({\n model: \"teamMember\",\n where: [\n {\n field: \"teamId\",\n value: data.teamId\n }\n ]\n });\n return members;\n },\n countMembers: async (data) => {\n const count = await adapter.count({\n model: \"member\",\n where: [{ field: \"organizationId\", value: data.organizationId }]\n });\n return count;\n },\n listTeamsByUser: async (data) => {\n const members = await adapter.findMany({\n model: \"teamMember\",\n where: [\n {\n field: \"userId\",\n value: data.userId\n }\n ]\n });\n const teams = await adapter.findMany({\n model: \"team\",\n where: [\n {\n field: \"id\",\n operator: \"in\",\n value: members.map((m) => m.teamId)\n }\n ]\n });\n return teams;\n },\n findTeamMember: async (data) => {\n const member = await adapter.findOne({\n model: \"teamMember\",\n where: [\n {\n field: \"teamId\",\n value: data.teamId\n },\n {\n field: \"userId\",\n value: data.userId\n }\n ]\n });\n return member;\n },\n findOrCreateTeamMember: async (data) => {\n const member = await adapter.findOne({\n model: \"teamMember\",\n where: [\n {\n field: \"teamId\",\n value: data.teamId\n },\n {\n field: \"userId\",\n value: data.userId\n }\n ]\n });\n if (member) return member;\n return await adapter.create({\n model: \"teamMember\",\n data: {\n teamId: data.teamId,\n userId: data.userId,\n createdAt: /* @__PURE__ */ new Date()\n }\n });\n },\n removeTeamMember: async (data) => {\n await adapter.delete({\n model: \"teamMember\",\n where: [\n {\n field: \"teamId\",\n value: data.teamId\n },\n {\n field: \"userId\",\n value: data.userId\n }\n ]\n });\n },\n findInvitationsByTeamId: async (teamId) => {\n const invitations = await adapter.findMany({\n model: \"invitation\",\n where: [\n {\n field: \"teamId\",\n value: teamId\n }\n ]\n });\n return invitations;\n },\n listUserInvitations: async (email) => {\n const invitations = await adapter.findMany({\n model: \"invitation\",\n where: [{ field: \"email\", value: email }]\n });\n return invitations;\n },\n createInvitation: async ({\n invitation,\n user\n }) => {\n const defaultExpiration = 60 * 60 * 48;\n const expiresAt = getDate(\n options?.invitationExpiresIn || defaultExpiration,\n \"sec\"\n );\n const invite = await adapter.create({\n model: \"invitation\",\n data: {\n status: \"pending\",\n expiresAt,\n inviterId: user.id,\n ...invitation,\n teamId: invitation.teamIds.join(\",\")\n }\n });\n return invite;\n },\n findInvitationById: async (id) => {\n const invitation = await adapter.findOne({\n model: \"invitation\",\n where: [\n {\n field: \"id\",\n value: id\n }\n ]\n });\n return invitation;\n },\n findPendingInvitation: async (data) => {\n const invitation = await adapter.findMany({\n model: \"invitation\",\n where: [\n {\n field: \"email\",\n value: data.email\n },\n {\n field: \"organizationId\",\n value: data.organizationId\n },\n {\n field: \"status\",\n value: \"pending\"\n }\n ]\n });\n return invitation.filter(\n (invite) => new Date(invite.expiresAt) > /* @__PURE__ */ new Date()\n );\n },\n findPendingInvitations: async (data) => {\n const invitations = await adapter.findMany({\n model: \"invitation\",\n where: [\n {\n field: \"organizationId\",\n value: data.organizationId\n },\n {\n field: \"status\",\n value: \"pending\"\n }\n ]\n });\n return invitations.filter(\n (invite) => new Date(invite.expiresAt) > /* @__PURE__ */ new Date()\n );\n },\n listInvitations: async (data) => {\n const invitations = await adapter.findMany({\n model: \"invitation\",\n where: [\n {\n field: \"organizationId\",\n value: data.organizationId\n }\n ]\n });\n return invitations;\n },\n updateInvitation: async (data) => {\n const invitation = await adapter.update({\n model: \"invitation\",\n where: [\n {\n field: \"id\",\n value: data.invitationId\n }\n ],\n update: {\n status: data.status\n }\n });\n return invitation;\n }\n };\n};\n\nconst ORGANIZATION_ERROR_CODES = {\n YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION: \"You are not allowed to create a new organization\",\n YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS: \"You have reached the maximum number of organizations\",\n ORGANIZATION_ALREADY_EXISTS: \"Organization already exists\",\n ORGANIZATION_NOT_FOUND: \"Organization not found\",\n USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION: \"User is not a member of the organization\",\n YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION: \"You are not allowed to update this organization\",\n YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION: \"You are not allowed to delete this organization\",\n NO_ACTIVE_ORGANIZATION: \"No active organization\",\n USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION: \"User is already a member of this organization\",\n MEMBER_NOT_FOUND: \"Member not found\",\n ROLE_NOT_FOUND: \"Role not found\",\n YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM: \"You are not allowed to create a new team\",\n TEAM_ALREADY_EXISTS: \"Team already exists\",\n TEAM_NOT_FOUND: \"Team not found\",\n YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER: \"You cannot leave the organization as the only owner\",\n YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_MEMBER: \"You are not allowed to delete this member\",\n YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION: \"You are not allowed to invite users to this organization\",\n USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION: \"User is already invited to this organization\",\n INVITATION_NOT_FOUND: \"Invitation not found\",\n YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION: \"You are not the recipient of the invitation\",\n YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION: \"You are not allowed to cancel this invitation\",\n INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION: \"Inviter is no longer a member of the organization\",\n YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE: \"you are not allowed to invite user with this role\",\n FAILED_TO_RETRIEVE_INVITATION: \"Failed to retrieve invitation\",\n YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS: \"You have reached the maximum number of teams\",\n UNABLE_TO_REMOVE_LAST_TEAM: \"Unable to remove last team\",\n YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER: \"You are not allowed to update this member\",\n ORGANIZATION_MEMBERSHIP_LIMIT_REACHED: \"Organization membership limit reached\",\n YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION: \"You are not allowed to create teams in this organization\",\n YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION: \"You are not allowed to delete teams in this organization\",\n YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM: \"You are not allowed to update this team\",\n YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM: \"You are not allowed to delete this team\",\n INVITATION_LIMIT_REACHED: \"Invitation limit reached\",\n TEAM_MEMBER_LIMIT_REACHED: \"Team member limit reached\",\n USER_IS_NOT_A_MEMBER_OF_THE_TEAM: \"User is not a member of the team\",\n YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM: \"You are not allowed to list the members of this team\",\n YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM: \"You do not have an active team\",\n YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM_MEMBER: \"You are not allowed to create a new member\",\n YOU_ARE_NOT_ALLOWED_TO_REMOVE_A_TEAM_MEMBER: \"You are not allowed to remove a team member\",\n YOU_ARE_NOT_ALLOWED_TO_ACCESS_THIS_ORGANIZATION: \"You are not allowed to access this organization as an owner\"\n};\n\nconst createInvitation = (option) => {\n const additionalFieldsSchema = toZodSchema({\n fields: option?.schema?.invitation?.additionalFields || {},\n isClientSide: true\n });\n const baseSchema = z.object({\n email: z.string().meta({\n description: \"The email address of the user to invite\"\n }),\n role: z.union([\n z.string().meta({\n description: \"The role to assign to the user\"\n }),\n z.array(\n z.string().meta({\n description: \"The roles to assign to the user\"\n })\n )\n ]).meta({\n description: 'The role(s) to assign to the user. It can be `admin`, `member`, or `guest`. Eg: \"member\"'\n }),\n organizationId: z.string().meta({\n description: \"The organization ID to invite the user to\"\n }).optional(),\n resend: z.boolean().meta({\n description: \"Resend the invitation email, if the user is already invited. Eg: true\"\n }).optional(),\n teamId: z.union([\n z.string().meta({\n description: \"The team ID to invite the user to\"\n }).optional(),\n z.array(\n z.string().meta({\n description: \"The team ID to invite the user to\"\n })\n ).optional()\n ])\n });\n return createAuthEndpoint(\n \"/organization/invite-member\",\n {\n method: \"POST\",\n use: [orgMiddleware, orgSessionMiddleware],\n body: z.object({\n ...baseSchema.shape,\n ...additionalFieldsSchema.shape\n }),\n metadata: {\n $Infer: {\n body: {}\n },\n openapi: {\n description: \"Invite a user to an organization\",\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\"\n },\n email: {\n type: \"string\"\n },\n role: {\n type: \"string\"\n },\n organizationId: {\n type: \"string\"\n },\n inviterId: {\n type: \"string\"\n },\n status: {\n type: \"string\"\n },\n expiresAt: {\n type: \"string\"\n }\n },\n required: [\n \"id\",\n \"email\",\n \"role\",\n \"organizationId\",\n \"inviterId\",\n \"status\",\n \"expiresAt\"\n ]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = ctx.context.session;\n const organizationId = ctx.body.organizationId || session.session.activeOrganizationId;\n if (!organizationId) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND\n });\n }\n const adapter = getOrgAdapter(ctx.context, option);\n const member = await adapter.findMemberByOrgId({\n userId: session.user.id,\n organizationId\n });\n if (!member) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND\n });\n }\n const canInvite = hasPermission({\n role: member.role,\n options: ctx.context.orgOptions,\n permissions: {\n invitation: [\"create\"]\n }\n });\n if (!canInvite) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION\n });\n }\n const creatorRole = ctx.context.orgOptions.creatorRole || \"owner\";\n const roles = parseRoles(ctx.body.role);\n if (member.role !== creatorRole && roles.split(\",\").includes(creatorRole)) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE\n });\n }\n const alreadyMember = await adapter.findMemberByEmail({\n email: ctx.body.email,\n organizationId\n });\n if (alreadyMember) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION\n });\n }\n const alreadyInvited = await adapter.findPendingInvitation({\n email: ctx.body.email,\n organizationId\n });\n if (alreadyInvited.length && !ctx.body.resend) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION\n });\n }\n if (alreadyInvited.length && ctx.context.orgOptions.cancelPendingInvitationsOnReInvite) {\n await adapter.updateInvitation({\n invitationId: alreadyInvited[0].id,\n status: \"canceled\"\n });\n }\n const organization = await adapter.findOrganizationById(organizationId);\n if (!organization) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND\n });\n }\n const invitationLimit = typeof ctx.context.orgOptions.invitationLimit === \"function\" ? await ctx.context.orgOptions.invitationLimit(\n {\n user: session.user,\n organization,\n member\n },\n ctx.context\n ) : ctx.context.orgOptions.invitationLimit ?? 100;\n const pendingInvitations = await adapter.findPendingInvitations({\n organizationId\n });\n if (pendingInvitations.length >= invitationLimit) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.INVITATION_LIMIT_REACHED\n });\n }\n if (ctx.context.orgOptions.teams && ctx.context.orgOptions.teams.enabled && typeof ctx.context.orgOptions.teams.maximumMembersPerTeam !== \"undefined\" && \"teamId\" in ctx.body && ctx.body.teamId) {\n const teamIds2 = typeof ctx.body.teamId === \"string\" ? [ctx.body.teamId] : ctx.body.teamId;\n for (const teamId of teamIds2) {\n const team = await adapter.findTeamById({\n teamId,\n organizationId,\n includeTeamMembers: true\n });\n if (!team) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.TEAM_NOT_FOUND\n });\n }\n const maximumMembersPerTeam = typeof ctx.context.orgOptions.teams.maximumMembersPerTeam === \"function\" ? await ctx.context.orgOptions.teams.maximumMembersPerTeam({\n teamId,\n session,\n organizationId\n }) : ctx.context.orgOptions.teams.maximumMembersPerTeam;\n if (team.members.length >= maximumMembersPerTeam) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.TEAM_MEMBER_LIMIT_REACHED\n });\n }\n }\n }\n const teamIds = \"teamId\" in ctx.body ? typeof ctx.body.teamId === \"string\" ? [ctx.body.teamId] : ctx.body.teamId ?? [] : [];\n const {\n email: _,\n role: __,\n organizationId: ___,\n resend: ____,\n ...additionalFields\n } = ctx.body;\n const invitation = await adapter.createInvitation({\n invitation: {\n role: roles,\n email: ctx.body.email.toLowerCase(),\n organizationId,\n teamIds,\n ...additionalFields ? additionalFields : {}\n },\n user: session.user\n });\n await ctx.context.orgOptions.sendInvitationEmail?.(\n {\n id: invitation.id,\n role: invitation.role,\n email: invitation.email.toLowerCase(),\n organization,\n inviter: {\n ...member,\n user: session.user\n },\n //@ts-expect-error\n invitation\n },\n ctx.request\n );\n return ctx.json(invitation);\n }\n );\n};\nconst acceptInvitation = (options) => createAuthEndpoint(\n \"/organization/accept-invitation\",\n {\n method: \"POST\",\n body: z.object({\n invitationId: z.string().meta({\n description: \"The ID of the invitation to accept\"\n })\n }),\n use: [orgMiddleware, orgSessionMiddleware],\n metadata: {\n openapi: {\n description: \"Accept an invitation to an organization\",\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n invitation: {\n type: \"object\"\n },\n member: {\n type: \"object\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = ctx.context.session;\n const adapter = getOrgAdapter(ctx.context, options);\n const invitation = await adapter.findInvitationById(\n ctx.body.invitationId\n );\n if (!invitation || invitation.expiresAt < /* @__PURE__ */ new Date() || invitation.status !== \"pending\") {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.INVITATION_NOT_FOUND\n });\n }\n if (invitation.email.toLowerCase() !== session.user.email.toLowerCase()) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION\n });\n }\n const membershipLimit = ctx.context.orgOptions?.membershipLimit || 100;\n const members = await adapter.listMembers({\n organizationId: invitation.organizationId\n });\n if (members.length >= membershipLimit) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.ORGANIZATION_MEMBERSHIP_LIMIT_REACHED\n });\n }\n const acceptedI = await adapter.updateInvitation({\n invitationId: ctx.body.invitationId,\n status: \"accepted\"\n });\n if (!acceptedI) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.FAILED_TO_RETRIEVE_INVITATION\n });\n }\n if (ctx.context.orgOptions.teams && ctx.context.orgOptions.teams.enabled && \"teamId\" in acceptedI && acceptedI.teamId) {\n const teamIds = acceptedI.teamId.split(\",\");\n const onlyOne = teamIds.length === 1;\n for (const teamId of teamIds) {\n await adapter.findOrCreateTeamMember({\n teamId,\n userId: session.user.id\n });\n if (typeof ctx.context.orgOptions.teams.maximumMembersPerTeam !== \"undefined\") {\n const members2 = await adapter.listTeamMembers({ teamId });\n const maximumMembersPerTeam = typeof ctx.context.orgOptions.teams.maximumMembersPerTeam === \"function\" ? await ctx.context.orgOptions.teams.maximumMembersPerTeam({\n teamId,\n session,\n organizationId: invitation.organizationId\n }) : ctx.context.orgOptions.teams.maximumMembersPerTeam;\n if (members2.length >= maximumMembersPerTeam) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.TEAM_MEMBER_LIMIT_REACHED\n });\n }\n }\n }\n if (onlyOne) {\n const teamId = teamIds[0];\n const updatedSession = await adapter.setActiveTeam(\n session.session.token,\n teamId\n );\n await setSessionCookie(ctx, {\n session: updatedSession,\n user: session.user\n });\n }\n }\n const member = await adapter.createMember({\n organizationId: invitation.organizationId,\n userId: session.user.id,\n role: invitation.role,\n createdAt: /* @__PURE__ */ new Date()\n });\n await adapter.setActiveOrganization(\n session.session.token,\n invitation.organizationId\n );\n if (!acceptedI) {\n return ctx.json(null, {\n status: 400,\n body: {\n message: ORGANIZATION_ERROR_CODES.INVITATION_NOT_FOUND\n }\n });\n }\n return ctx.json({\n invitation: acceptedI,\n member\n });\n }\n);\nconst rejectInvitation = (options) => createAuthEndpoint(\n \"/organization/reject-invitation\",\n {\n method: \"POST\",\n body: z.object({\n invitationId: z.string().meta({\n description: \"The ID of the invitation to reject\"\n })\n }),\n use: [orgMiddleware, orgSessionMiddleware],\n metadata: {\n openapi: {\n description: \"Reject an invitation to an organization\",\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n invitation: {\n type: \"object\"\n },\n member: {\n type: \"null\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = ctx.context.session;\n const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);\n const invitation = await adapter.findInvitationById(\n ctx.body.invitationId\n );\n if (!invitation || invitation.expiresAt < /* @__PURE__ */ new Date() || invitation.status !== \"pending\") {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Invitation not found!\"\n });\n }\n if (invitation.email.toLowerCase() !== session.user.email.toLowerCase()) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION\n });\n }\n const rejectedI = await adapter.updateInvitation({\n invitationId: ctx.body.invitationId,\n status: \"rejected\"\n });\n return ctx.json({\n invitation: rejectedI,\n member: null\n });\n }\n);\nconst cancelInvitation = (options) => createAuthEndpoint(\n \"/organization/cancel-invitation\",\n {\n method: \"POST\",\n body: z.object({\n invitationId: z.string().meta({\n description: \"The ID of the invitation to cancel\"\n })\n }),\n use: [orgMiddleware, orgSessionMiddleware],\n openapi: {\n description: \"Cancel an invitation to an organization\",\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n invitation: {\n type: \"object\"\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = ctx.context.session;\n const adapter = getOrgAdapter(ctx.context, options);\n const invitation = await adapter.findInvitationById(\n ctx.body.invitationId\n );\n if (!invitation) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.INVITATION_NOT_FOUND\n });\n }\n const member = await adapter.findMemberByOrgId({\n userId: session.user.id,\n organizationId: invitation.organizationId\n });\n if (!member) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND\n });\n }\n const canCancel = hasPermission({\n role: member.role,\n options: ctx.context.orgOptions,\n permissions: {\n invitation: [\"cancel\"]\n }\n });\n if (!canCancel) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION\n });\n }\n const canceledI = await adapter.updateInvitation({\n invitationId: ctx.body.invitationId,\n status: \"canceled\"\n });\n return ctx.json(canceledI);\n }\n);\nconst getInvitation = (options) => createAuthEndpoint(\n \"/organization/get-invitation\",\n {\n method: \"GET\",\n use: [orgMiddleware],\n requireHeaders: true,\n query: z.object({\n id: z.string().meta({\n description: \"The ID of the invitation to get\"\n })\n }),\n metadata: {\n openapi: {\n description: \"Get an invitation by ID\",\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\"\n },\n email: {\n type: \"string\"\n },\n role: {\n type: \"string\"\n },\n organizationId: {\n type: \"string\"\n },\n inviterId: {\n type: \"string\"\n },\n status: {\n type: \"string\"\n },\n expiresAt: {\n type: \"string\"\n },\n organizationName: {\n type: \"string\"\n },\n organizationSlug: {\n type: \"string\"\n },\n inviterEmail: {\n type: \"string\"\n }\n },\n required: [\n \"id\",\n \"email\",\n \"role\",\n \"organizationId\",\n \"inviterId\",\n \"status\",\n \"expiresAt\",\n \"organizationName\",\n \"organizationSlug\",\n \"inviterEmail\"\n ]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = await getSessionFromCtx(ctx);\n if (!session) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: \"Not authenticated\"\n });\n }\n const adapter = getOrgAdapter(ctx.context, options);\n const invitation = await adapter.findInvitationById(ctx.query.id);\n if (!invitation || invitation.status !== \"pending\" || invitation.expiresAt < /* @__PURE__ */ new Date()) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Invitation not found!\"\n });\n }\n if (invitation.email.toLowerCase() !== session.user.email.toLowerCase()) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION\n });\n }\n const organization = await adapter.findOrganizationById(\n invitation.organizationId\n );\n if (!organization) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND\n });\n }\n const member = await adapter.findMemberByOrgId({\n userId: invitation.inviterId,\n organizationId: invitation.organizationId\n });\n if (!member) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION\n });\n }\n return ctx.json({\n ...invitation,\n organizationName: organization.name,\n organizationSlug: organization.slug,\n inviterEmail: member.user.email\n });\n }\n);\nconst listInvitations = (options) => createAuthEndpoint(\n \"/organization/list-invitations\",\n {\n method: \"GET\",\n use: [orgMiddleware, orgSessionMiddleware],\n query: z.object({\n organizationId: z.string().meta({\n description: \"The ID of the organization to list invitations for\"\n }).optional()\n }).optional()\n },\n async (ctx) => {\n const session = await getSessionFromCtx(ctx);\n if (!session) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: \"Not authenticated\"\n });\n }\n const orgId = ctx.query?.organizationId || session.session.activeOrganizationId;\n if (!orgId) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Organization ID is required\"\n });\n }\n const adapter = getOrgAdapter(ctx.context, options);\n const isMember = await adapter.findMemberByOrgId({\n userId: session.user.id,\n organizationId: orgId\n });\n if (!isMember) {\n throw new APIError(\"FORBIDDEN\", {\n message: \"You are not a member of this organization\"\n });\n }\n const invitations = await adapter.listInvitations({\n organizationId: orgId\n });\n return ctx.json(invitations);\n }\n);\nconst listUserInvitations = (options) => createAuthEndpoint(\n \"/organization/list-user-invitations\",\n {\n method: \"GET\",\n use: [orgMiddleware],\n query: z.object({\n email: z.string().meta({\n description: \"The email of the user to list invitations for. This only works for server side API calls.\"\n }).optional()\n }).optional()\n },\n async (ctx) => {\n const session = await getSessionFromCtx(ctx);\n if (ctx.request && ctx.query?.email) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"User email cannot be passed for client side API calls.\"\n });\n }\n const userEmail = session?.user.email || ctx.query?.email;\n if (!userEmail) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Missing session headers, or email query parameter.\"\n });\n }\n const adapter = getOrgAdapter(ctx.context, options);\n const invitations = await adapter.listUserInvitations(userEmail);\n return ctx.json(invitations);\n }\n);\n\nconst addMember = (option) => {\n const additionalFieldsSchema = toZodSchema({\n fields: option?.schema?.member?.additionalFields || {},\n isClientSide: true\n });\n const baseSchema = z.object({\n userId: z.coerce.string().meta({\n description: 'The user Id which represents the user to be added as a member. If `null` is provided, then it\\'s expected to provide session headers. Eg: \"user-id\"'\n }),\n role: z.union([z.string(), z.array(z.string())]).meta({\n description: 'The role(s) to assign to the new member. Eg: [\"admin\", \"sale\"]'\n }),\n organizationId: z.string().meta({\n description: `An optional organization ID to pass. If not provided, will default to the user's active organization. Eg: \"org-id\"`\n }).optional(),\n teamId: z.string().meta({\n description: 'An optional team ID to add the member to. Eg: \"team-id\"'\n }).optional()\n });\n return createAuthEndpoint(\n \"/organization/add-member\",\n {\n method: \"POST\",\n body: z.object({\n ...baseSchema.shape,\n ...additionalFieldsSchema.shape\n }),\n use: [orgMiddleware],\n metadata: {\n SERVER_ONLY: true,\n $Infer: {\n body: {}\n }\n }\n },\n async (ctx) => {\n const session = ctx.body.userId ? await getSessionFromCtx(ctx).catch((e) => null) : null;\n const orgId = ctx.body.organizationId || session?.session.activeOrganizationId;\n if (!orgId) {\n return ctx.json(null, {\n status: 400,\n body: {\n message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION\n }\n });\n }\n const teamId = \"teamId\" in ctx.body ? ctx.body.teamId : void 0;\n if (teamId && !ctx.context.orgOptions.teams?.enabled) {\n ctx.context.logger.error(\"Teams are not enabled\");\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Teams are not enabled\"\n });\n }\n const adapter = getOrgAdapter(ctx.context, option);\n const user = await ctx.context.internalAdapter.findUserById(\n ctx.body.userId\n );\n if (!user) {\n throw new APIError(\"BAD_REQUEST\", {\n message: BASE_ERROR_CODES.USER_NOT_FOUND\n });\n }\n const alreadyMember = await adapter.findMemberByEmail({\n email: user.email,\n organizationId: orgId\n });\n if (alreadyMember) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION\n });\n }\n if (teamId) {\n const team = await adapter.findTeamById({\n teamId,\n organizationId: orgId\n });\n if (!team || team.organizationId !== orgId) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.TEAM_NOT_FOUND\n });\n }\n }\n const membershipLimit = ctx.context.orgOptions?.membershipLimit || 100;\n const count = await adapter.countMembers({ organizationId: orgId });\n if (count >= membershipLimit) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.ORGANIZATION_MEMBERSHIP_LIMIT_REACHED\n });\n }\n const {\n role: _,\n userId: __,\n organizationId: ___,\n ...additionalFields\n } = ctx.body;\n const createdMember = await adapter.createMember({\n organizationId: orgId,\n userId: user.id,\n role: parseRoles(ctx.body.role),\n createdAt: /* @__PURE__ */ new Date(),\n ...additionalFields ? additionalFields : {}\n });\n if (teamId) {\n await adapter.findOrCreateTeamMember({\n userId: user.id,\n teamId\n });\n }\n return ctx.json(createdMember);\n }\n );\n};\nconst removeMember = (options) => createAuthEndpoint(\n \"/organization/remove-member\",\n {\n method: \"POST\",\n body: z.object({\n memberIdOrEmail: z.string().meta({\n description: \"The ID or email of the member to remove\"\n }),\n /**\n * If not provided, the active organization will be used\n */\n organizationId: z.string().meta({\n description: 'The ID of the organization to remove the member from. If not provided, the active organization will be used. Eg: \"org-id\"'\n })\n }),\n use: [orgMiddleware, orgSessionMiddleware],\n metadata: {\n openapi: {\n description: \"Remove a member from an organization\",\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n member: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\"\n },\n userId: {\n type: \"string\"\n },\n organizationId: {\n type: \"string\"\n },\n role: {\n type: \"string\"\n }\n },\n required: [\"id\", \"userId\", \"organizationId\", \"role\"]\n }\n },\n required: [\"member\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = ctx.context.session;\n const organizationId = ctx.body.organizationId || session.session.activeOrganizationId;\n if (!organizationId) {\n return ctx.json(null, {\n status: 400,\n body: {\n message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION\n }\n });\n }\n const adapter = getOrgAdapter(ctx.context, options);\n const member = await adapter.findMemberByOrgId({\n userId: session.user.id,\n organizationId\n });\n if (!member) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND\n });\n }\n let toBeRemovedMember = null;\n if (ctx.body.memberIdOrEmail.includes(\"@\")) {\n toBeRemovedMember = await adapter.findMemberByEmail({\n email: ctx.body.memberIdOrEmail,\n organizationId\n });\n } else {\n toBeRemovedMember = await adapter.findMemberById(\n ctx.body.memberIdOrEmail\n );\n }\n if (!toBeRemovedMember) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND\n });\n }\n const roles = toBeRemovedMember.role.split(\",\");\n const creatorRole = ctx.context.orgOptions?.creatorRole || \"owner\";\n const isOwner = roles.includes(creatorRole);\n if (isOwner) {\n if (member.role !== creatorRole) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER\n });\n }\n const members = await adapter.listMembers({\n organizationId\n });\n const owners = members.filter((member2) => {\n const roles2 = member2.role.split(\",\");\n return roles2.includes(creatorRole);\n });\n if (owners.length <= 1) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER\n });\n }\n }\n const canDeleteMember = hasPermission({\n role: member.role,\n options: ctx.context.orgOptions,\n permissions: {\n member: [\"delete\"]\n }\n });\n if (!canDeleteMember) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_MEMBER\n });\n }\n if (toBeRemovedMember?.organizationId !== organizationId) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND\n });\n }\n await adapter.deleteMember(toBeRemovedMember.id);\n if (session.user.id === toBeRemovedMember.userId && session.session.activeOrganizationId === toBeRemovedMember.organizationId) {\n await adapter.setActiveOrganization(session.session.token, null);\n }\n return ctx.json({\n member: toBeRemovedMember\n });\n }\n);\nconst updateMemberRole = (option) => createAuthEndpoint(\n \"/organization/update-member-role\",\n {\n method: \"POST\",\n body: z.object({\n role: z.union([z.string(), z.array(z.string())]).meta({\n description: 'The new role to be applied. This can be a string or array of strings representing the roles. Eg: [\"admin\", \"sale\"]'\n }),\n memberId: z.string().meta({\n description: 'The member id to apply the role update to. Eg: \"member-id\"'\n }),\n organizationId: z.string().meta({\n description: 'An optional organization ID which the member is a part of to apply the role update. If not provided, you must provide session headers to get the active organization. Eg: \"organization-id\"'\n }).optional()\n }),\n use: [orgMiddleware, orgSessionMiddleware],\n metadata: {\n $Infer: {\n body: {}\n },\n openapi: {\n description: \"Update the role of a member in an organization\",\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n member: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\"\n },\n userId: {\n type: \"string\"\n },\n organizationId: {\n type: \"string\"\n },\n role: {\n type: \"string\"\n }\n },\n required: [\"id\", \"userId\", \"organizationId\", \"role\"]\n }\n },\n required: [\"member\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = ctx.context.session;\n if (!ctx.body.role) {\n throw new APIError(\"BAD_REQUEST\");\n }\n const organizationId = ctx.body.organizationId || session.session.activeOrganizationId;\n if (!organizationId) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION\n });\n }\n const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);\n const roleToSet = Array.isArray(ctx.body.role) ? ctx.body.role : ctx.body.role ? [ctx.body.role] : [];\n const member = await adapter.findMemberByOrgId({\n userId: session.user.id,\n organizationId\n });\n if (!member) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND\n });\n }\n const toBeUpdatedMember = member.id !== ctx.body.memberId ? await adapter.findMemberById(ctx.body.memberId) : member;\n if (!toBeUpdatedMember) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND\n });\n }\n const creatorRole = ctx.context.orgOptions?.creatorRole || \"owner\";\n const updatingMemberRoles = member.role.split(\",\");\n const toBeUpdatedMemberRoles = toBeUpdatedMember.role.split(\",\");\n const isUpdatingCreator = toBeUpdatedMemberRoles.includes(creatorRole);\n const updaterIsCreator = updatingMemberRoles.includes(creatorRole);\n const isSettingCreatorRole = roleToSet.includes(creatorRole);\n if (isUpdatingCreator && !updaterIsCreator || isSettingCreatorRole && !updaterIsCreator) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER\n });\n }\n const canUpdateMember = hasPermission({\n role: member.role,\n options: ctx.context.orgOptions,\n permissions: {\n member: [\"update\"]\n },\n allowCreatorAllPermissions: true\n });\n if (!canUpdateMember) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER\n });\n }\n const updatedMember = await adapter.updateMember(\n ctx.body.memberId,\n parseRoles(ctx.body.role)\n );\n if (!updatedMember) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND\n });\n }\n return ctx.json(updatedMember);\n }\n);\nconst getActiveMember = (options) => createAuthEndpoint(\n \"/organization/get-active-member\",\n {\n method: \"GET\",\n use: [orgMiddleware, orgSessionMiddleware],\n requireHeaders: true,\n metadata: {\n openapi: {\n description: \"Get the member details of the active organization\",\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\"\n },\n userId: {\n type: \"string\"\n },\n organizationId: {\n type: \"string\"\n },\n role: {\n type: \"string\"\n }\n },\n required: [\"id\", \"userId\", \"organizationId\", \"role\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = ctx.context.session;\n const organizationId = session.session.activeOrganizationId;\n if (!organizationId) {\n return ctx.json(null, {\n status: 400,\n body: {\n message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION\n }\n });\n }\n const adapter = getOrgAdapter(ctx.context, options);\n const member = await adapter.findMemberByOrgId({\n userId: session.user.id,\n organizationId\n });\n if (!member) {\n return ctx.json(null, {\n status: 400,\n body: {\n message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND\n }\n });\n }\n return ctx.json(member);\n }\n);\nconst leaveOrganization = (options) => createAuthEndpoint(\n \"/organization/leave\",\n {\n method: \"POST\",\n body: z.object({\n organizationId: z.string().meta({\n description: 'The organization Id for the member to leave. Eg: \"organization-id\"'\n })\n }),\n requireHeaders: true,\n use: [sessionMiddleware, orgMiddleware]\n },\n async (ctx) => {\n const session = ctx.context.session;\n const adapter = getOrgAdapter(ctx.context, options);\n const member = await adapter.findMemberByOrgId({\n userId: session.user.id,\n organizationId: ctx.body.organizationId\n });\n if (!member) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND\n });\n }\n const isOwnerLeaving = member.role === (ctx.context.orgOptions?.creatorRole || \"owner\");\n if (isOwnerLeaving) {\n const members = await ctx.context.adapter.findMany({\n model: \"member\",\n where: [\n {\n field: \"organizationId\",\n value: ctx.body.organizationId\n }\n ]\n });\n const owners = members.filter(\n (member2) => member2.role === (ctx.context.orgOptions?.creatorRole || \"owner\")\n );\n if (owners.length <= 1) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER\n });\n }\n }\n await adapter.deleteMember(member.id);\n if (session.session.activeOrganizationId === ctx.body.organizationId) {\n await adapter.setActiveOrganization(session.session.token, null);\n }\n return ctx.json(member);\n }\n);\n\nconst createOrganization = (options) => {\n const additionalFieldsSchema = toZodSchema({\n fields: options?.schema?.organization?.additionalFields || {},\n isClientSide: true\n });\n const baseSchema = z.object({\n name: z.string().meta({\n description: \"The name of the organization\"\n }),\n slug: z.string().meta({\n description: \"The slug of the organization\"\n }),\n userId: z.coerce.string().meta({\n description: 'The user id of the organization creator. If not provided, the current user will be used. Should only be used by admins or when called by the server. server-only. Eg: \"user-id\"'\n }).optional(),\n logo: z.string().meta({\n description: \"The logo of the organization\"\n }).optional(),\n metadata: z.record(z.string(), z.any()).meta({\n description: \"The metadata of the organization\"\n }).optional(),\n keepCurrentActiveOrganization: z.boolean().meta({\n description: \"Whether to keep the current active organization active after creating a new one. Eg: true\"\n }).optional()\n });\n return createAuthEndpoint(\n \"/organization/create\",\n {\n method: \"POST\",\n body: z.object({\n ...baseSchema.shape,\n ...additionalFieldsSchema.shape\n }),\n use: [orgMiddleware],\n metadata: {\n $Infer: {\n body: {}\n },\n openapi: {\n description: \"Create an organization\",\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n description: \"The organization that was created\",\n $ref: \"#/components/schemas/Organization\"\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = await getSessionFromCtx(ctx);\n if (!session && (ctx.request || ctx.headers)) {\n throw new APIError(\"UNAUTHORIZED\");\n }\n let user = session?.user || null;\n if (!user) {\n if (!ctx.body.userId) {\n throw new APIError(\"UNAUTHORIZED\");\n }\n user = await ctx.context.internalAdapter.findUserById(ctx.body.userId);\n }\n if (!user) {\n return ctx.json(null, {\n status: 401\n });\n }\n const options2 = ctx.context.orgOptions;\n const canCreateOrg = typeof options2?.allowUserToCreateOrganization === \"function\" ? await options2.allowUserToCreateOrganization(user) : options2?.allowUserToCreateOrganization === void 0 ? true : options2.allowUserToCreateOrganization;\n if (!canCreateOrg) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION\n });\n }\n const adapter = getOrgAdapter(ctx.context, options2);\n const userOrganizations = await adapter.listOrganizations(user.id);\n const hasReachedOrgLimit = typeof options2.organizationLimit === \"number\" ? userOrganizations.length >= options2.organizationLimit : typeof options2.organizationLimit === \"function\" ? await options2.organizationLimit(user) : false;\n if (hasReachedOrgLimit) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS\n });\n }\n const existingOrganization = await adapter.findOrganizationBySlug(\n ctx.body.slug\n );\n if (existingOrganization) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.ORGANIZATION_ALREADY_EXISTS\n });\n }\n const {\n keepCurrentActiveOrganization: _,\n userId: __,\n ...orgData\n } = ctx.body;\n let hookResponse = void 0;\n if (options2.organizationCreation?.beforeCreate) {\n const response = await options2.organizationCreation.beforeCreate(\n {\n organization: {\n ...orgData,\n createdAt: /* @__PURE__ */ new Date()\n },\n user\n },\n ctx.request\n );\n if (response && typeof response === \"object\" && \"data\" in response) {\n hookResponse = response;\n }\n }\n const organization = await adapter.createOrganization({\n organization: {\n ...orgData,\n createdAt: /* @__PURE__ */ new Date(),\n ...hookResponse?.data || {}\n }\n });\n let member;\n let teamMember = null;\n if (options2?.teams?.enabled && options2.teams.defaultTeam?.enabled !== false) {\n const defaultTeam = await options2.teams.defaultTeam?.customCreateDefaultTeam?.(\n organization,\n ctx.request\n ) || await adapter.createTeam({\n organizationId: organization.id,\n name: `${organization.name}`,\n createdAt: /* @__PURE__ */ new Date()\n });\n member = await adapter.createMember({\n userId: user.id,\n organizationId: organization.id,\n role: ctx.context.orgOptions.creatorRole || \"owner\"\n });\n teamMember = await adapter.findOrCreateTeamMember({\n teamId: defaultTeam.id,\n userId: user.id\n });\n } else {\n member = await adapter.createMember({\n userId: user.id,\n organizationId: organization.id,\n role: ctx.context.orgOptions.creatorRole || \"owner\"\n });\n }\n if (options2.organizationCreation?.afterCreate) {\n await options2.organizationCreation.afterCreate(\n {\n organization,\n user,\n member\n },\n ctx.request\n );\n }\n if (ctx.context.session && !ctx.body.keepCurrentActiveOrganization) {\n await adapter.setActiveOrganization(\n ctx.context.session.session.token,\n organization.id\n );\n }\n if (teamMember && ctx.context.session && !ctx.body.keepCurrentActiveOrganization) {\n await adapter.setActiveTeam(\n ctx.context.session.session.token,\n teamMember.teamId\n );\n }\n return ctx.json({\n ...organization,\n metadata: ctx.body.metadata,\n members: [member]\n });\n }\n );\n};\nconst checkOrganizationSlug = (options) => createAuthEndpoint(\n \"/organization/check-slug\",\n {\n method: \"POST\",\n body: z.object({\n slug: z.string().meta({\n description: 'The organization slug to check. Eg: \"my-org\"'\n })\n }),\n use: [requestOnlySessionMiddleware, orgMiddleware]\n },\n async (ctx) => {\n const orgAdapter = getOrgAdapter(ctx.context, options);\n const org = await orgAdapter.findOrganizationBySlug(ctx.body.slug);\n if (!org) {\n return ctx.json({\n status: true\n });\n }\n throw new APIError(\"BAD_REQUEST\", {\n message: \"slug is taken\"\n });\n }\n);\nconst updateOrganization = (options) => {\n const additionalFieldsSchema = toZodSchema({\n fields: options?.schema?.organization?.additionalFields || {},\n isClientSide: true\n });\n return createAuthEndpoint(\n \"/organization/update\",\n {\n method: \"POST\",\n body: z.object({\n data: z.object({\n ...additionalFieldsSchema.shape,\n name: z.string().meta({\n description: \"The name of the organization\"\n }).optional(),\n slug: z.string().meta({\n description: \"The slug of the organization\"\n }).optional(),\n logo: z.string().meta({\n description: \"The logo of the organization\"\n }).optional(),\n metadata: z.record(z.string(), z.any()).meta({\n description: \"The metadata of the organization\"\n }).optional()\n }).partial(),\n organizationId: z.string().meta({\n description: 'The organization ID. Eg: \"org-id\"'\n }).optional()\n }),\n requireHeaders: true,\n use: [orgMiddleware],\n metadata: {\n $Infer: {\n body: {}\n },\n openapi: {\n description: \"Update an organization\",\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n description: \"The updated organization\",\n $ref: \"#/components/schemas/Organization\"\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = await ctx.context.getSession(ctx);\n if (!session) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: \"User not found\"\n });\n }\n const organizationId = ctx.body.organizationId || session.session.activeOrganizationId;\n if (!organizationId) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND\n });\n }\n const adapter = getOrgAdapter(ctx.context, options);\n const member = await adapter.findMemberByOrgId({\n userId: session.user.id,\n organizationId\n });\n if (!member) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION\n });\n }\n const canUpdateOrg = hasPermission({\n permissions: {\n organization: [\"update\"]\n },\n role: member.role,\n options: ctx.context.orgOptions\n });\n if (!canUpdateOrg) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION\n });\n }\n const updatedOrg = await adapter.updateOrganization(\n organizationId,\n ctx.body.data\n );\n return ctx.json(updatedOrg);\n }\n );\n};\nconst deleteOrganization = (options) => {\n return createAuthEndpoint(\n \"/organization/delete\",\n {\n method: \"POST\",\n body: z.object({\n organizationId: z.string().meta({\n description: \"The organization id to delete\"\n })\n }),\n requireHeaders: true,\n use: [orgMiddleware],\n metadata: {\n openapi: {\n description: \"Delete an organization\",\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"string\",\n description: \"The organization id that was deleted\"\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = await ctx.context.getSession(ctx);\n if (!session) {\n return ctx.json(null, {\n status: 401\n });\n }\n const organizationId = ctx.body.organizationId;\n if (!organizationId) {\n return ctx.json(null, {\n status: 400,\n body: {\n message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND\n }\n });\n }\n const adapter = getOrgAdapter(ctx.context, options);\n const member = await adapter.findMemberByOrgId({\n userId: session.user.id,\n organizationId\n });\n if (!member) {\n return ctx.json(null, {\n status: 400,\n body: {\n message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION\n }\n });\n }\n const canDeleteOrg = hasPermission({\n role: member.role,\n permissions: {\n organization: [\"delete\"]\n },\n options: ctx.context.orgOptions\n });\n if (!canDeleteOrg) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION\n });\n }\n if (organizationId === session.session.activeOrganizationId) {\n await adapter.setActiveOrganization(session.session.token, null);\n }\n const option = ctx.context.orgOptions.organizationDeletion;\n if (option?.disabled) {\n throw new APIError(\"FORBIDDEN\");\n }\n const org = await adapter.findOrganizationById(organizationId);\n if (!org) {\n throw new APIError(\"BAD_REQUEST\");\n }\n if (option?.beforeDelete) {\n await option.beforeDelete({\n organization: org,\n user: session.user\n });\n }\n await adapter.deleteOrganization(organizationId);\n if (option?.afterDelete) {\n await option.afterDelete({\n organization: org,\n user: session.user\n });\n }\n return ctx.json(org);\n }\n );\n};\nconst getFullOrganization = (options) => createAuthEndpoint(\n \"/organization/get-full-organization\",\n {\n method: \"GET\",\n query: z.optional(\n z.object({\n organizationId: z.string().meta({\n description: \"The organization id to get\"\n }).optional(),\n organizationSlug: z.string().meta({\n description: \"The organization slug to get\"\n }).optional()\n })\n ),\n requireHeaders: true,\n use: [orgMiddleware, orgSessionMiddleware],\n metadata: {\n openapi: {\n description: \"Get the full organization\",\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n description: \"The organization\",\n $ref: \"#/components/schemas/Organization\"\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = ctx.context.session;\n const organizationId = ctx.query?.organizationSlug || ctx.query?.organizationId || session.session.activeOrganizationId;\n if (!organizationId) {\n return ctx.json(null, {\n status: 200\n });\n }\n const adapter = getOrgAdapter(ctx.context, options);\n const organization = await adapter.findFullOrganization({\n organizationId,\n isSlug: !!ctx.query?.organizationSlug,\n includeTeams: ctx.context.orgOptions.teams?.enabled\n });\n const isMember = organization?.members.find(\n (member) => member.userId === session.user.id\n );\n if (!isMember) {\n await adapter.setActiveOrganization(session.session.token, null);\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION\n });\n }\n if (!organization) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND\n });\n }\n return ctx.json(organization);\n }\n);\nconst setActiveOrganization = (options) => {\n return createAuthEndpoint(\n \"/organization/set-active\",\n {\n method: \"POST\",\n body: z.object({\n organizationId: z.string().meta({\n description: 'The organization id to set as active. It can be null to unset the active organization. Eg: \"org-id\"'\n }).nullable().optional(),\n organizationSlug: z.string().meta({\n description: 'The organization slug to set as active. It can be null to unset the active organization if organizationId is not provided. Eg: \"org-slug\"'\n }).optional()\n }),\n use: [orgSessionMiddleware, orgMiddleware],\n metadata: {\n openapi: {\n description: \"Set the active organization\",\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n description: \"The organization\",\n $ref: \"#/components/schemas/Organization\"\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const adapter = getOrgAdapter(ctx.context, options);\n const session = ctx.context.session;\n let organizationId = ctx.body.organizationSlug || ctx.body.organizationId;\n if (organizationId === null) {\n const sessionOrgId = session.session.activeOrganizationId;\n if (!sessionOrgId) {\n return ctx.json(null);\n }\n const updatedSession2 = await adapter.setActiveOrganization(\n session.session.token,\n null\n );\n await setSessionCookie(ctx, {\n session: updatedSession2,\n user: session.user\n });\n return ctx.json(null);\n }\n if (!organizationId) {\n const sessionOrgId = session.session.activeOrganizationId;\n if (!sessionOrgId) {\n return ctx.json(null);\n }\n organizationId = sessionOrgId;\n }\n const isMember = await adapter.checkMembership({\n userId: session.user.id,\n organizationId\n });\n if (!isMember) {\n await adapter.setActiveOrganization(session.session.token, null);\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION\n });\n }\n let organization = await adapter.findOrganizationById(organizationId);\n if (!organization) {\n if (ctx.body.organizationSlug) {\n organization = await adapter.findOrganizationBySlug(organizationId);\n }\n if (!organization) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND\n });\n }\n }\n const updatedSession = await adapter.setActiveOrganization(\n session.session.token,\n organization.id\n );\n await setSessionCookie(ctx, {\n session: updatedSession,\n user: session.user\n });\n return ctx.json(organization);\n }\n );\n};\nconst listOrganizations = (options) => createAuthEndpoint(\n \"/organization/list\",\n {\n method: \"GET\",\n use: [orgMiddleware, orgSessionMiddleware],\n metadata: {\n openapi: {\n description: \"List all organizations\",\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"array\",\n items: {\n $ref: \"#/components/schemas/Organization\"\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const adapter = getOrgAdapter(ctx.context, options);\n const organizations = await adapter.listOrganizations(\n ctx.context.session.user.id\n );\n return ctx.json(organizations);\n }\n);\n\nconst createTeam = (options) => {\n const additionalFieldsSchema = toZodSchema({\n fields: options?.schema?.team?.additionalFields ?? {},\n isClientSide: true\n });\n const baseSchema = z.object({\n name: z.string().meta({\n description: 'The name of the team. Eg: \"my-team\"'\n }),\n organizationId: z.string().meta({\n description: 'The organization ID which the team will be created in. Defaults to the active organization. Eg: \"organization-id\"'\n }).optional()\n });\n return createAuthEndpoint(\n \"/organization/create-team\",\n {\n method: \"POST\",\n body: z.object({\n ...baseSchema.shape,\n ...additionalFieldsSchema.shape\n }),\n use: [orgMiddleware],\n metadata: {\n $Infer: {\n body: {}\n },\n openapi: {\n description: \"Create a new team within an organization\",\n responses: {\n \"200\": {\n description: \"Team created successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\",\n description: \"Unique identifier of the created team\"\n },\n name: {\n type: \"string\",\n description: \"Name of the team\"\n },\n organizationId: {\n type: \"string\",\n description: \"ID of the organization the team belongs to\"\n },\n createdAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Timestamp when the team was created\"\n },\n updatedAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Timestamp when the team was last updated\"\n }\n },\n required: [\n \"id\",\n \"name\",\n \"organizationId\",\n \"createdAt\",\n \"updatedAt\"\n ]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = await getSessionFromCtx(ctx);\n const organizationId = ctx.body.organizationId || session?.session.activeOrganizationId;\n if (!session && (ctx.request || ctx.headers)) {\n throw new APIError(\"UNAUTHORIZED\");\n }\n if (!organizationId) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION\n });\n }\n const adapter = getOrgAdapter(ctx.context, options);\n if (session) {\n const member = await adapter.findMemberByOrgId({\n userId: session.user.id,\n organizationId\n });\n if (!member) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION\n });\n }\n const canCreate = hasPermission({\n role: member.role,\n options: ctx.context.orgOptions,\n permissions: {\n team: [\"create\"]\n }\n });\n if (!canCreate) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION\n });\n }\n }\n const existingTeams = await adapter.listTeams(organizationId);\n const maximum = typeof ctx.context.orgOptions.teams?.maximumTeams === \"function\" ? await ctx.context.orgOptions.teams?.maximumTeams(\n {\n organizationId,\n session\n },\n ctx.request\n ) : ctx.context.orgOptions.teams?.maximumTeams;\n const maxTeamsReached = maximum ? existingTeams.length >= maximum : false;\n if (maxTeamsReached) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS\n });\n }\n const { name, organizationId: _, ...additionalFields } = ctx.body;\n const createdTeam = await adapter.createTeam({\n name,\n organizationId,\n createdAt: /* @__PURE__ */ new Date(),\n updatedAt: /* @__PURE__ */ new Date(),\n ...additionalFields\n });\n return ctx.json(createdTeam);\n }\n );\n};\nconst removeTeam = (options) => createAuthEndpoint(\n \"/organization/remove-team\",\n {\n method: \"POST\",\n body: z.object({\n teamId: z.string().meta({\n description: `The team ID of the team to remove. Eg: \"team-id\"`\n }),\n organizationId: z.string().meta({\n description: `The organization ID which the team falls under. If not provided, it will default to the user's active organization. Eg: \"organization-id\"`\n }).optional()\n }),\n use: [orgMiddleware],\n metadata: {\n openapi: {\n description: \"Remove a team from an organization\",\n responses: {\n \"200\": {\n description: \"Team removed successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n message: {\n type: \"string\",\n description: \"Confirmation message indicating successful removal\",\n enum: [\"Team removed successfully.\"]\n }\n },\n required: [\"message\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = await getSessionFromCtx(ctx);\n const organizationId = ctx.body.organizationId || session?.session.activeOrganizationId;\n if (!organizationId) {\n return ctx.json(null, {\n status: 400,\n body: {\n message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION\n }\n });\n }\n if (!session && (ctx.request || ctx.headers)) {\n throw new APIError(\"UNAUTHORIZED\");\n }\n const adapter = getOrgAdapter(ctx.context, options);\n if (session) {\n const member = await adapter.findMemberByOrgId({\n userId: session.user.id,\n organizationId\n });\n if (!member || session.session?.activeTeamId === ctx.body.teamId) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM\n });\n }\n const canRemove = hasPermission({\n role: member.role,\n options: ctx.context.orgOptions,\n permissions: {\n team: [\"delete\"]\n }\n });\n if (!canRemove) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION\n });\n }\n }\n const team = await adapter.findTeamById({\n teamId: ctx.body.teamId,\n organizationId\n });\n if (!team || team.organizationId !== organizationId) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.TEAM_NOT_FOUND\n });\n }\n if (!ctx.context.orgOptions.teams?.allowRemovingAllTeams) {\n const teams = await adapter.listTeams(organizationId);\n if (teams.length <= 1) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.UNABLE_TO_REMOVE_LAST_TEAM\n });\n }\n }\n await adapter.deleteTeam(team.id);\n return ctx.json({ message: \"Team removed successfully.\" });\n }\n);\nconst updateTeam = (options) => {\n const additionalFieldsSchema = toZodSchema({\n fields: options?.schema?.team?.additionalFields ?? {},\n isClientSide: true\n });\n return createAuthEndpoint(\n \"/organization/update-team\",\n {\n method: \"POST\",\n body: z.object({\n teamId: z.string().meta({\n description: `The ID of the team to be updated. Eg: \"team-id\"`\n }),\n data: z.object({\n ...teamSchema.shape,\n ...additionalFieldsSchema.shape\n }).partial()\n }),\n requireHeaders: true,\n use: [orgMiddleware, orgSessionMiddleware],\n metadata: {\n $Infer: { body: {} },\n openapi: {\n description: \"Update an existing team in an organization\",\n responses: {\n \"200\": {\n description: \"Team updated successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\",\n description: \"Unique identifier of the updated team\"\n },\n name: {\n type: \"string\",\n description: \"Updated name of the team\"\n },\n organizationId: {\n type: \"string\",\n description: \"ID of the organization the team belongs to\"\n },\n createdAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Timestamp when the team was created\"\n },\n updatedAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Timestamp when the team was last updated\"\n }\n },\n required: [\n \"id\",\n \"name\",\n \"organizationId\",\n \"createdAt\",\n \"updatedAt\"\n ]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = ctx.context.session;\n const organizationId = ctx.body.data.organizationId || session.session.activeOrganizationId;\n if (!organizationId) {\n return ctx.json(null, {\n status: 400,\n body: {\n message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION\n }\n });\n }\n const adapter = getOrgAdapter(ctx.context, options);\n const member = await adapter.findMemberByOrgId({\n userId: session.user.id,\n organizationId\n });\n if (!member) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM\n });\n }\n const canUpdate = hasPermission({\n role: member.role,\n options: ctx.context.orgOptions,\n permissions: {\n team: [\"update\"]\n }\n });\n if (!canUpdate) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM\n });\n }\n const team = await adapter.findTeamById({\n teamId: ctx.body.teamId,\n organizationId\n });\n if (!team || team.organizationId !== organizationId) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.TEAM_NOT_FOUND\n });\n }\n const { name, organizationId: __, ...additionalFields } = ctx.body.data;\n const updatedTeam = await adapter.updateTeam(team.id, {\n name,\n ...additionalFields\n });\n return ctx.json(updatedTeam);\n }\n );\n};\nconst listOrganizationTeams = (options) => createAuthEndpoint(\n \"/organization/list-teams\",\n {\n method: \"GET\",\n query: z.optional(\n z.object({\n organizationId: z.string().meta({\n description: `The organization ID which the teams are under to list. Defaults to the users active organization. Eg: \"organziation-id\"`\n }).optional()\n })\n ),\n requireHeaders: true,\n metadata: {\n openapi: {\n description: \"List all teams in an organization\",\n responses: {\n \"200\": {\n description: \"Teams retrieved successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\",\n description: \"Unique identifier of the team\"\n },\n name: {\n type: \"string\",\n description: \"Name of the team\"\n },\n organizationId: {\n type: \"string\",\n description: \"ID of the organization the team belongs to\"\n },\n createdAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Timestamp when the team was created\"\n },\n updatedAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Timestamp when the team was last updated\"\n }\n },\n required: [\n \"id\",\n \"name\",\n \"organizationId\",\n \"createdAt\",\n \"updatedAt\"\n ]\n },\n description: \"Array of team objects within the organization\"\n }\n }\n }\n }\n }\n }\n },\n use: [orgMiddleware, orgSessionMiddleware]\n },\n async (ctx) => {\n const session = ctx.context.session;\n const organizationId = ctx.query?.organizationId || session?.session.activeOrganizationId;\n if (!organizationId) {\n throw ctx.error(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION\n });\n }\n const adapter = getOrgAdapter(ctx.context, options);\n const member = await adapter.findMemberByOrgId({\n userId: session.user.id,\n organizationId: organizationId || \"\"\n });\n if (!member) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_ACCESS_THIS_ORGANIZATION\n });\n }\n const teams = await adapter.listTeams(organizationId);\n return ctx.json(teams);\n }\n);\nconst setActiveTeam = (options) => createAuthEndpoint(\n \"/organization/set-active-team\",\n {\n method: \"POST\",\n body: z.object({\n teamId: z.string().meta({\n description: \"The team id to set as active. It can be null to unset the active team\"\n }).nullable().optional()\n }),\n use: [orgSessionMiddleware, orgMiddleware],\n metadata: {\n openapi: {\n description: \"Set the active team\",\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n description: \"The team\",\n $ref: \"#/components/schemas/Team\"\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);\n const session = ctx.context.session;\n if (ctx.body.teamId === null) {\n const sessionTeamId = session.session.activeTeamId;\n if (!sessionTeamId) {\n return ctx.json(null);\n }\n const updatedSession2 = await adapter.setActiveTeam(\n session.session.token,\n null\n );\n await setSessionCookie(ctx, {\n session: updatedSession2,\n user: session.user\n });\n return ctx.json(null);\n }\n let teamId;\n if (!ctx.body.teamId) {\n const sessionTeamId = session.session.activeTeamId;\n if (!sessionTeamId) {\n return ctx.json(null);\n } else {\n teamId = sessionTeamId;\n }\n } else {\n teamId = ctx.body.teamId;\n }\n const team = await adapter.findTeamById({ teamId });\n if (!team) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.TEAM_NOT_FOUND\n });\n }\n const member = await adapter.findTeamMember({\n teamId,\n userId: session.user.id\n });\n if (!member) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_TEAM\n });\n }\n const updatedSession = await adapter.setActiveTeam(\n session.session.token,\n team.id\n );\n await setSessionCookie(ctx, {\n session: updatedSession,\n user: session.user\n });\n return ctx.json(team);\n }\n);\nconst listUserTeams = (options) => createAuthEndpoint(\n \"/organization/list-user-teams\",\n {\n method: \"GET\",\n metadata: {\n openapi: {\n description: \"List all teams that the current user is a part of.\",\n responses: {\n \"200\": {\n description: \"Teams retrieved successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"array\",\n items: {\n type: \"object\",\n description: \"The team\",\n $ref: \"#/components/schemas/Team\"\n },\n description: \"Array of team objects within the organization\"\n }\n }\n }\n }\n }\n }\n },\n use: [orgMiddleware, orgSessionMiddleware]\n },\n async (ctx) => {\n const session = ctx.context.session;\n const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);\n const teams = await adapter.listTeamsByUser({\n userId: session.user.id\n });\n return ctx.json(teams);\n }\n);\nconst listTeamMembers = (options) => createAuthEndpoint(\n \"/organization/list-team-members\",\n {\n method: \"GET\",\n query: z.optional(\n z.object({\n teamId: z.string().optional().meta({\n description: \"The team whose members we should return. If this is not provided the members of the current active team get returned.\"\n })\n })\n ),\n metadata: {\n openapi: {\n description: \"List the members of the given team.\",\n responses: {\n \"200\": {\n description: \"Teams retrieved successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"array\",\n items: {\n type: \"object\",\n description: \"The team member\",\n properties: {\n id: {\n type: \"string\",\n description: \"Unique identifier of the team member\"\n },\n userId: {\n type: \"string\",\n description: \"The user ID of the team member\"\n },\n teamId: {\n type: \"string\",\n description: \"The team ID of the team the team member is in\"\n },\n createdAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Timestamp when the team member was created\"\n }\n },\n required: [\"id\", \"userId\", \"teamId\", \"createdAt\"]\n },\n description: \"Array of team member objects within the team\"\n }\n }\n }\n }\n }\n }\n },\n use: [orgMiddleware, orgSessionMiddleware]\n },\n async (ctx) => {\n const session = ctx.context.session;\n const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);\n let teamId = ctx.query?.teamId || session?.session.activeTeamId;\n if (!teamId) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM\n });\n }\n const member = await adapter.findTeamMember({\n userId: session.user.id,\n teamId\n });\n if (!member) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_TEAM\n });\n }\n const members = await adapter.listTeamMembers({\n teamId\n });\n return ctx.json(members);\n }\n);\nconst addTeamMember = (options) => createAuthEndpoint(\n \"/organization/add-team-member\",\n {\n method: \"POST\",\n body: z.object({\n teamId: z.string().meta({\n description: \"The team the user should be a member of.\"\n }),\n userId: z.coerce.string().meta({\n description: \"The user Id which represents the user to be added as a member.\"\n })\n }),\n metadata: {\n openapi: {\n description: \"The newly created member\",\n responses: {\n \"200\": {\n description: \"Team member created successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n description: \"The team member\",\n properties: {\n id: {\n type: \"string\",\n description: \"Unique identifier of the team member\"\n },\n userId: {\n type: \"string\",\n description: \"The user ID of the team member\"\n },\n teamId: {\n type: \"string\",\n description: \"The team ID of the team the team member is in\"\n },\n createdAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Timestamp when the team member was created\"\n }\n },\n required: [\"id\", \"userId\", \"teamId\", \"createdAt\"]\n }\n }\n }\n }\n }\n }\n },\n use: [orgMiddleware, orgSessionMiddleware]\n },\n async (ctx) => {\n const session = ctx.context.session;\n const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);\n if (!session.session.activeOrganizationId) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION\n });\n }\n const currentMember = await adapter.findMemberByOrgId({\n userId: session.user.id,\n organizationId: session.session.activeOrganizationId\n });\n if (!currentMember) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION\n });\n }\n const canUpdateMember = hasPermission({\n role: currentMember.role,\n options: ctx.context.orgOptions,\n permissions: {\n member: [\"update\"]\n }\n });\n if (!canUpdateMember) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM_MEMBER\n });\n }\n const toBeAddedMember = await adapter.findMemberByOrgId({\n userId: ctx.body.userId,\n organizationId: session.session.activeOrganizationId\n });\n if (!toBeAddedMember) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION\n });\n }\n const teamMember = await adapter.findOrCreateTeamMember({\n teamId: ctx.body.teamId,\n userId: ctx.body.userId\n });\n return ctx.json(teamMember);\n }\n);\nconst removeTeamMember = (options) => createAuthEndpoint(\n \"/organization/remove-team-member\",\n {\n method: \"POST\",\n body: z.object({\n teamId: z.string().meta({\n description: \"The team the user should be removed from.\"\n }),\n userId: z.coerce.string().meta({\n description: \"The user which should be removed from the team.\"\n })\n }),\n metadata: {\n openapi: {\n description: \"Remove a member from a team\",\n responses: {\n \"200\": {\n description: \"Team member removed successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n message: {\n type: \"string\",\n description: \"Confirmation message indicating successful removal\",\n enum: [\"Team member removed successfully.\"]\n }\n },\n required: [\"message\"]\n }\n }\n }\n }\n }\n }\n },\n use: [orgMiddleware, orgSessionMiddleware]\n },\n async (ctx) => {\n const session = ctx.context.session;\n const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);\n if (!session.session.activeOrganizationId) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION\n });\n }\n const currentMember = await adapter.findMemberByOrgId({\n userId: session.user.id,\n organizationId: session.session.activeOrganizationId\n });\n if (!currentMember) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION\n });\n }\n const canDeleteMember = hasPermission({\n role: currentMember.role,\n options: ctx.context.orgOptions,\n permissions: {\n member: [\"delete\"]\n }\n });\n if (!canDeleteMember) {\n throw new APIError(\"FORBIDDEN\", {\n message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REMOVE_A_TEAM_MEMBER\n });\n }\n const toBeAddedMember = await adapter.findMemberByOrgId({\n userId: ctx.body.userId,\n organizationId: session.session.activeOrganizationId\n });\n if (!toBeAddedMember) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION\n });\n }\n await adapter.removeTeamMember({\n teamId: ctx.body.teamId,\n userId: ctx.body.userId\n });\n return ctx.json({ message: \"Team member removed successfully.\" });\n }\n);\n\nfunction parseRoles(roles) {\n return Array.isArray(roles) ? roles.join(\",\") : roles;\n}\nconst organization = (options) => {\n let endpoints = {\n /**\n * ### Endpoint\n *\n * POST `/organization/create`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.createOrganization`\n *\n * **client:**\n * `authClient.organization.create`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-create)\n */\n createOrganization: createOrganization(options),\n /**\n * ### Endpoint\n *\n * POST `/organization/update`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.updateOrganization`\n *\n * **client:**\n * `authClient.organization.update`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-update)\n */\n updateOrganization: updateOrganization(options),\n /**\n * ### Endpoint\n *\n * POST `/organization/delete`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.deleteOrganization`\n *\n * **client:**\n * `authClient.organization.delete`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-delete)\n */\n deleteOrganization: deleteOrganization(options),\n /**\n * ### Endpoint\n *\n * POST `/organization/set-active`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.setActiveOrganization`\n *\n * **client:**\n * `authClient.organization.setActive`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-set-active)\n */\n setActiveOrganization: setActiveOrganization(options),\n /**\n * ### Endpoint\n *\n * GET `/organization/get-full-organization`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.getFullOrganization`\n *\n * **client:**\n * `authClient.organization.getFullOrganization`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-get-full-organization)\n */\n getFullOrganization: getFullOrganization(options),\n /**\n * ### Endpoint\n *\n * GET `/organization/list`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.listOrganizations`\n *\n * **client:**\n * `authClient.organization.list`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-list)\n */\n listOrganizations: listOrganizations(options),\n /**\n * ### Endpoint\n *\n * POST `/organization/invite-member`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.createInvitation`\n *\n * **client:**\n * `authClient.organization.inviteMember`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-invite-member)\n */\n createInvitation: createInvitation(options),\n /**\n * ### Endpoint\n *\n * POST `/organization/cancel-invitation`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.cancelInvitation`\n *\n * **client:**\n * `authClient.organization.cancelInvitation`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-cancel-invitation)\n */\n cancelInvitation: cancelInvitation(options),\n /**\n * ### Endpoint\n *\n * POST `/organization/accept-invitation`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.acceptInvitation`\n *\n * **client:**\n * `authClient.organization.acceptInvitation`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-accept-invitation)\n */\n acceptInvitation: acceptInvitation(options),\n /**\n * ### Endpoint\n *\n * GET `/organization/get-invitation`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.getInvitation`\n *\n * **client:**\n * `authClient.organization.getInvitation`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-get-invitation)\n */\n getInvitation: getInvitation(options),\n /**\n * ### Endpoint\n *\n * POST `/organization/reject-invitation`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.rejectInvitation`\n *\n * **client:**\n * `authClient.organization.rejectInvitation`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-reject-invitation)\n */\n rejectInvitation: rejectInvitation(),\n /**\n * ### Endpoint\n *\n * GET `/organization/list-invitations`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.listInvitations`\n *\n * **client:**\n * `authClient.organization.listInvitations`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-list-invitations)\n */\n listInvitations: listInvitations(options),\n /**\n * ### Endpoint\n *\n * GET `/organization/get-active-member`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.getActiveMember`\n *\n * **client:**\n * `authClient.organization.getActiveMember`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-get-active-member)\n */\n getActiveMember: getActiveMember(options),\n /**\n * ### Endpoint\n *\n * POST `/organization/check-slug`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.checkOrganizationSlug`\n *\n * **client:**\n * `authClient.organization.checkSlug`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-check-slug)\n */\n checkOrganizationSlug: checkOrganizationSlug(options),\n /**\n * ### Endpoint\n *\n * POST `/organization/add-member`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.addMember`\n *\n * **client:**\n * `authClient.organization.addMember`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-add-member)\n */\n addMember: addMember(options),\n /**\n * ### Endpoint\n *\n * POST `/organization/remove-member`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.removeMember`\n *\n * **client:**\n * `authClient.organization.removeMember`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-remove-member)\n */\n removeMember: removeMember(options),\n /**\n * ### Endpoint\n *\n * POST `/organization/update-member-role`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.updateMemberRole`\n *\n * **client:**\n * `authClient.organization.updateMemberRole`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-update-member-role)\n */\n updateMemberRole: updateMemberRole(),\n /**\n * ### Endpoint\n *\n * POST `/organization/leave`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.leaveOrganization`\n *\n * **client:**\n * `authClient.organization.leave`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-leave)\n */\n leaveOrganization: leaveOrganization(options),\n listUserInvitations: listUserInvitations(options)\n };\n const teamSupport = options?.teams?.enabled;\n const teamEndpoints = {\n /**\n * ### Endpoint\n *\n * POST `/organization/create-team`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.createTeam`\n *\n * **client:**\n * `authClient.organization.createTeam`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-create-team)\n */\n createTeam: createTeam(options),\n /**\n * ### Endpoint\n *\n * GET `/organization/list-teams`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.listOrganizationTeams`\n *\n * **client:**\n * `authClient.organization.listTeams`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-list-teams)\n */\n listOrganizationTeams: listOrganizationTeams(options),\n /**\n * ### Endpoint\n *\n * POST `/organization/remove-team`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.removeTeam`\n *\n * **client:**\n * `authClient.organization.removeTeam`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-remove-team)\n */\n removeTeam: removeTeam(options),\n /**\n * ### Endpoint\n *\n * POST `/organization/update-team`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.updateTeam`\n *\n * **client:**\n * `authClient.organization.updateTeam`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-update-team)\n */\n updateTeam: updateTeam(options),\n /**\n * ### Endpoint\n *\n * POST `/organization/set-active-team`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.setActiveTeam`\n *\n * **client:**\n * `authClient.organization.setActiveTeam`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-set-active-team)\n */\n setActiveTeam: setActiveTeam(),\n /**\n * ### Endpoint\n *\n * POST `/organization/list-user-teams`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.listUserTeams`\n *\n * **client:**\n * `authClient.organization.listUserTeams`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-set-active-team)\n */\n listUserTeams: listUserTeams(),\n /**\n * ### Endpoint\n *\n * POST `/organization/list-team-members`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.listTeamMembers`\n *\n * **client:**\n * `authClient.organization.listTeamMembers`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-set-active-team)\n */\n listTeamMembers: listTeamMembers(),\n /**\n * ### Endpoint\n *\n * POST `/organization/add-team-member`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.addTeamMember`\n *\n * **client:**\n * `authClient.organization.addTeamMember`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-add-team-member)\n */\n addTeamMember: addTeamMember(),\n /**\n * ### Endpoint\n *\n * POST `/organization/remove-team-member`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.removeTeamMember`\n *\n * **client:**\n * `authClient.organization.removeTeamMember`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-remove-team-member)\n */\n removeTeamMember: removeTeamMember()\n };\n if (teamSupport) {\n endpoints = {\n ...endpoints,\n ...teamEndpoints\n };\n }\n const roles = {\n ...defaultRoles,\n ...options?.roles\n };\n const teamSchema = teamSupport ? {\n team: {\n modelName: options?.schema?.team?.modelName,\n fields: {\n name: {\n type: \"string\",\n required: true,\n fieldName: options?.schema?.team?.fields?.name\n },\n organizationId: {\n type: \"string\",\n required: true,\n references: {\n model: \"organization\",\n field: \"id\"\n },\n fieldName: options?.schema?.team?.fields?.organizationId\n },\n createdAt: {\n type: \"date\",\n required: true,\n fieldName: options?.schema?.team?.fields?.createdAt\n },\n updatedAt: {\n type: \"date\",\n required: false,\n fieldName: options?.schema?.team?.fields?.updatedAt\n },\n ...options?.schema?.team?.additionalFields || {}\n }\n },\n teamMember: {\n modelName: options?.schema?.teamMember?.modelName,\n fields: {\n teamId: {\n type: \"string\",\n required: true,\n fieldName: options?.schema?.teamMember?.fields?.teamId\n },\n userId: {\n type: \"string\",\n required: true,\n references: {\n model: \"user\",\n field: \"id\"\n },\n fieldName: options?.schema?.teamMember?.fields?.userId\n },\n createdAt: {\n type: \"date\",\n required: false,\n fieldName: options?.schema?.teamMember?.fields?.createdAt\n }\n }\n }\n } : void 0;\n const api = shimContext(endpoints, {\n orgOptions: options || {},\n roles,\n getSession: async (context) => {\n return await getSessionFromCtx(context);\n }\n });\n return {\n id: \"organization\",\n endpoints: {\n // ...endpoints,\n ...api,\n hasPermission: createAuthEndpoint(\n \"/organization/has-permission\",\n {\n method: \"POST\",\n requireHeaders: true,\n body: z.object({\n organizationId: z.string().optional()\n }).and(\n z.union([\n z.object({\n permission: z.record(z.string(), z.array(z.string())),\n permissions: z.undefined()\n }),\n z.object({\n permission: z.undefined(),\n permissions: z.record(z.string(), z.array(z.string()))\n })\n ])\n ),\n use: [orgSessionMiddleware],\n metadata: {\n $Infer: {\n body: {}\n },\n openapi: {\n description: \"Check if the user has permission\",\n requestBody: {\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n permission: {\n type: \"object\",\n description: \"The permission to check\",\n deprecated: true\n },\n permissions: {\n type: \"object\",\n description: \"The permission to check\"\n }\n },\n required: [\"permissions\"]\n }\n }\n }\n },\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n error: {\n type: \"string\"\n },\n success: {\n type: \"boolean\"\n }\n },\n required: [\"success\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const activeOrganizationId = ctx.body.organizationId || ctx.context.session.session.activeOrganizationId;\n if (!activeOrganizationId) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION\n });\n }\n const adapter = getOrgAdapter(ctx.context, options);\n const member = await adapter.findMemberByOrgId({\n userId: ctx.context.session.user.id,\n organizationId: activeOrganizationId\n });\n if (!member) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION\n });\n }\n const result = hasPermission({\n role: member.role,\n options: options || {},\n permissions: ctx.body.permissions ?? ctx.body.permission\n });\n return ctx.json({\n error: null,\n success: result\n });\n }\n )\n },\n schema: {\n session: {\n fields: {\n activeOrganizationId: {\n type: \"string\",\n required: false,\n fieldName: options?.schema?.session?.fields?.activeOrganizationId\n },\n ...teamSupport ? {\n activeTeamId: {\n type: \"string\",\n required: false,\n fieldName: options?.schema?.session?.fields?.activeTeamId\n }\n } : {}\n }\n },\n organization: {\n modelName: options?.schema?.organization?.modelName,\n fields: {\n name: {\n type: \"string\",\n required: true,\n sortable: true,\n fieldName: options?.schema?.organization?.fields?.name\n },\n slug: {\n type: \"string\",\n unique: true,\n sortable: true,\n fieldName: options?.schema?.organization?.fields?.slug\n },\n logo: {\n type: \"string\",\n required: false,\n fieldName: options?.schema?.organization?.fields?.logo\n },\n createdAt: {\n type: \"date\",\n required: true,\n fieldName: options?.schema?.organization?.fields?.createdAt\n },\n metadata: {\n type: \"string\",\n required: false,\n fieldName: options?.schema?.organization?.fields?.metadata\n },\n ...options?.schema?.organization?.additionalFields || {}\n }\n },\n member: {\n modelName: options?.schema?.member?.modelName,\n fields: {\n organizationId: {\n type: \"string\",\n required: true,\n references: {\n model: \"organization\",\n field: \"id\"\n },\n fieldName: options?.schema?.member?.fields?.organizationId\n },\n userId: {\n type: \"string\",\n required: true,\n fieldName: options?.schema?.member?.fields?.userId,\n references: {\n model: \"user\",\n field: \"id\"\n }\n },\n role: {\n type: \"string\",\n required: true,\n sortable: true,\n defaultValue: \"member\",\n fieldName: options?.schema?.member?.fields?.role\n },\n createdAt: {\n type: \"date\",\n required: true,\n fieldName: options?.schema?.member?.fields?.createdAt\n },\n ...options?.schema?.member?.additionalFields || {}\n }\n },\n invitation: {\n modelName: options?.schema?.invitation?.modelName,\n fields: {\n organizationId: {\n type: \"string\",\n required: true,\n references: {\n model: \"organization\",\n field: \"id\"\n },\n fieldName: options?.schema?.invitation?.fields?.organizationId\n },\n email: {\n type: \"string\",\n required: true,\n sortable: true,\n fieldName: options?.schema?.invitation?.fields?.email\n },\n role: {\n type: \"string\",\n required: false,\n sortable: true,\n fieldName: options?.schema?.invitation?.fields?.role\n },\n ...teamSupport ? {\n teamId: {\n type: \"string\",\n required: false,\n sortable: true,\n fieldName: options?.schema?.invitation?.fields?.teamId\n }\n } : {},\n status: {\n type: \"string\",\n required: true,\n sortable: true,\n defaultValue: \"pending\",\n fieldName: options?.schema?.invitation?.fields?.status\n },\n expiresAt: {\n type: \"date\",\n required: true,\n fieldName: options?.schema?.invitation?.fields?.expiresAt\n },\n inviterId: {\n type: \"string\",\n references: {\n model: \"user\",\n field: \"id\"\n },\n fieldName: options?.schema?.invitation?.fields?.inviterId,\n required: true\n },\n ...options?.schema?.invitation?.additionalFields || {}\n }\n },\n ...teamSupport ? teamSchema : {}\n },\n $Infer: {\n Organization: {},\n Invitation: {},\n Member: {},\n Team: teamSupport ? {} : {},\n TeamMember: teamSupport ? {} : {},\n ActiveOrganization: {}\n },\n $ERROR_CODES: ORGANIZATION_ERROR_CODES,\n options\n };\n};\n\nexport { organization as o, parseRoles as p };\n","const PROTO_POLLUTION_PATTERNS = {\n proto: /\"(?:_|\\\\u0{2}5[Ff]){2}(?:p|\\\\u0{2}70)(?:r|\\\\u0{2}72)(?:o|\\\\u0{2}6[Ff])(?:t|\\\\u0{2}74)(?:o|\\\\u0{2}6[Ff])(?:_|\\\\u0{2}5[Ff]){2}\"\\s*:/,\n constructor: /\"(?:c|\\\\u0063)(?:o|\\\\u006[Ff])(?:n|\\\\u006[Ee])(?:s|\\\\u0073)(?:t|\\\\u0074)(?:r|\\\\u0072)(?:u|\\\\u0075)(?:c|\\\\u0063)(?:t|\\\\u0074)(?:o|\\\\u006[Ff])(?:r|\\\\u0072)\"\\s*:/,\n protoShort: /\"__proto__\"\\s*:/,\n constructorShort: /\"constructor\"\\s*:/\n};\nconst JSON_SIGNATURE = /^\\s*[\"[{]|^\\s*-?\\d{1,16}(\\.\\d{1,17})?([Ee][+-]?\\d+)?\\s*$/;\nconst SPECIAL_VALUES = {\n true: true,\n false: false,\n null: null,\n undefined: void 0,\n nan: Number.NaN,\n infinity: Number.POSITIVE_INFINITY,\n \"-infinity\": Number.NEGATIVE_INFINITY\n};\nconst ISO_DATE_REGEX = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,7}))?(?:Z|([+-])(\\d{2}):(\\d{2}))$/;\nfunction isValidDate(date) {\n return date instanceof Date && !isNaN(date.getTime());\n}\nfunction parseISODate(value) {\n const match = ISO_DATE_REGEX.exec(value);\n if (!match) return null;\n const [\n ,\n year,\n month,\n day,\n hour,\n minute,\n second,\n ms,\n offsetSign,\n offsetHour,\n offsetMinute\n ] = match;\n let date = new Date(\n Date.UTC(\n parseInt(year, 10),\n parseInt(month, 10) - 1,\n parseInt(day, 10),\n parseInt(hour, 10),\n parseInt(minute, 10),\n parseInt(second, 10),\n ms ? parseInt(ms.padEnd(3, \"0\"), 10) : 0\n )\n );\n if (offsetSign) {\n const offset = (parseInt(offsetHour, 10) * 60 + parseInt(offsetMinute, 10)) * (offsetSign === \"+\" ? -1 : 1);\n date.setUTCMinutes(date.getUTCMinutes() + offset);\n }\n return isValidDate(date) ? date : null;\n}\nfunction betterJSONParse(value, options = {}) {\n const {\n strict = false,\n warnings = false,\n reviver,\n parseDates = true\n } = options;\n if (typeof value !== \"string\") {\n return value;\n }\n const trimmed = value.trim();\n if (trimmed[0] === '\"' && trimmed.endsWith('\"') && !trimmed.slice(1, -1).includes('\"')) {\n return trimmed.slice(1, -1);\n }\n const lowerValue = trimmed.toLowerCase();\n if (lowerValue.length <= 9 && lowerValue in SPECIAL_VALUES) {\n return SPECIAL_VALUES[lowerValue];\n }\n if (!JSON_SIGNATURE.test(trimmed)) {\n if (strict) {\n throw new SyntaxError(\"[better-json] Invalid JSON\");\n }\n return value;\n }\n const hasProtoPattern = Object.entries(PROTO_POLLUTION_PATTERNS).some(\n ([key, pattern]) => {\n const matches = pattern.test(trimmed);\n if (matches && warnings) {\n console.warn(\n `[better-json] Detected potential prototype pollution attempt using ${key} pattern`\n );\n }\n return matches;\n }\n );\n if (hasProtoPattern && strict) {\n throw new Error(\n \"[better-json] Potential prototype pollution attempt detected\"\n );\n }\n try {\n const secureReviver = (key, value2) => {\n if (key === \"__proto__\" || key === \"constructor\" && value2 && typeof value2 === \"object\" && \"prototype\" in value2) {\n if (warnings) {\n console.warn(\n `[better-json] Dropping \"${key}\" key to prevent prototype pollution`\n );\n }\n return void 0;\n }\n if (parseDates && typeof value2 === \"string\") {\n const date = parseISODate(value2);\n if (date) {\n return date;\n }\n }\n return reviver ? reviver(key, value2) : value2;\n };\n return JSON.parse(trimmed, secureReviver);\n } catch (error) {\n if (strict) {\n throw error;\n }\n return value;\n }\n}\nfunction parseJSON(value, options = { strict: true }) {\n return betterJSONParse(value, options);\n}\n\nexport { parseJSON as p };\n","import { i as createAuthMiddleware, l as sessionMiddleware } from './better-auth.D4HhkCZJ.mjs';\nimport 'better-call';\nimport * as z from 'zod/v4';\nimport './better-auth.8zoxzg-F.mjs';\nimport '@better-auth/utils/base64';\nimport '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport './better-auth.n2KFGwjY.mjs';\nimport { g as generateId } from './better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport '@better-auth/utils/hash';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport 'jose';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport './better-auth.B4Qoxdgc.mjs';\n\nconst orgMiddleware = createAuthMiddleware(async () => {\n return {};\n});\nconst orgSessionMiddleware = createAuthMiddleware(\n {\n use: [sessionMiddleware]\n },\n async (ctx) => {\n const session = ctx.context.session;\n return {\n session\n };\n }\n);\n\nconst role = z.string();\nconst invitationStatus = z.enum([\"pending\", \"accepted\", \"rejected\", \"canceled\"]).default(\"pending\");\nz.object({\n id: z.string().default(generateId),\n name: z.string(),\n slug: z.string(),\n logo: z.string().nullish().optional(),\n metadata: z.record(z.string(), z.unknown()).or(z.string().transform((v) => JSON.parse(v))).optional(),\n createdAt: z.date()\n});\nz.object({\n id: z.string().default(generateId),\n organizationId: z.string(),\n userId: z.coerce.string(),\n role,\n createdAt: z.date().default(() => /* @__PURE__ */ new Date())\n});\nz.object({\n id: z.string().default(generateId),\n organizationId: z.string(),\n email: z.string(),\n role,\n status: invitationStatus,\n teamId: z.string().optional(),\n inviterId: z.string(),\n expiresAt: z.date()\n});\nconst teamSchema = z.object({\n id: z.string().default(generateId),\n name: z.string().min(1),\n organizationId: z.string(),\n createdAt: z.date(),\n updatedAt: z.date().optional()\n});\nz.object({\n id: z.string().default(generateId),\n teamId: z.string(),\n userId: z.string(),\n createdAt: z.date().default(() => /* @__PURE__ */ new Date())\n});\nconst defaultRoles = [\"admin\", \"member\", \"owner\"];\nz.union([\n z.enum(defaultRoles),\n z.array(z.enum(defaultRoles))\n]);\n\nexport { orgSessionMiddleware as a, orgMiddleware as o, teamSchema as t };\n","import { B as BetterAuthError } from '../../shared/better-auth.DdzSJf-n.mjs';\n\nfunction role(statements) {\n return {\n authorize(request, connector = \"AND\") {\n let success = false;\n for (const [requestedResource, requestedActions] of Object.entries(\n request\n )) {\n const allowedActions = statements[requestedResource];\n if (!allowedActions) {\n return {\n success: false,\n error: `You are not allowed to access resource: ${requestedResource}`\n };\n }\n if (Array.isArray(requestedActions)) {\n success = requestedActions.every(\n (requestedAction) => allowedActions.includes(requestedAction)\n );\n } else {\n if (typeof requestedActions === \"object\") {\n const actions = requestedActions;\n if (actions.connector === \"OR\") {\n success = actions.actions.some(\n (requestedAction) => allowedActions.includes(requestedAction)\n );\n } else {\n success = actions.actions.every(\n (requestedAction) => allowedActions.includes(requestedAction)\n );\n }\n } else {\n throw new BetterAuthError(\"Invalid access control request\");\n }\n }\n if (success && connector === \"OR\") {\n return { success };\n }\n if (!success && connector === \"AND\") {\n return {\n success: false,\n error: `unauthorized to access resource \"${requestedResource}\"`\n };\n }\n }\n if (success) {\n return {\n success\n };\n }\n return {\n success: false,\n error: \"Not authorized\"\n };\n },\n statements\n };\n}\nfunction createAccessControl(s) {\n return {\n newRole(statements) {\n return role(statements);\n },\n statements: s\n };\n}\n\nexport { createAccessControl, role };\n","import { createAccessControl } from '../../access/index.mjs';\nimport '../../../shared/better-auth.DdzSJf-n.mjs';\n\nconst defaultStatements = {\n organization: [\"update\", \"delete\"],\n member: [\"create\", \"update\", \"delete\"],\n invitation: [\"create\", \"cancel\"],\n team: [\"create\", \"update\", \"delete\"]\n};\nconst defaultAc = createAccessControl(defaultStatements);\nconst adminAc = defaultAc.newRole({\n organization: [\"update\"],\n invitation: [\"create\", \"cancel\"],\n member: [\"create\", \"update\", \"delete\"],\n team: [\"create\", \"update\", \"delete\"]\n});\nconst ownerAc = defaultAc.newRole({\n organization: [\"update\", \"delete\"],\n member: [\"create\", \"update\", \"delete\"],\n invitation: [\"create\", \"cancel\"],\n team: [\"create\", \"update\", \"delete\"]\n});\nconst memberAc = defaultAc.newRole({\n organization: [],\n member: [],\n invitation: [],\n team: []\n});\nconst defaultRoles = {\n admin: adminAc,\n owner: ownerAc,\n member: memberAc\n};\n\nexport { adminAc, defaultAc, defaultRoles, defaultStatements, memberAc, ownerAc };\n","import * as z from 'zod/v4';\n\nfunction toZodSchema({\n fields,\n isClientSide\n}) {\n const zodFields = Object.keys(fields).reduce((acc, key) => {\n const field = fields[key];\n if (!field) {\n return acc;\n }\n if (isClientSide && field.input === false) {\n return acc;\n }\n if (field.type === \"string[]\" || field.type === \"number[]\") {\n return {\n ...acc,\n [key]: z.array(field.type === \"string[]\" ? z.string() : z.number())\n };\n }\n if (Array.isArray(field.type)) {\n return {\n ...acc,\n [key]: z.any()\n };\n }\n let schema2 = z[field.type]();\n if (field?.required === false) {\n schema2 = schema2.optional();\n }\n if (field?.returned === false) {\n return acc;\n }\n return {\n ...acc,\n [key]: schema2\n };\n }, {});\n const schema = z.object(zodFields);\n return schema;\n}\n\nexport { toZodSchema as t };\n","import { g as generateRandomString } from '../../shared/better-auth.B4Qoxdgc.mjs';\nimport * as z from 'zod/v4';\nimport { k as getSessionFromCtx, j as createAuthEndpoint, l as sessionMiddleware, B as BASE_ERROR_CODES, i as createAuthMiddleware } from '../../shared/better-auth.D4HhkCZJ.mjs';\nimport { APIError } from 'better-call';\nimport { setSessionCookie, deleteSessionCookie } from '../../cookies/index.mjs';\nimport { m as mergeSchema } from '../../shared/better-auth.n2KFGwjY.mjs';\nimport '../../shared/better-auth.8zoxzg-F.mjs';\nimport '../../shared/better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport { symmetricEncrypt, symmetricDecrypt } from '../../crypto/index.mjs';\nimport { base64Url } from '@better-auth/utils/base64';\nimport { createHMAC } from '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport { createHash } from '@better-auth/utils/hash';\nimport { createOTP } from '@better-auth/utils/otp';\nimport { v as validatePassword } from '../../shared/better-auth.YwDQhoPc.mjs';\nexport { t as twoFactorClient } from '../../shared/better-auth.Ddw8bVyV.mjs';\nimport '@better-auth/utils/random';\nimport '../../shared/better-auth.CW6D9eSx.mjs';\nimport '@better-fetch/fetch';\nimport 'jose';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport '../../shared/better-auth.VTXNLFMT.mjs';\nimport '../../shared/better-auth.DdzSJf-n.mjs';\nimport '../../shared/better-auth.tB5eU6EY.mjs';\nimport 'jose/errors';\n\nconst TWO_FACTOR_ERROR_CODES = {\n OTP_NOT_ENABLED: \"OTP not enabled\",\n OTP_HAS_EXPIRED: \"OTP has expired\",\n TOTP_NOT_ENABLED: \"TOTP not enabled\",\n TWO_FACTOR_NOT_ENABLED: \"Two factor isn't enabled\",\n BACKUP_CODES_NOT_ENABLED: \"Backup codes aren't enabled\",\n INVALID_BACKUP_CODE: \"Invalid backup code\",\n INVALID_CODE: \"Invalid code\",\n TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE: \"Too many attempts. Please request a new code.\",\n INVALID_TWO_FACTOR_COOKIE: \"Invalid two factor cookie\"\n};\n\nconst TWO_FACTOR_COOKIE_NAME = \"two_factor\";\nconst TRUST_DEVICE_COOKIE_NAME = \"trust_device\";\n\nasync function verifyTwoFactor(ctx) {\n const session = await getSessionFromCtx(ctx);\n if (!session) {\n const cookieName = ctx.context.createAuthCookie(TWO_FACTOR_COOKIE_NAME);\n const twoFactorCookie = await ctx.getSignedCookie(\n cookieName.name,\n ctx.context.secret\n );\n if (!twoFactorCookie) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: TWO_FACTOR_ERROR_CODES.INVALID_TWO_FACTOR_COOKIE\n });\n }\n const verificationToken = await ctx.context.internalAdapter.findVerificationValue(twoFactorCookie);\n if (!verificationToken) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: TWO_FACTOR_ERROR_CODES.INVALID_TWO_FACTOR_COOKIE\n });\n }\n const user = await ctx.context.internalAdapter.findUserById(\n verificationToken.value\n );\n if (!user) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: TWO_FACTOR_ERROR_CODES.INVALID_TWO_FACTOR_COOKIE\n });\n }\n const dontRememberMe = await ctx.getSignedCookie(\n ctx.context.authCookies.dontRememberToken.name,\n ctx.context.secret\n );\n return {\n valid: async (ctx2) => {\n const session2 = await ctx2.context.internalAdapter.createSession(\n verificationToken.value,\n ctx2,\n !!dontRememberMe\n );\n if (!session2) {\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n message: \"failed to create session\"\n });\n }\n await setSessionCookie(ctx2, {\n session: session2,\n user\n });\n if (ctx2.body.trustDevice) {\n const trustDeviceCookie = ctx2.context.createAuthCookie(\n TRUST_DEVICE_COOKIE_NAME,\n {\n maxAge: 30 * 24 * 60 * 60\n // 30 days, it'll be refreshed on sign in requests\n }\n );\n const token = await createHMAC(\"SHA-256\", \"base64urlnopad\").sign(\n ctx2.context.secret,\n `${user.id}!${session2.token}`\n );\n await ctx2.setSignedCookie(\n trustDeviceCookie.name,\n `${token}!${session2.token}`,\n ctx2.context.secret,\n trustDeviceCookie.attributes\n );\n ctx2.setCookie(ctx2.context.authCookies.dontRememberToken.name, \"\", {\n maxAge: 0\n });\n ctx2.setCookie(cookieName.name, \"\", {\n maxAge: 0\n });\n }\n return ctx2.json({\n token: session2.token,\n user: {\n id: user.id,\n email: user.email,\n emailVerified: user.emailVerified,\n name: user.name,\n image: user.image,\n createdAt: user.createdAt,\n updatedAt: user.updatedAt\n }\n });\n },\n invalid: async (errorKey) => {\n throw new APIError(\"UNAUTHORIZED\", {\n message: TWO_FACTOR_ERROR_CODES[errorKey]\n });\n },\n session: {\n session: null,\n user\n },\n key: twoFactorCookie\n };\n }\n return {\n valid: async (ctx2) => {\n return ctx2.json({\n token: session.session.token,\n user: {\n id: session.user.id,\n email: session.user.email,\n emailVerified: session.user.emailVerified,\n name: session.user.name,\n image: session.user.image,\n createdAt: session.user.createdAt,\n updatedAt: session.user.updatedAt\n }\n });\n },\n invalid: async () => {\n throw new APIError(\"UNAUTHORIZED\", {\n message: TWO_FACTOR_ERROR_CODES.INVALID_TWO_FACTOR_COOKIE\n });\n },\n session,\n key: `${session.user.id}!${session.session.id}`\n };\n}\n\nfunction generateBackupCodesFn(options) {\n return Array.from({ length: options?.amount ?? 10 }).fill(null).map(() => generateRandomString(options?.length ?? 10, \"a-z\", \"0-9\", \"A-Z\")).map((code) => `${code.slice(0, 5)}-${code.slice(5)}`);\n}\nasync function generateBackupCodes(secret, options) {\n const key = secret;\n const backupCodes = options?.customBackupCodesGenerate ? options.customBackupCodesGenerate() : generateBackupCodesFn(options);\n const encCodes = await symmetricEncrypt({\n data: JSON.stringify(backupCodes),\n key\n });\n return {\n backupCodes,\n encryptedBackupCodes: encCodes\n };\n}\nasync function verifyBackupCode(data, key) {\n const codes = await getBackupCodes(data.backupCodes, key);\n if (!codes) {\n return {\n status: false,\n updated: null\n };\n }\n return {\n status: codes.includes(data.code),\n updated: codes.filter((code) => code !== data.code)\n };\n}\nasync function getBackupCodes(backupCodes, key) {\n const secret = new TextDecoder(\"utf-8\").decode(\n new TextEncoder().encode(\n await symmetricDecrypt({ key, data: backupCodes })\n )\n );\n const data = JSON.parse(secret);\n const result = z.array(z.string()).safeParse(data);\n if (result.success) {\n return result.data;\n }\n return null;\n}\nconst backupCode2fa = (options) => {\n const twoFactorTable = \"twoFactor\";\n async function storeBackupCodes(ctx, backupCodes) {\n if (options?.storeBackupCodes === \"encrypted\") {\n return await symmetricEncrypt({\n key: ctx.context.secret,\n data: backupCodes\n });\n }\n if (typeof options?.storeBackupCodes === \"object\" && \"encrypt\" in options?.storeBackupCodes) {\n return await options?.storeBackupCodes.encrypt(backupCodes);\n }\n return backupCodes;\n }\n async function decryptBackupCodes(ctx, backupCodes) {\n if (options?.storeBackupCodes === \"encrypted\") {\n return await symmetricDecrypt({\n key: ctx.context.secret,\n data: backupCodes\n });\n }\n if (typeof options?.storeBackupCodes === \"object\" && \"decrypt\" in options?.storeBackupCodes) {\n return await options?.storeBackupCodes.decrypt(backupCodes);\n }\n return backupCodes;\n }\n return {\n id: \"backup_code\",\n endpoints: {\n /**\n * ### Endpoint\n *\n * POST `/two-factor/verify-backup-code`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.verifyBackupCode`\n *\n * **client:**\n * `authClient.twoFactor.verifyBackupCode`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/2fa#api-method-two-factor-verify-backup-code)\n */\n verifyBackupCode: createAuthEndpoint(\n \"/two-factor/verify-backup-code\",\n {\n method: \"POST\",\n body: z.object({\n code: z.string().meta({\n description: `A backup code to verify. Eg: \"123456\"`\n }),\n /**\n * Disable setting the session cookie\n */\n disableSession: z.boolean().meta({\n description: \"If true, the session cookie will not be set.\"\n }).optional(),\n /**\n * if true, the device will be trusted\n * for 30 days. It'll be refreshed on\n * every sign in request within this time.\n */\n trustDevice: z.boolean().meta({\n description: \"If true, the device will be trusted for 30 days. It'll be refreshed on every sign in request within this time. Eg: true\"\n }).optional()\n }),\n metadata: {\n openapi: {\n description: \"Verify a backup code for two-factor authentication\",\n responses: {\n \"200\": {\n description: \"Backup code verified successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n user: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\",\n description: \"Unique identifier of the user\"\n },\n email: {\n type: \"string\",\n format: \"email\",\n nullable: true,\n description: \"User's email address\"\n },\n emailVerified: {\n type: \"boolean\",\n nullable: true,\n description: \"Whether the email is verified\"\n },\n name: {\n type: \"string\",\n nullable: true,\n description: \"User's name\"\n },\n image: {\n type: \"string\",\n format: \"uri\",\n nullable: true,\n description: \"User's profile image URL\"\n },\n twoFactorEnabled: {\n type: \"boolean\",\n description: \"Whether two-factor authentication is enabled for the user\"\n },\n createdAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Timestamp when the user was created\"\n },\n updatedAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Timestamp when the user was last updated\"\n }\n },\n required: [\n \"id\",\n \"twoFactorEnabled\",\n \"createdAt\",\n \"updatedAt\"\n ],\n description: \"The authenticated user object with two-factor details\"\n },\n session: {\n type: \"object\",\n properties: {\n token: {\n type: \"string\",\n description: \"Session token\"\n },\n userId: {\n type: \"string\",\n description: \"ID of the user associated with the session\"\n },\n createdAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Timestamp when the session was created\"\n },\n expiresAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Timestamp when the session expires\"\n }\n },\n required: [\n \"token\",\n \"userId\",\n \"createdAt\",\n \"expiresAt\"\n ],\n description: \"The current session object, included unless disableSession is true\"\n }\n },\n required: [\"user\", \"session\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const { session, valid } = await verifyTwoFactor(ctx);\n const user = session.user;\n const twoFactor = await ctx.context.adapter.findOne({\n model: twoFactorTable,\n where: [\n {\n field: \"userId\",\n value: user.id\n }\n ]\n });\n if (!twoFactor) {\n throw new APIError(\"BAD_REQUEST\", {\n message: TWO_FACTOR_ERROR_CODES.BACKUP_CODES_NOT_ENABLED\n });\n }\n const decryptedBackupCodes = await decryptBackupCodes(\n ctx,\n twoFactor.backupCodes\n );\n const validate = await verifyBackupCode(\n {\n backupCodes: decryptedBackupCodes,\n code: ctx.body.code\n },\n ctx.context.secret\n );\n if (!validate.status) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: TWO_FACTOR_ERROR_CODES.INVALID_BACKUP_CODE\n });\n }\n const updatedBackupCodes = await symmetricEncrypt({\n key: ctx.context.secret,\n data: JSON.stringify(validate.updated)\n });\n await ctx.context.adapter.updateMany({\n model: twoFactorTable,\n update: {\n backupCodes: updatedBackupCodes\n },\n where: [\n {\n field: \"userId\",\n value: user.id\n }\n ]\n });\n if (!ctx.body.disableSession) {\n return valid(ctx);\n }\n return ctx.json({\n token: session.session?.token,\n user: {\n id: session.user?.id,\n email: session.user.email,\n emailVerified: session.user.emailVerified,\n name: session.user.name,\n image: session.user.image,\n createdAt: session.user.createdAt,\n updatedAt: session.user.updatedAt\n }\n });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/two-factor/generate-backup-codes`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.generateBackupCodes`\n *\n * **client:**\n * `authClient.twoFactor.generateBackupCodes`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/2fa#api-method-two-factor-generate-backup-codes)\n */\n generateBackupCodes: createAuthEndpoint(\n \"/two-factor/generate-backup-codes\",\n {\n method: \"POST\",\n body: z.object({\n password: z.string().meta({\n description: \"The users password.\"\n })\n }),\n use: [sessionMiddleware],\n metadata: {\n openapi: {\n description: \"Generate new backup codes for two-factor authentication\",\n responses: {\n \"200\": {\n description: \"Backup codes generated successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n status: {\n type: \"boolean\",\n description: \"Indicates if the backup codes were generated successfully\",\n enum: [true]\n },\n backupCodes: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Array of generated backup codes in plain text\"\n }\n },\n required: [\"status\", \"backupCodes\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const user = ctx.context.session.user;\n if (!user.twoFactorEnabled) {\n throw new APIError(\"BAD_REQUEST\", {\n message: TWO_FACTOR_ERROR_CODES.TWO_FACTOR_NOT_ENABLED\n });\n }\n await ctx.context.password.checkPassword(user.id, ctx);\n const backupCodes = await generateBackupCodes(\n ctx.context.secret,\n options\n );\n const storedBackupCodes = await storeBackupCodes(\n ctx,\n backupCodes.encryptedBackupCodes\n );\n await ctx.context.adapter.update({\n model: twoFactorTable,\n update: {\n backupCodes: storedBackupCodes\n },\n where: [\n {\n field: \"userId\",\n value: ctx.context.session.user.id\n }\n ]\n });\n return ctx.json({\n status: true,\n backupCodes: backupCodes.backupCodes\n });\n }\n ),\n /**\n * ### Endpoint\n *\n * GET `/two-factor/view-backup-codes`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.viewBackupCodes`\n *\n * **client:**\n * `authClient.twoFactor.viewBackupCodes`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/2fa#api-method-two-factor-view-backup-codes)\n */\n viewBackupCodes: createAuthEndpoint(\n \"/two-factor/view-backup-codes\",\n {\n method: \"GET\",\n body: z.object({\n userId: z.coerce.string().meta({\n description: `The user ID to view all backup codes. Eg: \"user-id\"`\n })\n }),\n metadata: {\n SERVER_ONLY: true\n }\n },\n async (ctx) => {\n const twoFactor = await ctx.context.adapter.findOne({\n model: twoFactorTable,\n where: [\n {\n field: \"userId\",\n value: ctx.body.userId\n }\n ]\n });\n if (!twoFactor) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Backup codes aren't enabled\"\n });\n }\n const backupCodes = await getBackupCodes(\n twoFactor.backupCodes,\n ctx.context.secret\n );\n if (!backupCodes) {\n throw new APIError(\"BAD_REQUEST\", {\n message: TWO_FACTOR_ERROR_CODES.BACKUP_CODES_NOT_ENABLED\n });\n }\n const decryptedBackupCodes = await decryptBackupCodes(\n ctx,\n twoFactor.backupCodes\n );\n return ctx.json({\n status: true,\n backupCodes: decryptedBackupCodes\n });\n }\n )\n }\n };\n};\n\nconst defaultKeyHasher = async (token) => {\n const hash = await createHash(\"SHA-256\").digest(\n new TextEncoder().encode(token)\n );\n const hashed = base64Url.encode(new Uint8Array(hash), {\n padding: false\n });\n return hashed;\n};\n\nconst otp2fa = (options) => {\n const opts = {\n storeOTP: \"plain\",\n digits: 6,\n ...options,\n period: (options?.period || 3) * 60 * 1e3\n };\n async function storeOTP(ctx, otp) {\n if (opts.storeOTP === \"hashed\") {\n return await defaultKeyHasher(otp);\n }\n if (typeof opts.storeOTP === \"object\" && \"hash\" in opts.storeOTP) {\n return await opts.storeOTP.hash(otp);\n }\n if (typeof opts.storeOTP === \"object\" && \"encrypt\" in opts.storeOTP) {\n return await opts.storeOTP.encrypt(otp);\n }\n if (opts.storeOTP === \"encrypted\") {\n return await symmetricEncrypt({\n key: ctx.context.secret,\n data: otp\n });\n }\n return otp;\n }\n async function decryptOTP(ctx, otp) {\n if (opts.storeOTP === \"hashed\") {\n return await defaultKeyHasher(otp);\n }\n if (opts.storeOTP === \"encrypted\") {\n return await symmetricDecrypt({\n key: ctx.context.secret,\n data: otp\n });\n }\n if (typeof opts.storeOTP === \"object\" && \"encrypt\" in opts.storeOTP) {\n return await opts.storeOTP.decrypt(otp);\n }\n if (typeof opts.storeOTP === \"object\" && \"hash\" in opts.storeOTP) {\n return await opts.storeOTP.hash(otp);\n }\n return otp;\n }\n const send2FaOTP = createAuthEndpoint(\n \"/two-factor/send-otp\",\n {\n method: \"POST\",\n body: z.object({\n /**\n * if true, the device will be trusted\n * for 30 days. It'll be refreshed on\n * every sign in request within this time.\n */\n trustDevice: z.boolean().optional().meta({\n description: \"If true, the device will be trusted for 30 days. It'll be refreshed on every sign in request within this time. Eg: true\"\n })\n }).optional(),\n metadata: {\n openapi: {\n summary: \"Send two factor OTP\",\n description: \"Send two factor OTP to the user\",\n responses: {\n 200: {\n description: \"Successful response\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n status: {\n type: \"boolean\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n if (!options || !options.sendOTP) {\n ctx.context.logger.error(\n \"send otp isn't configured. Please configure the send otp function on otp options.\"\n );\n throw new APIError(\"BAD_REQUEST\", {\n message: \"otp isn't configured\"\n });\n }\n const { session, key } = await verifyTwoFactor(ctx);\n if (!session.user.twoFactorEnabled) {\n throw new APIError(\"BAD_REQUEST\", {\n message: TWO_FACTOR_ERROR_CODES.OTP_NOT_ENABLED\n });\n }\n const code = generateRandomString(opts.digits, \"0-9\");\n const hashedCode = await storeOTP(ctx, code);\n await ctx.context.internalAdapter.createVerificationValue(\n {\n value: `${hashedCode}:0`,\n identifier: `2fa-otp-${key}`,\n expiresAt: new Date(Date.now() + opts.period)\n },\n ctx\n );\n await options.sendOTP(\n { user: session.user, otp: code },\n ctx.request\n );\n return ctx.json({ status: true });\n }\n );\n const verifyOTP = createAuthEndpoint(\n \"/two-factor/verify-otp\",\n {\n method: \"POST\",\n body: z.object({\n code: z.string().meta({\n description: 'The otp code to verify. Eg: \"012345\"'\n }),\n /**\n * if true, the device will be trusted\n * for 30 days. It'll be refreshed on\n * every sign in request within this time.\n */\n trustDevice: z.boolean().optional().meta({\n description: \"If true, the device will be trusted for 30 days. It'll be refreshed on every sign in request within this time. Eg: true\"\n })\n }),\n metadata: {\n openapi: {\n summary: \"Verify two factor OTP\",\n description: \"Verify two factor OTP\",\n responses: {\n \"200\": {\n description: \"Two-factor OTP verified successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n token: {\n type: \"string\",\n description: \"Session token for the authenticated session\"\n },\n user: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\",\n description: \"Unique identifier of the user\"\n },\n email: {\n type: \"string\",\n format: \"email\",\n nullable: true,\n description: \"User's email address\"\n },\n emailVerified: {\n type: \"boolean\",\n nullable: true,\n description: \"Whether the email is verified\"\n },\n name: {\n type: \"string\",\n nullable: true,\n description: \"User's name\"\n },\n image: {\n type: \"string\",\n format: \"uri\",\n nullable: true,\n description: \"User's profile image URL\"\n },\n createdAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Timestamp when the user was created\"\n },\n updatedAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Timestamp when the user was last updated\"\n }\n },\n required: [\"id\", \"createdAt\", \"updatedAt\"],\n description: \"The authenticated user object\"\n }\n },\n required: [\"token\", \"user\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const { session, key, valid, invalid } = await verifyTwoFactor(ctx);\n const toCheckOtp = await ctx.context.internalAdapter.findVerificationValue(\n `2fa-otp-${key}`\n );\n const [otp, counter] = toCheckOtp?.value?.split(\":\") ?? [];\n const decryptedOtp = await decryptOTP(ctx, otp);\n if (!toCheckOtp || toCheckOtp.expiresAt < /* @__PURE__ */ new Date()) {\n if (toCheckOtp) {\n await ctx.context.internalAdapter.deleteVerificationValue(\n toCheckOtp.id\n );\n }\n throw new APIError(\"BAD_REQUEST\", {\n message: TWO_FACTOR_ERROR_CODES.OTP_HAS_EXPIRED\n });\n }\n const allowedAttempts = options?.allowedAttempts || 5;\n if (parseInt(counter) >= allowedAttempts) {\n await ctx.context.internalAdapter.deleteVerificationValue(\n toCheckOtp.id\n );\n throw new APIError(\"BAD_REQUEST\", {\n message: TWO_FACTOR_ERROR_CODES.TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE\n });\n }\n if (decryptedOtp === ctx.body.code) {\n if (!session.user.twoFactorEnabled) {\n if (!session.session) {\n throw new APIError(\"BAD_REQUEST\", {\n message: BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION\n });\n }\n const updatedUser = await ctx.context.internalAdapter.updateUser(\n session.user.id,\n {\n twoFactorEnabled: true\n }\n );\n const newSession = await ctx.context.internalAdapter.createSession(\n session.user.id,\n ctx,\n false,\n session.session\n );\n await ctx.context.internalAdapter.deleteSession(\n session.session.token\n );\n await setSessionCookie(ctx, {\n session: newSession,\n user: updatedUser\n });\n return ctx.json({\n token: newSession.token,\n user: {\n id: updatedUser.id,\n email: updatedUser.email,\n emailVerified: updatedUser.emailVerified,\n name: updatedUser.name,\n image: updatedUser.image,\n createdAt: updatedUser.createdAt,\n updatedAt: updatedUser.updatedAt\n }\n });\n }\n return valid(ctx);\n } else {\n await ctx.context.internalAdapter.updateVerificationValue(\n toCheckOtp.id,\n {\n value: `${otp}:${(parseInt(counter, 10) || 0) + 1}`\n }\n );\n return invalid(\"INVALID_CODE\");\n }\n }\n );\n return {\n id: \"otp\",\n endpoints: {\n /**\n * ### Endpoint\n *\n * POST `/two-factor/send-otp`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.send2FaOTP`\n *\n * **client:**\n * `authClient.twoFactor.sendOtp`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/2fa#api-method-two-factor-send-otp)\n */\n sendTwoFactorOTP: send2FaOTP,\n /**\n * ### Endpoint\n *\n * POST `/two-factor/verify-otp`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.verifyOTP`\n *\n * **client:**\n * `authClient.twoFactor.verifyOtp`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/2fa#api-method-two-factor-verify-otp)\n */\n verifyTwoFactorOTP: verifyOTP\n }\n };\n};\n\nconst totp2fa = (options) => {\n const opts = {\n ...options,\n digits: options?.digits || 6,\n period: options?.period || 30\n };\n const twoFactorTable = \"twoFactor\";\n const generateTOTP = createAuthEndpoint(\n \"/totp/generate\",\n {\n method: \"POST\",\n body: z.object({\n secret: z.string().meta({\n description: \"The secret to generate the TOTP code\"\n })\n }),\n metadata: {\n openapi: {\n summary: \"Generate TOTP code\",\n description: \"Use this endpoint to generate a TOTP code\",\n responses: {\n 200: {\n description: \"Successful response\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n code: {\n type: \"string\"\n }\n }\n }\n }\n }\n }\n }\n },\n SERVER_ONLY: true\n }\n },\n async (ctx) => {\n if (options?.disable) {\n ctx.context.logger.error(\n \"totp isn't configured. please pass totp option on two factor plugin to enable totp\"\n );\n throw new APIError(\"BAD_REQUEST\", {\n message: \"totp isn't configured\"\n });\n }\n const code = await createOTP(ctx.body.secret, {\n period: opts.period,\n digits: opts.digits\n }).totp();\n return { code };\n }\n );\n const getTOTPURI = createAuthEndpoint(\n \"/two-factor/get-totp-uri\",\n {\n method: \"POST\",\n use: [sessionMiddleware],\n body: z.object({\n password: z.string().meta({\n description: \"User password\"\n })\n }),\n metadata: {\n openapi: {\n summary: \"Get TOTP URI\",\n description: \"Use this endpoint to get the TOTP URI\",\n responses: {\n 200: {\n description: \"Successful response\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n totpURI: {\n type: \"string\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n if (options?.disable) {\n ctx.context.logger.error(\n \"totp isn't configured. please pass totp option on two factor plugin to enable totp\"\n );\n throw new APIError(\"BAD_REQUEST\", {\n message: \"totp isn't configured\"\n });\n }\n const user = ctx.context.session.user;\n const twoFactor = await ctx.context.adapter.findOne({\n model: twoFactorTable,\n where: [\n {\n field: \"userId\",\n value: user.id\n }\n ]\n });\n if (!twoFactor) {\n throw new APIError(\"BAD_REQUEST\", {\n message: TWO_FACTOR_ERROR_CODES.TOTP_NOT_ENABLED\n });\n }\n const secret = await symmetricDecrypt({\n key: ctx.context.secret,\n data: twoFactor.secret\n });\n await ctx.context.password.checkPassword(user.id, ctx);\n const totpURI = createOTP(secret, {\n digits: opts.digits,\n period: opts.period\n }).url(options?.issuer || ctx.context.appName, user.email);\n return {\n totpURI\n };\n }\n );\n const verifyTOTP = createAuthEndpoint(\n \"/two-factor/verify-totp\",\n {\n method: \"POST\",\n body: z.object({\n code: z.string().meta({\n description: 'The otp code to verify. Eg: \"012345\"'\n }),\n /**\n * if true, the device will be trusted\n * for 30 days. It'll be refreshed on\n * every sign in request within this time.\n */\n trustDevice: z.boolean().meta({\n description: \"If true, the device will be trusted for 30 days. It'll be refreshed on every sign in request within this time. Eg: true\"\n }).optional()\n }),\n metadata: {\n openapi: {\n summary: \"Verify two factor TOTP\",\n description: \"Verify two factor TOTP\",\n responses: {\n 200: {\n description: \"Successful response\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n status: {\n type: \"boolean\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n if (options?.disable) {\n ctx.context.logger.error(\n \"totp isn't configured. please pass totp option on two factor plugin to enable totp\"\n );\n throw new APIError(\"BAD_REQUEST\", {\n message: \"totp isn't configured\"\n });\n }\n const { session, valid, invalid } = await verifyTwoFactor(ctx);\n const user = session.user;\n const twoFactor = await ctx.context.adapter.findOne({\n model: twoFactorTable,\n where: [\n {\n field: \"userId\",\n value: user.id\n }\n ]\n });\n if (!twoFactor) {\n throw new APIError(\"BAD_REQUEST\", {\n message: TWO_FACTOR_ERROR_CODES.TOTP_NOT_ENABLED\n });\n }\n const decrypted = await symmetricDecrypt({\n key: ctx.context.secret,\n data: twoFactor.secret\n });\n const status = await createOTP(decrypted, {\n period: opts.period,\n digits: opts.digits\n }).verify(ctx.body.code);\n if (!status) {\n return invalid(\"INVALID_CODE\");\n }\n if (!user.twoFactorEnabled) {\n if (!session.session) {\n throw new APIError(\"BAD_REQUEST\", {\n message: BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION\n });\n }\n const updatedUser = await ctx.context.internalAdapter.updateUser(\n user.id,\n {\n twoFactorEnabled: true\n },\n ctx\n );\n const newSession = await ctx.context.internalAdapter.createSession(user.id, ctx, false, session.session).catch((e) => {\n throw e;\n });\n await ctx.context.internalAdapter.deleteSession(session.session.token);\n await setSessionCookie(ctx, {\n session: newSession,\n user: updatedUser\n });\n }\n return valid(ctx);\n }\n );\n return {\n id: \"totp\",\n endpoints: {\n /**\n * ### Endpoint\n *\n * POST `/totp/generate`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.generateTOTP`\n *\n * **client:**\n * `authClient.totp.generate`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/totp#api-method-totp-generate)\n */\n generateTOTP,\n /**\n * ### Endpoint\n *\n * POST `/two-factor/get-totp-uri`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.getTOTPURI`\n *\n * **client:**\n * `authClient.twoFactor.getTotpUri`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/two-factor#api-method-two-factor-get-totp-uri)\n */\n getTOTPURI,\n verifyTOTP\n }\n };\n};\n\nconst schema = {\n user: {\n fields: {\n twoFactorEnabled: {\n type: \"boolean\",\n required: false,\n defaultValue: false,\n input: false\n }\n }\n },\n twoFactor: {\n fields: {\n secret: {\n type: \"string\",\n required: true,\n returned: false\n },\n backupCodes: {\n type: \"string\",\n required: true,\n returned: false\n },\n userId: {\n type: \"string\",\n required: true,\n returned: false,\n references: {\n model: \"user\",\n field: \"id\"\n }\n }\n }\n }\n};\n\nconst twoFactor = (options) => {\n const opts = {\n twoFactorTable: \"twoFactor\"\n };\n const totp = totp2fa(options?.totpOptions);\n const backupCode = backupCode2fa(options?.backupCodeOptions);\n const otp = otp2fa(options?.otpOptions);\n return {\n id: \"two-factor\",\n endpoints: {\n ...totp.endpoints,\n ...otp.endpoints,\n ...backupCode.endpoints,\n /**\n * ### Endpoint\n *\n * POST `/two-factor/enable`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.enableTwoFactor`\n *\n * **client:**\n * `authClient.twoFactor.enable`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/2fa#api-method-two-factor-enable)\n */\n enableTwoFactor: createAuthEndpoint(\n \"/two-factor/enable\",\n {\n method: \"POST\",\n body: z.object({\n password: z.string().meta({\n description: \"User password\"\n }),\n issuer: z.string().meta({\n description: \"Custom issuer for the TOTP URI\"\n }).optional()\n }),\n use: [sessionMiddleware],\n metadata: {\n openapi: {\n summary: \"Enable two factor authentication\",\n description: \"Use this endpoint to enable two factor authentication. This will generate a TOTP URI and backup codes. Once the user verifies the TOTP URI, the two factor authentication will be enabled.\",\n responses: {\n 200: {\n description: \"Successful response\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n totpURI: {\n type: \"string\",\n description: \"TOTP URI\"\n },\n backupCodes: {\n type: \"array\",\n items: {\n type: \"string\"\n },\n description: \"Backup codes\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const user = ctx.context.session.user;\n const { password, issuer } = ctx.body;\n const isPasswordValid = await validatePassword(ctx, {\n password,\n userId: user.id\n });\n if (!isPasswordValid) {\n throw new APIError(\"BAD_REQUEST\", {\n message: BASE_ERROR_CODES.INVALID_PASSWORD\n });\n }\n const secret = generateRandomString(32);\n const encryptedSecret = await symmetricEncrypt({\n key: ctx.context.secret,\n data: secret\n });\n const backupCodes = await generateBackupCodes(\n ctx.context.secret,\n options?.backupCodeOptions\n );\n if (options?.skipVerificationOnEnable) {\n const updatedUser = await ctx.context.internalAdapter.updateUser(\n user.id,\n {\n twoFactorEnabled: true\n },\n ctx\n );\n const newSession = await ctx.context.internalAdapter.createSession(\n updatedUser.id,\n ctx,\n false,\n ctx.context.session.session\n );\n await setSessionCookie(ctx, {\n session: newSession,\n user: updatedUser\n });\n await ctx.context.internalAdapter.deleteSession(\n ctx.context.session.session.token\n );\n }\n await ctx.context.adapter.deleteMany({\n model: opts.twoFactorTable,\n where: [\n {\n field: \"userId\",\n value: user.id\n }\n ]\n });\n await ctx.context.adapter.create({\n model: opts.twoFactorTable,\n data: {\n secret: encryptedSecret,\n backupCodes: backupCodes.encryptedBackupCodes,\n userId: user.id\n }\n });\n const totpURI = createOTP(secret, {\n digits: options?.totpOptions?.digits || 6,\n period: options?.totpOptions?.period\n }).url(issuer || options?.issuer || ctx.context.appName, user.email);\n return ctx.json({ totpURI, backupCodes: backupCodes.backupCodes });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/two-factor/disable`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.disableTwoFactor`\n *\n * **client:**\n * `authClient.twoFactor.disable`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/2fa#api-method-two-factor-disable)\n */\n disableTwoFactor: createAuthEndpoint(\n \"/two-factor/disable\",\n {\n method: \"POST\",\n body: z.object({\n password: z.string().meta({\n description: \"User password\"\n })\n }),\n use: [sessionMiddleware],\n metadata: {\n openapi: {\n summary: \"Disable two factor authentication\",\n description: \"Use this endpoint to disable two factor authentication.\",\n responses: {\n 200: {\n description: \"Successful response\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n status: {\n type: \"boolean\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const user = ctx.context.session.user;\n const { password } = ctx.body;\n const isPasswordValid = await validatePassword(ctx, {\n password,\n userId: user.id\n });\n if (!isPasswordValid) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Invalid password\"\n });\n }\n const updatedUser = await ctx.context.internalAdapter.updateUser(\n user.id,\n {\n twoFactorEnabled: false\n },\n ctx\n );\n await ctx.context.adapter.delete({\n model: opts.twoFactorTable,\n where: [\n {\n field: \"userId\",\n value: updatedUser.id\n }\n ]\n });\n const newSession = await ctx.context.internalAdapter.createSession(\n updatedUser.id,\n ctx,\n false,\n ctx.context.session.session\n );\n await setSessionCookie(ctx, {\n session: newSession,\n user: updatedUser\n });\n await ctx.context.internalAdapter.deleteSession(\n ctx.context.session.session.token\n );\n return ctx.json({ status: true });\n }\n )\n },\n options,\n hooks: {\n after: [\n {\n matcher(context) {\n return context.path === \"/sign-in/email\" || context.path === \"/sign-in/username\" || context.path === \"/sign-in/phone-number\";\n },\n handler: createAuthMiddleware(async (ctx) => {\n const data = ctx.context.newSession;\n if (!data) {\n return;\n }\n if (!data?.user.twoFactorEnabled) {\n return;\n }\n const trustDeviceCookieName = ctx.context.createAuthCookie(\n TRUST_DEVICE_COOKIE_NAME\n );\n const trustDeviceCookie = await ctx.getSignedCookie(\n trustDeviceCookieName.name,\n ctx.context.secret\n );\n if (trustDeviceCookie) {\n const [token, sessionToken] = trustDeviceCookie.split(\"!\");\n const expectedToken = await createHMAC(\n \"SHA-256\",\n \"base64urlnopad\"\n ).sign(ctx.context.secret, `${data.user.id}!${sessionToken}`);\n if (token === expectedToken) {\n const newToken = await createHMAC(\n \"SHA-256\",\n \"base64urlnopad\"\n ).sign(ctx.context.secret, `${data.user.id}!${sessionToken}`);\n await ctx.setSignedCookie(\n trustDeviceCookieName.name,\n `${newToken}!${data.session.token}`,\n ctx.context.secret,\n trustDeviceCookieName.attributes\n );\n return;\n }\n }\n deleteSessionCookie(ctx, true);\n await ctx.context.internalAdapter.deleteSession(data.session.token);\n const maxAge = (options?.otpOptions?.period ?? 3) * 60;\n const twoFactorCookie = ctx.context.createAuthCookie(\n TWO_FACTOR_COOKIE_NAME,\n {\n maxAge\n }\n );\n const identifier = `2fa-${generateRandomString(20)}`;\n await ctx.context.internalAdapter.createVerificationValue(\n {\n value: data.user.id,\n identifier,\n expiresAt: new Date(Date.now() + maxAge * 1e3)\n },\n ctx\n );\n await ctx.setSignedCookie(\n twoFactorCookie.name,\n identifier,\n ctx.context.secret,\n twoFactorCookie.attributes\n );\n return ctx.json({\n twoFactorRedirect: true\n });\n })\n }\n ]\n },\n schema: mergeSchema(schema, options?.schema),\n rateLimit: [\n {\n pathMatcher(path) {\n return path.startsWith(\"/two-factor/\");\n },\n window: 10,\n max: 3\n }\n ],\n $ERROR_CODES: TWO_FACTOR_ERROR_CODES\n };\n};\n\nexport { TWO_FACTOR_ERROR_CODES, twoFactor };\n","import * as z from 'zod/v4';\nimport { i as createAuthMiddleware, j as createAuthEndpoint, u as sendVerificationEmailFn, B as BASE_ERROR_CODES } from '../../shared/better-auth.D4HhkCZJ.mjs';\nimport { APIError } from 'better-call';\nimport { setSessionCookie } from '../../cookies/index.mjs';\nimport { m as mergeSchema } from '../../shared/better-auth.n2KFGwjY.mjs';\nimport '../../shared/better-auth.8zoxzg-F.mjs';\nimport '../../shared/better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport '../../shared/better-auth.CW6D9eSx.mjs';\nimport '@better-auth/utils/hash';\nimport '@better-auth/utils/base64';\nimport '../../crypto/index.mjs';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport 'jose';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport '../../shared/better-auth.B4Qoxdgc.mjs';\nimport '@better-auth/utils/random';\nimport '@better-fetch/fetch';\nimport '../../shared/better-auth.VTXNLFMT.mjs';\nimport '../../shared/better-auth.DdzSJf-n.mjs';\nimport '../../shared/better-auth.tB5eU6EY.mjs';\nimport '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport 'jose/errors';\n\nconst getSchema = (normalizer) => {\n return {\n user: {\n fields: {\n username: {\n type: \"string\",\n required: false,\n sortable: true,\n unique: true,\n returned: true\n },\n displayUsername: {\n type: \"string\",\n required: false,\n transform: {\n input(value) {\n return value == null ? value : normalizer(value);\n }\n }\n }\n }\n }\n };\n};\n\nconst USERNAME_ERROR_CODES = {\n INVALID_USERNAME_OR_PASSWORD: \"invalid username or password\",\n EMAIL_NOT_VERIFIED: \"email not verified\",\n UNEXPECTED_ERROR: \"unexpected error\",\n USERNAME_IS_ALREADY_TAKEN: \"username is already taken. please try another.\",\n USERNAME_TOO_SHORT: \"username is too short\",\n USERNAME_TOO_LONG: \"username is too long\",\n INVALID_USERNAME: \"username is invalid\"\n};\n\nfunction defaultUsernameValidator(username2) {\n return /^[a-zA-Z0-9_.]+$/.test(username2);\n}\nconst username = (options) => {\n const normalizer = (username2) => {\n if (options?.usernameNormalization === false) {\n return username2;\n }\n if (options?.usernameNormalization) {\n return options.usernameNormalization(username2);\n }\n return username2.toLowerCase();\n };\n return {\n id: \"username\",\n endpoints: {\n signInUsername: createAuthEndpoint(\n \"/sign-in/username\",\n {\n method: \"POST\",\n body: z.object({\n username: z.string().meta({ description: \"The username of the user\" }),\n password: z.string().meta({ description: \"The password of the user\" }),\n rememberMe: z.boolean().meta({\n description: \"Remember the user session\"\n }).optional(),\n callbackURL: z.string().meta({\n description: \"The URL to redirect to after email verification\"\n }).optional()\n }),\n metadata: {\n openapi: {\n summary: \"Sign in with username\",\n description: \"Sign in with username\",\n responses: {\n 200: {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n token: {\n type: \"string\",\n description: \"Session token for the authenticated session\"\n },\n user: {\n $ref: \"#/components/schemas/User\"\n }\n },\n required: [\"token\", \"user\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n if (!ctx.body.username || !ctx.body.password) {\n ctx.context.logger.error(\"Username or password not found\");\n throw new APIError(\"UNAUTHORIZED\", {\n message: USERNAME_ERROR_CODES.INVALID_USERNAME_OR_PASSWORD\n });\n }\n const minUsernameLength = options?.minUsernameLength || 3;\n const maxUsernameLength = options?.maxUsernameLength || 30;\n if (ctx.body.username.length < minUsernameLength) {\n ctx.context.logger.error(\"Username too short\", {\n username: ctx.body.username\n });\n throw new APIError(\"UNPROCESSABLE_ENTITY\", {\n message: USERNAME_ERROR_CODES.USERNAME_TOO_SHORT\n });\n }\n if (ctx.body.username.length > maxUsernameLength) {\n ctx.context.logger.error(\"Username too long\", {\n username: ctx.body.username\n });\n throw new APIError(\"UNPROCESSABLE_ENTITY\", {\n message: USERNAME_ERROR_CODES.USERNAME_TOO_LONG\n });\n }\n const validator = options?.usernameValidator || defaultUsernameValidator;\n if (!validator(ctx.body.username)) {\n throw new APIError(\"UNPROCESSABLE_ENTITY\", {\n message: USERNAME_ERROR_CODES.INVALID_USERNAME\n });\n }\n const user = await ctx.context.adapter.findOne({\n model: \"user\",\n where: [\n {\n field: \"username\",\n value: normalizer(ctx.body.username)\n }\n ]\n });\n if (!user) {\n await ctx.context.password.hash(ctx.body.password);\n ctx.context.logger.error(\"User not found\", {\n username: ctx.body.username\n });\n throw new APIError(\"UNAUTHORIZED\", {\n message: USERNAME_ERROR_CODES.INVALID_USERNAME_OR_PASSWORD\n });\n }\n if (!user.emailVerified && ctx.context.options.emailAndPassword?.requireEmailVerification) {\n await sendVerificationEmailFn(ctx, user);\n throw new APIError(\"FORBIDDEN\", {\n message: USERNAME_ERROR_CODES.EMAIL_NOT_VERIFIED\n });\n }\n const account = await ctx.context.adapter.findOne({\n model: \"account\",\n where: [\n {\n field: \"userId\",\n value: user.id\n },\n {\n field: \"providerId\",\n value: \"credential\"\n }\n ]\n });\n if (!account) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: USERNAME_ERROR_CODES.INVALID_USERNAME_OR_PASSWORD\n });\n }\n const currentPassword = account?.password;\n if (!currentPassword) {\n ctx.context.logger.error(\"Password not found\", {\n username: ctx.body.username\n });\n throw new APIError(\"UNAUTHORIZED\", {\n message: USERNAME_ERROR_CODES.INVALID_USERNAME_OR_PASSWORD\n });\n }\n const validPassword = await ctx.context.password.verify({\n hash: currentPassword,\n password: ctx.body.password\n });\n if (!validPassword) {\n ctx.context.logger.error(\"Invalid password\");\n throw new APIError(\"UNAUTHORIZED\", {\n message: USERNAME_ERROR_CODES.INVALID_USERNAME_OR_PASSWORD\n });\n }\n const session = await ctx.context.internalAdapter.createSession(\n user.id,\n ctx,\n ctx.body.rememberMe === false\n );\n if (!session) {\n return ctx.json(null, {\n status: 500,\n body: {\n message: BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION\n }\n });\n }\n await setSessionCookie(\n ctx,\n { session, user },\n ctx.body.rememberMe === false\n );\n return ctx.json({\n token: session.token,\n user: {\n id: user.id,\n email: user.email,\n emailVerified: user.emailVerified,\n username: user.username,\n name: user.name,\n image: user.image,\n createdAt: user.createdAt,\n updatedAt: user.updatedAt\n }\n });\n }\n ),\n isUsernameAvailable: createAuthEndpoint(\n \"/is-username-available\",\n {\n method: \"POST\",\n body: z.object({\n username: z.string().meta({\n description: \"The username to check\"\n })\n })\n },\n async (ctx) => {\n const username2 = ctx.body.username;\n if (!username2) {\n throw new APIError(\"UNPROCESSABLE_ENTITY\", {\n message: USERNAME_ERROR_CODES.INVALID_USERNAME\n });\n }\n const user = await ctx.context.adapter.findOne({\n model: \"user\",\n where: [\n {\n field: \"username\",\n value: username2.toLowerCase()\n }\n ]\n });\n if (user) {\n return ctx.json({\n available: false\n });\n }\n return ctx.json({\n available: true\n });\n }\n )\n },\n schema: mergeSchema(getSchema(normalizer), options?.schema),\n hooks: {\n before: [\n {\n matcher(context) {\n return context.path === \"/sign-up/email\" || context.path === \"/update-user\";\n },\n handler: createAuthMiddleware(async (ctx) => {\n const username2 = ctx.body.username;\n if (username2 !== void 0 && typeof username2 === \"string\") {\n const minUsernameLength = options?.minUsernameLength || 3;\n const maxUsernameLength = options?.maxUsernameLength || 30;\n if (username2.length < minUsernameLength) {\n throw new APIError(\"UNPROCESSABLE_ENTITY\", {\n message: USERNAME_ERROR_CODES.USERNAME_TOO_SHORT\n });\n }\n if (username2.length > maxUsernameLength) {\n throw new APIError(\"UNPROCESSABLE_ENTITY\", {\n message: USERNAME_ERROR_CODES.USERNAME_TOO_LONG\n });\n }\n const validator = options?.usernameValidator || defaultUsernameValidator;\n const valid = await validator(username2);\n if (!valid) {\n throw new APIError(\"UNPROCESSABLE_ENTITY\", {\n message: USERNAME_ERROR_CODES.INVALID_USERNAME\n });\n }\n const user = await ctx.context.adapter.findOne({\n model: \"user\",\n where: [\n {\n field: \"username\",\n value: normalizer(username2)\n }\n ]\n });\n const blockChangeSignUp = ctx.path === \"/sign-up/email\" && user;\n const blockChangeUpdateUser = ctx.path === \"/update-user\" && user && ctx.context.session && user.id !== ctx.context.session.session.userId;\n if (blockChangeSignUp || blockChangeUpdateUser) {\n throw new APIError(\"UNPROCESSABLE_ENTITY\", {\n message: USERNAME_ERROR_CODES.USERNAME_IS_ALREADY_TAKEN\n });\n }\n }\n })\n },\n {\n matcher(context) {\n return context.path === \"/sign-up/email\" || context.path === \"/update-user\";\n },\n handler: createAuthMiddleware(async (ctx) => {\n if (ctx.body.username) {\n ctx.body.displayUsername ||= ctx.body.username;\n ctx.body.username = normalizer(ctx.body.username);\n }\n })\n }\n ]\n },\n $ERROR_CODES: USERNAME_ERROR_CODES\n };\n};\n\nexport { USERNAME_ERROR_CODES, username };\n","import { serializeSignedCookie } from 'better-call';\nimport '../../shared/better-auth.8zoxzg-F.mjs';\nimport '@better-auth/utils/base64';\nimport { createHMAC } from '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport { parseSetCookieHeader } from '../../cookies/index.mjs';\nimport { i as createAuthMiddleware } from '../../shared/better-auth.D4HhkCZJ.mjs';\nimport 'zod/v4';\nimport '../../shared/better-auth.n2KFGwjY.mjs';\nimport '../../shared/better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport '../../shared/better-auth.DdzSJf-n.mjs';\nimport '../../shared/better-auth.CW6D9eSx.mjs';\nimport '../../shared/better-auth.tB5eU6EY.mjs';\nimport '../../shared/better-auth.VTXNLFMT.mjs';\nimport '@better-auth/utils/hash';\nimport '../../crypto/index.mjs';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport 'jose';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport '../../shared/better-auth.B4Qoxdgc.mjs';\nimport '@better-auth/utils/random';\nimport '@better-fetch/fetch';\nimport 'jose/errors';\n\nconst bearer = (options) => {\n return {\n id: \"bearer\",\n hooks: {\n before: [\n {\n matcher(context) {\n return Boolean(\n context.request?.headers.get(\"authorization\") || context.headers?.get(\"authorization\")\n );\n },\n handler: createAuthMiddleware(async (c) => {\n const token = c.request?.headers.get(\"authorization\")?.replace(\"Bearer \", \"\") || c.headers?.get(\"Authorization\")?.replace(\"Bearer \", \"\");\n if (!token) {\n return;\n }\n let signedToken = \"\";\n if (token.includes(\".\")) {\n signedToken = token.replace(\"=\", \"\");\n } else {\n if (options?.requireSignature) {\n return;\n }\n signedToken = (await serializeSignedCookie(\"\", token, c.context.secret)).replace(\"=\", \"\");\n }\n try {\n const decodedToken = decodeURIComponent(signedToken);\n const isValid = await createHMAC(\n \"SHA-256\",\n \"base64urlnopad\"\n ).verify(\n c.context.secret,\n decodedToken.split(\".\")[0],\n decodedToken.split(\".\")[1]\n );\n if (!isValid) {\n return;\n }\n } catch (e) {\n return;\n }\n const existingHeaders = c.request?.headers || c.headers;\n const headers = new Headers({\n ...Object.fromEntries(existingHeaders?.entries())\n });\n headers.append(\n \"cookie\",\n `${c.context.authCookies.sessionToken.name}=${signedToken}`\n );\n return {\n context: {\n headers\n }\n };\n })\n }\n ],\n after: [\n {\n matcher(context) {\n return true;\n },\n handler: createAuthMiddleware(async (ctx) => {\n const setCookie = ctx.context.responseHeaders?.get(\"set-cookie\");\n if (!setCookie) {\n return;\n }\n const parsedCookies = parseSetCookieHeader(setCookie);\n const cookieName = ctx.context.authCookies.sessionToken.name;\n const sessionCookie = parsedCookies.get(cookieName);\n if (!sessionCookie || !sessionCookie.value || sessionCookie[\"max-age\"] === 0) {\n return;\n }\n const token = sessionCookie.value;\n const exposedHeaders = ctx.context.responseHeaders?.get(\n \"access-control-expose-headers\"\n ) || \"\";\n const headersSet = new Set(\n exposedHeaders.split(\",\").map((header) => header.trim()).filter(Boolean)\n );\n headersSet.add(\"set-auth-token\");\n ctx.setHeader(\"set-auth-token\", token);\n ctx.setHeader(\n \"Access-Control-Expose-Headers\",\n Array.from(headersSet).join(\", \")\n );\n })\n }\n ]\n }\n };\n};\n\nexport { bearer };\n","import * as z from 'zod/v4';\nimport { j as createAuthEndpoint, o as originCheck, B as BASE_ERROR_CODES } from '../../shared/better-auth.D4HhkCZJ.mjs';\nimport { APIError } from 'better-call';\nimport { setSessionCookie } from '../../cookies/index.mjs';\nimport { createHash } from '@better-auth/utils/hash';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport { base64Url } from '@better-auth/utils/base64';\nimport 'jose';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport { g as generateRandomString } from '../../shared/better-auth.B4Qoxdgc.mjs';\nimport '../../shared/better-auth.n2KFGwjY.mjs';\nimport '../../shared/better-auth.8zoxzg-F.mjs';\nimport '../../shared/better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport '../../shared/better-auth.CW6D9eSx.mjs';\nimport '../../crypto/index.mjs';\nimport '@better-fetch/fetch';\nimport '../../shared/better-auth.VTXNLFMT.mjs';\nimport '../../shared/better-auth.DdzSJf-n.mjs';\nimport '../../shared/better-auth.tB5eU6EY.mjs';\nimport '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport 'jose/errors';\nimport '@better-auth/utils/random';\n\nconst defaultKeyHasher = async (otp) => {\n const hash = await createHash(\"SHA-256\").digest(\n new TextEncoder().encode(otp)\n );\n const hashed = base64Url.encode(new Uint8Array(hash), {\n padding: false\n });\n return hashed;\n};\n\nconst magicLink = (options) => {\n const opts = {\n storeToken: \"plain\",\n ...options\n };\n async function storeToken(ctx, token) {\n if (opts.storeToken === \"hashed\") {\n return await defaultKeyHasher(token);\n }\n if (typeof opts.storeToken === \"object\" && \"type\" in opts.storeToken && opts.storeToken.type === \"custom-hasher\") {\n return await opts.storeToken.hash(token);\n }\n return token;\n }\n return {\n id: \"magic-link\",\n endpoints: {\n /**\n * ### Endpoint\n *\n * POST `/sign-in/magic-link`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.signInMagicLink`\n *\n * **client:**\n * `authClient.signIn.magicLink`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/sign-in#api-method-sign-in-magic-link)\n */\n signInMagicLink: createAuthEndpoint(\n \"/sign-in/magic-link\",\n {\n method: \"POST\",\n requireHeaders: true,\n body: z.object({\n email: z.string().meta({\n description: \"Email address to send the magic link\"\n }).email(),\n name: z.string().meta({\n description: 'User display name. Only used if the user is registering for the first time. Eg: \"my-name\"'\n }).optional(),\n callbackURL: z.string().meta({\n description: \"URL to redirect after magic link verification\"\n }).optional(),\n newUserCallbackURL: z.string().meta({\n description: \"URL to redirect after new user signup. Only used if the user is registering for the first time.\"\n }).optional(),\n errorCallbackURL: z.string().meta({\n description: \"URL to redirect after error.\"\n }).optional()\n }),\n metadata: {\n openapi: {\n description: \"Sign in with magic link\",\n responses: {\n 200: {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n status: {\n type: \"boolean\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const { email } = ctx.body;\n if (opts.disableSignUp) {\n const user = await ctx.context.internalAdapter.findUserByEmail(email);\n if (!user) {\n throw new APIError(\"BAD_REQUEST\", {\n message: BASE_ERROR_CODES.USER_NOT_FOUND\n });\n }\n }\n const verificationToken = opts?.generateToken ? await opts.generateToken(email) : generateRandomString(32, \"a-z\", \"A-Z\");\n const storedToken = await storeToken(ctx, verificationToken);\n await ctx.context.internalAdapter.createVerificationValue(\n {\n identifier: storedToken,\n value: JSON.stringify({ email, name: ctx.body.name }),\n expiresAt: new Date(\n Date.now() + (opts.expiresIn || 60 * 5) * 1e3\n )\n },\n ctx\n );\n const realBaseURL = new URL(ctx.context.baseURL);\n const url = new URL(\n `${realBaseURL.pathname}/magic-link/verify`,\n realBaseURL.origin\n );\n url.searchParams.set(\"token\", verificationToken);\n url.searchParams.set(\"callbackURL\", ctx.body.callbackURL || \"/\");\n if (ctx.body.newUserCallbackURL) {\n url.searchParams.set(\n \"newUserCallbackURL\",\n ctx.body.newUserCallbackURL\n );\n }\n if (ctx.body.errorCallbackURL) {\n url.searchParams.set(\"errorCallbackURL\", ctx.body.errorCallbackURL);\n }\n await options.sendMagicLink(\n {\n email,\n url: url.toString(),\n token: verificationToken\n },\n ctx.request\n );\n return ctx.json({\n status: true\n });\n }\n ),\n /**\n * ### Endpoint\n *\n * GET `/magic-link/verify`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.magicLinkVerify`\n *\n * **client:**\n * `authClient.magicLink.verify`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/magic-link#api-method-magic-link-verify)\n */\n magicLinkVerify: createAuthEndpoint(\n \"/magic-link/verify\",\n {\n method: \"GET\",\n query: z.object({\n token: z.string().meta({\n description: \"Verification token\"\n }),\n callbackURL: z.string().meta({\n description: 'URL to redirect after magic link verification, if not provided the user will be redirected to the root URL. Eg: \"/dashboard\"'\n }).optional(),\n errorCallbackURL: z.string().meta({\n description: \"URL to redirect after error.\"\n }).optional(),\n newUserCallbackURL: z.string().meta({\n description: \"URL to redirect after new user signup. Only used if the user is registering for the first time.\"\n }).optional()\n }),\n use: [\n originCheck((ctx) => {\n return ctx.query.callbackURL ? decodeURIComponent(ctx.query.callbackURL) : \"/\";\n }),\n originCheck((ctx) => {\n return ctx.query.newUserCallbackURL ? decodeURIComponent(ctx.query.newUserCallbackURL) : \"/\";\n }),\n originCheck((ctx) => {\n return ctx.query.errorCallbackURL ? decodeURIComponent(ctx.query.errorCallbackURL) : \"/\";\n })\n ],\n requireHeaders: true,\n metadata: {\n openapi: {\n description: \"Verify magic link\",\n responses: {\n 200: {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n session: {\n $ref: \"#/components/schemas/Session\"\n },\n user: {\n $ref: \"#/components/schemas/User\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const token = ctx.query.token;\n const callbackURL = new URL(\n ctx.query.callbackURL ? decodeURIComponent(ctx.query.callbackURL) : \"/\",\n ctx.context.baseURL\n ).toString();\n const errorCallbackURL = new URL(\n ctx.query.errorCallbackURL ? decodeURIComponent(ctx.query.errorCallbackURL) : callbackURL,\n ctx.context.baseURL\n ).toString();\n const newUserCallbackURL = new URL(\n ctx.query.newUserCallbackURL ? decodeURIComponent(ctx.query.newUserCallbackURL) : callbackURL,\n ctx.context.baseURL\n ).toString();\n callbackURL?.startsWith(\"http\") ? callbackURL : callbackURL ? `${ctx.context.options.baseURL}${callbackURL}` : ctx.context.options.baseURL;\n const storedToken = await storeToken(ctx, token);\n const tokenValue = await ctx.context.internalAdapter.findVerificationValue(\n storedToken\n );\n if (!tokenValue) {\n throw ctx.redirect(`${errorCallbackURL}?error=INVALID_TOKEN`);\n }\n if (tokenValue.expiresAt < /* @__PURE__ */ new Date()) {\n await ctx.context.internalAdapter.deleteVerificationValue(\n tokenValue.id\n );\n throw ctx.redirect(`${errorCallbackURL}?error=EXPIRED_TOKEN`);\n }\n await ctx.context.internalAdapter.deleteVerificationValue(\n tokenValue.id\n );\n const { email, name } = JSON.parse(tokenValue.value);\n let isNewUser = false;\n let user = await ctx.context.internalAdapter.findUserByEmail(email).then((res) => res?.user);\n if (!user) {\n if (!opts.disableSignUp) {\n const newUser = await ctx.context.internalAdapter.createUser(\n {\n email,\n emailVerified: true,\n name: name || \"\"\n },\n ctx\n );\n isNewUser = true;\n user = newUser;\n if (!user) {\n throw ctx.redirect(\n `${errorCallbackURL}?error=failed_to_create_user`\n );\n }\n } else {\n throw ctx.redirect(\n `${errorCallbackURL}?error=new_user_signup_disabled`\n );\n }\n }\n if (!user.emailVerified) {\n await ctx.context.internalAdapter.updateUser(\n user.id,\n {\n emailVerified: true\n },\n ctx\n );\n }\n const session = await ctx.context.internalAdapter.createSession(\n user.id,\n ctx\n );\n if (!session) {\n throw ctx.redirect(\n `${errorCallbackURL}?error=failed_to_create_session`\n );\n }\n await setSessionCookie(ctx, {\n session,\n user\n });\n if (!ctx.query.callbackURL) {\n return ctx.json({\n token: session.token,\n user: {\n id: user.id,\n email: user.email,\n emailVerified: user.emailVerified,\n name: user.name,\n image: user.image,\n createdAt: user.createdAt,\n updatedAt: user.updatedAt\n }\n });\n }\n if (isNewUser) {\n throw ctx.redirect(newUserCallbackURL);\n }\n throw ctx.redirect(callbackURL);\n }\n )\n },\n rateLimit: [\n {\n pathMatcher(path) {\n return path.startsWith(\"/sign-in/magic-link\") || path.startsWith(\"/magic-link/verify\");\n },\n window: opts.rateLimit?.window || 60,\n max: opts.rateLimit?.max || 5\n }\n ]\n };\n};\n\nexport { magicLink };\n","import * as z from 'zod/v4';\nimport { j as createAuthEndpoint, k as getSessionFromCtx, B as BASE_ERROR_CODES } from '../../shared/better-auth.D4HhkCZJ.mjs';\nimport { APIError } from 'better-call';\nimport { m as mergeSchema } from '../../shared/better-auth.n2KFGwjY.mjs';\nimport { g as generateRandomString } from '../../shared/better-auth.B4Qoxdgc.mjs';\nimport { setSessionCookie } from '../../cookies/index.mjs';\nimport '../../shared/better-auth.8zoxzg-F.mjs';\nimport '../../shared/better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport { g as getDate } from '../../shared/better-auth.CW6D9eSx.mjs';\nimport '@better-auth/utils/hash';\nimport '@better-auth/utils/base64';\nimport '../../crypto/index.mjs';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport 'jose';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport '@better-fetch/fetch';\nimport '../../shared/better-auth.VTXNLFMT.mjs';\nimport '../../shared/better-auth.DdzSJf-n.mjs';\nimport '../../shared/better-auth.tB5eU6EY.mjs';\nimport '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport 'jose/errors';\nimport '@better-auth/utils/random';\n\nconst ERROR_CODES = {\n INVALID_PHONE_NUMBER: \"Invalid phone number\",\n PHONE_NUMBER_EXIST: \"Phone number already exists\",\n INVALID_PHONE_NUMBER_OR_PASSWORD: \"Invalid phone number or password\",\n UNEXPECTED_ERROR: \"Unexpected error\",\n OTP_NOT_FOUND: \"OTP not found\",\n OTP_EXPIRED: \"OTP expired\",\n INVALID_OTP: \"Invalid OTP\",\n PHONE_NUMBER_NOT_VERIFIED: \"Phone number not verified\"\n};\n\nfunction generateOTP(size) {\n return generateRandomString(size, \"0-9\");\n}\nconst phoneNumber = (options) => {\n const opts = {\n expiresIn: options?.expiresIn || 300,\n otpLength: options?.otpLength || 6,\n ...options,\n phoneNumber: \"phoneNumber\",\n phoneNumberVerified: \"phoneNumberVerified\",\n code: \"code\",\n createdAt: \"createdAt\"\n };\n return {\n id: \"phone-number\",\n endpoints: {\n /**\n * ### Endpoint\n *\n * POST `/sign-in/phone-number`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.signInPhoneNumber`\n *\n * **client:**\n * `authClient.signIn.phoneNumber`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/phone-number#api-method-sign-in-phone-number)\n */\n signInPhoneNumber: createAuthEndpoint(\n \"/sign-in/phone-number\",\n {\n method: \"POST\",\n body: z.object({\n phoneNumber: z.string().meta({\n description: 'Phone number to sign in. Eg: \"+1234567890\"'\n }),\n password: z.string().meta({\n description: \"Password to use for sign in.\"\n }),\n rememberMe: z.boolean().meta({\n description: \"Remember the session. Eg: true\"\n }).optional()\n }),\n metadata: {\n openapi: {\n summary: \"Sign in with phone number\",\n description: \"Use this endpoint to sign in with phone number\",\n responses: {\n 200: {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n user: {\n $ref: \"#/components/schemas/User\"\n },\n session: {\n $ref: \"#/components/schemas/Session\"\n }\n }\n }\n }\n }\n },\n 400: {\n description: \"Invalid phone number or password\"\n }\n }\n }\n }\n },\n async (ctx) => {\n const { password, phoneNumber: phoneNumber2 } = ctx.body;\n if (opts.phoneNumberValidator) {\n const isValidNumber = await opts.phoneNumberValidator(\n ctx.body.phoneNumber\n );\n if (!isValidNumber) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_PHONE_NUMBER\n });\n }\n }\n const user = await ctx.context.adapter.findOne({\n model: \"user\",\n where: [\n {\n field: \"phoneNumber\",\n value: phoneNumber2\n }\n ]\n });\n if (!user) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: ERROR_CODES.INVALID_PHONE_NUMBER_OR_PASSWORD\n });\n }\n if (opts.requireVerification) {\n if (!user.phoneNumberVerified) {\n const otp = generateOTP(opts.otpLength);\n await ctx.context.internalAdapter.createVerificationValue(\n {\n value: otp,\n identifier: phoneNumber2,\n expiresAt: getDate(opts.expiresIn, \"sec\")\n },\n ctx\n );\n await opts.sendOTP?.(\n {\n phoneNumber: phoneNumber2,\n code: otp\n },\n ctx.request\n );\n throw new APIError(\"UNAUTHORIZED\", {\n message: ERROR_CODES.PHONE_NUMBER_NOT_VERIFIED\n });\n }\n }\n const accounts = await ctx.context.internalAdapter.findAccountByUserId(user.id);\n const credentialAccount = accounts.find(\n (a) => a.providerId === \"credential\"\n );\n if (!credentialAccount) {\n ctx.context.logger.error(\"Credential account not found\", {\n phoneNumber: phoneNumber2\n });\n throw new APIError(\"UNAUTHORIZED\", {\n message: ERROR_CODES.INVALID_PHONE_NUMBER_OR_PASSWORD\n });\n }\n const currentPassword = credentialAccount?.password;\n if (!currentPassword) {\n ctx.context.logger.error(\"Password not found\", { phoneNumber: phoneNumber2 });\n throw new APIError(\"UNAUTHORIZED\", {\n message: ERROR_CODES.UNEXPECTED_ERROR\n });\n }\n const validPassword = await ctx.context.password.verify({\n hash: currentPassword,\n password\n });\n if (!validPassword) {\n ctx.context.logger.error(\"Invalid password\");\n throw new APIError(\"UNAUTHORIZED\", {\n message: ERROR_CODES.INVALID_PHONE_NUMBER_OR_PASSWORD\n });\n }\n const session = await ctx.context.internalAdapter.createSession(\n user.id,\n ctx,\n ctx.body.rememberMe === false\n );\n if (!session) {\n ctx.context.logger.error(\"Failed to create session\");\n throw new APIError(\"UNAUTHORIZED\", {\n message: BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION\n });\n }\n await setSessionCookie(\n ctx,\n {\n session,\n user\n },\n ctx.body.rememberMe === false\n );\n return ctx.json({\n token: session.token,\n user: {\n id: user.id,\n email: user.email,\n emailVerified: user.emailVerified,\n name: user.name,\n image: user.image,\n phoneNumber: user.phoneNumber,\n phoneNumberVerified: user.phoneNumberVerified,\n createdAt: user.createdAt,\n updatedAt: user.updatedAt\n }\n });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/phone-number/send-otp`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.sendPhoneNumberOTP`\n *\n * **client:**\n * `authClient.phoneNumber.sendOtp`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/phone-number#api-method-phone-number-send-otp)\n */\n sendPhoneNumberOTP: createAuthEndpoint(\n \"/phone-number/send-otp\",\n {\n method: \"POST\",\n body: z.object({\n phoneNumber: z.string().meta({\n description: 'Phone number to send OTP. Eg: \"+1234567890\"'\n })\n }),\n metadata: {\n openapi: {\n summary: \"Send OTP to phone number\",\n description: \"Use this endpoint to send OTP to phone number\",\n responses: {\n 200: {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n message: {\n type: \"string\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n if (!options?.sendOTP) {\n ctx.context.logger.warn(\"sendOTP not implemented\");\n throw new APIError(\"NOT_IMPLEMENTED\", {\n message: \"sendOTP not implemented\"\n });\n }\n if (opts.phoneNumberValidator) {\n const isValidNumber = await opts.phoneNumberValidator(\n ctx.body.phoneNumber\n );\n if (!isValidNumber) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_PHONE_NUMBER\n });\n }\n }\n const code = generateOTP(opts.otpLength);\n await ctx.context.internalAdapter.createVerificationValue(\n {\n value: `${code}:0`,\n identifier: ctx.body.phoneNumber,\n expiresAt: getDate(opts.expiresIn, \"sec\")\n },\n ctx\n );\n await options.sendOTP(\n {\n phoneNumber: ctx.body.phoneNumber,\n code\n },\n ctx.request\n );\n return ctx.json({ message: \"code sent\" });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/phone-number/verify`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.verifyPhoneNumber`\n *\n * **client:**\n * `authClient.phoneNumber.verify`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/phone-number#api-method-phone-number-verify)\n */\n verifyPhoneNumber: createAuthEndpoint(\n \"/phone-number/verify\",\n {\n method: \"POST\",\n body: z.object({\n /**\n * Phone number\n */\n phoneNumber: z.string().meta({\n description: 'Phone number to verify. Eg: \"+1234567890\"'\n }),\n /**\n * OTP code\n */\n code: z.string().meta({\n description: 'OTP code. Eg: \"123456\"'\n }),\n /**\n * Disable session creation after verification\n * @default false\n */\n disableSession: z.boolean().meta({\n description: \"Disable session creation after verification. Eg: false\"\n }).optional(),\n /**\n * This checks if there is a session already\n * and updates the phone number with the provided\n * phone number\n */\n updatePhoneNumber: z.boolean().meta({\n description: \"Check if there is a session and update the phone number. Eg: true\"\n }).optional()\n }),\n metadata: {\n openapi: {\n summary: \"Verify phone number\",\n description: \"Use this endpoint to verify phone number\",\n responses: {\n \"200\": {\n description: \"Phone number verified successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n status: {\n type: \"boolean\",\n description: \"Indicates if the verification was successful\",\n enum: [true]\n },\n token: {\n type: \"string\",\n nullable: true,\n description: \"Session token if session is created, null if disableSession is true or no session is created\"\n },\n user: {\n type: \"object\",\n nullable: true,\n properties: {\n id: {\n type: \"string\",\n description: \"Unique identifier of the user\"\n },\n email: {\n type: \"string\",\n format: \"email\",\n nullable: true,\n description: \"User's email address\"\n },\n emailVerified: {\n type: \"boolean\",\n nullable: true,\n description: \"Whether the email is verified\"\n },\n name: {\n type: \"string\",\n nullable: true,\n description: \"User's name\"\n },\n image: {\n type: \"string\",\n format: \"uri\",\n nullable: true,\n description: \"User's profile image URL\"\n },\n phoneNumber: {\n type: \"string\",\n description: \"User's phone number\"\n },\n phoneNumberVerified: {\n type: \"boolean\",\n description: \"Whether the phone number is verified\"\n },\n createdAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Timestamp when the user was created\"\n },\n updatedAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Timestamp when the user was last updated\"\n }\n },\n required: [\n \"id\",\n \"phoneNumber\",\n \"phoneNumberVerified\",\n \"createdAt\",\n \"updatedAt\"\n ],\n description: \"User object with phone number details, null if no user is created or found\"\n }\n },\n required: [\"status\"]\n }\n }\n }\n },\n 400: {\n description: \"Invalid OTP\"\n }\n }\n }\n }\n },\n async (ctx) => {\n const otp = await ctx.context.internalAdapter.findVerificationValue(\n ctx.body.phoneNumber\n );\n if (!otp || otp.expiresAt < /* @__PURE__ */ new Date()) {\n if (otp && otp.expiresAt < /* @__PURE__ */ new Date()) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"OTP expired\"\n });\n }\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.OTP_NOT_FOUND\n });\n }\n const [otpValue, attempts] = otp.value.split(\":\");\n const allowedAttempts = options?.allowedAttempts || 3;\n if (attempts && parseInt(attempts) >= allowedAttempts) {\n await ctx.context.internalAdapter.deleteVerificationValue(otp.id);\n throw new APIError(\"FORBIDDEN\", {\n message: \"Too many attempts\"\n });\n }\n if (otpValue !== ctx.body.code) {\n await ctx.context.internalAdapter.updateVerificationValue(otp.id, {\n value: `${otpValue}:${parseInt(attempts || \"0\") + 1}`\n });\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Invalid OTP\"\n });\n }\n await ctx.context.internalAdapter.deleteVerificationValue(otp.id);\n if (ctx.body.updatePhoneNumber) {\n const session = await getSessionFromCtx(ctx);\n if (!session) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: BASE_ERROR_CODES.USER_NOT_FOUND\n });\n }\n const existingUser = await ctx.context.adapter.findMany({\n model: \"user\",\n where: [\n {\n field: \"phoneNumber\",\n value: ctx.body.phoneNumber\n }\n ]\n });\n if (existingUser.length) {\n throw ctx.error(\"BAD_REQUEST\", {\n message: ERROR_CODES.PHONE_NUMBER_EXIST\n });\n }\n let user2 = await ctx.context.internalAdapter.updateUser(\n session.user.id,\n {\n [opts.phoneNumber]: ctx.body.phoneNumber,\n [opts.phoneNumberVerified]: true\n },\n ctx\n );\n return ctx.json({\n status: true,\n token: session.session.token,\n user: {\n id: user2.id,\n email: user2.email,\n emailVerified: user2.emailVerified,\n name: user2.name,\n image: user2.image,\n phoneNumber: user2.phoneNumber,\n phoneNumberVerified: user2.phoneNumberVerified,\n createdAt: user2.createdAt,\n updatedAt: user2.updatedAt\n }\n });\n }\n let user = await ctx.context.adapter.findOne({\n model: \"user\",\n where: [\n {\n value: ctx.body.phoneNumber,\n field: opts.phoneNumber\n }\n ]\n });\n if (!user) {\n if (options?.signUpOnVerification) {\n user = await ctx.context.internalAdapter.createUser(\n {\n email: options.signUpOnVerification.getTempEmail(\n ctx.body.phoneNumber\n ),\n name: options.signUpOnVerification.getTempName ? options.signUpOnVerification.getTempName(\n ctx.body.phoneNumber\n ) : ctx.body.phoneNumber,\n [opts.phoneNumber]: ctx.body.phoneNumber,\n [opts.phoneNumberVerified]: true\n },\n ctx\n );\n if (!user) {\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n message: BASE_ERROR_CODES.FAILED_TO_CREATE_USER\n });\n }\n }\n } else {\n user = await ctx.context.internalAdapter.updateUser(\n user.id,\n {\n [opts.phoneNumberVerified]: true\n },\n ctx\n );\n }\n if (!user) {\n return ctx.json(null);\n }\n await options?.callbackOnVerification?.(\n {\n phoneNumber: ctx.body.phoneNumber,\n user\n },\n ctx.request\n );\n if (!user) {\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n message: BASE_ERROR_CODES.FAILED_TO_UPDATE_USER\n });\n }\n if (!ctx.body.disableSession) {\n const session = await ctx.context.internalAdapter.createSession(\n user.id,\n ctx\n );\n if (!session) {\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n message: BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION\n });\n }\n await setSessionCookie(ctx, {\n session,\n user\n });\n return ctx.json({\n status: true,\n token: session.token,\n user: {\n id: user.id,\n email: user.email,\n emailVerified: user.emailVerified,\n name: user.name,\n image: user.image,\n phoneNumber: user.phoneNumber,\n phoneNumberVerified: user.phoneNumberVerified,\n createdAt: user.createdAt,\n updatedAt: user.updatedAt\n }\n });\n }\n return ctx.json({\n status: true,\n token: null,\n user: {\n id: user.id,\n email: user.email,\n emailVerified: user.emailVerified,\n name: user.name,\n image: user.image,\n phoneNumber: user.phoneNumber,\n phoneNumberVerified: user.phoneNumberVerified,\n createdAt: user.createdAt,\n updatedAt: user.updatedAt\n }\n });\n }\n ),\n /**\n * @deprecated Use requestPasswordResetPhoneNumber instead. This endpoint will be removed in the next major version.\n */\n forgetPasswordPhoneNumber: createAuthEndpoint(\n \"/phone-number/forget-password\",\n {\n method: \"POST\",\n body: z.object({\n phoneNumber: z.string().meta({\n description: `The phone number which is associated with the user. Eg: \"+1234567890\"`\n })\n }),\n metadata: {\n openapi: {\n description: \"Request OTP for password reset via phone number\",\n responses: {\n \"200\": {\n description: \"OTP sent successfully for password reset\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n status: {\n type: \"boolean\",\n description: \"Indicates if the OTP was sent successfully\",\n enum: [true]\n }\n },\n required: [\"status\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const user = await ctx.context.adapter.findOne({\n model: \"user\",\n where: [\n {\n value: ctx.body.phoneNumber,\n field: opts.phoneNumber\n }\n ]\n });\n if (!user) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"phone number isn't registered\"\n });\n }\n const code = generateOTP(opts.otpLength);\n await ctx.context.internalAdapter.createVerificationValue(\n {\n value: `${code}:0`,\n identifier: `${ctx.body.phoneNumber}-request-password-reset`,\n expiresAt: getDate(opts.expiresIn, \"sec\")\n },\n ctx\n );\n await options?.sendForgetPasswordOTP?.(\n {\n phoneNumber: ctx.body.phoneNumber,\n code\n },\n ctx.request\n );\n return ctx.json({\n status: true\n });\n }\n ),\n requestPasswordResetPhoneNumber: createAuthEndpoint(\n \"/phone-number/request-password-reset\",\n {\n method: \"POST\",\n body: z.object({\n phoneNumber: z.string()\n }),\n metadata: {\n openapi: {\n description: \"Request OTP for password reset via phone number\",\n responses: {\n \"200\": {\n description: \"OTP sent successfully for password reset\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n status: {\n type: \"boolean\",\n description: \"Indicates if the OTP was sent successfully\",\n enum: [true]\n }\n },\n required: [\"status\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const user = await ctx.context.adapter.findOne({\n model: \"user\",\n where: [\n {\n value: ctx.body.phoneNumber,\n field: opts.phoneNumber\n }\n ]\n });\n if (!user) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"phone number isn't registered\"\n });\n }\n const code = generateOTP(opts.otpLength);\n await ctx.context.internalAdapter.createVerificationValue(\n {\n value: `${code}:0`,\n identifier: `${ctx.body.phoneNumber}-request-password-reset`,\n expiresAt: getDate(opts.expiresIn, \"sec\")\n },\n ctx\n );\n await options?.sendPasswordResetOTP?.(\n {\n phoneNumber: ctx.body.phoneNumber,\n code\n },\n ctx.request\n );\n return ctx.json({\n status: true\n });\n }\n ),\n resetPasswordPhoneNumber: createAuthEndpoint(\n \"/phone-number/reset-password\",\n {\n method: \"POST\",\n body: z.object({\n otp: z.string().meta({\n description: 'The one time password to reset the password. Eg: \"123456\"'\n }),\n phoneNumber: z.string().meta({\n description: 'The phone number to the account which intends to reset the password for. Eg: \"+1234567890\"'\n }),\n newPassword: z.string().meta({\n description: `The new password. Eg: \"new-and-secure-password\"`\n })\n }),\n metadata: {\n openapi: {\n description: \"Reset password using phone number OTP\",\n responses: {\n \"200\": {\n description: \"Password reset successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n status: {\n type: \"boolean\",\n description: \"Indicates if the password was reset successfully\",\n enum: [true]\n }\n },\n required: [\"status\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const verification = await ctx.context.internalAdapter.findVerificationValue(\n `${ctx.body.phoneNumber}-request-password-reset`\n );\n if (!verification) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.OTP_NOT_FOUND\n });\n }\n if (verification.expiresAt < /* @__PURE__ */ new Date()) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.OTP_EXPIRED\n });\n }\n const [otpValue, attempts] = verification.value.split(\":\");\n const allowedAttempts = options?.allowedAttempts || 3;\n if (attempts && parseInt(attempts) >= allowedAttempts) {\n await ctx.context.internalAdapter.deleteVerificationValue(\n verification.id\n );\n throw new APIError(\"FORBIDDEN\", {\n message: \"Too many attempts\"\n });\n }\n if (ctx.body.otp !== otpValue) {\n await ctx.context.internalAdapter.updateVerificationValue(\n verification.id,\n {\n value: `${otpValue}:${parseInt(attempts || \"0\") + 1}`\n }\n );\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_OTP\n });\n }\n const user = await ctx.context.adapter.findOne({\n model: \"user\",\n where: [\n {\n field: \"phoneNumber\",\n value: ctx.body.phoneNumber\n }\n ]\n });\n if (!user) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.UNEXPECTED_ERROR\n });\n }\n const hashedPassword = await ctx.context.password.hash(\n ctx.body.newPassword\n );\n await ctx.context.internalAdapter.updatePassword(\n user.id,\n hashedPassword\n );\n await ctx.context.internalAdapter.deleteVerificationValue(\n verification.id\n );\n return ctx.json({\n status: true\n });\n }\n )\n },\n schema: mergeSchema(schema, options?.schema),\n rateLimit: [\n {\n pathMatcher(path) {\n return path.startsWith(\"/phone-number\");\n },\n window: 60 * 1e3,\n max: 10\n }\n ],\n $ERROR_CODES: ERROR_CODES\n };\n};\nconst schema = {\n user: {\n fields: {\n phoneNumber: {\n type: \"string\",\n required: false,\n unique: true,\n sortable: true,\n returned: true\n },\n phoneNumberVerified: {\n type: \"boolean\",\n required: false,\n returned: true,\n input: false\n }\n }\n }\n};\n\nexport { phoneNumber };\n","import { APIError } from 'better-call';\nimport { i as createAuthMiddleware, j as createAuthEndpoint, k as getSessionFromCtx } from '../../shared/better-auth.D4HhkCZJ.mjs';\nimport 'zod/v4';\nimport { parseSetCookieHeader, setSessionCookie } from '../../cookies/index.mjs';\nimport { m as mergeSchema } from '../../shared/better-auth.n2KFGwjY.mjs';\nimport '../../shared/better-auth.8zoxzg-F.mjs';\nimport '../../shared/better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport { g as getOrigin } from '../../shared/better-auth.VTXNLFMT.mjs';\nimport '../../shared/better-auth.CW6D9eSx.mjs';\nimport '@better-auth/utils/hash';\nimport '@better-auth/utils/base64';\nimport '../../crypto/index.mjs';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport 'jose';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport '../../shared/better-auth.B4Qoxdgc.mjs';\nimport '@better-auth/utils/random';\nimport '@better-fetch/fetch';\nimport '../../shared/better-auth.tB5eU6EY.mjs';\nimport '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport 'jose/errors';\nimport '../../shared/better-auth.DdzSJf-n.mjs';\n\nconst schema = {\n user: {\n fields: {\n isAnonymous: {\n type: \"boolean\",\n required: false\n }\n }\n }\n};\nconst anonymous = (options) => {\n const ERROR_CODES = {\n FAILED_TO_CREATE_USER: \"Failed to create user\",\n COULD_NOT_CREATE_SESSION: \"Could not create session\",\n ANONYMOUS_USERS_CANNOT_SIGN_IN_AGAIN_ANONYMOUSLY: \"Anonymous users cannot sign in again anonymously\"\n };\n return {\n id: \"anonymous\",\n endpoints: {\n signInAnonymous: createAuthEndpoint(\n \"/sign-in/anonymous\",\n {\n method: \"POST\",\n metadata: {\n openapi: {\n description: \"Sign in anonymously\",\n responses: {\n 200: {\n description: \"Sign in anonymously\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n user: {\n $ref: \"#/components/schemas/User\"\n },\n session: {\n $ref: \"#/components/schemas/Session\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const { emailDomainName = getOrigin(ctx.context.baseURL) } = options || {};\n const id = ctx.context.generateId({ model: \"user\" });\n const email = `temp-${id}@${emailDomainName}`;\n const name = await options?.generateName?.(ctx) || \"Anonymous\";\n const newUser = await ctx.context.internalAdapter.createUser(\n {\n email,\n emailVerified: false,\n isAnonymous: true,\n name,\n createdAt: /* @__PURE__ */ new Date(),\n updatedAt: /* @__PURE__ */ new Date()\n },\n ctx\n );\n if (!newUser) {\n throw ctx.error(\"INTERNAL_SERVER_ERROR\", {\n message: ERROR_CODES.FAILED_TO_CREATE_USER\n });\n }\n const session = await ctx.context.internalAdapter.createSession(\n newUser.id,\n ctx\n );\n if (!session) {\n return ctx.json(null, {\n status: 400,\n body: {\n message: ERROR_CODES.COULD_NOT_CREATE_SESSION\n }\n });\n }\n await setSessionCookie(ctx, {\n session,\n user: newUser\n });\n return ctx.json({\n token: session.token,\n user: {\n id: newUser.id,\n email: newUser.email,\n emailVerified: newUser.emailVerified,\n name: newUser.name,\n createdAt: newUser.createdAt,\n updatedAt: newUser.updatedAt\n }\n });\n }\n )\n },\n hooks: {\n after: [\n {\n matcher(ctx) {\n return ctx.path.startsWith(\"/sign-in\") || ctx.path.startsWith(\"/sign-up\") || ctx.path.startsWith(\"/callback\") || ctx.path.startsWith(\"/oauth2/callback\") || ctx.path.startsWith(\"/magic-link/verify\") || ctx.path.startsWith(\"/email-otp/verify-email\") || ctx.path.startsWith(\"/one-tap/callback\") || ctx.path.startsWith(\"/passkey/verify-authentication\");\n },\n handler: createAuthMiddleware(async (ctx) => {\n const setCookie = ctx.context.responseHeaders?.get(\"set-cookie\");\n const sessionTokenName = ctx.context.authCookies.sessionToken.name;\n const sessionCookie = parseSetCookieHeader(setCookie || \"\").get(sessionTokenName)?.value.split(\".\")[0];\n if (!sessionCookie) {\n return;\n }\n const session = await getSessionFromCtx(\n ctx,\n {\n disableRefresh: true\n }\n );\n if (!session || !session.user.isAnonymous) {\n return;\n }\n if (ctx.path === \"/sign-in/anonymous\") {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.ANONYMOUS_USERS_CANNOT_SIGN_IN_AGAIN_ANONYMOUSLY\n });\n }\n const newSession = ctx.context.newSession;\n if (!newSession) {\n return;\n }\n if (options?.onLinkAccount) {\n await options?.onLinkAccount?.({\n anonymousUser: session,\n newUser: newSession\n });\n }\n if (!options?.disableDeleteAnonymousUser) {\n await ctx.context.internalAdapter.deleteUser(session.user.id);\n }\n })\n }\n ]\n },\n schema: mergeSchema(schema, options?.schema),\n $ERROR_CODES: ERROR_CODES\n };\n};\n\nexport { anonymous };\n","import * as z from 'zod/v4';\nimport { APIError } from 'better-call';\nimport { i as createAuthMiddleware, k as getSessionFromCtx, j as createAuthEndpoint } from './better-auth.D4HhkCZJ.mjs';\nimport { setSessionCookie, deleteSessionCookie } from '../cookies/index.mjs';\nimport { m as mergeSchema } from './better-auth.n2KFGwjY.mjs';\nimport './better-auth.8zoxzg-F.mjs';\nimport './better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport { g as getDate } from './better-auth.CW6D9eSx.mjs';\nimport { g as getEndpointResponse } from './better-auth.DQI8AD7d.mjs';\nimport { h as hasPermission } from './better-auth.bkwPl2G4.mjs';\n\nconst ADMIN_ERROR_CODES = {\n FAILED_TO_CREATE_USER: \"Failed to create user\",\n USER_ALREADY_EXISTS: \"User already exists\",\n YOU_CANNOT_BAN_YOURSELF: \"You cannot ban yourself\",\n YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE: \"You are not allowed to change users role\",\n YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS: \"You are not allowed to create users\",\n YOU_ARE_NOT_ALLOWED_TO_LIST_USERS: \"You are not allowed to list users\",\n YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS: \"You are not allowed to list users sessions\",\n YOU_ARE_NOT_ALLOWED_TO_BAN_USERS: \"You are not allowed to ban users\",\n YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS: \"You are not allowed to impersonate users\",\n YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS: \"You are not allowed to revoke users sessions\",\n YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS: \"You are not allowed to delete users\",\n YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD: \"You are not allowed to set users password\",\n BANNED_USER: \"You have been banned from this application\",\n NO_DATA_TO_UPDATE: \"No data to update\",\n YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS: \"You are not allowed to update users\"\n};\n\nconst schema = {\n user: {\n fields: {\n role: {\n type: \"string\",\n required: false,\n input: false\n },\n banned: {\n type: \"boolean\",\n defaultValue: false,\n required: false,\n input: false\n },\n banReason: {\n type: \"string\",\n required: false,\n input: false\n },\n banExpires: {\n type: \"date\",\n required: false,\n input: false\n }\n }\n },\n session: {\n fields: {\n impersonatedBy: {\n type: \"string\",\n required: false\n }\n }\n }\n};\n\nfunction parseRoles(roles) {\n return Array.isArray(roles) ? roles.join(\",\") : roles;\n}\nconst admin = (options) => {\n const opts = {\n defaultRole: options?.defaultRole ?? \"user\",\n adminRoles: options?.adminRoles ?? [\"admin\"],\n bannedUserMessage: options?.bannedUserMessage ?? \"You have been banned from this application. Please contact support if you believe this is an error.\",\n ...options\n };\n const adminMiddleware = createAuthMiddleware(async (ctx) => {\n const session = await getSessionFromCtx(ctx);\n if (!session) {\n throw new APIError(\"UNAUTHORIZED\");\n }\n return {\n session\n };\n });\n return {\n id: \"admin\",\n init() {\n return {\n options: {\n databaseHooks: {\n user: {\n create: {\n async before(user) {\n return {\n data: {\n role: options?.defaultRole ?? \"user\",\n ...user\n }\n };\n }\n }\n },\n session: {\n create: {\n async before(session, ctx) {\n if (!ctx) {\n return;\n }\n const user = await ctx.context.internalAdapter.findUserById(\n session.userId\n );\n if (user.banned) {\n if (user.banExpires && user.banExpires.getTime() < Date.now()) {\n await ctx.context.internalAdapter.updateUser(\n session.userId,\n {\n banned: false,\n banReason: null,\n banExpires: null\n }\n );\n return;\n }\n if (ctx && (ctx.path.startsWith(\"/callback\") || ctx.path.startsWith(\"/oauth2/callback\"))) {\n const redirectURI = ctx.context.options.onAPIError?.errorURL || `${ctx.context.baseURL}/error`;\n throw ctx.redirect(\n `${redirectURI}?error=banned&error_description=${opts.bannedUserMessage}`\n );\n }\n throw new APIError(\"FORBIDDEN\", {\n message: opts.bannedUserMessage,\n code: \"BANNED_USER\"\n });\n }\n }\n }\n }\n }\n }\n };\n },\n hooks: {\n after: [\n {\n matcher(context) {\n return context.path === \"/list-sessions\";\n },\n handler: createAuthMiddleware(async (ctx) => {\n const response = await getEndpointResponse(ctx);\n if (!response) {\n return;\n }\n const newJson = response.filter((session) => {\n return !session.impersonatedBy;\n });\n return ctx.json(newJson);\n })\n }\n ]\n },\n endpoints: {\n /**\n * ### Endpoint\n *\n * POST `/admin/set-role`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.setRole`\n *\n * **client:**\n * `authClient.admin.setRole`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-set-role)\n */\n setRole: createAuthEndpoint(\n \"/admin/set-role\",\n {\n method: \"POST\",\n body: z.object({\n userId: z.coerce.string().meta({\n description: \"The user id\"\n }),\n role: z.union([\n z.string().meta({\n description: \"The role to set. `admin` or `user` by default\"\n }),\n z.array(\n z.string().meta({\n description: \"The roles to set. `admin` or `user` by default\"\n })\n )\n ]).meta({\n description: \"The role to set, this can be a string or an array of strings. Eg: `admin` or `[admin, user]`\"\n })\n }),\n requireHeaders: true,\n use: [adminMiddleware],\n metadata: {\n openapi: {\n operationId: \"setRole\",\n summary: \"Set the role of a user\",\n description: \"Set the role of a user\",\n responses: {\n 200: {\n description: \"User role updated\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n user: {\n $ref: \"#/components/schemas/User\"\n }\n }\n }\n }\n }\n }\n }\n },\n $Infer: {\n body: {}\n }\n }\n },\n async (ctx) => {\n const canSetRole = hasPermission({\n userId: ctx.context.session.user.id,\n role: ctx.context.session.user.role,\n options: opts,\n permissions: {\n user: [\"set-role\"]\n }\n });\n if (!canSetRole) {\n throw new APIError(\"FORBIDDEN\", {\n message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE\n });\n }\n const updatedUser = await ctx.context.internalAdapter.updateUser(\n ctx.body.userId,\n {\n role: parseRoles(ctx.body.role)\n },\n ctx\n );\n return ctx.json({\n user: updatedUser\n });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/admin/create-user`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.createUser`\n *\n * **client:**\n * `authClient.admin.createUser`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-create-user)\n */\n createUser: createAuthEndpoint(\n \"/admin/create-user\",\n {\n method: \"POST\",\n body: z.object({\n email: z.string().meta({\n description: \"The email of the user\"\n }),\n password: z.string().meta({\n description: \"The password of the user\"\n }),\n name: z.string().meta({\n description: \"The name of the user\"\n }),\n role: z.union([\n z.string().meta({\n description: \"The role of the user\"\n }),\n z.array(\n z.string().meta({\n description: \"The roles of user\"\n })\n )\n ]).optional().meta({\n description: `A string or array of strings representing the roles to apply to the new user. Eg: \"user\"`\n }),\n /**\n * extra fields for user\n */\n data: z.record(z.string(), z.any()).optional().meta({\n description: \"Extra fields for the user. Including custom additional fields.\"\n })\n }),\n metadata: {\n openapi: {\n operationId: \"createUser\",\n summary: \"Create a new user\",\n description: \"Create a new user\",\n responses: {\n 200: {\n description: \"User created\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n user: {\n $ref: \"#/components/schemas/User\"\n }\n }\n }\n }\n }\n }\n }\n },\n $Infer: {\n body: {}\n }\n }\n },\n async (ctx) => {\n const session = await getSessionFromCtx(ctx);\n if (!session && (ctx.request || ctx.headers)) {\n throw ctx.error(\"UNAUTHORIZED\");\n }\n if (session) {\n const canCreateUser = hasPermission({\n userId: session.user.id,\n role: session.user.role,\n options: opts,\n permissions: {\n user: [\"create\"]\n }\n });\n if (!canCreateUser) {\n throw new APIError(\"FORBIDDEN\", {\n message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS\n });\n }\n }\n const existUser = await ctx.context.internalAdapter.findUserByEmail(\n ctx.body.email\n );\n if (existUser) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ADMIN_ERROR_CODES.USER_ALREADY_EXISTS\n });\n }\n const user = await ctx.context.internalAdapter.createUser(\n {\n email: ctx.body.email,\n name: ctx.body.name,\n role: (ctx.body.role && parseRoles(ctx.body.role)) ?? options?.defaultRole ?? \"user\",\n ...ctx.body.data\n },\n ctx\n );\n if (!user) {\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n message: ADMIN_ERROR_CODES.FAILED_TO_CREATE_USER\n });\n }\n const hashedPassword = await ctx.context.password.hash(\n ctx.body.password\n );\n await ctx.context.internalAdapter.linkAccount(\n {\n accountId: user.id,\n providerId: \"credential\",\n password: hashedPassword,\n userId: user.id\n },\n ctx\n );\n return ctx.json({\n user\n });\n }\n ),\n adminUpdateUser: createAuthEndpoint(\n \"/admin/update-user\",\n {\n method: \"POST\",\n body: z.object({\n userId: z.coerce.string().meta({\n description: \"The user id\"\n }),\n data: z.record(z.any(), z.any()).meta({\n description: \"The user data to update\"\n })\n }),\n use: [adminMiddleware],\n metadata: {\n openapi: {\n operationId: \"updateUser\",\n summary: \"Update a user\",\n description: \"Update a user's details\",\n responses: {\n 200: {\n description: \"User updated\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n user: {\n $ref: \"#/components/schemas/User\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const canUpdateUser = hasPermission({\n userId: ctx.context.session.user.id,\n role: ctx.context.session.user.role,\n options: opts,\n permissions: {\n user: [\"update\"]\n }\n });\n if (!canUpdateUser) {\n throw ctx.error(\"FORBIDDEN\", {\n message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS,\n code: \"YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS\"\n });\n }\n if (Object.keys(ctx.body.data).length === 0) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ADMIN_ERROR_CODES.NO_DATA_TO_UPDATE\n });\n }\n const updatedUser = await ctx.context.internalAdapter.updateUser(\n ctx.body.userId,\n ctx.body.data,\n ctx\n );\n return ctx.json(updatedUser);\n }\n ),\n listUsers: createAuthEndpoint(\n \"/admin/list-users\",\n {\n method: \"GET\",\n use: [adminMiddleware],\n query: z.object({\n searchValue: z.string().optional().meta({\n description: 'The value to search for. Eg: \"some name\"'\n }),\n searchField: z.enum([\"email\", \"name\"]).meta({\n description: 'The field to search in, defaults to email. Can be `email` or `name`. Eg: \"name\"'\n }).optional(),\n searchOperator: z.enum([\"contains\", \"starts_with\", \"ends_with\"]).meta({\n description: 'The operator to use for the search. Can be `contains`, `starts_with` or `ends_with`. Eg: \"contains\"'\n }).optional(),\n limit: z.string().meta({\n description: \"The number of users to return\"\n }).or(z.number()).optional(),\n offset: z.string().meta({\n description: \"The offset to start from\"\n }).or(z.number()).optional(),\n sortBy: z.string().meta({\n description: \"The field to sort by\"\n }).optional(),\n sortDirection: z.enum([\"asc\", \"desc\"]).meta({\n description: \"The direction to sort by\"\n }).optional(),\n filterField: z.string().meta({\n description: \"The field to filter by\"\n }).optional(),\n filterValue: z.string().meta({\n description: \"The value to filter by\"\n }).or(z.number()).or(z.boolean()).optional(),\n filterOperator: z.enum([\"eq\", \"ne\", \"lt\", \"lte\", \"gt\", \"gte\", \"contains\"]).meta({\n description: \"The operator to use for the filter\"\n }).optional()\n }),\n metadata: {\n openapi: {\n operationId: \"listUsers\",\n summary: \"List users\",\n description: \"List users\",\n responses: {\n 200: {\n description: \"List of users\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n users: {\n type: \"array\",\n items: {\n $ref: \"#/components/schemas/User\"\n }\n },\n total: {\n type: \"number\"\n },\n limit: {\n type: \"number\"\n },\n offset: {\n type: \"number\"\n }\n },\n required: [\"users\", \"total\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = ctx.context.session;\n const canListUsers = hasPermission({\n userId: ctx.context.session.user.id,\n role: session.user.role,\n options: opts,\n permissions: {\n user: [\"list\"]\n }\n });\n if (!canListUsers) {\n throw new APIError(\"FORBIDDEN\", {\n message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_LIST_USERS\n });\n }\n const where = [];\n if (ctx.query?.searchValue) {\n where.push({\n field: ctx.query.searchField || \"email\",\n operator: ctx.query.searchOperator || \"contains\",\n value: ctx.query.searchValue\n });\n }\n if (ctx.query?.filterValue) {\n where.push({\n field: ctx.query.filterField || \"email\",\n operator: ctx.query.filterOperator || \"eq\",\n value: ctx.query.filterValue\n });\n }\n try {\n const users = await ctx.context.internalAdapter.listUsers(\n Number(ctx.query?.limit) || void 0,\n Number(ctx.query?.offset) || void 0,\n ctx.query?.sortBy ? {\n field: ctx.query.sortBy,\n direction: ctx.query.sortDirection || \"asc\"\n } : void 0,\n where.length ? where : void 0\n );\n const total = await ctx.context.internalAdapter.countTotalUsers(\n where.length ? where : void 0\n );\n return ctx.json({\n users,\n total,\n limit: Number(ctx.query?.limit) || void 0,\n offset: Number(ctx.query?.offset) || void 0\n });\n } catch (e) {\n return ctx.json({\n users: [],\n total: 0\n });\n }\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/admin/list-user-sessions`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.listUserSessions`\n *\n * **client:**\n * `authClient.admin.listUserSessions`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-list-user-sessions)\n */\n listUserSessions: createAuthEndpoint(\n \"/admin/list-user-sessions\",\n {\n method: \"POST\",\n use: [adminMiddleware],\n body: z.object({\n userId: z.coerce.string().meta({\n description: \"The user id\"\n })\n }),\n metadata: {\n openapi: {\n operationId: \"listUserSessions\",\n summary: \"List user sessions\",\n description: \"List user sessions\",\n responses: {\n 200: {\n description: \"List of user sessions\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n sessions: {\n type: \"array\",\n items: {\n $ref: \"#/components/schemas/Session\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = ctx.context.session;\n const canListSessions = hasPermission({\n userId: ctx.context.session.user.id,\n role: session.user.role,\n options: opts,\n permissions: {\n session: [\"list\"]\n }\n });\n if (!canListSessions) {\n throw new APIError(\"FORBIDDEN\", {\n message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS\n });\n }\n const sessions = await ctx.context.internalAdapter.listSessions(\n ctx.body.userId\n );\n return {\n sessions\n };\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/admin/unban-user`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.unbanUser`\n *\n * **client:**\n * `authClient.admin.unbanUser`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-unban-user)\n */\n unbanUser: createAuthEndpoint(\n \"/admin/unban-user\",\n {\n method: \"POST\",\n body: z.object({\n userId: z.coerce.string().meta({\n description: \"The user id\"\n })\n }),\n use: [adminMiddleware],\n metadata: {\n openapi: {\n operationId: \"unbanUser\",\n summary: \"Unban a user\",\n description: \"Unban a user\",\n responses: {\n 200: {\n description: \"User unbanned\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n user: {\n $ref: \"#/components/schemas/User\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = ctx.context.session;\n const canBanUser = hasPermission({\n userId: ctx.context.session.user.id,\n role: session.user.role,\n options: opts,\n permissions: {\n user: [\"ban\"]\n }\n });\n if (!canBanUser) {\n throw new APIError(\"FORBIDDEN\", {\n message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_BAN_USERS\n });\n }\n const user = await ctx.context.internalAdapter.updateUser(\n ctx.body.userId,\n {\n banned: false,\n banExpires: null,\n banReason: null,\n updatedAt: /* @__PURE__ */ new Date()\n }\n );\n return ctx.json({\n user\n });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/admin/ban-user`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.banUser`\n *\n * **client:**\n * `authClient.admin.banUser`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-ban-user)\n */\n banUser: createAuthEndpoint(\n \"/admin/ban-user\",\n {\n method: \"POST\",\n body: z.object({\n userId: z.coerce.string().meta({\n description: \"The user id\"\n }),\n /**\n * Reason for the ban\n */\n banReason: z.string().meta({\n description: \"The reason for the ban\"\n }).optional(),\n /**\n * Number of seconds until the ban expires\n */\n banExpiresIn: z.number().meta({\n description: \"The number of seconds until the ban expires\"\n }).optional()\n }),\n use: [adminMiddleware],\n metadata: {\n openapi: {\n operationId: \"banUser\",\n summary: \"Ban a user\",\n description: \"Ban a user\",\n responses: {\n 200: {\n description: \"User banned\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n user: {\n $ref: \"#/components/schemas/User\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = ctx.context.session;\n const canBanUser = hasPermission({\n userId: ctx.context.session.user.id,\n role: session.user.role,\n options: opts,\n permissions: {\n user: [\"ban\"]\n }\n });\n if (!canBanUser) {\n throw new APIError(\"FORBIDDEN\", {\n message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_BAN_USERS\n });\n }\n if (ctx.body.userId === ctx.context.session.user.id) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ADMIN_ERROR_CODES.YOU_CANNOT_BAN_YOURSELF\n });\n }\n const user = await ctx.context.internalAdapter.updateUser(\n ctx.body.userId,\n {\n banned: true,\n banReason: ctx.body.banReason || options?.defaultBanReason || \"No reason\",\n banExpires: ctx.body.banExpiresIn ? getDate(ctx.body.banExpiresIn, \"sec\") : options?.defaultBanExpiresIn ? getDate(options.defaultBanExpiresIn, \"sec\") : void 0,\n updatedAt: /* @__PURE__ */ new Date()\n },\n ctx\n );\n await ctx.context.internalAdapter.deleteSessions(ctx.body.userId);\n return ctx.json({\n user\n });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/admin/impersonate-user`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.impersonateUser`\n *\n * **client:**\n * `authClient.admin.impersonateUser`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-impersonate-user)\n */\n impersonateUser: createAuthEndpoint(\n \"/admin/impersonate-user\",\n {\n method: \"POST\",\n body: z.object({\n userId: z.coerce.string().meta({\n description: \"The user id\"\n })\n }),\n use: [adminMiddleware],\n metadata: {\n openapi: {\n operationId: \"impersonateUser\",\n summary: \"Impersonate a user\",\n description: \"Impersonate a user\",\n responses: {\n 200: {\n description: \"Impersonation session created\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n session: {\n $ref: \"#/components/schemas/Session\"\n },\n user: {\n $ref: \"#/components/schemas/User\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const canImpersonateUser = hasPermission({\n userId: ctx.context.session.user.id,\n role: ctx.context.session.user.role,\n options: opts,\n permissions: {\n user: [\"impersonate\"]\n }\n });\n if (!canImpersonateUser) {\n throw new APIError(\"FORBIDDEN\", {\n message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS\n });\n }\n const targetUser = await ctx.context.internalAdapter.findUserById(\n ctx.body.userId\n );\n if (!targetUser) {\n throw new APIError(\"NOT_FOUND\", {\n message: \"User not found\"\n });\n }\n const session = await ctx.context.internalAdapter.createSession(\n targetUser.id,\n ctx,\n true,\n {\n impersonatedBy: ctx.context.session.user.id,\n expiresAt: options?.impersonationSessionDuration ? getDate(options.impersonationSessionDuration, \"sec\") : getDate(60 * 60, \"sec\")\n // 1 hour\n },\n true\n );\n if (!session) {\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n message: ADMIN_ERROR_CODES.FAILED_TO_CREATE_USER\n });\n }\n const authCookies = ctx.context.authCookies;\n deleteSessionCookie(ctx);\n const dontRememberMeCookie = await ctx.getSignedCookie(\n ctx.context.authCookies.dontRememberToken.name,\n ctx.context.secret\n );\n const adminCookieProp = ctx.context.createAuthCookie(\"admin_session\");\n await ctx.setSignedCookie(\n adminCookieProp.name,\n `${ctx.context.session.session.token}:${dontRememberMeCookie || \"\"}`,\n ctx.context.secret,\n authCookies.sessionToken.options\n );\n await setSessionCookie(\n ctx,\n {\n session,\n user: targetUser\n },\n true\n );\n return ctx.json({\n session,\n user: targetUser\n });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/admin/stop-impersonating`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.stopImpersonating`\n *\n * **client:**\n * `authClient.admin.stopImpersonating`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-stop-impersonating)\n */\n stopImpersonating: createAuthEndpoint(\n \"/admin/stop-impersonating\",\n {\n method: \"POST\",\n requireHeaders: true\n },\n async (ctx) => {\n const session = await getSessionFromCtx(ctx);\n if (!session) {\n throw new APIError(\"UNAUTHORIZED\");\n }\n if (!session.session.impersonatedBy) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"You are not impersonating anyone\"\n });\n }\n const user = await ctx.context.internalAdapter.findUserById(\n session.session.impersonatedBy\n );\n if (!user) {\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n message: \"Failed to find user\"\n });\n }\n const adminCookieName = ctx.context.createAuthCookie(\"admin_session\").name;\n const adminCookie = await ctx.getSignedCookie(\n adminCookieName,\n ctx.context.secret\n );\n if (!adminCookie) {\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n message: \"Failed to find admin session\"\n });\n }\n const [adminSessionToken, dontRememberMeCookie] = adminCookie?.split(\":\");\n const adminSession = await ctx.context.internalAdapter.findSession(adminSessionToken);\n if (!adminSession || adminSession.session.userId !== user.id) {\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n message: \"Failed to find admin session\"\n });\n }\n await ctx.context.internalAdapter.deleteSession(\n session.session.token\n );\n await setSessionCookie(ctx, adminSession, !!dontRememberMeCookie);\n return ctx.json(adminSession);\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/admin/revoke-user-session`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.revokeUserSession`\n *\n * **client:**\n * `authClient.admin.revokeUserSession`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-revoke-user-session)\n */\n revokeUserSession: createAuthEndpoint(\n \"/admin/revoke-user-session\",\n {\n method: \"POST\",\n body: z.object({\n sessionToken: z.string().meta({\n description: \"The session token\"\n })\n }),\n use: [adminMiddleware],\n metadata: {\n openapi: {\n operationId: \"revokeUserSession\",\n summary: \"Revoke a user session\",\n description: \"Revoke a user session\",\n responses: {\n 200: {\n description: \"Session revoked\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n success: {\n type: \"boolean\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = ctx.context.session;\n const canRevokeSession = hasPermission({\n userId: ctx.context.session.user.id,\n role: session.user.role,\n options: opts,\n permissions: {\n session: [\"revoke\"]\n }\n });\n if (!canRevokeSession) {\n throw new APIError(\"FORBIDDEN\", {\n message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS\n });\n }\n await ctx.context.internalAdapter.deleteSession(\n ctx.body.sessionToken\n );\n return ctx.json({\n success: true\n });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/admin/revoke-user-sessions`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.revokeUserSessions`\n *\n * **client:**\n * `authClient.admin.revokeUserSessions`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-revoke-user-sessions)\n */\n revokeUserSessions: createAuthEndpoint(\n \"/admin/revoke-user-sessions\",\n {\n method: \"POST\",\n body: z.object({\n userId: z.coerce.string().meta({\n description: \"The user id\"\n })\n }),\n use: [adminMiddleware],\n metadata: {\n openapi: {\n operationId: \"revokeUserSessions\",\n summary: \"Revoke all user sessions\",\n description: \"Revoke all user sessions\",\n responses: {\n 200: {\n description: \"Sessions revoked\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n success: {\n type: \"boolean\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = ctx.context.session;\n const canRevokeSession = hasPermission({\n userId: ctx.context.session.user.id,\n role: session.user.role,\n options: opts,\n permissions: {\n session: [\"revoke\"]\n }\n });\n if (!canRevokeSession) {\n throw new APIError(\"FORBIDDEN\", {\n message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS\n });\n }\n await ctx.context.internalAdapter.deleteSessions(ctx.body.userId);\n return ctx.json({\n success: true\n });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/admin/remove-user`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.removeUser`\n *\n * **client:**\n * `authClient.admin.removeUser`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-remove-user)\n */\n removeUser: createAuthEndpoint(\n \"/admin/remove-user\",\n {\n method: \"POST\",\n body: z.object({\n userId: z.coerce.string().meta({\n description: \"The user id\"\n })\n }),\n use: [adminMiddleware],\n metadata: {\n openapi: {\n operationId: \"removeUser\",\n summary: \"Remove a user\",\n description: \"Delete a user and all their sessions and accounts. Cannot be undone.\",\n responses: {\n 200: {\n description: \"User removed\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n success: {\n type: \"boolean\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = ctx.context.session;\n const canDeleteUser = hasPermission({\n userId: ctx.context.session.user.id,\n role: session.user.role,\n options: opts,\n permissions: {\n user: [\"delete\"]\n }\n });\n if (!canDeleteUser) {\n throw new APIError(\"FORBIDDEN\", {\n message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS\n });\n }\n const user = await ctx.context.internalAdapter.findUserById(\n ctx.body.userId\n );\n if (!user) {\n throw new APIError(\"NOT_FOUND\", {\n message: \"User not found\"\n });\n }\n await ctx.context.internalAdapter.deleteUser(ctx.body.userId);\n return ctx.json({\n success: true\n });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/admin/set-user-password`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.setUserPassword`\n *\n * **client:**\n * `authClient.admin.setUserPassword`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-set-user-password)\n */\n setUserPassword: createAuthEndpoint(\n \"/admin/set-user-password\",\n {\n method: \"POST\",\n body: z.object({\n newPassword: z.string().meta({\n description: \"The new password\"\n }),\n userId: z.coerce.string().meta({\n description: \"The user id\"\n })\n }),\n use: [adminMiddleware],\n metadata: {\n openapi: {\n operationId: \"setUserPassword\",\n summary: \"Set a user's password\",\n description: \"Set a user's password\",\n responses: {\n 200: {\n description: \"Password set\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n status: {\n type: \"boolean\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const canSetUserPassword = hasPermission({\n userId: ctx.context.session.user.id,\n role: ctx.context.session.user.role,\n options: opts,\n permissions: {\n user: [\"set-password\"]\n }\n });\n if (!canSetUserPassword) {\n throw new APIError(\"FORBIDDEN\", {\n message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD\n });\n }\n const hashedPassword = await ctx.context.password.hash(\n ctx.body.newPassword\n );\n await ctx.context.internalAdapter.updatePassword(\n ctx.body.userId,\n hashedPassword\n );\n return ctx.json({\n status: true\n });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/admin/has-permission`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.userHasPermission`\n *\n * **client:**\n * `authClient.admin.hasPermission`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-has-permission)\n */\n userHasPermission: createAuthEndpoint(\n \"/admin/has-permission\",\n {\n method: \"POST\",\n body: z.object({\n userId: z.coerce.string().optional().meta({\n description: `The user id. Eg: \"user-id\"`\n }),\n role: z.string().optional().meta({\n description: `The role to check permission for. Eg: \"admin\"`\n })\n }).and(\n z.union([\n z.object({\n permission: z.record(z.string(), z.array(z.string())),\n permissions: z.undefined()\n }),\n z.object({\n permission: z.undefined(),\n permissions: z.record(z.string(), z.array(z.string()))\n })\n ])\n ),\n metadata: {\n openapi: {\n description: \"Check if the user has permission\",\n requestBody: {\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n permission: {\n type: \"object\",\n description: \"The permission to check\",\n deprecated: true\n },\n permissions: {\n type: \"object\",\n description: \"The permission to check\"\n }\n },\n required: [\"permissions\"]\n }\n }\n }\n },\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n error: {\n type: \"string\"\n },\n success: {\n type: \"boolean\"\n }\n },\n required: [\"success\"]\n }\n }\n }\n }\n }\n },\n $Infer: {\n body: {}\n }\n }\n },\n async (ctx) => {\n if (!ctx.body?.permission && !ctx.body?.permissions) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"invalid permission check. no permission(s) were passed.\"\n });\n }\n const session = await getSessionFromCtx(ctx);\n if (!session && (ctx.request || ctx.headers) && !ctx.body.userId && !ctx.body.role) {\n throw new APIError(\"UNAUTHORIZED\");\n }\n const user = session?.user || await ctx.context.internalAdapter.findUserById(\n ctx.body.userId\n ) || (ctx.body.role ? { id: \"\", role: ctx.body.role } : null);\n if (!user) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"user not found\"\n });\n }\n const result = hasPermission({\n userId: user.id,\n role: user.role,\n options,\n permissions: ctx.body.permissions ?? ctx.body.permission\n });\n return ctx.json({\n error: null,\n success: result\n });\n }\n )\n },\n $ERROR_CODES: ADMIN_ERROR_CODES,\n schema: mergeSchema(schema, opts.schema),\n options\n };\n};\n\nexport { admin as a };\n","import { createAccessControl } from '../../access/index.mjs';\nimport '../../../shared/better-auth.DdzSJf-n.mjs';\n\nconst defaultStatements = {\n user: [\n \"create\",\n \"list\",\n \"set-role\",\n \"ban\",\n \"impersonate\",\n \"delete\",\n \"set-password\",\n \"update\"\n ],\n session: [\"list\", \"revoke\", \"delete\"]\n};\nconst defaultAc = createAccessControl(defaultStatements);\nconst adminAc = defaultAc.newRole({\n user: [\n \"create\",\n \"list\",\n \"set-role\",\n \"ban\",\n \"impersonate\",\n \"delete\",\n \"set-password\",\n \"update\"\n ],\n session: [\"list\", \"revoke\", \"delete\"]\n});\nconst userAc = defaultAc.newRole({\n user: [],\n session: []\n});\nconst defaultRoles = {\n admin: adminAc,\n user: userAc\n};\n\nexport { adminAc, defaultAc, defaultRoles, defaultStatements, userAc };\n","import { betterFetch } from '@better-fetch/fetch';\nimport { APIError } from 'better-call';\nimport { decodeJwt } from 'jose';\nimport * as z from 'zod/v4';\nimport { j as createAuthEndpoint, l as sessionMiddleware, B as BASE_ERROR_CODES, g as generateState, c as createAuthorizationURL, p as parseState, v as validateAuthorizationCode, h as handleOAuthUserInfo, r as refreshAccessToken } from '../../shared/better-auth.D4HhkCZJ.mjs';\nimport { setSessionCookie } from '../../cookies/index.mjs';\nimport '../../shared/better-auth.n2KFGwjY.mjs';\nimport '../../shared/better-auth.8zoxzg-F.mjs';\nimport '../../shared/better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport '@better-auth/utils/hash';\nimport '@better-auth/utils/base64';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport '../../shared/better-auth.B4Qoxdgc.mjs';\nimport '../../shared/better-auth.CW6D9eSx.mjs';\nimport '../../crypto/index.mjs';\nimport '../../shared/better-auth.VTXNLFMT.mjs';\nimport '../../shared/better-auth.DdzSJf-n.mjs';\nimport '../../shared/better-auth.tB5eU6EY.mjs';\nimport '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport 'jose/errors';\nimport '@better-auth/utils/random';\n\nasync function getUserInfo(tokens, finalUserInfoUrl) {\n if (tokens.idToken) {\n const decoded = decodeJwt(tokens.idToken);\n if (decoded) {\n if (decoded.sub && decoded.email) {\n return {\n id: decoded.sub,\n emailVerified: decoded.email_verified,\n image: decoded.picture,\n ...decoded\n };\n }\n }\n }\n if (!finalUserInfoUrl) {\n return null;\n }\n const userInfo = await betterFetch(finalUserInfoUrl, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${tokens.accessToken}`\n }\n });\n return {\n id: userInfo.data?.sub,\n emailVerified: userInfo.data?.email_verified,\n email: userInfo.data?.email,\n image: userInfo.data?.picture,\n name: userInfo.data?.name,\n ...userInfo.data\n };\n}\nconst genericOAuth = (options) => {\n const ERROR_CODES = {\n INVALID_OAUTH_CONFIGURATION: \"Invalid OAuth configuration\"\n };\n return {\n id: \"generic-oauth\",\n init: (ctx) => {\n const genericProviders = options.config.map((c) => {\n let finalUserInfoUrl = c.userInfoUrl;\n return {\n id: c.providerId,\n name: c.providerId,\n createAuthorizationURL(data) {\n return createAuthorizationURL({\n id: c.providerId,\n options: {\n clientId: c.clientId,\n clientSecret: c.clientSecret,\n redirectURI: c.redirectURI\n },\n authorizationEndpoint: c.authorizationUrl,\n state: data.state,\n codeVerifier: c.pkce ? data.codeVerifier : void 0,\n scopes: c.scopes || [],\n redirectURI: `${ctx.baseURL}/oauth2/callback/${c.providerId}`\n });\n },\n async validateAuthorizationCode(data) {\n let finalTokenUrl = c.tokenUrl;\n if (c.discoveryUrl) {\n const discovery = await betterFetch(c.discoveryUrl, {\n method: \"GET\",\n headers: c.discoveryHeaders\n });\n if (discovery.data) {\n finalTokenUrl = discovery.data.token_endpoint;\n finalUserInfoUrl = discovery.data.userinfo_endpoint;\n }\n }\n if (!finalTokenUrl) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Invalid OAuth configuration. Token URL not found.\"\n });\n }\n return validateAuthorizationCode({\n headers: c.authorizationHeaders,\n code: data.code,\n codeVerifier: data.codeVerifier,\n redirectURI: data.redirectURI,\n options: {\n clientId: c.clientId,\n clientSecret: c.clientSecret,\n redirectURI: c.redirectURI\n },\n tokenEndpoint: finalTokenUrl,\n authentication: c.authentication\n });\n },\n async refreshAccessToken(refreshToken) {\n let finalTokenUrl = c.tokenUrl;\n if (c.discoveryUrl) {\n const discovery = await betterFetch(c.discoveryUrl, {\n method: \"GET\",\n headers: c.discoveryHeaders\n });\n if (discovery.data) {\n finalTokenUrl = discovery.data.token_endpoint;\n }\n }\n if (!finalTokenUrl) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Invalid OAuth configuration. Token URL not found.\"\n });\n }\n return refreshAccessToken({\n refreshToken,\n options: {\n clientId: c.clientId,\n clientSecret: c.clientSecret\n },\n authentication: c.authentication,\n tokenEndpoint: finalTokenUrl\n });\n },\n async getUserInfo(tokens) {\n const userInfo = c.getUserInfo ? await c.getUserInfo(tokens) : await getUserInfo(tokens, finalUserInfoUrl);\n if (!userInfo) {\n return null;\n }\n return {\n user: {\n id: userInfo?.id,\n email: userInfo?.email,\n emailVerified: userInfo?.emailVerified,\n image: userInfo?.image,\n name: userInfo?.name,\n ...c.mapProfileToUser?.(userInfo)\n },\n data: userInfo\n };\n }\n };\n });\n return {\n context: {\n socialProviders: genericProviders.concat(ctx.socialProviders)\n }\n };\n },\n endpoints: {\n /**\n * ### Endpoint\n *\n * POST `/sign-in/oauth2`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.signInWithOAuth2`\n *\n * **client:**\n * `authClient.signIn.oauth2`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/sign-in#api-method-sign-in-oauth2)\n */\n signInWithOAuth2: createAuthEndpoint(\n \"/sign-in/oauth2\",\n {\n method: \"POST\",\n body: z.object({\n providerId: z.string().meta({\n description: \"The provider ID for the OAuth provider\"\n }),\n callbackURL: z.string().meta({\n description: \"The URL to redirect to after sign in\"\n }).optional(),\n errorCallbackURL: z.string().meta({\n description: \"The URL to redirect to if an error occurs\"\n }).optional(),\n newUserCallbackURL: z.string().meta({\n description: 'The URL to redirect to after login if the user is new. Eg: \"/welcome\"'\n }).optional(),\n disableRedirect: z.boolean().meta({\n description: \"Disable redirect\"\n }).optional(),\n scopes: z.array(z.string()).meta({\n description: \"Scopes to be passed to the provider authorization request.\"\n }).optional(),\n requestSignUp: z.boolean().meta({\n description: \"Explicitly request sign-up. Useful when disableImplicitSignUp is true for this provider. Eg: false\"\n }).optional()\n }),\n metadata: {\n openapi: {\n description: \"Sign in with OAuth2\",\n responses: {\n 200: {\n description: \"Sign in with OAuth2\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n url: {\n type: \"string\"\n },\n redirect: {\n type: \"boolean\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const { providerId } = ctx.body;\n const config = options.config.find(\n (c) => c.providerId === providerId\n );\n if (!config) {\n throw new APIError(\"BAD_REQUEST\", {\n message: `No config found for provider ${providerId}`\n });\n }\n const {\n discoveryUrl,\n authorizationUrl,\n tokenUrl,\n clientId,\n clientSecret,\n scopes,\n redirectURI,\n responseType,\n pkce,\n prompt,\n accessType,\n authorizationUrlParams,\n responseMode,\n authentication\n } = config;\n let finalAuthUrl = authorizationUrl;\n let finalTokenUrl = tokenUrl;\n if (discoveryUrl) {\n const discovery = await betterFetch(discoveryUrl, {\n method: \"GET\",\n headers: config.discoveryHeaders,\n onError(context) {\n ctx.context.logger.error(context.error.message, context.error, {\n discoveryUrl\n });\n }\n });\n if (discovery.data) {\n finalAuthUrl = discovery.data.authorization_endpoint;\n finalTokenUrl = discovery.data.token_endpoint;\n }\n }\n if (!finalAuthUrl || !finalTokenUrl) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_OAUTH_CONFIGURATION\n });\n }\n if (authorizationUrlParams) {\n const withAdditionalParams = new URL(finalAuthUrl);\n for (const [paramName, paramValue] of Object.entries(\n authorizationUrlParams\n )) {\n withAdditionalParams.searchParams.set(paramName, paramValue);\n }\n finalAuthUrl = withAdditionalParams.toString();\n }\n const { state, codeVerifier } = await generateState(ctx);\n const authUrl = await createAuthorizationURL({\n id: providerId,\n options: {\n clientId,\n redirectURI\n },\n authorizationEndpoint: finalAuthUrl,\n state,\n codeVerifier: pkce ? codeVerifier : void 0,\n scopes: ctx.body.scopes ? [...ctx.body.scopes, ...scopes || []] : scopes || [],\n redirectURI: `${ctx.context.baseURL}/oauth2/callback/${providerId}`,\n prompt,\n accessType,\n responseType,\n responseMode,\n additionalParams: authorizationUrlParams\n });\n return ctx.json({\n url: authUrl.toString(),\n redirect: !ctx.body.disableRedirect\n });\n }\n ),\n oAuth2Callback: createAuthEndpoint(\n \"/oauth2/callback/:providerId\",\n {\n method: \"GET\",\n query: z.object({\n code: z.string().meta({\n description: \"The OAuth2 code\"\n }).optional(),\n error: z.string().meta({\n description: \"The error message, if any\"\n }).optional(),\n error_description: z.string().meta({\n description: \"The error description, if any\"\n }).optional(),\n state: z.string().meta({\n description: \"The state parameter from the OAuth2 request\"\n }).optional()\n }),\n metadata: {\n client: false,\n openapi: {\n description: \"OAuth2 callback\",\n responses: {\n 200: {\n description: \"OAuth2 callback\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n url: {\n type: \"string\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const defaultErrorURL = ctx.context.options.onAPIError?.errorURL || `${ctx.context.baseURL}/error`;\n if (ctx.query.error || !ctx.query.code) {\n throw ctx.redirect(\n `${defaultErrorURL}?error=${ctx.query.error || \"oAuth_code_missing\"}&error_description=${ctx.query.error_description}`\n );\n }\n const provider = options.config.find(\n (p) => p.providerId === ctx.params.providerId\n );\n if (!provider) {\n throw new APIError(\"BAD_REQUEST\", {\n message: `No config found for provider ${ctx.params.providerId}`\n });\n }\n let tokens = void 0;\n const parsedState = await parseState(ctx);\n const {\n callbackURL,\n codeVerifier,\n errorURL,\n requestSignUp,\n newUserURL,\n link\n } = parsedState;\n const code = ctx.query.code;\n function redirectOnError(error) {\n const defaultErrorURL2 = ctx.context.options.onAPIError?.errorURL || `${ctx.context.baseURL}/error`;\n let url = errorURL || defaultErrorURL2;\n if (url.includes(\"?\")) {\n url = `${url}&error=${error}`;\n } else {\n url = `${url}?error=${error}`;\n }\n throw ctx.redirect(url);\n }\n let finalTokenUrl = provider.tokenUrl;\n let finalUserInfoUrl = provider.userInfoUrl;\n if (provider.discoveryUrl) {\n const discovery = await betterFetch(provider.discoveryUrl, {\n method: \"GET\",\n headers: provider.discoveryHeaders\n });\n if (discovery.data) {\n finalTokenUrl = discovery.data.token_endpoint;\n finalUserInfoUrl = discovery.data.userinfo_endpoint;\n }\n }\n try {\n if (!finalTokenUrl) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Invalid OAuth configuration.\"\n });\n }\n tokens = await validateAuthorizationCode({\n headers: provider.authorizationHeaders,\n code,\n codeVerifier: provider.pkce ? codeVerifier : void 0,\n redirectURI: `${ctx.context.baseURL}/oauth2/callback/${provider.providerId}`,\n options: {\n clientId: provider.clientId,\n clientSecret: provider.clientSecret,\n redirectURI: provider.redirectURI\n },\n tokenEndpoint: finalTokenUrl,\n authentication: provider.authentication,\n additionalParams: provider.tokenUrlParams\n });\n } catch (e) {\n ctx.context.logger.error(\n e && typeof e === \"object\" && \"name\" in e ? e.name : \"\",\n e\n );\n throw redirectOnError(\"oauth_code_verification_failed\");\n }\n if (!tokens) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Invalid OAuth configuration.\"\n });\n }\n const userInfo = provider.getUserInfo ? await provider.getUserInfo(tokens) : await getUserInfo(tokens, finalUserInfoUrl);\n if (!userInfo) {\n throw redirectOnError(\"user_info_is_missing\");\n }\n const mapUser = provider.mapProfileToUser ? await provider.mapProfileToUser(userInfo) : userInfo;\n if (!mapUser?.email) {\n ctx.context.logger.error(\"Unable to get user info\", userInfo);\n throw redirectOnError(\"email_is_missing\");\n }\n if (link) {\n if (ctx.context.options.account?.accountLinking?.allowDifferentEmails !== true && link.email !== mapUser.email.toLowerCase()) {\n return redirectOnError(\"email_doesn't_match\");\n }\n const existingAccount = await ctx.context.internalAdapter.findAccountByProviderId(\n userInfo.id,\n provider.providerId\n );\n if (existingAccount) {\n if (existingAccount.userId !== link.userId) {\n return redirectOnError(\n \"account_already_linked_to_different_user\"\n );\n }\n const updateData = Object.fromEntries(\n Object.entries({\n accessToken: tokens.accessToken,\n idToken: tokens.idToken,\n refreshToken: tokens.refreshToken,\n accessTokenExpiresAt: tokens.accessTokenExpiresAt,\n refreshTokenExpiresAt: tokens.refreshTokenExpiresAt,\n scope: tokens.scopes?.join(\",\")\n }).filter(([_, value]) => value !== void 0)\n );\n await ctx.context.internalAdapter.updateAccount(\n existingAccount.id,\n updateData\n );\n } else {\n const newAccount = await ctx.context.internalAdapter.createAccount({\n userId: link.userId,\n providerId: provider.providerId,\n accountId: userInfo.id,\n accessToken: tokens.accessToken,\n accessTokenExpiresAt: tokens.accessTokenExpiresAt,\n refreshTokenExpiresAt: tokens.refreshTokenExpiresAt,\n scope: tokens.scopes?.join(\",\"),\n refreshToken: tokens.refreshToken,\n idToken: tokens.idToken\n });\n if (!newAccount) {\n return redirectOnError(\"unable_to_link_account\");\n }\n }\n let toRedirectTo2;\n try {\n const url = callbackURL;\n toRedirectTo2 = url.toString();\n } catch {\n toRedirectTo2 = callbackURL;\n }\n throw ctx.redirect(toRedirectTo2);\n }\n const result = await handleOAuthUserInfo(ctx, {\n userInfo: {\n ...userInfo,\n ...mapUser\n },\n account: {\n providerId: provider.providerId,\n accountId: userInfo.id,\n ...tokens,\n scope: tokens.scopes?.join(\",\")\n },\n callbackURL,\n disableSignUp: provider.disableImplicitSignUp && !requestSignUp || provider.disableSignUp,\n overrideUserInfo: provider.overrideUserInfo\n });\n if (result.error) {\n return redirectOnError(result.error.split(\" \").join(\"_\"));\n }\n const { session, user } = result.data;\n await setSessionCookie(ctx, {\n session,\n user\n });\n let toRedirectTo;\n try {\n const url = result.isRegister ? newUserURL || callbackURL : callbackURL;\n toRedirectTo = url.toString();\n } catch {\n toRedirectTo = result.isRegister ? newUserURL || callbackURL : callbackURL;\n }\n throw ctx.redirect(toRedirectTo);\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/oauth2/link`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.oAuth2LinkAccount`\n *\n * **client:**\n * `authClient.oauth2.link`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/generic-oauth#api-method-oauth2-link)\n */\n oAuth2LinkAccount: createAuthEndpoint(\n \"/oauth2/link\",\n {\n method: \"POST\",\n body: z.object({\n providerId: z.string(),\n /**\n * Callback URL to redirect to after the user has signed in.\n */\n callbackURL: z.string(),\n /**\n * Additional scopes to request when linking the account.\n * This is useful for requesting additional permissions when\n * linking a social account compared to the initial authentication.\n */\n scopes: z.array(z.string()).meta({\n description: \"Additional scopes to request when linking the account\"\n }).optional(),\n /**\n * The URL to redirect to if there is an error during the link process.\n */\n errorCallbackURL: z.string().meta({\n description: \"The URL to redirect to if there is an error during the link process\"\n }).optional()\n }),\n use: [sessionMiddleware],\n metadata: {\n openapi: {\n description: \"Link an OAuth2 account to the current user session\",\n responses: {\n \"200\": {\n description: \"Authorization URL generated successfully for linking an OAuth2 account\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n url: {\n type: \"string\",\n format: \"uri\",\n description: \"The authorization URL to redirect the user to for linking the OAuth2 account\"\n },\n redirect: {\n type: \"boolean\",\n description: \"Indicates that the client should redirect to the provided URL\",\n enum: [true]\n }\n },\n required: [\"url\", \"redirect\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (c) => {\n const session = c.context.session;\n const provider = options.config.find(\n (p) => p.providerId === c.body.providerId\n );\n if (!provider) {\n throw new APIError(\"NOT_FOUND\", {\n message: BASE_ERROR_CODES.PROVIDER_NOT_FOUND\n });\n }\n const {\n providerId,\n clientId,\n clientSecret,\n redirectURI,\n authorizationUrl,\n discoveryUrl,\n pkce,\n scopes,\n prompt,\n accessType,\n authorizationUrlParams\n } = provider;\n let finalAuthUrl = authorizationUrl;\n if (!finalAuthUrl) {\n if (!discoveryUrl) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_OAUTH_CONFIGURATION\n });\n }\n const discovery = await betterFetch(discoveryUrl, {\n method: \"GET\",\n headers: provider.discoveryHeaders,\n onError(context) {\n c.context.logger.error(context.error.message, context.error, {\n discoveryUrl\n });\n }\n });\n if (discovery.data) {\n finalAuthUrl = discovery.data.authorization_endpoint;\n }\n }\n if (!finalAuthUrl) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_OAUTH_CONFIGURATION\n });\n }\n const state = await generateState(c, {\n userId: session.user.id,\n email: session.user.email\n });\n const url = await createAuthorizationURL({\n id: providerId,\n options: {\n clientId,\n redirectURI: redirectURI || `${c.context.baseURL}/oauth2/callback/${providerId}`\n },\n authorizationEndpoint: finalAuthUrl,\n state: state.state,\n codeVerifier: pkce ? state.codeVerifier : void 0,\n scopes: c.body.scopes || scopes || [],\n redirectURI: redirectURI || `${c.context.baseURL}/oauth2/callback/${providerId}`,\n prompt,\n accessType,\n additionalParams: authorizationUrlParams\n });\n return c.json({\n url: url.toString(),\n redirect: true\n });\n }\n )\n },\n $ERROR_CODES: ERROR_CODES\n };\n};\n\nexport { genericOAuth };\n","import * as z from 'zod/v4';\n\nconst schema = {\n jwks: {\n fields: {\n publicKey: {\n type: \"string\",\n required: true\n },\n privateKey: {\n type: \"string\",\n required: true\n },\n createdAt: {\n type: \"date\",\n required: true\n }\n }\n }\n};\nz.object({\n id: z.string(),\n publicKey: z.string(),\n privateKey: z.string(),\n createdAt: z.date()\n});\n\nexport { schema as s };\n","import { s as schema } from '../../shared/better-auth.CGrHn1Ih.mjs';\nimport { importJWK, SignJWT, generateKeyPair, exportJWK } from 'jose';\nimport { B as BetterAuthError } from '../../shared/better-auth.DdzSJf-n.mjs';\nimport { symmetricEncrypt, symmetricDecrypt } from '../../crypto/index.mjs';\nimport 'better-call';\nimport { i as createAuthMiddleware, j as createAuthEndpoint, l as sessionMiddleware } from '../../shared/better-auth.D4HhkCZJ.mjs';\nimport 'zod/v4';\nimport '../../shared/better-auth.8zoxzg-F.mjs';\nimport '@better-auth/utils/base64';\nimport '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport { m as mergeSchema } from '../../shared/better-auth.n2KFGwjY.mjs';\nimport '../../shared/better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport '@better-auth/utils/hash';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport '../../shared/better-auth.B4Qoxdgc.mjs';\nimport '@better-auth/utils/random';\nimport '../../shared/better-auth.CW6D9eSx.mjs';\nimport '@better-fetch/fetch';\nimport '../../shared/better-auth.VTXNLFMT.mjs';\nimport '../../cookies/index.mjs';\nimport '../../shared/better-auth.tB5eU6EY.mjs';\nimport 'jose/errors';\n\nconst getJwksAdapter = (adapter) => {\n return {\n getAllKeys: async () => {\n return await adapter.findMany({\n model: \"jwks\"\n });\n },\n getLatestKey: async () => {\n const key = await adapter.findMany({\n model: \"jwks\",\n sortBy: {\n field: \"createdAt\",\n direction: \"desc\"\n },\n limit: 1\n });\n return key[0];\n },\n createJwk: async (webKey) => {\n const jwk = await adapter.create({\n model: \"jwks\",\n data: {\n ...webKey,\n createdAt: /* @__PURE__ */ new Date()\n }\n });\n return jwk;\n }\n };\n};\n\nasync function getJwtToken(ctx, options) {\n const adapter = getJwksAdapter(ctx.context.adapter);\n let key = await adapter.getLatestKey();\n const privateKeyEncryptionEnabled = !options?.jwks?.disablePrivateKeyEncryption;\n if (key === void 0) {\n const { publicWebKey, privateWebKey: privateWebKey2 } = await generateExportedKeyPair(options);\n const stringifiedPrivateWebKey = JSON.stringify(privateWebKey2);\n let jwk = {\n publicKey: JSON.stringify(publicWebKey),\n privateKey: privateKeyEncryptionEnabled ? JSON.stringify(\n await symmetricEncrypt({\n key: ctx.context.secret,\n data: stringifiedPrivateWebKey\n })\n ) : stringifiedPrivateWebKey,\n createdAt: /* @__PURE__ */ new Date()\n };\n key = await adapter.createJwk(jwk);\n }\n let privateWebKey = privateKeyEncryptionEnabled ? await symmetricDecrypt({\n key: ctx.context.secret,\n data: JSON.parse(key.privateKey)\n }).catch(() => {\n throw new BetterAuthError(\n \"Failed to decrypt private private key. Make sure the secret currently in use is the same as the one used to encrypt the private key. If you are using a different secret, either cleanup your jwks or disable private key encryption.\"\n );\n }) : key.privateKey;\n const privateKey = await importJWK(\n JSON.parse(privateWebKey),\n options?.jwks?.keyPairConfig?.alg ?? \"EdDSA\"\n );\n const payload = !options?.jwt?.definePayload ? ctx.context.session.user : await options?.jwt.definePayload(ctx.context.session);\n const jwt = await new SignJWT(payload).setProtectedHeader({\n alg: options?.jwks?.keyPairConfig?.alg ?? \"EdDSA\",\n kid: key.id\n }).setIssuedAt().setIssuer(options?.jwt?.issuer ?? ctx.context.options.baseURL).setAudience(options?.jwt?.audience ?? ctx.context.options.baseURL).setExpirationTime(options?.jwt?.expirationTime ?? \"15m\").setSubject(\n await options?.jwt?.getSubject?.(ctx.context.session) ?? ctx.context.session.user.id\n ).sign(privateKey);\n return jwt;\n}\n\nasync function generateExportedKeyPair(options) {\n const { alg, ...cfg } = options?.jwks?.keyPairConfig ?? {\n alg: \"EdDSA\",\n crv: \"Ed25519\"\n };\n const keyPairConfig = {\n ...cfg,\n extractable: true\n };\n const { publicKey, privateKey } = await generateKeyPair(alg, keyPairConfig);\n const publicWebKey = await exportJWK(publicKey);\n const privateWebKey = await exportJWK(privateKey);\n return { publicWebKey, privateWebKey };\n}\nconst jwt = (options) => {\n return {\n id: \"jwt\",\n endpoints: {\n getJwks: createAuthEndpoint(\n \"/jwks\",\n {\n method: \"GET\",\n metadata: {\n openapi: {\n description: \"Get the JSON Web Key Set\",\n responses: {\n \"200\": {\n description: \"JSON Web Key Set retrieved successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n keys: {\n type: \"array\",\n description: \"Array of public JSON Web Keys\",\n items: {\n type: \"object\",\n properties: {\n kid: {\n type: \"string\",\n description: \"Key ID uniquely identifying the key, corresponds to the 'id' from the stored Jwk\"\n },\n kty: {\n type: \"string\",\n description: \"Key type (e.g., 'RSA', 'EC', 'OKP')\"\n },\n alg: {\n type: \"string\",\n description: \"Algorithm intended for use with the key (e.g., 'EdDSA', 'RS256')\"\n },\n use: {\n type: \"string\",\n description: \"Intended use of the public key (e.g., 'sig' for signature)\",\n enum: [\"sig\"],\n nullable: true\n },\n n: {\n type: \"string\",\n description: \"Modulus for RSA keys (base64url-encoded)\",\n nullable: true\n },\n e: {\n type: \"string\",\n description: \"Exponent for RSA keys (base64url-encoded)\",\n nullable: true\n },\n crv: {\n type: \"string\",\n description: \"Curve name for elliptic curve keys (e.g., 'Ed25519', 'P-256')\",\n nullable: true\n },\n x: {\n type: \"string\",\n description: \"X coordinate for elliptic curve keys (base64url-encoded)\",\n nullable: true\n },\n y: {\n type: \"string\",\n description: \"Y coordinate for elliptic curve keys (base64url-encoded)\",\n nullable: true\n }\n },\n required: [\"kid\", \"kty\", \"alg\"]\n }\n }\n },\n required: [\"keys\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const adapter = getJwksAdapter(ctx.context.adapter);\n const keySets = await adapter.getAllKeys();\n if (keySets.length === 0) {\n const { alg, ...cfg } = options?.jwks?.keyPairConfig ?? {\n alg: \"EdDSA\",\n crv: \"Ed25519\"\n };\n const keyPairConfig = {\n ...cfg,\n extractable: true\n };\n const { publicKey, privateKey } = await generateKeyPair(\n alg,\n keyPairConfig\n );\n const publicWebKey = await exportJWK(publicKey);\n const privateWebKey = await exportJWK(privateKey);\n const stringifiedPrivateWebKey = JSON.stringify(privateWebKey);\n const privateKeyEncryptionEnabled = !options?.jwks?.disablePrivateKeyEncryption;\n let jwk = {\n publicKey: JSON.stringify({ alg, ...publicWebKey }),\n privateKey: privateKeyEncryptionEnabled ? JSON.stringify(\n await symmetricEncrypt({\n key: ctx.context.secret,\n data: stringifiedPrivateWebKey\n })\n ) : stringifiedPrivateWebKey,\n createdAt: /* @__PURE__ */ new Date()\n };\n await adapter.createJwk(jwk);\n return ctx.json({\n keys: [\n {\n ...publicWebKey,\n alg,\n kid: jwk.id\n }\n ]\n });\n }\n return ctx.json({\n keys: keySets.map((keySet) => ({\n ...JSON.parse(keySet.publicKey),\n kid: keySet.id\n }))\n });\n }\n ),\n getToken: createAuthEndpoint(\n \"/token\",\n {\n method: \"GET\",\n requireHeaders: true,\n use: [sessionMiddleware],\n metadata: {\n openapi: {\n description: \"Get a JWT token\",\n responses: {\n 200: {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n token: {\n type: \"string\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const jwt2 = await getJwtToken(ctx, options);\n return ctx.json({\n token: jwt2\n });\n }\n )\n },\n hooks: {\n after: [\n {\n matcher(context) {\n return context.path === \"/get-session\";\n },\n handler: createAuthMiddleware(async (ctx) => {\n const session = ctx.context.session || ctx.context.newSession;\n if (session && session.session) {\n const jwt2 = await getJwtToken(ctx, options);\n const exposedHeaders = ctx.context.responseHeaders?.get(\n \"access-control-expose-headers\"\n ) || \"\";\n const headersSet = new Set(\n exposedHeaders.split(\",\").map((header) => header.trim()).filter(Boolean)\n );\n headersSet.add(\"set-auth-jwt\");\n ctx.setHeader(\"set-auth-jwt\", jwt2);\n ctx.setHeader(\n \"Access-Control-Expose-Headers\",\n Array.from(headersSet).join(\", \")\n );\n }\n })\n }\n ]\n },\n schema: mergeSchema(schema, options?.schema)\n };\n};\n\nexport { generateExportedKeyPair, getJwtToken, jwt };\n","import * as z from 'zod/v4';\nimport { APIError } from 'better-call';\nimport { i as createAuthMiddleware, j as createAuthEndpoint, l as sessionMiddleware } from '../../shared/better-auth.D4HhkCZJ.mjs';\nimport { parseSetCookieHeader, parseCookies, setSessionCookie, deleteSessionCookie } from '../../cookies/index.mjs';\nimport '../../shared/better-auth.n2KFGwjY.mjs';\nimport '../../shared/better-auth.8zoxzg-F.mjs';\nimport '../../shared/better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport '../../shared/better-auth.CW6D9eSx.mjs';\nimport '@better-auth/utils/hash';\nimport '@better-auth/utils/base64';\nimport '../../crypto/index.mjs';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport 'jose';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport '../../shared/better-auth.B4Qoxdgc.mjs';\nimport '@better-auth/utils/random';\nimport '@better-fetch/fetch';\nimport '../../shared/better-auth.VTXNLFMT.mjs';\nimport '../../shared/better-auth.DdzSJf-n.mjs';\nimport '../../shared/better-auth.tB5eU6EY.mjs';\nimport '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport 'jose/errors';\n\nconst multiSession = (options) => {\n const opts = {\n maximumSessions: 5,\n ...options\n };\n const isMultiSessionCookie = (key) => key.includes(\"_multi-\");\n const ERROR_CODES = {\n INVALID_SESSION_TOKEN: \"Invalid session token\"\n };\n return {\n id: \"multi-session\",\n endpoints: {\n /**\n * ### Endpoint\n *\n * GET `/multi-session/list-device-sessions`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.listDeviceSessions`\n *\n * **client:**\n * `authClient.multiSession.listDeviceSessions`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/multi-session#api-method-multi-session-list-device-sessions)\n */\n listDeviceSessions: createAuthEndpoint(\n \"/multi-session/list-device-sessions\",\n {\n method: \"GET\",\n requireHeaders: true\n },\n async (ctx) => {\n const cookieHeader = ctx.headers?.get(\"cookie\");\n if (!cookieHeader) return ctx.json([]);\n const cookies = Object.fromEntries(parseCookies(cookieHeader));\n const sessionTokens = (await Promise.all(\n Object.entries(cookies).filter(([key]) => isMultiSessionCookie(key)).map(\n async ([key]) => await ctx.getSignedCookie(key, ctx.context.secret)\n )\n )).filter((v) => v !== null);\n if (!sessionTokens.length) return ctx.json([]);\n const sessions = await ctx.context.internalAdapter.findSessions(sessionTokens);\n const validSessions = sessions.filter(\n (session) => session && session.session.expiresAt > /* @__PURE__ */ new Date()\n );\n const uniqueUserSessions = validSessions.reduce(\n (acc, session) => {\n if (!acc.find((s) => s.user.id === session.user.id)) {\n acc.push(session);\n }\n return acc;\n },\n []\n );\n return ctx.json(uniqueUserSessions);\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/multi-session/set-active`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.setActiveSession`\n *\n * **client:**\n * `authClient.multiSession.setActive`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/multi-session#api-method-multi-session-set-active)\n */\n setActiveSession: createAuthEndpoint(\n \"/multi-session/set-active\",\n {\n method: \"POST\",\n body: z.object({\n sessionToken: z.string().meta({\n description: \"The session token to set as active\"\n })\n }),\n requireHeaders: true,\n use: [sessionMiddleware],\n metadata: {\n openapi: {\n description: \"Set the active session\",\n responses: {\n 200: {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n session: {\n $ref: \"#/components/schemas/Session\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const sessionToken = ctx.body.sessionToken;\n const multiSessionCookieName = `${ctx.context.authCookies.sessionToken.name}_multi-${sessionToken.toLowerCase()}`;\n const sessionCookie = await ctx.getSignedCookie(\n multiSessionCookieName,\n ctx.context.secret\n );\n if (!sessionCookie) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: ERROR_CODES.INVALID_SESSION_TOKEN\n });\n }\n const session = await ctx.context.internalAdapter.findSession(sessionToken);\n if (!session || session.session.expiresAt < /* @__PURE__ */ new Date()) {\n ctx.setCookie(multiSessionCookieName, \"\", {\n ...ctx.context.authCookies.sessionToken.options,\n maxAge: 0\n });\n throw new APIError(\"UNAUTHORIZED\", {\n message: ERROR_CODES.INVALID_SESSION_TOKEN\n });\n }\n await setSessionCookie(ctx, session);\n return ctx.json(session);\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/multi-session/revoke`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.revokeDeviceSession`\n *\n * **client:**\n * `authClient.multiSession.revoke`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/multi-session#api-method-multi-session-revoke)\n */\n revokeDeviceSession: createAuthEndpoint(\n \"/multi-session/revoke\",\n {\n method: \"POST\",\n body: z.object({\n sessionToken: z.string().meta({\n description: \"The session token to revoke\"\n })\n }),\n requireHeaders: true,\n use: [sessionMiddleware],\n metadata: {\n openapi: {\n description: \"Revoke a device session\",\n responses: {\n 200: {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n status: {\n type: \"boolean\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const sessionToken = ctx.body.sessionToken;\n const multiSessionCookieName = `${ctx.context.authCookies.sessionToken.name}_multi-${sessionToken.toLowerCase()}`;\n const sessionCookie = await ctx.getSignedCookie(\n multiSessionCookieName,\n ctx.context.secret\n );\n if (!sessionCookie) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: ERROR_CODES.INVALID_SESSION_TOKEN\n });\n }\n await ctx.context.internalAdapter.deleteSession(sessionToken);\n ctx.setCookie(multiSessionCookieName, \"\", {\n ...ctx.context.authCookies.sessionToken.options,\n maxAge: 0\n });\n const isActive = ctx.context.session?.session.token === sessionToken;\n if (!isActive) return ctx.json({ status: true });\n const cookieHeader = ctx.headers?.get(\"cookie\");\n if (cookieHeader) {\n const cookies = Object.fromEntries(parseCookies(cookieHeader));\n const sessionTokens = (await Promise.all(\n Object.entries(cookies).filter(([key]) => isMultiSessionCookie(key)).map(\n async ([key]) => await ctx.getSignedCookie(key, ctx.context.secret)\n )\n )).filter((v) => v !== void 0);\n const internalAdapter = ctx.context.internalAdapter;\n if (sessionTokens.length > 0) {\n const sessions = await internalAdapter.findSessions(sessionTokens);\n const validSessions = sessions.filter(\n (session) => session && session.session.expiresAt > /* @__PURE__ */ new Date()\n );\n if (validSessions.length > 0) {\n const nextSession = validSessions[0];\n await setSessionCookie(ctx, nextSession);\n } else {\n deleteSessionCookie(ctx);\n }\n } else {\n deleteSessionCookie(ctx);\n }\n } else {\n deleteSessionCookie(ctx);\n }\n return ctx.json({\n status: true\n });\n }\n )\n },\n hooks: {\n after: [\n {\n matcher: () => true,\n handler: createAuthMiddleware(async (ctx) => {\n const cookieString = ctx.context.responseHeaders?.get(\"set-cookie\");\n if (!cookieString) return;\n const setCookies = parseSetCookieHeader(cookieString);\n const sessionCookieConfig = ctx.context.authCookies.sessionToken;\n const sessionToken = ctx.context.newSession?.session.token;\n if (!sessionToken) return;\n const cookies = parseCookies(ctx.headers?.get(\"cookie\") || \"\");\n const cookieName = `${sessionCookieConfig.name}_multi-${sessionToken.toLowerCase()}`;\n if (setCookies.get(cookieName) || cookies.get(cookieName)) return;\n const currentMultiSessions = Object.keys(Object.fromEntries(cookies)).filter(\n isMultiSessionCookie\n ).length + (cookieString.includes(\"session_token\") ? 1 : 0);\n if (currentMultiSessions >= opts.maximumSessions) {\n return;\n }\n await ctx.setSignedCookie(\n cookieName,\n sessionToken,\n ctx.context.secret,\n sessionCookieConfig.options\n );\n })\n },\n {\n matcher: (context) => context.path === \"/sign-out\",\n handler: createAuthMiddleware(async (ctx) => {\n const cookieHeader = ctx.headers?.get(\"cookie\");\n if (!cookieHeader) return;\n const cookies = Object.fromEntries(parseCookies(cookieHeader));\n const ids = Object.keys(cookies).map((key) => {\n if (isMultiSessionCookie(key)) {\n ctx.setCookie(key.toLowerCase(), \"\", {\n ...ctx.context.authCookies.sessionToken.options,\n maxAge: 0\n });\n const token = cookies[key].split(\".\")[0];\n return token;\n }\n return null;\n }).filter((v) => v !== null);\n await ctx.context.internalAdapter.deleteSessions(ids);\n })\n }\n ]\n },\n $ERROR_CODES: ERROR_CODES\n };\n};\n\nexport { multiSession };\n","import * as z from 'zod/v4';\nimport { APIError } from 'better-call';\nimport { j as createAuthEndpoint, i as createAuthMiddleware } from '../../shared/better-auth.D4HhkCZJ.mjs';\nimport { setSessionCookie } from '../../cookies/index.mjs';\nimport '../../shared/better-auth.n2KFGwjY.mjs';\nimport '../../shared/better-auth.8zoxzg-F.mjs';\nimport '../../shared/better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport { symmetricDecrypt, symmetricEncrypt } from '../../crypto/index.mjs';\nimport { g as getDate } from '../../shared/better-auth.CW6D9eSx.mjs';\nimport { g as getEndpointResponse } from '../../shared/better-auth.DQI8AD7d.mjs';\nimport { createHash } from '@better-auth/utils/hash';\nimport { base64Url } from '@better-auth/utils/base64';\nimport { g as generateRandomString } from '../../shared/better-auth.B4Qoxdgc.mjs';\nimport '@better-fetch/fetch';\nimport 'jose';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport '../../shared/better-auth.VTXNLFMT.mjs';\nimport '../../shared/better-auth.DdzSJf-n.mjs';\nimport '../../shared/better-auth.tB5eU6EY.mjs';\nimport '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport 'jose/errors';\nimport '@better-auth/utils/random';\n\nconst defaultKeyHasher = async (otp) => {\n const hash = await createHash(\"SHA-256\").digest(\n new TextEncoder().encode(otp)\n );\n const hashed = base64Url.encode(new Uint8Array(hash), {\n padding: false\n });\n return hashed;\n};\nfunction splitAtLastColon(input) {\n const idx = input.lastIndexOf(\":\");\n if (idx === -1) {\n return [input, \"\"];\n }\n return [input.slice(0, idx), input.slice(idx + 1)];\n}\n\nconst types = [\"email-verification\", \"sign-in\", \"forget-password\"];\nconst emailOTP = (options) => {\n const opts = {\n expiresIn: 5 * 60,\n generateOTP: () => generateRandomString(options.otpLength ?? 6, \"0-9\"),\n storeOTP: \"plain\",\n ...options\n };\n const ERROR_CODES = {\n OTP_EXPIRED: \"otp expired\",\n INVALID_OTP: \"Invalid OTP\",\n INVALID_EMAIL: \"Invalid email\",\n USER_NOT_FOUND: \"User not found\",\n TOO_MANY_ATTEMPTS: \"Too many attempts\"\n };\n async function storeOTP(ctx, otp) {\n if (opts.storeOTP === \"encrypted\") {\n return await symmetricEncrypt({\n key: ctx.context.secret,\n data: otp\n });\n }\n if (opts.storeOTP === \"hashed\") {\n return await defaultKeyHasher(otp);\n }\n if (typeof opts.storeOTP === \"object\" && \"hash\" in opts.storeOTP) {\n return await opts.storeOTP.hash(otp);\n }\n if (typeof opts.storeOTP === \"object\" && \"encrypt\" in opts.storeOTP) {\n return await opts.storeOTP.encrypt(otp);\n }\n return otp;\n }\n async function verifyStoredOTP(ctx, storedOtp, otp) {\n if (opts.storeOTP === \"encrypted\") {\n return await symmetricDecrypt({\n key: ctx.context.secret,\n data: storedOtp\n }) === otp;\n }\n if (opts.storeOTP === \"hashed\") {\n const hashedOtp = await defaultKeyHasher(otp);\n return hashedOtp === storedOtp;\n }\n if (typeof opts.storeOTP === \"object\" && \"hash\" in opts.storeOTP) {\n const hashedOtp = await opts.storeOTP.hash(otp);\n return hashedOtp === storedOtp;\n }\n if (typeof opts.storeOTP === \"object\" && \"decrypt\" in opts.storeOTP) {\n const decryptedOtp = await opts.storeOTP.decrypt(storedOtp);\n return decryptedOtp === otp;\n }\n return otp === storedOtp;\n }\n const endpoints = {\n /**\n * ### Endpoint\n *\n * POST `/email-otp/send-verification-otp`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.sendVerificationOTP`\n *\n * **client:**\n * `authClient.emailOtp.sendVerificationOtp`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-send-verification-otp)\n */\n sendVerificationOTP: createAuthEndpoint(\n \"/email-otp/send-verification-otp\",\n {\n method: \"POST\",\n body: z.object({\n email: z.string({}).meta({\n description: \"Email address to send the OTP\"\n }),\n type: z.enum(types).meta({\n description: \"Type of the OTP\"\n })\n }),\n metadata: {\n openapi: {\n description: \"Send verification OTP\",\n responses: {\n 200: {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n success: {\n type: \"boolean\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n if (!options?.sendVerificationOTP) {\n ctx.context.logger.error(\n \"send email verification is not implemented\"\n );\n throw new APIError(\"BAD_REQUEST\", {\n message: \"send email verification is not implemented\"\n });\n }\n const email = ctx.body.email;\n const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/;\n if (!emailRegex.test(email)) {\n throw ctx.error(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_EMAIL\n });\n }\n if (opts.disableSignUp) {\n const user = await ctx.context.internalAdapter.findUserByEmail(email);\n if (!user) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.USER_NOT_FOUND\n });\n }\n } else if (ctx.body.type === \"forget-password\") {\n const user = await ctx.context.internalAdapter.findUserByEmail(email);\n if (!user) {\n return ctx.json({\n success: true\n });\n }\n }\n let otp = opts.generateOTP({ email, type: ctx.body.type }, ctx.request);\n let storedOTP = await storeOTP(ctx, otp);\n await ctx.context.internalAdapter.createVerificationValue(\n {\n value: `${storedOTP}:0`,\n identifier: `${ctx.body.type}-otp-${email}`,\n expiresAt: getDate(opts.expiresIn, \"sec\")\n },\n ctx\n ).catch(async (error) => {\n await ctx.context.internalAdapter.deleteVerificationByIdentifier(\n `${ctx.body.type}-otp-${email}`\n );\n await ctx.context.internalAdapter.createVerificationValue(\n {\n value: `${storedOTP}:0`,\n identifier: `${ctx.body.type}-otp-${email}`,\n expiresAt: getDate(opts.expiresIn, \"sec\")\n },\n ctx\n );\n });\n await options.sendVerificationOTP(\n {\n email,\n otp,\n type: ctx.body.type\n },\n ctx.request\n );\n return ctx.json({\n success: true\n });\n }\n )\n };\n return {\n id: \"email-otp\",\n init(ctx) {\n return {\n options: {\n emailVerification: {\n ...opts.overrideDefaultEmailVerification ? {\n async sendVerificationEmail(data, request) {\n await endpoints.sendVerificationOTP({\n //@ts-expect-error - we need to pass the context\n context: ctx,\n request,\n body: {\n email: data.user.email,\n type: \"email-verification\"\n },\n ctx\n });\n }\n } : {}\n }\n }\n };\n },\n endpoints: {\n ...endpoints,\n createVerificationOTP: createAuthEndpoint(\n \"/email-otp/create-verification-otp\",\n {\n method: \"POST\",\n body: z.object({\n email: z.string({}).meta({\n description: \"Email address to send the OTP\"\n }),\n type: z.enum(types).meta({\n required: true,\n description: \"Type of the OTP\"\n })\n }),\n metadata: {\n SERVER_ONLY: true,\n openapi: {\n description: \"Create verification OTP\",\n responses: {\n 200: {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"string\"\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const email = ctx.body.email;\n const otp = opts.generateOTP(\n { email, type: ctx.body.type },\n ctx.request\n );\n let storedOTP = await storeOTP(ctx, otp);\n await ctx.context.internalAdapter.createVerificationValue(\n {\n value: `${storedOTP}:0`,\n identifier: `${ctx.body.type}-otp-${email}`,\n expiresAt: getDate(opts.expiresIn, \"sec\")\n },\n ctx\n );\n return otp;\n }\n ),\n /**\n * ### Endpoint\n *\n * GET `/email-otp/get-verification-otp`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.getVerificationOTP`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-get-verification-otp)\n */\n getVerificationOTP: createAuthEndpoint(\n \"/email-otp/get-verification-otp\",\n {\n method: \"GET\",\n query: z.object({\n email: z.string({}).meta({\n description: \"Email address to get the OTP\"\n }),\n type: z.enum(types).meta({\n required: true,\n description: \"Type of the OTP\"\n })\n }),\n metadata: {\n SERVER_ONLY: true,\n openapi: {\n description: \"Get verification OTP\",\n responses: {\n \"200\": {\n description: \"OTP retrieved successfully or not found/expired\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n otp: {\n type: \"string\",\n nullable: true,\n description: \"The stored OTP, or null if not found or expired\"\n }\n },\n required: [\"otp\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const email = ctx.query.email;\n const verificationValue = await ctx.context.internalAdapter.findVerificationValue(\n `${ctx.query.type}-otp-${email}`\n );\n if (!verificationValue || verificationValue.expiresAt < /* @__PURE__ */ new Date()) {\n return ctx.json({\n otp: null\n });\n }\n if (opts.storeOTP === \"hashed\" || typeof opts.storeOTP === \"object\" && \"hash\" in opts.storeOTP) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"OTP is hashed, cannot return the plain text OTP\"\n });\n }\n let [storedOtp, _attempts] = splitAtLastColon(\n verificationValue.value\n );\n let otp = storedOtp;\n if (opts.storeOTP === \"encrypted\") {\n otp = await symmetricDecrypt({\n key: ctx.context.secret,\n data: storedOtp\n });\n }\n if (typeof opts.storeOTP === \"object\" && \"decrypt\" in opts.storeOTP) {\n otp = await opts.storeOTP.decrypt(storedOtp);\n }\n return ctx.json({\n otp\n });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/email-otp/verify-email`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.verifyEmailOTP`\n *\n * **client:**\n * `authClient.emailOtp.verifyEmail`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-verify-email)\n */\n verifyEmailOTP: createAuthEndpoint(\n \"/email-otp/verify-email\",\n {\n method: \"POST\",\n body: z.object({\n email: z.string({}).meta({\n description: \"Email address to verify\"\n }),\n otp: z.string().meta({\n required: true,\n description: \"OTP to verify\"\n })\n }),\n metadata: {\n openapi: {\n description: \"Verify email OTP\",\n responses: {\n 200: {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n status: {\n type: \"boolean\",\n description: \"Indicates if the verification was successful\",\n enum: [true]\n },\n token: {\n type: \"string\",\n nullable: true,\n description: \"Session token if autoSignInAfterVerification is enabled, otherwise null\"\n },\n user: {\n $ref: \"#/components/schemas/User\"\n },\n required: [\"status\", \"token\", \"user\"]\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const email = ctx.body.email;\n const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/;\n if (!emailRegex.test(email)) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_EMAIL\n });\n }\n const verificationValue = await ctx.context.internalAdapter.findVerificationValue(\n `email-verification-otp-${email}`\n );\n if (!verificationValue) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_OTP\n });\n }\n if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.OTP_EXPIRED\n });\n }\n const [otpValue, attempts] = splitAtLastColon(\n verificationValue.value\n );\n const allowedAttempts = options?.allowedAttempts || 3;\n if (attempts && parseInt(attempts) >= allowedAttempts) {\n await ctx.context.internalAdapter.deleteVerificationValue(\n verificationValue.id\n );\n throw new APIError(\"FORBIDDEN\", {\n message: ERROR_CODES.TOO_MANY_ATTEMPTS\n });\n }\n const verified = await verifyStoredOTP(ctx, otpValue, ctx.body.otp);\n if (!verified) {\n await ctx.context.internalAdapter.updateVerificationValue(\n verificationValue.id,\n {\n value: `${otpValue}:${parseInt(attempts || \"0\") + 1}`\n }\n );\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_OTP\n });\n }\n await ctx.context.internalAdapter.deleteVerificationValue(\n verificationValue.id\n );\n const user = await ctx.context.internalAdapter.findUserByEmail(email);\n if (!user) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.USER_NOT_FOUND\n });\n }\n const updatedUser = await ctx.context.internalAdapter.updateUser(\n user.user.id,\n {\n email,\n emailVerified: true\n },\n ctx\n );\n await ctx.context.options.emailVerification?.onEmailVerification?.(\n updatedUser,\n ctx.request\n );\n if (ctx.context.options.emailVerification?.autoSignInAfterVerification) {\n const session = await ctx.context.internalAdapter.createSession(\n updatedUser.id,\n ctx\n );\n await setSessionCookie(ctx, {\n session,\n user: updatedUser\n });\n return ctx.json({\n status: true,\n token: session.token,\n user: {\n id: updatedUser.id,\n email: updatedUser.email,\n emailVerified: updatedUser.emailVerified,\n name: updatedUser.name,\n image: updatedUser.image,\n createdAt: updatedUser.createdAt,\n updatedAt: updatedUser.updatedAt\n }\n });\n }\n return ctx.json({\n status: true,\n token: null,\n user: {\n id: updatedUser.id,\n email: updatedUser.email,\n emailVerified: updatedUser.emailVerified,\n name: updatedUser.name,\n image: updatedUser.image,\n createdAt: updatedUser.createdAt,\n updatedAt: updatedUser.updatedAt\n }\n });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/sign-in/email-otp`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.signInEmailOTP`\n *\n * **client:**\n * `authClient.signIn.emailOtp`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-sign-in-email-otp)\n */\n signInEmailOTP: createAuthEndpoint(\n \"/sign-in/email-otp\",\n {\n method: \"POST\",\n body: z.object({\n email: z.string({}).meta({\n description: \"Email address to sign in\"\n }),\n otp: z.string().meta({\n required: true,\n description: \"OTP sent to the email\"\n })\n }),\n metadata: {\n openapi: {\n description: \"Sign in with email OTP\",\n responses: {\n 200: {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n token: {\n type: \"string\",\n description: \"Session token for the authenticated session\"\n },\n user: {\n $ref: \"#/components/schemas/User\"\n }\n },\n required: [\"token\", \"user\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const email = ctx.body.email;\n const verificationValue = await ctx.context.internalAdapter.findVerificationValue(\n `sign-in-otp-${email}`\n );\n if (!verificationValue) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_OTP\n });\n }\n if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.OTP_EXPIRED\n });\n }\n const [otpValue, attempts] = splitAtLastColon(\n verificationValue.value\n );\n const allowedAttempts = options?.allowedAttempts || 3;\n if (attempts && parseInt(attempts) >= allowedAttempts) {\n await ctx.context.internalAdapter.deleteVerificationValue(\n verificationValue.id\n );\n throw new APIError(\"FORBIDDEN\", {\n message: ERROR_CODES.TOO_MANY_ATTEMPTS\n });\n }\n const verified = await verifyStoredOTP(ctx, otpValue, ctx.body.otp);\n if (!verified) {\n await ctx.context.internalAdapter.updateVerificationValue(\n verificationValue.id,\n {\n value: `${otpValue}:${parseInt(attempts || \"0\") + 1}`\n }\n );\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_OTP\n });\n }\n await ctx.context.internalAdapter.deleteVerificationValue(\n verificationValue.id\n );\n const user = await ctx.context.internalAdapter.findUserByEmail(email);\n if (!user) {\n if (opts.disableSignUp) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.USER_NOT_FOUND\n });\n }\n const newUser = await ctx.context.internalAdapter.createUser(\n {\n email,\n emailVerified: true,\n name: \"\"\n },\n ctx\n );\n const session2 = await ctx.context.internalAdapter.createSession(\n newUser.id,\n ctx\n );\n await setSessionCookie(ctx, {\n session: session2,\n user: newUser\n });\n return ctx.json({\n token: session2.token,\n user: {\n id: newUser.id,\n email: newUser.email,\n emailVerified: newUser.emailVerified,\n name: newUser.name,\n image: newUser.image,\n createdAt: newUser.createdAt,\n updatedAt: newUser.updatedAt\n }\n });\n }\n if (!user.user.emailVerified) {\n await ctx.context.internalAdapter.updateUser(\n user.user.id,\n {\n emailVerified: true\n },\n ctx\n );\n }\n const session = await ctx.context.internalAdapter.createSession(\n user.user.id,\n ctx\n );\n await setSessionCookie(ctx, {\n session,\n user: user.user\n });\n return ctx.json({\n token: session.token,\n user: {\n id: user.user.id,\n email: user.user.email,\n emailVerified: user.user.emailVerified,\n name: user.user.name,\n image: user.user.image,\n createdAt: user.user.createdAt,\n updatedAt: user.user.updatedAt\n }\n });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/forget-password/email-otp`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.forgetPasswordEmailOTP`\n *\n * **client:**\n * `authClient.forgetPassword.emailOtp`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-forget-password-email-otp)\n */\n forgetPasswordEmailOTP: createAuthEndpoint(\n \"/forget-password/email-otp\",\n {\n method: \"POST\",\n body: z.object({\n email: z.string().meta({\n description: \"Email address to send the OTP\"\n })\n }),\n metadata: {\n openapi: {\n description: \"Forget password with email OTP\",\n responses: {\n 200: {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n success: {\n type: \"boolean\",\n description: \"Indicates if the OTP was sent successfully\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const email = ctx.body.email;\n const user = await ctx.context.internalAdapter.findUserByEmail(email);\n if (!user) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.USER_NOT_FOUND\n });\n }\n const otp = opts.generateOTP(\n { email, type: \"forget-password\" },\n ctx.request\n );\n let storedOTP = await storeOTP(ctx, otp);\n await ctx.context.internalAdapter.createVerificationValue(\n {\n value: `${storedOTP}:0`,\n identifier: `forget-password-otp-${email}`,\n expiresAt: getDate(opts.expiresIn, \"sec\")\n },\n ctx\n );\n await options.sendVerificationOTP(\n {\n email,\n otp,\n type: \"forget-password\"\n },\n ctx.request\n );\n return ctx.json({\n success: true\n });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/email-otp/reset-password`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.resetPasswordEmailOTP`\n *\n * **client:**\n * `authClient.emailOtp.resetPassword`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-reset-password)\n */\n resetPasswordEmailOTP: createAuthEndpoint(\n \"/email-otp/reset-password\",\n {\n method: \"POST\",\n body: z.object({\n email: z.string().meta({\n description: \"Email address to reset the password\"\n }),\n otp: z.string().meta({\n description: \"OTP sent to the email\"\n }),\n password: z.string().meta({\n description: \"New password\"\n })\n }),\n metadata: {\n openapi: {\n description: \"Reset password with email OTP\",\n responses: {\n 200: {\n description: \"Success\",\n contnt: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n success: {\n type: \"boolean\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const email = ctx.body.email;\n const user = await ctx.context.internalAdapter.findUserByEmail(\n email,\n {\n includeAccounts: true\n }\n );\n if (!user) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.USER_NOT_FOUND\n });\n }\n const verificationValue = await ctx.context.internalAdapter.findVerificationValue(\n `forget-password-otp-${email}`\n );\n if (!verificationValue) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_OTP\n });\n }\n if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) {\n await ctx.context.internalAdapter.deleteVerificationValue(\n verificationValue.id\n );\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.OTP_EXPIRED\n });\n }\n const [otpValue, attempts] = splitAtLastColon(\n verificationValue.value\n );\n const allowedAttempts = options?.allowedAttempts || 3;\n if (attempts && parseInt(attempts) >= allowedAttempts) {\n await ctx.context.internalAdapter.deleteVerificationValue(\n verificationValue.id\n );\n throw new APIError(\"FORBIDDEN\", {\n message: ERROR_CODES.TOO_MANY_ATTEMPTS\n });\n }\n const verified = await verifyStoredOTP(ctx, otpValue, ctx.body.otp);\n if (!verified) {\n await ctx.context.internalAdapter.updateVerificationValue(\n verificationValue.id,\n {\n value: `${otpValue}:${parseInt(attempts || \"0\") + 1}`\n }\n );\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_OTP\n });\n }\n await ctx.context.internalAdapter.deleteVerificationValue(\n verificationValue.id\n );\n const passwordHash = await ctx.context.password.hash(\n ctx.body.password\n );\n const account = user.accounts.find(\n (account2) => account2.providerId === \"credential\"\n );\n if (!account) {\n await ctx.context.internalAdapter.createAccount(\n {\n userId: user.user.id,\n providerId: \"credential\",\n accountId: user.user.id,\n password: passwordHash\n },\n ctx\n );\n } else {\n await ctx.context.internalAdapter.updatePassword(\n user.user.id,\n passwordHash,\n ctx\n );\n }\n if (!user.user.emailVerified) {\n await ctx.context.internalAdapter.updateUser(\n user.user.id,\n {\n emailVerified: true\n },\n ctx\n );\n }\n return ctx.json({\n success: true\n });\n }\n )\n },\n hooks: {\n after: [\n {\n matcher(context) {\n return !!(context.path?.startsWith(\"/sign-up\") && opts.sendVerificationOnSignUp);\n },\n handler: createAuthMiddleware(async (ctx) => {\n const response = await getEndpointResponse(ctx);\n const email = response?.user.email;\n if (email) {\n const otp = opts.generateOTP(\n { email, type: ctx.body.type },\n ctx.request\n );\n let storedOTP = await storeOTP(ctx, otp);\n await ctx.context.internalAdapter.createVerificationValue(\n {\n value: `${storedOTP}:0`,\n identifier: `email-verification-otp-${email}`,\n expiresAt: getDate(opts.expiresIn, \"sec\")\n },\n ctx\n );\n await options.sendVerificationOTP(\n {\n email,\n otp,\n type: \"email-verification\"\n },\n ctx.request\n );\n }\n })\n }\n ]\n },\n $ERROR_CODES: ERROR_CODES,\n rateLimit: [\n {\n pathMatcher(path) {\n return path === \"/email-otp/send-verification-otp\";\n },\n window: 60,\n max: 3\n },\n {\n pathMatcher(path) {\n return path === \"/email-otp/verify-email\";\n },\n window: 60,\n max: 3\n },\n {\n pathMatcher(path) {\n return path === \"/sign-in/email-otp\";\n },\n window: 60,\n max: 3\n }\n ]\n };\n};\n\nexport { emailOTP };\n","import * as z from 'zod/v4';\nimport { APIError } from 'better-call';\nimport { j as createAuthEndpoint } from '../../shared/better-auth.D4HhkCZJ.mjs';\nimport { setSessionCookie } from '../../cookies/index.mjs';\nimport '../../shared/better-auth.n2KFGwjY.mjs';\nimport '../../shared/better-auth.8zoxzg-F.mjs';\nimport '../../shared/better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport { createRemoteJWKSet, jwtVerify } from 'jose';\nimport '../../shared/better-auth.CW6D9eSx.mjs';\nimport '@better-auth/utils/hash';\nimport '@better-auth/utils/base64';\nimport '../../crypto/index.mjs';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport '../../shared/better-auth.B4Qoxdgc.mjs';\nimport '@better-auth/utils/random';\nimport '@better-fetch/fetch';\nimport '../../shared/better-auth.VTXNLFMT.mjs';\nimport '../../shared/better-auth.DdzSJf-n.mjs';\nimport '../../shared/better-auth.tB5eU6EY.mjs';\nimport '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport 'jose/errors';\n\nfunction toBoolean(value) {\n return value === \"true\" || value === true;\n}\n\nconst oneTap = (options) => ({\n id: \"one-tap\",\n endpoints: {\n oneTapCallback: createAuthEndpoint(\n \"/one-tap/callback\",\n {\n method: \"POST\",\n body: z.object({\n idToken: z.string().meta({\n description: \"Google ID token, which the client obtains from the One Tap API\"\n })\n }),\n metadata: {\n openapi: {\n summary: \"One tap callback\",\n description: \"Use this endpoint to authenticate with Google One Tap\",\n responses: {\n 200: {\n description: \"Successful response\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n session: {\n $ref: \"#/components/schemas/Session\"\n },\n user: {\n $ref: \"#/components/schemas/User\"\n }\n }\n }\n }\n }\n },\n 400: {\n description: \"Invalid token\"\n }\n }\n }\n }\n },\n async (ctx) => {\n const { idToken } = ctx.body;\n let payload;\n try {\n const JWKS = createRemoteJWKSet(\n new URL(\"https://www.googleapis.com/oauth2/v3/certs\")\n );\n const { payload: verifiedPayload } = await jwtVerify(\n idToken,\n JWKS,\n {\n issuer: [\"https://accounts.google.com\", \"accounts.google.com\"],\n audience: options?.clientId || ctx.context.options.socialProviders?.google?.clientId\n }\n );\n payload = verifiedPayload;\n } catch (error) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"invalid id token\"\n });\n }\n const { email, email_verified, name, picture, sub } = payload;\n if (!email) {\n return ctx.json({ error: \"Email not available in token\" });\n }\n const user = await ctx.context.internalAdapter.findUserByEmail(email);\n if (!user) {\n if (options?.disableSignup) {\n throw new APIError(\"BAD_GATEWAY\", {\n message: \"User not found\"\n });\n }\n const newUser = await ctx.context.internalAdapter.createOAuthUser(\n {\n email,\n emailVerified: typeof email_verified === \"boolean\" ? email_verified : toBoolean(email_verified),\n name,\n image: picture\n },\n {\n providerId: \"google\",\n accountId: sub\n },\n ctx\n );\n if (!newUser) {\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n message: \"Could not create user\"\n });\n }\n const session2 = await ctx.context.internalAdapter.createSession(\n newUser.user.id,\n ctx\n );\n await setSessionCookie(ctx, {\n user: newUser.user,\n session: session2\n });\n return ctx.json({\n token: session2.token,\n user: {\n id: newUser.user.id,\n email: newUser.user.email,\n emailVerified: newUser.user.emailVerified,\n name: newUser.user.name,\n image: newUser.user.image,\n createdAt: newUser.user.createdAt,\n updatedAt: newUser.user.updatedAt\n }\n });\n }\n const account = await ctx.context.internalAdapter.findAccount(sub);\n if (!account) {\n const accountLinking = ctx.context.options.account?.accountLinking;\n const shouldLinkAccount = accountLinking?.enabled && (accountLinking.trustedProviders?.includes(\"google\") || email_verified);\n if (shouldLinkAccount) {\n await ctx.context.internalAdapter.linkAccount({\n userId: user.user.id,\n providerId: \"google\",\n accountId: sub,\n scope: \"openid,profile,email\",\n idToken\n });\n } else {\n throw new APIError(\"UNAUTHORIZED\", {\n message: \"Google sub doesn't match\"\n });\n }\n }\n const session = await ctx.context.internalAdapter.createSession(\n user.user.id,\n ctx\n );\n await setSessionCookie(ctx, {\n user: user.user,\n session\n });\n return ctx.json({\n token: session.token,\n user: {\n id: user.user.id,\n email: user.user.email,\n emailVerified: user.user.emailVerified,\n name: user.user.name,\n image: user.user.image,\n createdAt: user.user.createdAt,\n updatedAt: user.user.updatedAt\n }\n });\n }\n )\n }\n});\n\nexport { oneTap };\n","import * as z from 'zod/v4';\nimport 'better-call';\nimport { i as createAuthMiddleware, j as createAuthEndpoint, o as originCheck } from '../../shared/better-auth.D4HhkCZJ.mjs';\nimport { e as env } from '../../shared/better-auth.8zoxzg-F.mjs';\nimport '@better-auth/utils/base64';\nimport '@better-auth/utils/hmac';\nimport { g as getOrigin } from '../../shared/better-auth.VTXNLFMT.mjs';\nimport '@better-auth/utils/binary';\nimport '../../shared/better-auth.n2KFGwjY.mjs';\nimport '../../shared/better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport { symmetricEncrypt, symmetricDecrypt } from '../../crypto/index.mjs';\nimport '../../shared/better-auth.CW6D9eSx.mjs';\nimport '@better-auth/utils/hash';\nimport '@better-fetch/fetch';\nimport 'jose';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport '../../shared/better-auth.B4Qoxdgc.mjs';\nimport '@better-auth/utils/random';\nimport '../../cookies/index.mjs';\nimport '../../shared/better-auth.DdzSJf-n.mjs';\nimport '../../shared/better-auth.tB5eU6EY.mjs';\nimport 'jose/errors';\n\nfunction getVenderBaseURL() {\n const vercel = env.VERCEL_URL ? `https://${env.VERCEL_URL}` : void 0;\n const netlify = env.NETLIFY_URL;\n const render = env.RENDER_URL;\n const aws = env.AWS_LAMBDA_FUNCTION_NAME;\n const google = env.GOOGLE_CLOUD_FUNCTION_NAME;\n const azure = env.AZURE_FUNCTION_NAME;\n return vercel || netlify || render || aws || google || azure;\n}\nconst oAuthProxy = (opts) => {\n const resolveCurrentURL = (ctx) => {\n return new URL(\n opts?.currentURL || ctx.request?.url || getVenderBaseURL() || ctx.context.baseURL\n );\n };\n return {\n id: \"oauth-proxy\",\n endpoints: {\n oAuthProxy: createAuthEndpoint(\n \"/oauth-proxy-callback\",\n {\n method: \"GET\",\n query: z.object({\n callbackURL: z.string().meta({\n description: \"The URL to redirect to after the proxy\"\n }),\n cookies: z.string().meta({\n description: \"The cookies to set after the proxy\"\n })\n }),\n use: [originCheck((ctx) => ctx.query.callbackURL)],\n metadata: {\n openapi: {\n description: \"OAuth Proxy Callback\",\n parameters: [\n {\n in: \"query\",\n name: \"callbackURL\",\n required: true,\n description: \"The URL to redirect to after the proxy\"\n },\n {\n in: \"query\",\n name: \"cookies\",\n required: true,\n description: \"The cookies to set after the proxy\"\n }\n ],\n responses: {\n 302: {\n description: \"Redirect\",\n headers: {\n Location: {\n description: \"The URL to redirect to\",\n schema: {\n type: \"string\"\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const cookies = ctx.query.cookies;\n const decryptedCookies = await symmetricDecrypt({\n key: ctx.context.secret,\n data: cookies\n }).catch((e) => {\n ctx.context.logger.error(e);\n return null;\n });\n const error = ctx.context.options.onAPIError?.errorURL || `${ctx.context.options.baseURL}/api/auth/error`;\n if (!decryptedCookies) {\n throw ctx.redirect(\n `${error}?error=OAuthProxy - Invalid cookies or secret`\n );\n }\n const isSecureContext = resolveCurrentURL(ctx).protocol === \"https:\";\n const prefix = ctx.context.options.advanced?.cookiePrefix || \"better-auth\";\n const cookieToSet = isSecureContext ? decryptedCookies : decryptedCookies.replace(\"Secure;\", \"\").replace(`__Secure-${prefix}`, prefix);\n ctx.setHeader(\"set-cookie\", cookieToSet);\n throw ctx.redirect(ctx.query.callbackURL);\n }\n )\n },\n hooks: {\n after: [\n {\n matcher(context) {\n return context.path?.startsWith(\"/callback\") || context.path?.startsWith(\"/oauth2/callback\");\n },\n handler: createAuthMiddleware(async (ctx) => {\n const headers = ctx.context.responseHeaders;\n const location = headers?.get(\"location\");\n if (location?.includes(\"/oauth-proxy-callback?callbackURL\")) {\n if (!location.startsWith(\"http\")) {\n return;\n }\n const locationURL = new URL(location);\n const origin = locationURL.origin;\n if (origin === getOrigin(ctx.context.baseURL)) {\n const newLocation = locationURL.searchParams.get(\"callbackURL\");\n if (!newLocation) {\n return;\n }\n ctx.setHeader(\"location\", newLocation);\n return;\n }\n const setCookies = headers?.get(\"set-cookie\");\n if (!setCookies) {\n return;\n }\n const encryptedCookies = await symmetricEncrypt({\n key: ctx.context.secret,\n data: setCookies\n });\n const locationWithCookies = `${location}&cookies=${encodeURIComponent(\n encryptedCookies\n )}`;\n ctx.setHeader(\"location\", locationWithCookies);\n }\n })\n }\n ],\n before: [\n {\n matcher(context) {\n return context.path?.startsWith(\"/sign-in/social\") || context.path?.startsWith(\"/sign-in/oauth2\");\n },\n handler: createAuthMiddleware(async (ctx) => {\n const skipProxy = ctx.request?.headers.get(\"x-skip-oauth-proxy\");\n if (skipProxy) {\n return;\n }\n const url = resolveCurrentURL(ctx);\n const productionURL = opts?.productionURL || env.BETTER_AUTH_URL;\n if (productionURL === ctx.context.options.baseURL) {\n return;\n }\n ctx.body.callbackURL = `${url.origin}${ctx.context.options.basePath || \"/api/auth\"}/oauth-proxy-callback?callbackURL=${encodeURIComponent(\n ctx.body.callbackURL || ctx.context.baseURL\n )}`;\n return {\n context: ctx\n };\n })\n }\n ]\n }\n };\n};\n\nexport { oAuthProxy };\n","import * as z from 'zod/v4';\nimport 'better-call';\nimport { j as createAuthEndpoint, m as getSession } from '../../shared/better-auth.D4HhkCZJ.mjs';\nimport '../../shared/better-auth.8zoxzg-F.mjs';\nimport '@better-auth/utils/base64';\nimport '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport '../../shared/better-auth.n2KFGwjY.mjs';\nimport '../../shared/better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport '../../shared/better-auth.CW6D9eSx.mjs';\nimport '@better-auth/utils/hash';\nimport '../../crypto/index.mjs';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport 'jose';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport '../../shared/better-auth.B4Qoxdgc.mjs';\nimport '@better-auth/utils/random';\nimport '@better-fetch/fetch';\nimport '../../shared/better-auth.VTXNLFMT.mjs';\nimport '../../shared/better-auth.DdzSJf-n.mjs';\nimport '../../cookies/index.mjs';\nimport '../../shared/better-auth.tB5eU6EY.mjs';\nimport 'jose/errors';\n\nconst customSession = (fn, options) => {\n return {\n id: \"custom-session\",\n endpoints: {\n getSession: createAuthEndpoint(\n \"/get-session\",\n {\n method: \"GET\",\n query: z.optional(\n z.object({\n /**\n * If cookie cache is enabled, it will disable the cache\n * and fetch the session from the database\n */\n disableCookieCache: z.boolean().meta({\n description: \"Disable cookie cache and fetch session from database\"\n }).or(z.string().transform((v) => v === \"true\")).optional(),\n disableRefresh: z.boolean().meta({\n description: \"Disable session refresh. Useful for checking session status, without updating the session\"\n }).optional()\n })\n ),\n metadata: {\n CUSTOM_SESSION: true,\n openapi: {\n description: \"Get custom session data\",\n responses: {\n \"200\": {\n description: \"Success\",\n content: {\n \"application/json\": {\n schema: {\n type: \"array\",\n nullable: true,\n items: {\n $ref: \"#/components/schemas/Session\"\n }\n }\n }\n }\n }\n }\n }\n },\n requireHeaders: true\n },\n async (ctx) => {\n const session = await getSession()({\n ...ctx,\n asResponse: false,\n headers: ctx.headers,\n returnHeaders: true\n }).catch((e) => {\n return null;\n });\n if (!session?.response) {\n return ctx.json(null);\n }\n const fnResult = await fn(session.response, ctx);\n session.headers.forEach((value, key) => {\n ctx.setHeader(key, value);\n });\n return ctx.json(fnResult);\n }\n )\n }\n };\n};\n\nexport { customSession };\n","import { ZodObject, ZodType, ZodOptional, z, ZodString, ZodNumber, ZodBoolean, ZodArray } from 'zod/v4';\nimport { getEndpoints } from '../../api/index.mjs';\nimport '../../shared/better-auth.n2KFGwjY.mjs';\nimport '../../shared/better-auth.8zoxzg-F.mjs';\nimport '@better-auth/utils/random';\nimport { APIError } from 'better-call';\nimport '@better-auth/utils/hash';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport '@better-auth/utils/base64';\nimport 'jose';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport '../../shared/better-auth.B4Qoxdgc.mjs';\nimport '../../shared/better-auth.DBGfIDnh.mjs';\nimport { g as getAuthTables } from '../../shared/better-auth.DORkW_Ge.mjs';\nimport 'kysely';\nimport { j as createAuthEndpoint } from '../../shared/better-auth.D4HhkCZJ.mjs';\nimport '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport 'defu';\nimport '../../cookies/index.mjs';\nimport '../../shared/better-auth.DdzSJf-n.mjs';\nimport '../../shared/better-auth.CW6D9eSx.mjs';\nimport '../../shared/better-auth.tB5eU6EY.mjs';\nimport '../../shared/better-auth.VTXNLFMT.mjs';\nimport '../../shared/better-auth.DcfNPS8q.mjs';\nimport '../../crypto/index.mjs';\nimport '@better-fetch/fetch';\nimport 'jose/errors';\n\nfunction getTypeFromZodType(zodType) {\n if (zodType instanceof ZodString) {\n return \"string\";\n } else if (zodType instanceof ZodNumber) {\n return \"number\";\n } else if (zodType instanceof ZodBoolean) {\n return \"boolean\";\n } else if (zodType instanceof ZodObject) {\n return \"object\";\n } else if (zodType instanceof ZodArray) {\n return \"array\";\n }\n return \"string\";\n}\nfunction getFieldSchema(field) {\n const schema = {\n type: field.type === \"date\" ? \"string\" : field.type\n };\n if (field.defaultValue !== void 0) {\n schema.default = typeof field.defaultValue === \"function\" ? \"Generated at runtime\" : field.defaultValue;\n }\n if (field.input === false) {\n schema.readOnly = true;\n }\n return schema;\n}\nfunction getParameters(options) {\n const parameters = [];\n if (options.metadata?.openapi?.parameters) {\n parameters.push(...options.metadata.openapi.parameters);\n return parameters;\n }\n if (options.query instanceof ZodObject) {\n Object.entries(options.query.shape).forEach(([key, value]) => {\n if (value instanceof ZodType) {\n parameters.push({\n name: key,\n in: \"query\",\n schema: {\n type: getTypeFromZodType(value),\n ...\"minLength\" in value && value.minLength ? {\n minLength: value.minLength\n } : {},\n description: value.description\n }\n });\n }\n });\n }\n return parameters;\n}\nfunction getRequestBody(options) {\n if (options.metadata?.openapi?.requestBody) {\n return options.metadata.openapi.requestBody;\n }\n if (!options.body) return void 0;\n if (options.body instanceof ZodObject || options.body instanceof ZodOptional) {\n const shape = options.body.shape;\n if (!shape) return void 0;\n const properties = {};\n const required = [];\n Object.entries(shape).forEach(([key, value]) => {\n if (value instanceof ZodType) {\n properties[key] = {\n type: getTypeFromZodType(value),\n description: value.description\n };\n if (!(value instanceof z.ZodOptional)) {\n required.push(key);\n }\n }\n });\n return {\n required: options.body instanceof ZodOptional ? false : options.body ? true : false,\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties,\n required\n }\n }\n }\n };\n }\n return void 0;\n}\nfunction getResponse(responses) {\n return {\n \"400\": {\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n message: {\n type: \"string\"\n }\n },\n required: [\"message\"]\n }\n }\n },\n description: \"Bad Request. Usually due to missing parameters, or invalid parameters.\"\n },\n \"401\": {\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n message: {\n type: \"string\"\n }\n },\n required: [\"message\"]\n }\n }\n },\n description: \"Unauthorized. Due to missing or invalid authentication.\"\n },\n \"403\": {\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n message: {\n type: \"string\"\n }\n }\n }\n }\n },\n description: \"Forbidden. You do not have permission to access this resource or to perform this action.\"\n },\n \"404\": {\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n message: {\n type: \"string\"\n }\n }\n }\n }\n },\n description: \"Not Found. The requested resource was not found.\"\n },\n \"429\": {\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n message: {\n type: \"string\"\n }\n }\n }\n }\n },\n description: \"Too Many Requests. You have exceeded the rate limit. Try again later.\"\n },\n \"500\": {\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n message: {\n type: \"string\"\n }\n }\n }\n }\n },\n description: \"Internal Server Error. This is a problem with the server that you cannot fix.\"\n },\n ...responses\n };\n}\nfunction toOpenApiPath(path) {\n return path.split(\"/\").map((part) => part.startsWith(\":\") ? `{${part.slice(1)}}` : part).join(\"/\");\n}\nasync function generator(ctx, options) {\n const baseEndpoints = getEndpoints(ctx, {\n ...options,\n plugins: []\n });\n const tables = getAuthTables(options);\n const models = Object.entries(tables).reduce((acc, [key, value]) => {\n const modelName = key.charAt(0).toUpperCase() + key.slice(1);\n const fields = value.fields;\n const required = [];\n const properties = {\n id: { type: \"string\" }\n };\n Object.entries(fields).forEach(([fieldKey, fieldValue]) => {\n if (!fieldValue) return;\n properties[fieldKey] = getFieldSchema(fieldValue);\n if (fieldValue.required && fieldValue.input !== false) {\n required.push(fieldKey);\n }\n });\n acc[modelName] = {\n type: \"object\",\n properties,\n ...required.length > 0 ? { required } : {}\n };\n return acc;\n }, {});\n const components = {\n schemas: {\n ...models\n }\n };\n const paths = {};\n Object.entries(baseEndpoints.api).forEach(([_, value]) => {\n if (ctx.options.disabledPaths?.includes(value.path)) return;\n const options2 = value.options;\n if (options2.metadata?.SERVER_ONLY) return;\n const path = toOpenApiPath(value.path);\n if (options2.method === \"GET\") {\n paths[path] = {\n get: {\n tags: [\"Default\", ...options2.metadata?.openapi?.tags || []],\n description: options2.metadata?.openapi?.description,\n operationId: options2.metadata?.openapi?.operationId,\n security: [\n {\n bearerAuth: []\n }\n ],\n parameters: getParameters(options2),\n responses: getResponse(options2.metadata?.openapi?.responses)\n }\n };\n }\n if (options2.method === \"POST\") {\n const body = getRequestBody(options2);\n paths[path] = {\n post: {\n tags: [\"Default\", ...options2.metadata?.openapi?.tags || []],\n description: options2.metadata?.openapi?.description,\n operationId: options2.metadata?.openapi?.operationId,\n security: [\n {\n bearerAuth: []\n }\n ],\n parameters: getParameters(options2),\n ...body ? { requestBody: body } : {\n requestBody: {\n //set body none\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {}\n }\n }\n }\n }\n },\n responses: getResponse(options2.metadata?.openapi?.responses)\n }\n };\n }\n });\n for (const plugin of options.plugins || []) {\n if (plugin.id === \"open-api\") {\n continue;\n }\n const pluginEndpoints = getEndpoints(ctx, {\n ...options,\n plugins: [plugin]\n });\n const api = Object.keys(pluginEndpoints.api).map((key) => {\n if (baseEndpoints.api[key] === void 0) {\n return pluginEndpoints.api[key];\n }\n return null;\n }).filter((x) => x !== null);\n Object.entries(api).forEach(([key, value]) => {\n if (ctx.options.disabledPaths?.includes(value.path)) return;\n const options2 = value.options;\n if (options2.metadata?.SERVER_ONLY) return;\n const path = toOpenApiPath(value.path);\n if (options2.method === \"GET\") {\n paths[path] = {\n get: {\n tags: options2.metadata?.openapi?.tags || [\n plugin.id.charAt(0).toUpperCase() + plugin.id.slice(1)\n ],\n description: options2.metadata?.openapi?.description,\n operationId: options2.metadata?.openapi?.operationId,\n security: [\n {\n bearerAuth: []\n }\n ],\n parameters: getParameters(options2),\n responses: getResponse(options2.metadata?.openapi?.responses)\n }\n };\n }\n if (options2.method === \"POST\") {\n paths[path] = {\n post: {\n tags: options2.metadata?.openapi?.tags || [\n plugin.id.charAt(0).toUpperCase() + plugin.id.slice(1)\n ],\n description: options2.metadata?.openapi?.description,\n operationId: options2.metadata?.openapi?.operationId,\n security: [\n {\n bearerAuth: []\n }\n ],\n parameters: getParameters(options2),\n requestBody: getRequestBody(options2),\n responses: getResponse(options2.metadata?.openapi?.responses)\n }\n };\n }\n });\n }\n const res = {\n openapi: \"3.1.1\",\n info: {\n title: \"Better Auth\",\n description: \"API Reference for your Better Auth Instance\",\n version: \"1.1.0\"\n },\n components: {\n ...components,\n securitySchemes: {\n apiKeyCookie: {\n type: \"apiKey\",\n in: \"cookie\",\n name: \"apiKeyCookie\",\n description: \"API Key authentication via cookie\"\n },\n bearerAuth: {\n type: \"http\",\n scheme: \"bearer\",\n description: \"Bearer token authentication\"\n }\n }\n },\n security: [\n {\n apiKeyCookie: [],\n bearerAuth: []\n }\n ],\n servers: [\n {\n url: ctx.baseURL\n }\n ],\n tags: [\n {\n name: \"Default\",\n description: \"Default endpoints that are included with Better Auth by default. These endpoints are not part of any plugin.\"\n }\n ],\n paths\n };\n return res;\n}\n\nconst logo = `<svg width=\"75\" height=\"75\" viewBox=\"0 0 75 75\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n<rect width=\"75\" height=\"75\" fill=\"url(#pattern0_21_12)\"/>\n<defs>\n<pattern id=\"pattern0_21_12\" patternContentUnits=\"objectBoundingBox\" width=\"1\" height=\"1\">\n<use xlink:href=\"#image0_21_12\" transform=\"scale(0.00094697)\"/>\n</pattern>\n<image id=\"image0_21_12\" width=\"1056\" height=\"1056\" xlink:href=\"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBARXhpZgAATU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAEIKADAAQAAAABAAAEIAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/+ICKElDQ19QUk9GSUxFAAEBAAACGGFwcGwEAAAAbW50clJHQiBYWVogB+YAAQABAAAAAAAAYWNzcEFQUEwAAAAAQVBQTAAAAAAAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1hcHBs7P2jjjiFR8NttL1PetoYLwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKZGVzYwAAAPwAAAAwY3BydAAAASwAAABQd3RwdAAAAXwAAAAUclhZWgAAAZAAAAAUZ1hZWgAAAaQAAAAUYlhZWgAAAbgAAAAUclRSQwAAAcwAAAAgY2hhZAAAAewAAAAsYlRSQwAAAcwAAAAgZ1RSQwAAAcwAAAAgbWx1YwAAAAAAAAABAAAADGVuVVMAAAAUAAAAHABEAGkAcwBwAGwAYQB5ACAAUAAzbWx1YwAAAAAAAAABAAAADGVuVVMAAAA0AAAAHABDAG8AcAB5AHIAaQBnAGgAdAAgAEEAcABwAGwAZQAgAEkAbgBjAC4ALAAgADIAMAAyADJYWVogAAAAAAAA9tUAAQAAAADTLFhZWiAAAAAAAACD3wAAPb////+7WFlaIAAAAAAAAEq/AACxNwAACrlYWVogAAAAAAAAKDgAABELAADIuXBhcmEAAAAAAAMAAAACZmYAAPKnAAANWQAAE9AAAApbc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeTAAD9kP//+6L///2jAAAD3AAAwG7/wAARCAQgBCADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwACAgICAgIDAgIDBAMDAwQFBAQEBAUHBQUFBQUHCAcHBwcHBwgICAgICAgICgoKCgoKCwsLCwsNDQ0NDQ0NDQ0N/9sAQwECAgIDAwMGAwMGDQkHCQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0N/90ABABC/9oADAMBAAIRAxEAPwD9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//Q/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/0f38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9L9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//T/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/1P38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9X9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//W/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/1/38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9D9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//R/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/0v38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9P9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//U/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/1f38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9b9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//X/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/0P38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9H9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//S/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/0/38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9T9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//V/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/1v38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK/Ln/gq38a/in8Dvgp4T8R/CfxFdeG9SvvFMdlcXFoELyW5srqQxnzEcY3op4Gciv1Gr8Z/+C2X/JvXgj/sc4v/AE33lAH4z/8ADwv9tD/oq2tf9823/wAZo/4eF/tof9FW1r/vm2/+M18Z0UAfZn/Dwv8AbQ/6KtrX/fNt/wDGaP8Ah4X+2h/0VbWv++bb/wCM18Z0UAfZn/Dwv9tD/oq2tf8AfNt/8Zo/4eF/tof9FW1r/vm2/wDjNfGdFAH2Z/w8L/bQ/wCira1/3zbf/GaP+Hhf7aH/AEVbWv8Avm2/+M18Z0UAfZn/AA8L/bQ/6KtrX/fNt/8AGaP+Hhf7aH/RVta/75tv/jNfGdFAH2Z/w8L/AG0P+ira1/3zbf8Axmj/AIeF/tof9FW1r/vm2/8AjNfGdFAH63/sVftq/tTfEb9qb4deCfG3xF1TVtD1bVGgvbKdYBHPGIJW2ttiVsblB4I6V/UbX8Z//BPT/k9D4U/9hpv/AEmmr+zCgAooooAKKKKACiiigAooooAKKKKACiiigD+M/wD4eF/tof8ARVta/wC+bb/4zR/w8L/bQ/6KtrX/AHzbf/Ga+M6KAPsz/h4X+2h/0VbWv++bb/4zR/w8L/bQ/wCira1/3zbf/Ga+M6KAPsz/AIeF/tof9FW1r/vm2/8AjNH/AA8L/bQ/6KtrX/fNt/8AGa+M6KAPsz/h4X+2h/0VbWv++bb/AOM0f8PC/wBtD/oq2tf9823/AMZr4zooA+zP+Hhf7aH/AEVbWv8Avm2/+M0f8PC/20P+ira1/wB823/xmvjOigD7M/4eF/tof9FW1r/vm2/+M0f8PC/20P8Aoq2tf9823/xmvjOigD7M/wCHhf7aH/RVta/75tv/AIzR/wAPC/20P+ira1/3zbf/ABmvjOigD7M/4eF/tof9FW1r/vm2/wDjNH/Dwv8AbQ/6KtrX/fNt/wDGa+M6KAPsz/h4X+2h/wBFW1r/AL5tv/jNH/Dwv9tD/oq2tf8AfNt/8Zr4zooA+zP+Hhf7aH/RVta/75tv/jNH/Dwv9tD/AKKtrX/fNt/8Zr4zooA+zP8Ah4X+2h/0VbWv++bb/wCM0f8ADwv9tD/oq2tf9823/wAZr4zooA+zP+Hhf7aH/RVta/75tv8A4zR/w8L/AG0P+ira1/3zbf8AxmvjOigD7M/4eF/tof8ARVta/wC+bb/4zR/w8L/bQ/6KtrX/AHzbf/Ga+M6KAP6tv+CUnxr+Kfxx+CnizxH8WPEV14k1Kx8UyWVvcXYQPHbiytZBGPLRBje7HkZya/Uavxn/AOCJv/JvXjf/ALHOX/032dfsxQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAf//X/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr8Z/+C2X/JvXgj/sc4v/AE33lfsxX4z/APBbL/k3rwR/2OcX/pvvKAP5m6KKKACiiigAooooAKKKKACiiigAooooA+zP+Cen/J6Hwp/7DTf+k01f2YV/Gf8A8E9P+T0PhT/2Gm/9Jpq/swoAKKKKACiiigAooooAKKKKACiiigAooooA/gDooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/pk/4Im/8m9eN/8Asc5f/TfZ1+zFfjP/AMETf+TevG//AGOcv/pvs6/ZigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/9D9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACvxn/4LZf8m9eCP+xzi/8ATfeV+zFfjP8A8Fsv+TevBH/Y5xf+m+8oA/mbooooAKKKKACiiigAooooAKKKKACiiigD7M/4J6f8nofCn/sNN/6TTV/ZhX8Z/wDwT0/5PQ+FP/Yab/0mmr+zCgAooooAKKKKACiiigAooooAKKKKACiiigD+AOiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD+mT/gib/yb143/wCxzl/9N9nX7MV+M/8AwRN/5N68b/8AY5y/+m+zr9mKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/0f38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK/Gf/gtl/yb14I/7HOL/wBN95X7MV+M/wDwWy/5N68Ef9jnF/6b7ygD+ZuiiigAooooAKKKKACiiigAooooAKKKKAPsz/gnp/yeh8Kf+w03/pNNX9mFfxn/APBPT/k9D4U/9hpv/Saav7MKACiiigAooooAKKKKACiiigAooooAKKKKAP4A6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP6ZP+CJv/JvXjf/ALHOX/032dfsxX4z/wDBE3/k3rxv/wBjnL/6b7Ov2YoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/S/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr8Z/+C2X/JvXgj/sc4v/AE33lfsxX4z/APBbL/k3rwR/2OcX/pvvKAP5m6KKKACiiigAooooAKKKKACiiigAooooA+zP+Cen/J6Hwp/7DTf+k01f2YV/Gf8A8E9P+T0PhT/2Gm/9Jpq/swoAKKKKACiiigAooooAKKKKACiiigAooooA/gDooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/pk/4Im/8m9eN/8Asc5f/TfZ1+zFfjP/AMETf+TevG//AGOcv/pvs6/ZigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/9P9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACvxn/4LZf8m9eCP+xzi/8ATfeV+zFfjP8A8Fsv+TevBH/Y5xf+m+8oA/mbooooAKKKKACiiigAooooAKKKKACiiigD7M/4J6f8nofCn/sNN/6TTV/ZhX8Z/wDwT0/5PQ+FP/Yab/0mmr+zCgAooooAKKKKACiiigAooooAKKKKACiiigD+AOiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD+mT/gib/yb143/wCxzl/9N9nX7MV+M/8AwRN/5N68b/8AY5y/+m+zr9mKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/1P38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK/Gf/gtl/yb14I/7HOL/wBN95X7MV+M/wDwWy/5N68Ef9jnF/6b7ygD+ZuiiigAooooAKKKKACiiigAooooAKKKKAPsz/gnp/yeh8Kf+w03/pNNX9mFfwq/BT4r638Dvin4d+LHhy0tb7UvDd0bu3t70ObeRzG0eJBGyPjDnowOa/Ub/h9l+0L/ANCR4M/79ah/8m0Af0yUV/M3/wAPsv2hf+hI8Gf9+tQ/+TaP+H2X7Qv/AEJHgz/v1qH/AMm0Af0yUV/M3/w+y/aF/wChI8Gf9+tQ/wDk2j/h9l+0L/0JHgz/AL9ah/8AJtAH9MlFfzN/8Psv2hf+hI8Gf9+tQ/8Ak2j/AIfZftC/9CR4M/79ah/8m0Af0yUV/M3/AMPsv2hf+hI8Gf8AfrUP/k2j/h9l+0L/ANCR4M/79ah/8m0Af0yUV/M3/wAPsv2hf+hI8Gf9+tQ/+TaP+H2X7Qv/AEJHgz/v1qH/AMm0Af0yUV/M3/w+y/aF/wChI8Gf9+tQ/wDk2j/h9l+0L/0JHgz/AL9ah/8AJtAH4z0V/TJ/w5N/Z6/6Hfxn/wB/dP8A/kKj/hyb+z1/0O/jP/v7p/8A8hUAfzN0V/TJ/wAOTf2ev+h38Z/9/dP/APkKj/hyb+z1/wBDv4z/AO/un/8AyFQB/M3RX9Mn/Dk39nr/AKHfxn/390//AOQqP+HJv7PX/Q7+M/8Av7p//wAhUAfzN0V/TJ/w5N/Z6/6Hfxn/AN/dP/8AkKj/AIcm/s9f9Dv4z/7+6f8A/IVAH8zdFf0yf8OTf2ev+h38Z/8Af3T/AP5Co/4cm/s9f9Dv4z/7+6f/APIVAH8zdFf0yf8ADk39nr/od/Gf/f3T/wD5Co/4cm/s9f8AQ7+M/wDv7p//AMhUAfzN0V/TJ/w5N/Z6/wCh38Z/9/dP/wDkKvwV/ag+FGifA74++M/hP4cu7q+03w3fi0t7i9KG4kQxRyZkMaomcueigYoA8FooooAKKKKACiiigAooooAKKKKACiiigD+mT/gib/yb143/AOxzl/8ATfZ1+zFfjP8A8ETf+TevG/8A2Ocv/pvs6/ZigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//9X9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACvxn/4LZf8m9eCP+xzi/8ATfeV+zFfjP8A8Fsv+TevBH/Y5xf+m+8oA/mbooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/v8ooooAKKKKACiiigAooooAKKKKACiiigAr+M//goX/wAnofFb/sNL/wCk0Nf2YV/Gf/wUL/5PQ+K3/YaX/wBJoaAPjOiiigAooooAKKKKACiiigAooooAKKKKAP6ZP+CJv/JvXjf/ALHOX/032dfsxX4z/wDBE3/k3rxv/wBjnL/6b7Ov2YoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/W/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr8Z/+C2X/JvXgj/sc4v/AE33lfsxX4z/APBbL/k3rwR/2OcX/pvvKAP5m6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP7/KKKKACiiigAooooAKKKKACiiigAooooAK/jP/4KF/8AJ6HxW/7DS/8ApNDX9mFfxn/8FC/+T0Pit/2Gl/8ASaGgD4zooooAKKKKACiiigAooooAKKKKACiiigD+mT/gib/yb143/wCxzl/9N9nX7MV+M/8AwRN/5N68b/8AY5y/+m+zr9mKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/1/38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK/Gf/gtl/yb14I/7HOL/wBN95X7MV+M/wDwWy/5N68Ef9jnF/6b7ygD+ZuiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD+/yiiigAooooAKKKKACiiigAooooAKKKKACv4z/+Chf/ACeh8Vv+w0v/AKTQ1/ZhX8Z//BQv/k9D4rf9hpf/AEmhoA+M6KKKACiiigAooooAKKKKACiiigAooooA/pk/4Im/8m9eN/8Asc5f/TfZ1+zFfjP/AMETf+TevG//AGOcv/pvs6/ZigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/9D9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACvxn/4LZf8m9eCP+xzi/8ATfeV+zFfjP8A8Fsv+TevBH/Y5xf+m+8oA/mbooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/v8ooooAKKKKACiiigAooooAKKKKACiiigAr+M//goX/wAnofFb/sNL/wCk0Nf2YV/Gf/wUL/5PQ+K3/YaX/wBJoaAPjOiiigAooooAKKKKACiiigAooooAKKKKAP6ZP+CJv/JvXjf/ALHOX/032dfsxX4z/wDBE3/k3rxv/wBjnL/6b7Ov2YoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/R/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr8Z/+C2X/JvXgj/sc4v/AE33lfsxX4z/APBbL/k3rwR/2OcX/pvvKAP5m6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP7/KKKKACiiigAooooAKKKKACiiigAooooAK/jP/4KF/8AJ6HxW/7DS/8ApNDX9mFfxn/8FC/+T0Pit/2Gl/8ASaGgD4zooooAKKKKACiiigAooooAKKKKACiiigD+mT/gib/yb143/wCxzl/9N9nX7MV+M/8AwRN/5N68b/8AY5y/+m+zr9mKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/0v38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK/Gf/gtl/yb14I/7HOL/wBN95X7MV+M/wDwWy/5N68Ef9jnF/6b7ygD+ZuiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD+/yiiigAooooAKKKKACiiigAooooAKKKKACv4z/+Chf/ACeh8Vv+w0v/AKTQ1/ZhX8Z//BQv/k9D4rf9hpf/AEmhoA+M6KKKACiiigAooooAKKKKACiiigAooooA/pk/4Im/8m9eN/8Asc5f/TfZ1+zFfjP/AMETf+TevG//AGOcv/pvs6/ZigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/9P9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACvxn/4LZf8m9eCP+xzi/8ATfeV+zFfjP8A8Fsv+TevBH/Y5xf+m+8oA/mbooooAKKKKACiiigAooooAKKKKACiiigD1L4KfCjW/jj8U/Dvwn8OXdrY6l4kujaW9xelxbxuI2kzIY1d8YQ9FJzX6jf8OTf2hf8Aod/Bn/f3UP8A5Cr4z/4J6f8AJ6Hwp/7DTf8ApNNX9mFAH8zf/Dk39oX/AKHfwZ/391D/AOQqP+HJv7Qv/Q7+DP8Av7qH/wAhV/TJRQB/M3/w5N/aF/6HfwZ/391D/wCQqP8Ahyb+0L/0O/gz/v7qH/yFX9MlFAH8zf8Aw5N/aF/6HfwZ/wB/dQ/+QqP+HJv7Qv8A0O/gz/v7qH/yFX9MlFAH8zf/AA5N/aF/6HfwZ/391D/5Co/4cm/tC/8AQ7+DP+/uof8AyFX9MlFAH8zf/Dk39oX/AKHfwZ/391D/AOQqP+HJv7Qv/Q7+DP8Av7qH/wAhV/TJRQB/M3/w5N/aF/6HfwZ/391D/wCQqP8Ahyb+0L/0O/gz/v7qH/yFX9MlFAH4z/8AD7L9nr/oSPGf/frT/wD5No/4fZfs9f8AQkeM/wDv1p//AMm1/M3RQB/TJ/w+y/Z6/wChI8Z/9+tP/wDk2j/h9l+z1/0JHjP/AL9af/8AJtfzN0UAf0yf8Psv2ev+hI8Z/wDfrT//AJNo/wCH2X7PX/QkeM/+/Wn/APybX8zdFAH9Mn/D7L9nr/oSPGf/AH60/wD+TaP+H2X7PX/QkeM/+/Wn/wDybX8zdFAH9Mn/AA+y/Z6/6Ejxn/360/8A+TaP+H2X7PX/AEJHjP8A79af/wDJtfzN0UAf0yf8Psv2ev8AoSPGf/frT/8A5No/4fZfs9f9CR4z/wC/Wn//ACbX8zdFAH9Mn/D7L9nr/oSPGf8A360//wCTa/BX9qD4r6J8cfj74z+LHhy0urHTfEl+Lu3t70ILiNBFHHiQRs6Zyh6MRivBaKACiiigAooooAKKKKACiiigAooooAKKKKAP6ZP+CJv/ACb143/7HOX/ANN9nX7MV+M//BE3/k3rxv8A9jnL/wCm+zr9mKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//U/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr8Z/+C2X/JvXgj/sc4v/AE33lfsxX4z/APBbL/k3rwR/2OcX/pvvKAP5m6KKKACiiigAooooAKKKKACiiigAooooA+zP+Cen/J6Hwp/7DTf+k01f2YV/Gf8A8E9P+T0PhT/2Gm/9Jpq/swoAKKKKACiiigAooooAKKKKACiiigAooooA/gDooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/pk/4Im/8m9eN/8Asc5f/TfZ1+zFfjP/AMETf+TevG//AGOcv/pvs6/ZigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/9X9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACvxn/4LZf8m9eCP+xzi/8ATfeV+zFfjP8A8Fsv+TevBH/Y5xf+m+8oA/mbooooAKKKKACiiigAooooAKKKKACiiigD7M/4J6f8nofCn/sNN/6TTV/ZhX8Z/wDwT0/5PQ+FP/Yab/0mmr+zCgAooooAKKKKACiiigAooooAKKKKACiiigD+AOiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD+mT/gib/yb143/wCxzl/9N9nX7MV+M/8AwRN/5N68b/8AY5y/+m+zr9mKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/1v38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK/Gf/gtl/yb14I/7HOL/wBN95X7MV+M/wDwWy/5N68Ef9jnF/6b7ygD+ZuiiigAooooAKKKKACiiigAooooAKKKKAPsz/gnp/yeh8Kf+w03/pNNX9mFfxn/APBPT/k9D4U/9hpv/Saav7MKACiiigAooooAKKKKACiiigAooooAKKKKAP4A6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP6ZP+CJv/JvXjf/ALHOX/032dfsxX4z/wDBE3/k3rxv/wBjnL/6b7Ov2YoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/X/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr8Z/+C2X/JvXgj/sc4v/AE33lfsxX4z/APBbL/k3rwR/2OcX/pvvKAP5m6KKKACiiigAooooAKKKKACiiigAooooA+zP+Cen/J6Hwp/7DTf+k01f2YV/Gf8A8E9P+T0PhT/2Gm/9Jpq/swoAKKKKACiiigAooooAKKKKACiiigAooooA/gDooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/pk/4Im/8m9eN/8Asc5f/TfZ1+zFfjP/AMETf+TevG//AGOcv/pvs6/ZigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/9D9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACvxn/4LZf8m9eCP+xzi/8ATfeV+zFfjP8A8Fsv+TevBH/Y5xf+m+8oA/mbooooAKKKKACiiigAooooAKKKKACiiigD7M/4J6f8nofCn/sNN/6TTV/ZhX8Z/wDwT0/5PQ+FP/Yab/0mmr+zCgAooooAKKKKACiiigAooooAKKKKACiiigD+AOiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD+mT/gib/yb143/wCxzl/9N9nX7MV+M/8AwRN/5N68b/8AY5y/+m+zr9mKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/0f38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK/Gf/gtl/yb14I/7HOL/wBN95X7MV+XP/BVv4KfFP44/BTwn4c+E/h268SalY+KY724t7QoHjtxZXUZkPmOgxvdRwc5NAH8pNFfZn/DvT9tD/olOtf99W3/AMeo/wCHen7aH/RKda/76tv/AI9QB8Z0V9mf8O9P20P+iU61/wB9W3/x6j/h3p+2h/0SnWv++rb/AOPUAfGdFfZn/DvT9tD/AKJTrX/fVt/8eo/4d6ftof8ARKda/wC+rb/49QB8Z0V9mf8ADvT9tD/olOtf99W3/wAeo/4d6ftof9Ep1r/vq2/+PUAfGdFfZn/DvT9tD/olOtf99W3/AMeo/wCHen7aH/RKda/76tv/AI9QB8Z0V9mf8O9P20P+iU61/wB9W3/x6j/h3p+2h/0SnWv++rb/AOPUAH/BPT/k9D4U/wDYab/0mmr+zCv5cv2Kv2Kv2pvhz+1N8OvG3jb4dappOh6TqjT3t7O0BjgjMEq7m2ys2NzAcA9a/qNoAKKKKACiiigAooooAKKKKACiiigAooooA/gDor7M/wCHen7aH/RKda/76tv/AI9R/wAO9P20P+iU61/31bf/AB6gD4zor7M/4d6ftof9Ep1r/vq2/wDj1H/DvT9tD/olOtf99W3/AMeoA+M6K+zP+Hen7aH/AESnWv8Avq2/+PUf8O9P20P+iU61/wB9W3/x6gD4zor7M/4d6ftof9Ep1r/vq2/+PUf8O9P20P8AolOtf99W3/x6gD4zor7M/wCHen7aH/RKda/76tv/AI9R/wAO9P20P+iU61/31bf/AB6gD4zor7M/4d6ftof9Ep1r/vq2/wDj1H/DvT9tD/olOtf99W3/AMeoA+M6K+zP+Hen7aH/AESnWv8Avq2/+PUf8O9P20P+iU61/wB9W3/x6gD4zor7M/4d6ftof9Ep1r/vq2/+PUf8O9P20P8AolOtf99W3/x6gD4zor7M/wCHen7aH/RKda/76tv/AI9R/wAO9P20P+iU61/31bf/AB6gD4zor7M/4d6ftof9Ep1r/vq2/wDj1H/DvT9tD/olOtf99W3/AMeoA+M6K+zP+Hen7aH/AESnWv8Avq2/+PUf8O9P20P+iU61/wB9W3/x6gD4zor7M/4d6ftof9Ep1r/vq2/+PUf8O9P20P8AolOtf99W3/x6gD4zor7M/wCHen7aH/RKda/76tv/AI9R/wAO9P20P+iU61/31bf/AB6gD9mP+CJv/JvXjf8A7HOX/wBN9nX7MV+XP/BKT4KfFP4HfBTxZ4c+LHh268N6lfeKZL23t7soXktzZWsYkHlu4xvRhyc5FfqNQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAf/9L9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//T/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/1P38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9X9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//W/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/1/38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9D9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//R/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/0v38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9P9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//U/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/1f38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9b9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//X/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/0P38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9H9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//S/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/0/38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9T9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//V/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/1v38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9f9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//Q/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/2Q==\"/>\n</defs>\n</svg>\n`;\n\nconst getHTML = (apiReference) => `<!doctype html>\n<html>\n <head>\n <title>Scalar API Reference</title>\n <meta charset=\"utf-8\" />\n <meta\n name=\"viewport\"\n content=\"width=device-width, initial-scale=1\" />\n </head>\n <body>\n <script\n id=\"api-reference\"\n type=\"application/json\">\n ${JSON.stringify(apiReference)}\n <\\/script>\n\t <script>\n var configuration = {\n\t \tfavicon: \"data:image/svg+xml;utf8,${encodeURIComponent(logo)}\",\n\t \ttheme: \"saturn\",\n metaData: {\n\t\t\ttitle: \"Better Auth API\",\n\t\t\tdescription: \"API Reference for your Better Auth Instance\",\n\t\t}\n }\n\n document.getElementById('api-reference').dataset.configuration =\n JSON.stringify(configuration)\n <\\/script>\n\t <script src=\"https://cdn.jsdelivr.net/npm/@scalar/api-reference\"><\\/script>\n </body>\n</html>`;\nconst openAPI = (options) => {\n const path = options?.path ?? \"/reference\";\n return {\n id: \"open-api\",\n endpoints: {\n generateOpenAPISchema: createAuthEndpoint(\n \"/open-api/generate-schema\",\n {\n method: \"GET\"\n },\n async (ctx) => {\n const schema = await generator(ctx.context, ctx.context.options);\n return ctx.json(schema);\n }\n ),\n openAPIReference: createAuthEndpoint(\n path,\n {\n method: \"GET\",\n metadata: {\n isAction: false\n }\n },\n async (ctx) => {\n if (options?.disableDefaultReference) {\n throw new APIError(\"NOT_FOUND\");\n }\n const schema = await generator(ctx.context, ctx.context.options);\n return new Response(getHTML(schema), {\n headers: {\n \"Content-Type\": \"text/html\"\n }\n });\n }\n )\n }\n };\n};\n\nexport { openAPI };\n","import * as z from 'zod/v4';\nimport { SignJWT } from 'jose';\nimport { APIError } from 'better-call';\nimport { k as getSessionFromCtx, j as createAuthEndpoint, i as createAuthMiddleware, l as sessionMiddleware } from './better-auth.D4HhkCZJ.mjs';\nimport './better-auth.8zoxzg-F.mjs';\nimport { base64Url, base64 } from '@better-auth/utils/base64';\nimport '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport { parseSetCookieHeader } from '../cookies/index.mjs';\nimport './better-auth.n2KFGwjY.mjs';\nimport './better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport { symmetricEncrypt, symmetricDecrypt } from '../crypto/index.mjs';\nimport { createHash } from '@better-auth/utils/hash';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport { g as generateRandomString } from './better-auth.B4Qoxdgc.mjs';\nimport { getJwtToken } from '../plugins/jwt/index.mjs';\n\nconst schema = {\n oauthApplication: {\n modelName: \"oauthApplication\",\n fields: {\n name: {\n type: \"string\"\n },\n icon: {\n type: \"string\",\n required: false\n },\n metadata: {\n type: \"string\",\n required: false\n },\n clientId: {\n type: \"string\",\n unique: true\n },\n clientSecret: {\n type: \"string\",\n required: false\n },\n redirectURLs: {\n type: \"string\"\n },\n type: {\n type: \"string\"\n },\n disabled: {\n type: \"boolean\",\n required: false,\n defaultValue: false\n },\n userId: {\n type: \"string\",\n required: false\n },\n createdAt: {\n type: \"date\"\n },\n updatedAt: {\n type: \"date\"\n }\n }\n },\n oauthAccessToken: {\n modelName: \"oauthAccessToken\",\n fields: {\n accessToken: {\n type: \"string\",\n unique: true\n },\n refreshToken: {\n type: \"string\",\n unique: true\n },\n accessTokenExpiresAt: {\n type: \"date\"\n },\n refreshTokenExpiresAt: {\n type: \"date\"\n },\n clientId: {\n type: \"string\"\n },\n userId: {\n type: \"string\",\n required: false\n },\n scopes: {\n type: \"string\"\n },\n createdAt: {\n type: \"date\"\n },\n updatedAt: {\n type: \"date\"\n }\n }\n },\n oauthConsent: {\n modelName: \"oauthConsent\",\n fields: {\n clientId: {\n type: \"string\"\n },\n userId: {\n type: \"string\"\n },\n scopes: {\n type: \"string\"\n },\n createdAt: {\n type: \"date\"\n },\n updatedAt: {\n type: \"date\"\n },\n consentGiven: {\n type: \"boolean\"\n }\n }\n }\n};\n\nfunction formatErrorURL(url, error, description) {\n return `${url.includes(\"?\") ? \"&\" : \"?\"}error=${error}&error_description=${description}`;\n}\nfunction getErrorURL(ctx, error, description) {\n const baseURL = ctx.context.options.onAPIError?.errorURL || `${ctx.context.baseURL}/error`;\n const formattedURL = formatErrorURL(baseURL, error, description);\n return formattedURL;\n}\nasync function authorize(ctx, options) {\n const handleRedirect = (url) => {\n const fromFetch = ctx.request?.headers.get(\"sec-fetch-mode\") === \"cors\";\n if (fromFetch) {\n return ctx.json({\n redirect: true,\n url\n });\n } else {\n throw ctx.redirect(url);\n }\n };\n const opts = {\n codeExpiresIn: 600,\n defaultScope: \"openid\",\n ...options,\n scopes: [\n \"openid\",\n \"profile\",\n \"email\",\n \"offline_access\",\n ...options?.scopes || []\n ]\n };\n if (!ctx.request) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"request not found\",\n error: \"invalid_request\"\n });\n }\n const session = await getSessionFromCtx(ctx);\n if (!session) {\n await ctx.setSignedCookie(\n \"oidc_login_prompt\",\n JSON.stringify(ctx.query),\n ctx.context.secret,\n {\n maxAge: 600,\n path: \"/\",\n sameSite: \"lax\"\n }\n );\n const queryFromURL = ctx.request.url?.split(\"?\")[1];\n return handleRedirect(`${options.loginPage}?${queryFromURL}`);\n }\n const query = ctx.query;\n if (!query.client_id) {\n const errorURL = getErrorURL(\n ctx,\n \"invalid_client\",\n \"client_id is required\"\n );\n throw ctx.redirect(errorURL);\n }\n if (!query.response_type) {\n getErrorURL(\n ctx,\n \"invalid_request\",\n \"response_type is required\"\n );\n throw ctx.redirect(\n getErrorURL(ctx, \"invalid_request\", \"response_type is required\")\n );\n }\n const client = await getClient(\n ctx.query.client_id,\n ctx.context.adapter,\n options.trustedClients || []\n );\n if (!client) {\n const errorURL = getErrorURL(\n ctx,\n \"invalid_client\",\n \"client_id is required\"\n );\n throw ctx.redirect(errorURL);\n }\n const redirectURI = client.redirectURLs.find(\n (url) => url === ctx.query.redirect_uri\n );\n if (!redirectURI || !query.redirect_uri) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Invalid redirect URI\"\n });\n }\n if (client.disabled) {\n const errorURL = getErrorURL(ctx, \"client_disabled\", \"client is disabled\");\n throw ctx.redirect(errorURL);\n }\n if (query.response_type !== \"code\") {\n const errorURL = getErrorURL(\n ctx,\n \"unsupported_response_type\",\n \"unsupported response type\"\n );\n throw ctx.redirect(errorURL);\n }\n const requestScope = query.scope?.split(\" \").filter((s) => s) || opts.defaultScope.split(\" \");\n const invalidScopes = requestScope.filter((scope) => {\n return !opts.scopes.includes(scope);\n });\n if (invalidScopes.length) {\n return handleRedirect(\n formatErrorURL(\n query.redirect_uri,\n \"invalid_scope\",\n `The following scopes are invalid: ${invalidScopes.join(\", \")}`\n )\n );\n }\n if ((!query.code_challenge || !query.code_challenge_method) && options.requirePKCE) {\n return handleRedirect(\n formatErrorURL(query.redirect_uri, \"invalid_request\", \"pkce is required\")\n );\n }\n if (!query.code_challenge_method) {\n query.code_challenge_method = \"plain\";\n }\n if (![\n \"s256\",\n options.allowPlainCodeChallengeMethod ? \"plain\" : \"s256\"\n ].includes(query.code_challenge_method?.toLowerCase() || \"\")) {\n return handleRedirect(\n formatErrorURL(\n query.redirect_uri,\n \"invalid_request\",\n \"invalid code_challenge method\"\n )\n );\n }\n const code = generateRandomString(32, \"a-z\", \"A-Z\", \"0-9\");\n const codeExpiresInMs = opts.codeExpiresIn * 1e3;\n const expiresAt = new Date(Date.now() + codeExpiresInMs);\n try {\n await ctx.context.internalAdapter.createVerificationValue(\n {\n value: JSON.stringify({\n clientId: client.clientId,\n redirectURI: query.redirect_uri,\n scope: requestScope,\n userId: session.user.id,\n authTime: session.session.createdAt.getTime(),\n /**\n * If the prompt is set to `consent`, then we need\n * to require the user to consent to the scopes.\n *\n * This means the code now needs to be treated as a\n * consent request.\n *\n * once the user consents, the code will be updated\n * with the actual code. This is to prevent the\n * client from using the code before the user\n * consents.\n */\n requireConsent: query.prompt === \"consent\",\n state: query.prompt === \"consent\" ? query.state : null,\n codeChallenge: query.code_challenge,\n codeChallengeMethod: query.code_challenge_method,\n nonce: query.nonce\n }),\n identifier: code,\n expiresAt\n },\n ctx\n );\n } catch (e) {\n return handleRedirect(\n formatErrorURL(\n query.redirect_uri,\n \"server_error\",\n \"An error occurred while processing the request\"\n )\n );\n }\n const redirectURIWithCode = new URL(redirectURI);\n redirectURIWithCode.searchParams.set(\"code\", code);\n redirectURIWithCode.searchParams.set(\"state\", ctx.query.state);\n if (query.prompt !== \"consent\") {\n return handleRedirect(redirectURIWithCode.toString());\n }\n if (client.skipConsent) {\n return handleRedirect(redirectURIWithCode.toString());\n }\n const hasAlreadyConsented = await ctx.context.adapter.findOne({\n model: \"oauthConsent\",\n where: [\n {\n field: \"clientId\",\n value: client.clientId\n },\n {\n field: \"userId\",\n value: session.user.id\n }\n ]\n }).then((res) => !!res?.consentGiven);\n if (hasAlreadyConsented) {\n return handleRedirect(redirectURIWithCode.toString());\n }\n if (options?.consentPage) {\n await ctx.setSignedCookie(\"oidc_consent_prompt\", code, ctx.context.secret, {\n maxAge: 600,\n path: \"/\",\n sameSite: \"lax\"\n });\n const consentURI = `${options.consentPage}?client_id=${client.clientId}&scope=${requestScope.join(\" \")}`;\n return handleRedirect(consentURI);\n }\n const htmlFn = options?.getConsentHTML;\n if (!htmlFn) {\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n message: \"No consent page provided\"\n });\n }\n return new Response(\n htmlFn({\n scopes: requestScope,\n clientMetadata: client.metadata,\n clientIcon: client?.icon,\n clientId: client.clientId,\n clientName: client.name,\n code\n }),\n {\n headers: {\n \"content-type\": \"text/html\"\n }\n }\n );\n}\n\nconst defaultClientSecretHasher = async (clientSecret) => {\n const hash = await createHash(\"SHA-256\").digest(\n new TextEncoder().encode(clientSecret)\n );\n const hashed = base64Url.encode(new Uint8Array(hash), {\n padding: false\n });\n return hashed;\n};\n\nconst getJwtPlugin = (ctx) => {\n return ctx.context.options.plugins?.find(\n (plugin) => plugin.id === \"jwt\"\n );\n};\nasync function getClient(clientId, adapter, trustedClients = []) {\n const trustedClient = trustedClients.find(\n (client) => client.clientId === clientId\n );\n if (trustedClient) {\n return trustedClient;\n }\n const dbClient = await adapter.findOne({\n model: \"oauthApplication\",\n where: [{ field: \"clientId\", value: clientId }]\n }).then((res) => {\n if (!res) {\n return null;\n }\n return {\n ...res,\n redirectURLs: (res.redirectURLs ?? \"\").split(\",\"),\n metadata: res.metadata ? JSON.parse(res.metadata) : {}\n };\n });\n return dbClient;\n}\nconst getMetadata = (ctx, options) => {\n const jwtPlugin = getJwtPlugin(ctx);\n const issuer = jwtPlugin && jwtPlugin.options?.jwt && jwtPlugin.options.jwt.issuer ? jwtPlugin.options.jwt.issuer : ctx.context.options.baseURL;\n const baseURL = ctx.context.baseURL;\n const supportedAlgs = options?.useJWTPlugin ? [\"RS256\", \"EdDSA\", \"none\"] : [\"HS256\", \"none\"];\n return {\n issuer,\n authorization_endpoint: `${baseURL}/oauth2/authorize`,\n token_endpoint: `${baseURL}/oauth2/token`,\n userinfo_endpoint: `${baseURL}/oauth2/userinfo`,\n jwks_uri: `${baseURL}/jwks`,\n registration_endpoint: `${baseURL}/oauth2/register`,\n scopes_supported: [\"openid\", \"profile\", \"email\", \"offline_access\"],\n response_types_supported: [\"code\"],\n response_modes_supported: [\"query\"],\n grant_types_supported: [\"authorization_code\", \"refresh_token\"],\n acr_values_supported: [\n \"urn:mace:incommon:iap:silver\",\n \"urn:mace:incommon:iap:bronze\"\n ],\n subject_types_supported: [\"public\"],\n id_token_signing_alg_values_supported: supportedAlgs,\n token_endpoint_auth_methods_supported: [\n \"client_secret_basic\",\n \"client_secret_post\",\n \"none\"\n ],\n code_challenge_methods_supported: [\"S256\"],\n claims_supported: [\n \"sub\",\n \"iss\",\n \"aud\",\n \"exp\",\n \"nbf\",\n \"iat\",\n \"jti\",\n \"email\",\n \"email_verified\",\n \"name\"\n ],\n ...options?.metadata\n };\n};\nconst oidcProvider = (options) => {\n const modelName = {\n oauthClient: \"oauthApplication\",\n oauthAccessToken: \"oauthAccessToken\",\n oauthConsent: \"oauthConsent\"\n };\n const opts = {\n codeExpiresIn: 600,\n defaultScope: \"openid\",\n accessTokenExpiresIn: 3600,\n refreshTokenExpiresIn: 604800,\n allowPlainCodeChallengeMethod: true,\n storeClientSecret: \"plain\",\n ...options,\n scopes: [\n \"openid\",\n \"profile\",\n \"email\",\n \"offline_access\",\n ...options?.scopes || []\n ]\n };\n const trustedClients = options.trustedClients || [];\n async function storeClientSecret(ctx, clientSecret) {\n if (opts.storeClientSecret === \"encrypted\") {\n return await symmetricEncrypt({\n key: ctx.context.secret,\n data: clientSecret\n });\n }\n if (opts.storeClientSecret === \"hashed\") {\n return await defaultClientSecretHasher(clientSecret);\n }\n if (typeof opts.storeClientSecret === \"object\" && \"hash\" in opts.storeClientSecret) {\n return await opts.storeClientSecret.hash(clientSecret);\n }\n if (typeof opts.storeClientSecret === \"object\" && \"encrypt\" in opts.storeClientSecret) {\n return await opts.storeClientSecret.encrypt(clientSecret);\n }\n return clientSecret;\n }\n async function verifyStoredClientSecret(ctx, storedClientSecret, clientSecret) {\n if (opts.storeClientSecret === \"encrypted\") {\n return await symmetricDecrypt({\n key: ctx.context.secret,\n data: storedClientSecret\n }) === clientSecret;\n }\n if (opts.storeClientSecret === \"hashed\") {\n const hashedClientSecret = await defaultClientSecretHasher(clientSecret);\n return hashedClientSecret === storedClientSecret;\n }\n if (typeof opts.storeClientSecret === \"object\" && \"hash\" in opts.storeClientSecret) {\n const hashedClientSecret = await opts.storeClientSecret.hash(clientSecret);\n return hashedClientSecret === storedClientSecret;\n }\n if (typeof opts.storeClientSecret === \"object\" && \"decrypt\" in opts.storeClientSecret) {\n const decryptedClientSecret = await opts.storeClientSecret.decrypt(storedClientSecret);\n return decryptedClientSecret === clientSecret;\n }\n return clientSecret === storedClientSecret;\n }\n return {\n id: \"oidc\",\n hooks: {\n after: [\n {\n matcher() {\n return true;\n },\n handler: createAuthMiddleware(async (ctx) => {\n const cookie = await ctx.getSignedCookie(\n \"oidc_login_prompt\",\n ctx.context.secret\n );\n const cookieName = ctx.context.authCookies.sessionToken.name;\n const parsedSetCookieHeader = parseSetCookieHeader(\n ctx.context.responseHeaders?.get(\"set-cookie\") || \"\"\n );\n const hasSessionToken = parsedSetCookieHeader.has(cookieName);\n if (!cookie || !hasSessionToken) {\n return;\n }\n ctx.setCookie(\"oidc_login_prompt\", \"\", {\n maxAge: 0\n });\n const sessionCookie = parsedSetCookieHeader.get(cookieName)?.value;\n const sessionToken = sessionCookie?.split(\".\")[0];\n if (!sessionToken) {\n return;\n }\n const session = await ctx.context.internalAdapter.findSession(sessionToken);\n if (!session) {\n return;\n }\n ctx.query = JSON.parse(cookie);\n ctx.query.prompt = \"consent\";\n ctx.context.session = session;\n const response = await authorize(ctx, opts);\n return response;\n })\n }\n ]\n },\n endpoints: {\n getOpenIdConfig: createAuthEndpoint(\n \"/.well-known/openid-configuration\",\n {\n method: \"GET\",\n metadata: {\n isAction: false\n }\n },\n async (ctx) => {\n const metadata = getMetadata(ctx, options);\n return ctx.json(metadata);\n }\n ),\n oAuth2authorize: createAuthEndpoint(\n \"/oauth2/authorize\",\n {\n method: \"GET\",\n query: z.record(z.string(), z.any()),\n metadata: {\n openapi: {\n description: \"Authorize an OAuth2 request\",\n responses: {\n \"200\": {\n description: \"Authorization response generated successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n additionalProperties: true,\n description: \"Authorization response, contents depend on the authorize function implementation\"\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n return authorize(ctx, opts);\n }\n ),\n oAuthConsent: createAuthEndpoint(\n \"/oauth2/consent\",\n {\n method: \"POST\",\n body: z.object({\n accept: z.boolean()\n }),\n use: [sessionMiddleware],\n metadata: {\n openapi: {\n description: \"Handle OAuth2 consent\",\n responses: {\n \"200\": {\n description: \"Consent processed successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n redirectURI: {\n type: \"string\",\n format: \"uri\",\n description: \"The URI to redirect to, either with an authorization code or an error\"\n }\n },\n required: [\"redirectURI\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const storedCode = await ctx.getSignedCookie(\n \"oidc_consent_prompt\",\n ctx.context.secret\n );\n if (!storedCode) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"No consent prompt found\",\n error: \"invalid_request\"\n });\n }\n const verification = await ctx.context.internalAdapter.findVerificationValue(storedCode);\n if (!verification) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"Invalid code\",\n error: \"invalid_request\"\n });\n }\n if (verification.expiresAt < /* @__PURE__ */ new Date()) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"Code expired\",\n error: \"invalid_request\"\n });\n }\n const value = JSON.parse(verification.value);\n if (!value.requireConsent) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"Consent not required\",\n error: \"invalid_request\"\n });\n }\n if (!ctx.body.accept) {\n await ctx.context.internalAdapter.deleteVerificationValue(\n verification.id\n );\n return ctx.json({\n redirectURI: `${value.redirectURI}?error=access_denied&error_description=User denied access`\n });\n }\n const code = generateRandomString(32, \"a-z\", \"A-Z\", \"0-9\");\n const codeExpiresInMs = opts.codeExpiresIn * 1e3;\n const expiresAt = new Date(Date.now() + codeExpiresInMs);\n await ctx.context.internalAdapter.updateVerificationValue(\n verification.id,\n {\n value: JSON.stringify({\n ...value,\n requireConsent: false\n }),\n identifier: code,\n expiresAt\n }\n );\n await ctx.context.adapter.create({\n model: modelName.oauthConsent,\n data: {\n clientId: value.clientId,\n userId: value.userId,\n scopes: value.scope.join(\" \"),\n consentGiven: true,\n createdAt: /* @__PURE__ */ new Date(),\n updatedAt: /* @__PURE__ */ new Date()\n }\n });\n const redirectURI = new URL(value.redirectURI);\n redirectURI.searchParams.set(\"code\", code);\n if (value.state) redirectURI.searchParams.set(\"state\", value.state);\n return ctx.json({\n redirectURI: redirectURI.toString()\n });\n }\n ),\n oAuth2token: createAuthEndpoint(\n \"/oauth2/token\",\n {\n method: \"POST\",\n body: z.record(z.any(), z.any()),\n metadata: {\n isAction: false\n }\n },\n async (ctx) => {\n let { body } = ctx;\n if (!body) {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"request body not found\",\n error: \"invalid_request\"\n });\n }\n if (body instanceof FormData) {\n body = Object.fromEntries(body.entries());\n }\n if (!(body instanceof Object)) {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"request body is not an object\",\n error: \"invalid_request\"\n });\n }\n let { client_id, client_secret } = body;\n const authorization = ctx.request?.headers.get(\"authorization\") || null;\n if (authorization && !client_id && !client_secret && authorization.startsWith(\"Basic \")) {\n try {\n const encoded = authorization.replace(\"Basic \", \"\");\n const decoded = new TextDecoder().decode(base64.decode(encoded));\n if (!decoded.includes(\":\")) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid authorization header format\",\n error: \"invalid_client\"\n });\n }\n const [id, secret] = decoded.split(\":\");\n if (!id || !secret) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid authorization header format\",\n error: \"invalid_client\"\n });\n }\n client_id = id;\n client_secret = secret;\n } catch (error) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid authorization header format\",\n error: \"invalid_client\"\n });\n }\n }\n const {\n grant_type,\n code,\n redirect_uri,\n refresh_token,\n code_verifier\n } = body;\n if (grant_type === \"refresh_token\") {\n if (!refresh_token) {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"refresh_token is required\",\n error: \"invalid_request\"\n });\n }\n const token = await ctx.context.adapter.findOne({\n model: modelName.oauthAccessToken,\n where: [\n {\n field: \"refreshToken\",\n value: refresh_token.toString()\n }\n ]\n });\n if (!token) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid refresh token\",\n error: \"invalid_grant\"\n });\n }\n if (token.clientId !== client_id?.toString()) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid client_id\",\n error: \"invalid_client\"\n });\n }\n if (token.refreshTokenExpiresAt < /* @__PURE__ */ new Date()) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"refresh token expired\",\n error: \"invalid_grant\"\n });\n }\n const accessToken2 = generateRandomString(32, \"a-z\", \"A-Z\");\n const newRefreshToken = generateRandomString(32, \"a-z\", \"A-Z\");\n const accessTokenExpiresAt2 = new Date(\n Date.now() + opts.accessTokenExpiresIn * 1e3\n );\n const refreshTokenExpiresAt2 = new Date(\n Date.now() + opts.refreshTokenExpiresIn * 1e3\n );\n await ctx.context.adapter.create({\n model: modelName.oauthAccessToken,\n data: {\n accessToken: accessToken2,\n refreshToken: newRefreshToken,\n accessTokenExpiresAt: accessTokenExpiresAt2,\n refreshTokenExpiresAt: refreshTokenExpiresAt2,\n clientId: client_id.toString(),\n userId: token.userId,\n scopes: token.scopes,\n createdAt: /* @__PURE__ */ new Date(),\n updatedAt: /* @__PURE__ */ new Date()\n }\n });\n return ctx.json({\n access_token: accessToken2,\n token_type: \"bearer\",\n expires_in: opts.accessTokenExpiresIn,\n refresh_token: newRefreshToken,\n scope: token.scopes\n });\n }\n if (!code) {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"code is required\",\n error: \"invalid_request\"\n });\n }\n if (options.requirePKCE && !code_verifier) {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"code verifier is missing\",\n error: \"invalid_request\"\n });\n }\n const verificationValue = await ctx.context.internalAdapter.findVerificationValue(\n code.toString()\n );\n if (!verificationValue) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid code\",\n error: \"invalid_grant\"\n });\n }\n if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"code expired\",\n error: \"invalid_grant\"\n });\n }\n await ctx.context.internalAdapter.deleteVerificationValue(\n verificationValue.id\n );\n if (!client_id) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"client_id is required\",\n error: \"invalid_client\"\n });\n }\n if (!grant_type) {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"grant_type is required\",\n error: \"invalid_request\"\n });\n }\n if (grant_type !== \"authorization_code\") {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"grant_type must be 'authorization_code'\",\n error: \"unsupported_grant_type\"\n });\n }\n if (!redirect_uri) {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"redirect_uri is required\",\n error: \"invalid_request\"\n });\n }\n const client = await getClient(\n client_id.toString(),\n ctx.context.adapter,\n trustedClients\n );\n if (!client) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid client_id\",\n error: \"invalid_client\"\n });\n }\n if (client.disabled) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"client is disabled\",\n error: \"invalid_client\"\n });\n }\n const value = JSON.parse(\n verificationValue.value\n );\n if (value.clientId !== client_id.toString()) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid client_id\",\n error: \"invalid_client\"\n });\n }\n if (value.redirectURI !== redirect_uri.toString()) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid redirect_uri\",\n error: \"invalid_client\"\n });\n }\n if (value.codeChallenge && !code_verifier) {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"code verifier is missing\",\n error: \"invalid_request\"\n });\n }\n if (client.type === \"public\") {\n if (!code_verifier) {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"code verifier is required for public clients\",\n error: \"invalid_request\"\n });\n }\n } else {\n if (!client.clientSecret || !client_secret) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"client_secret is required for confidential clients\",\n error: \"invalid_client\"\n });\n }\n const isValidSecret = await verifyStoredClientSecret(\n ctx,\n client.clientSecret,\n client_secret.toString()\n );\n if (!isValidSecret) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid client_secret\",\n error: \"invalid_client\"\n });\n }\n }\n const challenge = value.codeChallengeMethod === \"plain\" ? code_verifier : await createHash(\"SHA-256\", \"base64urlnopad\").digest(\n code_verifier\n );\n if (challenge !== value.codeChallenge) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"code verification failed\",\n error: \"invalid_request\"\n });\n }\n const requestedScopes = value.scope;\n await ctx.context.internalAdapter.deleteVerificationValue(\n verificationValue.id\n );\n const accessToken = generateRandomString(32, \"a-z\", \"A-Z\");\n const refreshToken = generateRandomString(32, \"A-Z\", \"a-z\");\n const accessTokenExpiresAt = new Date(\n Date.now() + opts.accessTokenExpiresIn * 1e3\n );\n const refreshTokenExpiresAt = new Date(\n Date.now() + opts.refreshTokenExpiresIn * 1e3\n );\n await ctx.context.adapter.create({\n model: modelName.oauthAccessToken,\n data: {\n accessToken,\n refreshToken,\n accessTokenExpiresAt,\n refreshTokenExpiresAt,\n clientId: client_id.toString(),\n userId: value.userId,\n scopes: requestedScopes.join(\" \"),\n createdAt: /* @__PURE__ */ new Date(),\n updatedAt: /* @__PURE__ */ new Date()\n }\n });\n const user = await ctx.context.internalAdapter.findUserById(\n value.userId\n );\n if (!user) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"user not found\",\n error: \"invalid_grant\"\n });\n }\n const profile = {\n given_name: user.name.split(\" \")[0],\n family_name: user.name.split(\" \")[1],\n name: user.name,\n profile: user.image,\n updated_at: user.updatedAt.toISOString()\n };\n const email = {\n email: user.email,\n email_verified: user.emailVerified\n };\n const userClaims = {\n ...requestedScopes.includes(\"profile\") ? profile : {},\n ...requestedScopes.includes(\"email\") ? email : {}\n };\n const additionalUserClaims = options.getAdditionalUserInfoClaim ? await options.getAdditionalUserInfoClaim(user, requestedScopes) : {};\n const payload = {\n sub: user.id,\n aud: client_id.toString(),\n iat: Date.now(),\n auth_time: ctx.context.session?.session.createdAt.getTime(),\n nonce: value.nonce,\n acr: \"urn:mace:incommon:iap:silver\",\n // default to silver - ⚠︎ this should be configurable and should be validated against the client's metadata\n ...userClaims,\n ...additionalUserClaims\n };\n const expirationTime = Math.floor(Date.now() / 1e3) + opts.accessTokenExpiresIn;\n let idToken;\n if (options.useJWTPlugin) {\n const jwtPlugin = getJwtPlugin(ctx);\n if (!jwtPlugin) {\n ctx.context.logger.error(\n \"OIDC: `useJWTPlugin` is enabled but the JWT plugin is not available. Make sure you have the JWT Plugin in your plugins array or set `useJWTPlugin` to false.\"\n );\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n error_description: \"JWT plugin is not enabled\",\n error: \"internal_server_error\"\n });\n }\n idToken = await getJwtToken(\n {\n ...ctx,\n context: {\n ...ctx.context,\n session: {\n session: {\n id: generateRandomString(32, \"a-z\", \"A-Z\"),\n createdAt: /* @__PURE__ */ new Date(),\n updatedAt: /* @__PURE__ */ new Date(),\n userId: user.id,\n expiresAt: new Date(\n Date.now() + opts.accessTokenExpiresIn * 1e3\n ),\n token: accessToken,\n ipAddress: ctx.request?.headers.get(\"x-forwarded-for\")\n },\n user\n }\n }\n },\n {\n ...jwtPlugin.options,\n jwt: {\n ...jwtPlugin.options?.jwt,\n getSubject: () => user.id,\n audience: client_id.toString(),\n issuer: ctx.context.options.baseURL,\n expirationTime,\n definePayload: () => payload\n }\n }\n );\n } else {\n idToken = await new SignJWT(payload).setProtectedHeader({ alg: \"HS256\" }).setIssuedAt().setExpirationTime(expirationTime).sign(new TextEncoder().encode(client.clientSecret));\n }\n return ctx.json(\n {\n access_token: accessToken,\n token_type: \"Bearer\",\n expires_in: opts.accessTokenExpiresIn,\n refresh_token: requestedScopes.includes(\"offline_access\") ? refreshToken : void 0,\n scope: requestedScopes.join(\" \"),\n id_token: requestedScopes.includes(\"openid\") ? idToken : void 0\n },\n {\n headers: {\n \"Cache-Control\": \"no-store\",\n Pragma: \"no-cache\"\n }\n }\n );\n }\n ),\n oAuth2userInfo: createAuthEndpoint(\n \"/oauth2/userinfo\",\n {\n method: \"GET\",\n metadata: {\n isAction: false,\n openapi: {\n description: \"Get OAuth2 user information\",\n responses: {\n \"200\": {\n description: \"User information retrieved successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n sub: {\n type: \"string\",\n description: \"Subject identifier (user ID)\"\n },\n email: {\n type: \"string\",\n format: \"email\",\n nullable: true,\n description: \"User's email address, included if 'email' scope is granted\"\n },\n name: {\n type: \"string\",\n nullable: true,\n description: \"User's full name, included if 'profile' scope is granted\"\n },\n picture: {\n type: \"string\",\n format: \"uri\",\n nullable: true,\n description: \"User's profile picture URL, included if 'profile' scope is granted\"\n },\n given_name: {\n type: \"string\",\n nullable: true,\n description: \"User's given name, included if 'profile' scope is granted\"\n },\n family_name: {\n type: \"string\",\n nullable: true,\n description: \"User's family name, included if 'profile' scope is granted\"\n },\n email_verified: {\n type: \"boolean\",\n nullable: true,\n description: \"Whether the email is verified, included if 'email' scope is granted\"\n }\n },\n required: [\"sub\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n if (!ctx.request) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"request not found\",\n error: \"invalid_request\"\n });\n }\n const authorization = ctx.request.headers.get(\"authorization\");\n if (!authorization) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"authorization header not found\",\n error: \"invalid_request\"\n });\n }\n const token = authorization.replace(\"Bearer \", \"\");\n const accessToken = await ctx.context.adapter.findOne({\n model: modelName.oauthAccessToken,\n where: [\n {\n field: \"accessToken\",\n value: token\n }\n ]\n });\n if (!accessToken) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid access token\",\n error: \"invalid_token\"\n });\n }\n if (accessToken.accessTokenExpiresAt < /* @__PURE__ */ new Date()) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"The Access Token expired\",\n error: \"invalid_token\"\n });\n }\n const user = await ctx.context.internalAdapter.findUserById(\n accessToken.userId\n );\n if (!user) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"user not found\",\n error: \"invalid_token\"\n });\n }\n const requestedScopes = accessToken.scopes.split(\" \");\n const baseUserClaims = {\n sub: user.id,\n email: requestedScopes.includes(\"email\") ? user.email : void 0,\n name: requestedScopes.includes(\"profile\") ? user.name : void 0,\n picture: requestedScopes.includes(\"profile\") ? user.image : void 0,\n given_name: requestedScopes.includes(\"profile\") ? user.name.split(\" \")[0] : void 0,\n family_name: requestedScopes.includes(\"profile\") ? user.name.split(\" \")[1] : void 0,\n email_verified: requestedScopes.includes(\"email\") ? user.emailVerified : void 0\n };\n const userClaims = options.getAdditionalUserInfoClaim ? await options.getAdditionalUserInfoClaim(user, requestedScopes) : baseUserClaims;\n return ctx.json({\n ...baseUserClaims,\n ...userClaims\n });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/oauth2/register`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.registerOAuthApplication`\n *\n * **client:**\n * `authClient.oauth2.register`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/oidc-provider#api-method-oauth2-register)\n */\n registerOAuthApplication: createAuthEndpoint(\n \"/oauth2/register\",\n {\n method: \"POST\",\n body: z.object({\n redirect_uris: z.array(z.string()).meta({\n description: 'A list of redirect URIs. Eg: [\"https://client.example.com/callback\"]'\n }),\n token_endpoint_auth_method: z.enum([\"none\", \"client_secret_basic\", \"client_secret_post\"]).meta({\n description: 'The authentication method for the token endpoint. Eg: \"client_secret_basic\"'\n }).default(\"client_secret_basic\").optional(),\n grant_types: z.array(\n z.enum([\n \"authorization_code\",\n \"implicit\",\n \"password\",\n \"client_credentials\",\n \"refresh_token\",\n \"urn:ietf:params:oauth:grant-type:jwt-bearer\",\n \"urn:ietf:params:oauth:grant-type:saml2-bearer\"\n ])\n ).meta({\n description: 'The grant types supported by the application. Eg: [\"authorization_code\"]'\n }).default([\"authorization_code\"]).optional(),\n response_types: z.array(z.enum([\"code\", \"token\"])).meta({\n description: 'The response types supported by the application. Eg: [\"code\"]'\n }).default([\"code\"]).optional(),\n client_name: z.string().meta({\n description: 'The name of the application. Eg: \"My App\"'\n }).optional(),\n client_uri: z.string().meta({\n description: 'The URI of the application. Eg: \"https://client.example.com\"'\n }).optional(),\n logo_uri: z.string().meta({\n description: 'The URI of the application logo. Eg: \"https://client.example.com/logo.png\"'\n }).optional(),\n scope: z.string().meta({\n description: 'The scopes supported by the application. Separated by spaces. Eg: \"profile email\"'\n }).optional(),\n contacts: z.array(z.string()).meta({\n description: 'The contact information for the application. Eg: [\"admin@example.com\"]'\n }).optional(),\n tos_uri: z.string().meta({\n description: 'The URI of the application terms of service. Eg: \"https://client.example.com/tos\"'\n }).optional(),\n policy_uri: z.string().meta({\n description: 'The URI of the application privacy policy. Eg: \"https://client.example.com/policy\"'\n }).optional(),\n jwks_uri: z.string().meta({\n description: 'The URI of the application JWKS. Eg: \"https://client.example.com/jwks\"'\n }).optional(),\n jwks: z.record(z.any(), z.any()).meta({\n description: 'The JWKS of the application. Eg: {\"keys\": [{\"kty\": \"RSA\", \"alg\": \"RS256\", \"use\": \"sig\", \"n\": \"...\", \"e\": \"...\"}]}'\n }).optional(),\n metadata: z.record(z.any(), z.any()).meta({\n description: 'The metadata of the application. Eg: {\"key\": \"value\"}'\n }).optional(),\n software_id: z.string().meta({\n description: 'The software ID of the application. Eg: \"my-software\"'\n }).optional(),\n software_version: z.string().meta({\n description: 'The software version of the application. Eg: \"1.0.0\"'\n }).optional(),\n software_statement: z.string().meta({\n description: \"The software statement of the application.\"\n }).optional()\n }),\n metadata: {\n openapi: {\n description: \"Register an OAuth2 application\",\n responses: {\n \"200\": {\n description: \"OAuth2 application registered successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n name: {\n type: \"string\",\n description: \"Name of the OAuth2 application\"\n },\n icon: {\n type: \"string\",\n nullable: true,\n description: \"Icon URL for the application\"\n },\n metadata: {\n type: \"object\",\n additionalProperties: true,\n nullable: true,\n description: \"Additional metadata for the application\"\n },\n clientId: {\n type: \"string\",\n description: \"Unique identifier for the client\"\n },\n clientSecret: {\n type: \"string\",\n description: \"Secret key for the client\"\n },\n redirectURLs: {\n type: \"array\",\n items: { type: \"string\", format: \"uri\" },\n description: \"List of allowed redirect URLs\"\n },\n type: {\n type: \"string\",\n description: \"Type of the client\",\n enum: [\"web\"]\n },\n authenticationScheme: {\n type: \"string\",\n description: \"Authentication scheme used by the client\",\n enum: [\"client_secret\"]\n },\n disabled: {\n type: \"boolean\",\n description: \"Whether the client is disabled\",\n enum: [false]\n },\n userId: {\n type: \"string\",\n nullable: true,\n description: \"ID of the user who registered the client, null if registered anonymously\"\n },\n createdAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Creation timestamp\"\n },\n updatedAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Last update timestamp\"\n }\n },\n required: [\n \"name\",\n \"clientId\",\n \"clientSecret\",\n \"redirectURLs\",\n \"type\",\n \"authenticationScheme\",\n \"disabled\",\n \"createdAt\",\n \"updatedAt\"\n ]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const body = ctx.body;\n const session = await getSessionFromCtx(ctx);\n if (!session && !options.allowDynamicClientRegistration) {\n throw new APIError(\"UNAUTHORIZED\", {\n error: \"invalid_token\",\n error_description: \"Authentication required for client registration\"\n });\n }\n if ((!body.grant_types || body.grant_types.includes(\"authorization_code\") || body.grant_types.includes(\"implicit\")) && (!body.redirect_uris || body.redirect_uris.length === 0)) {\n throw new APIError(\"BAD_REQUEST\", {\n error: \"invalid_redirect_uri\",\n error_description: \"Redirect URIs are required for authorization_code and implicit grant types\"\n });\n }\n if (body.grant_types && body.response_types) {\n if (body.grant_types.includes(\"authorization_code\") && !body.response_types.includes(\"code\")) {\n throw new APIError(\"BAD_REQUEST\", {\n error: \"invalid_client_metadata\",\n error_description: \"When 'authorization_code' grant type is used, 'code' response type must be included\"\n });\n }\n if (body.grant_types.includes(\"implicit\") && !body.response_types.includes(\"token\")) {\n throw new APIError(\"BAD_REQUEST\", {\n error: \"invalid_client_metadata\",\n error_description: \"When 'implicit' grant type is used, 'token' response type must be included\"\n });\n }\n }\n const clientId = options.generateClientId?.() || generateRandomString(32, \"a-z\", \"A-Z\");\n const clientSecret = options.generateClientSecret?.() || generateRandomString(32, \"a-z\", \"A-Z\");\n const storedClientSecret = await storeClientSecret(ctx, clientSecret);\n const client = await ctx.context.adapter.create({\n model: modelName.oauthClient,\n data: {\n name: body.client_name,\n icon: body.logo_uri,\n metadata: body.metadata ? JSON.stringify(body.metadata) : null,\n clientId,\n clientSecret: storedClientSecret,\n redirectURLs: body.redirect_uris.join(\",\"),\n type: \"web\",\n authenticationScheme: body.token_endpoint_auth_method || \"client_secret_basic\",\n disabled: false,\n userId: session?.session.userId,\n createdAt: /* @__PURE__ */ new Date(),\n updatedAt: /* @__PURE__ */ new Date()\n }\n });\n return ctx.json(\n {\n client_id: clientId,\n ...client.type !== \"public\" ? {\n client_secret: clientSecret,\n client_secret_expires_at: 0\n // 0 means it doesn't expire\n } : {},\n client_id_issued_at: Math.floor(Date.now() / 1e3),\n client_secret_expires_at: 0,\n // 0 means it doesn't expire\n redirect_uris: body.redirect_uris,\n token_endpoint_auth_method: body.token_endpoint_auth_method || \"client_secret_basic\",\n grant_types: body.grant_types || [\"authorization_code\"],\n response_types: body.response_types || [\"code\"],\n client_name: body.client_name,\n client_uri: body.client_uri,\n logo_uri: body.logo_uri,\n scope: body.scope,\n contacts: body.contacts,\n tos_uri: body.tos_uri,\n policy_uri: body.policy_uri,\n jwks_uri: body.jwks_uri,\n jwks: body.jwks,\n software_id: body.software_id,\n software_version: body.software_version,\n software_statement: body.software_statement,\n metadata: body.metadata\n },\n {\n status: 201,\n headers: {\n \"Cache-Control\": \"no-store\",\n Pragma: \"no-cache\"\n }\n }\n );\n }\n ),\n getOAuthClient: createAuthEndpoint(\n \"/oauth2/client/:id\",\n {\n method: \"GET\",\n use: [sessionMiddleware],\n metadata: {\n openapi: {\n description: \"Get OAuth2 client details\",\n responses: {\n \"200\": {\n description: \"OAuth2 client retrieved successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n clientId: {\n type: \"string\",\n description: \"Unique identifier for the client\"\n },\n name: {\n type: \"string\",\n description: \"Name of the OAuth2 application\"\n },\n icon: {\n type: \"string\",\n nullable: true,\n description: \"Icon URL for the application\"\n }\n },\n required: [\"clientId\", \"name\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const client = await getClient(\n ctx.params.id,\n ctx.context.adapter,\n trustedClients\n );\n if (!client) {\n throw new APIError(\"NOT_FOUND\", {\n error_description: \"client not found\",\n error: \"not_found\"\n });\n }\n return ctx.json({\n clientId: client.clientId,\n name: client.name,\n icon: client.icon\n });\n }\n )\n },\n schema\n };\n};\n\nexport { getMetadata as a, getClient as g, oidcProvider as o, schema as s };\n","import { betterFetch } from '@better-fetch/fetch';\n\nconst defaultEndpoints = [\n \"/sign-up/email\",\n \"/sign-in/email\",\n \"/forget-password\"\n];\nconst Providers = {\n CLOUDFLARE_TURNSTILE: \"cloudflare-turnstile\",\n GOOGLE_RECAPTCHA: \"google-recaptcha\",\n HCAPTCHA: \"hcaptcha\"\n};\nconst siteVerifyMap = {\n [Providers.CLOUDFLARE_TURNSTILE]: \"https://challenges.cloudflare.com/turnstile/v0/siteverify\",\n [Providers.GOOGLE_RECAPTCHA]: \"https://www.google.com/recaptcha/api/siteverify\",\n [Providers.HCAPTCHA]: \"https://api.hcaptcha.com/siteverify\"\n};\n\nconst EXTERNAL_ERROR_CODES = {\n VERIFICATION_FAILED: \"Captcha verification failed\",\n MISSING_RESPONSE: \"Missing CAPTCHA response\",\n UNKNOWN_ERROR: \"Something went wrong\"\n};\nconst INTERNAL_ERROR_CODES = {\n MISSING_SECRET_KEY: \"Missing secret key\",\n SERVICE_UNAVAILABLE: \"CAPTCHA service unavailable\"\n};\n\nconst middlewareResponse = ({ message, status }) => ({\n response: new Response(\n JSON.stringify({\n message\n }),\n {\n status\n }\n )\n});\n\nconst cloudflareTurnstile = async ({\n siteVerifyURL,\n captchaResponse,\n secretKey,\n remoteIP\n}) => {\n const response = await betterFetch(siteVerifyURL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n secret: secretKey,\n response: captchaResponse,\n ...remoteIP && { remoteip: remoteIP }\n })\n });\n if (!response.data || response.error) {\n throw new Error(INTERNAL_ERROR_CODES.SERVICE_UNAVAILABLE);\n }\n if (!response.data.success) {\n return middlewareResponse({\n message: EXTERNAL_ERROR_CODES.VERIFICATION_FAILED,\n status: 403\n });\n }\n return void 0;\n};\n\nconst encodeToURLParams = (obj) => {\n if (typeof obj !== \"object\" || obj === null || Array.isArray(obj)) {\n throw new Error(\"Input must be a non-null object.\");\n }\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(obj)) {\n if (value !== void 0 && value !== null) {\n params.append(key, String(value));\n }\n }\n return params.toString();\n};\n\nconst isV3 = (response) => {\n return \"score\" in response && typeof response.score === \"number\";\n};\nconst googleRecaptcha = async ({\n siteVerifyURL,\n captchaResponse,\n secretKey,\n minScore = 0.5,\n remoteIP\n}) => {\n const response = await betterFetch(\n siteVerifyURL,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: encodeToURLParams({\n secret: secretKey,\n response: captchaResponse,\n ...remoteIP && { remoteip: remoteIP }\n })\n }\n );\n if (!response.data || response.error) {\n throw new Error(INTERNAL_ERROR_CODES.SERVICE_UNAVAILABLE);\n }\n if (!response.data.success || isV3(response.data) && response.data.score < minScore) {\n return middlewareResponse({\n message: EXTERNAL_ERROR_CODES.VERIFICATION_FAILED,\n status: 403\n });\n }\n return void 0;\n};\n\nconst hCaptcha = async ({\n siteVerifyURL,\n captchaResponse,\n secretKey,\n siteKey,\n remoteIP\n}) => {\n const response = await betterFetch(siteVerifyURL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: encodeToURLParams({\n secret: secretKey,\n response: captchaResponse,\n ...siteKey && { sitekey: siteKey },\n ...remoteIP && { remoteip: remoteIP }\n })\n });\n if (!response.data || response.error) {\n throw new Error(INTERNAL_ERROR_CODES.SERVICE_UNAVAILABLE);\n }\n if (!response.data.success) {\n return middlewareResponse({\n message: EXTERNAL_ERROR_CODES.VERIFICATION_FAILED,\n status: 403\n });\n }\n return void 0;\n};\n\nconst captcha = (options) => ({\n id: \"captcha\",\n onRequest: async (request, ctx) => {\n try {\n const endpoints = options.endpoints?.length ? options.endpoints : defaultEndpoints;\n if (!endpoints.some((endpoint) => request.url.includes(endpoint)))\n return void 0;\n if (!options.secretKey) {\n throw new Error(INTERNAL_ERROR_CODES.MISSING_SECRET_KEY);\n }\n const captchaResponse = request.headers.get(\"x-captcha-response\");\n const remoteUserIP = request.headers.get(\"x-captcha-user-remote-ip\") ?? void 0;\n if (!captchaResponse) {\n return middlewareResponse({\n message: EXTERNAL_ERROR_CODES.MISSING_RESPONSE,\n status: 400\n });\n }\n const siteVerifyURL = options.siteVerifyURLOverride || siteVerifyMap[options.provider];\n const handlerParams = {\n siteVerifyURL,\n captchaResponse,\n secretKey: options.secretKey,\n remoteIP: remoteUserIP\n };\n if (options.provider === Providers.CLOUDFLARE_TURNSTILE) {\n return await cloudflareTurnstile(handlerParams);\n }\n if (options.provider === Providers.GOOGLE_RECAPTCHA) {\n return await googleRecaptcha({\n ...handlerParams,\n minScore: options.minScore\n });\n }\n if (options.provider === Providers.HCAPTCHA) {\n return await hCaptcha({\n ...handlerParams,\n siteKey: options.siteKey\n });\n }\n } catch (_error) {\n const errorMessage = _error instanceof Error ? _error.message : void 0;\n ctx.logger.error(errorMessage ?? \"Unknown error\", {\n endpoint: request.url,\n message: _error\n });\n return middlewareResponse({\n message: EXTERNAL_ERROR_CODES.UNKNOWN_ERROR,\n status: 500\n });\n }\n }\n});\n\nexport { captcha };\n","import * as z from 'zod/v4';\nimport { APIError } from 'better-call';\nimport { j as createAuthEndpoint, k as getSessionFromCtx, l as sessionMiddleware, i as createAuthMiddleware } from './better-auth.D4HhkCZJ.mjs';\nimport './better-auth.8zoxzg-F.mjs';\nimport { base64Url } from '@better-auth/utils/base64';\nimport '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport { m as mergeSchema } from './better-auth.n2KFGwjY.mjs';\nimport './better-auth.DGaVMVAI.mjs';\nimport '../plugins/organization/access/index.mjs';\nimport '@better-auth/utils/random';\nimport { createHash } from '@better-auth/utils/hash';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport 'jose';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport { g as generateRandomString } from './better-auth.B4Qoxdgc.mjs';\nimport './better-auth.DBGfIDnh.mjs';\nimport 'kysely';\nimport 'defu';\nimport '@better-auth/utils/otp';\nimport '../plugins/admin/access/index.mjs';\nimport '@better-fetch/fetch';\nimport './better-auth.CGrHn1Ih.mjs';\nimport { g as getDate } from './better-auth.CW6D9eSx.mjs';\nimport { g as getIp } from './better-auth.DcfNPS8q.mjs';\nimport { p as parseJSON } from './better-auth.ffWeg50w.mjs';\nimport { s as safeJSONParse } from './better-auth.tB5eU6EY.mjs';\nimport { role } from '../plugins/access/index.mjs';\n\nconst apiKeySchema = ({\n timeWindow,\n rateLimitMax\n}) => ({\n apikey: {\n fields: {\n /**\n * The name of the key.\n */\n name: {\n type: \"string\",\n required: false,\n input: false\n },\n /**\n * Shows the first few characters of the API key\n * This allows you to show those few characters in the UI to make it easier for users to identify the API key.\n */\n start: {\n type: \"string\",\n required: false,\n input: false\n },\n /**\n * The prefix of the key.\n */\n prefix: {\n type: \"string\",\n required: false,\n input: false\n },\n /**\n * The hashed key value.\n */\n key: {\n type: \"string\",\n required: true,\n input: false\n },\n /**\n * The user id of the user who created the key.\n */\n userId: {\n type: \"string\",\n references: { model: \"user\", field: \"id\" },\n required: true,\n input: false\n },\n /**\n * The interval to refill the key in milliseconds.\n */\n refillInterval: {\n type: \"number\",\n required: false,\n input: false\n },\n /**\n * The amount to refill the remaining count of the key.\n */\n refillAmount: {\n type: \"number\",\n required: false,\n input: false\n },\n /**\n * The date and time when the key was last refilled.\n */\n lastRefillAt: {\n type: \"date\",\n required: false,\n input: false\n },\n /**\n * Whether the key is enabled.\n */\n enabled: {\n type: \"boolean\",\n required: false,\n input: false,\n defaultValue: true\n },\n /**\n * Whether the key has rate limiting enabled.\n */\n rateLimitEnabled: {\n type: \"boolean\",\n required: false,\n input: false,\n defaultValue: true\n },\n /**\n * The time window in milliseconds for the rate limit.\n */\n rateLimitTimeWindow: {\n type: \"number\",\n required: false,\n input: false,\n defaultValue: timeWindow\n },\n /**\n * The maximum number of requests allowed within the `rateLimitTimeWindow`.\n */\n rateLimitMax: {\n type: \"number\",\n required: false,\n input: false,\n defaultValue: rateLimitMax\n },\n /**\n * The number of requests made within the rate limit time window\n */\n requestCount: {\n type: \"number\",\n required: false,\n input: false,\n defaultValue: 0\n },\n /**\n * The remaining number of requests before the key is revoked.\n *\n * If this is null, then the key is not revoked.\n *\n * If `refillInterval` & `refillAmount` are provided, than this will refill accordingly.\n */\n remaining: {\n type: \"number\",\n required: false,\n input: false\n },\n /**\n * The date and time of the last request made to the key.\n */\n lastRequest: {\n type: \"date\",\n required: false,\n input: false\n },\n /**\n * The date and time when the key will expire.\n */\n expiresAt: {\n type: \"date\",\n required: false,\n input: false\n },\n /**\n * The date and time when the key was created.\n */\n createdAt: {\n type: \"date\",\n required: true,\n input: false\n },\n /**\n * The date and time when the key was last updated.\n */\n updatedAt: {\n type: \"date\",\n required: true,\n input: false\n },\n /**\n * The permissions of the key.\n */\n permissions: {\n type: \"string\",\n required: false,\n input: false\n },\n /**\n * Any additional metadata you want to store with the key.\n */\n metadata: {\n type: \"string\",\n required: false,\n input: true,\n transform: {\n input(value) {\n return JSON.stringify(value);\n },\n output(value) {\n if (!value) return null;\n return parseJSON(value);\n }\n }\n }\n }\n }\n});\n\nfunction createApiKey({\n keyGenerator,\n opts,\n schema,\n deleteAllExpiredApiKeys\n}) {\n return createAuthEndpoint(\n \"/api-key/create\",\n {\n method: \"POST\",\n body: z.object({\n name: z.string().meta({ description: \"Name of the Api Key\" }).optional(),\n expiresIn: z.number().meta({\n description: \"Expiration time of the Api Key in seconds\"\n }).min(1).optional().nullable().default(null),\n userId: z.coerce.string().meta({\n description: 'User Id of the user that the Api Key belongs to. server-only. Eg: \"user-id\"'\n }).optional(),\n prefix: z.string().meta({ description: \"Prefix of the Api Key\" }).regex(/^[a-zA-Z0-9_-]+$/, {\n message: \"Invalid prefix format, must be alphanumeric and contain only underscores and hyphens.\"\n }).optional(),\n remaining: z.number().meta({\n description: \"Remaining number of requests. Server side only\"\n }).min(0).optional().nullable().default(null),\n metadata: z.any().optional(),\n refillAmount: z.number().meta({\n description: \"Amount to refill the remaining count of the Api Key. server-only. Eg: 100\"\n }).min(1).optional(),\n refillInterval: z.number().meta({\n description: \"Interval to refill the Api Key in milliseconds. server-only. Eg: 1000\"\n }).optional(),\n rateLimitTimeWindow: z.number().meta({\n description: \"The duration in milliseconds where each request is counted. Once the `maxRequests` is reached, the request will be rejected until the `timeWindow` has passed, at which point the `timeWindow` will be reset. server-only. Eg: 1000\"\n }).optional(),\n rateLimitMax: z.number().meta({\n description: \"Maximum amount of requests allowed within a window. Once the `maxRequests` is reached, the request will be rejected until the `timeWindow` has passed, at which point the `timeWindow` will be reset. server-only. Eg: 100\"\n }).optional(),\n rateLimitEnabled: z.boolean().meta({\n description: \"Whether the key has rate limiting enabled. server-only. Eg: true\"\n }).optional(),\n permissions: z.record(z.string(), z.array(z.string())).meta({\n description: \"Permissions of the Api Key.\"\n }).optional()\n }),\n metadata: {\n openapi: {\n description: \"Create a new API key for a user\",\n responses: {\n \"200\": {\n description: \"API key created successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\",\n description: \"Unique identifier of the API key\"\n },\n createdAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Creation timestamp\"\n },\n updatedAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Last update timestamp\"\n },\n name: {\n type: \"string\",\n nullable: true,\n description: \"Name of the API key\"\n },\n prefix: {\n type: \"string\",\n nullable: true,\n description: \"Prefix of the API key\"\n },\n start: {\n type: \"string\",\n nullable: true,\n description: \"Starting characters of the key (if configured)\"\n },\n key: {\n type: \"string\",\n description: \"The full API key (only returned on creation)\"\n },\n enabled: {\n type: \"boolean\",\n description: \"Whether the key is enabled\"\n },\n expiresAt: {\n type: \"string\",\n format: \"date-time\",\n nullable: true,\n description: \"Expiration timestamp\"\n },\n userId: {\n type: \"string\",\n description: \"ID of the user owning the key\"\n },\n lastRefillAt: {\n type: \"string\",\n format: \"date-time\",\n nullable: true,\n description: \"Last refill timestamp\"\n },\n lastRequest: {\n type: \"string\",\n format: \"date-time\",\n nullable: true,\n description: \"Last request timestamp\"\n },\n metadata: {\n type: \"object\",\n nullable: true,\n additionalProperties: true,\n description: \"Metadata associated with the key\"\n },\n rateLimitMax: {\n type: \"number\",\n nullable: true,\n description: \"Maximum requests in time window\"\n },\n rateLimitTimeWindow: {\n type: \"number\",\n nullable: true,\n description: \"Rate limit time window in milliseconds\"\n },\n remaining: {\n type: \"number\",\n nullable: true,\n description: \"Remaining requests\"\n },\n refillAmount: {\n type: \"number\",\n nullable: true,\n description: \"Amount to refill\"\n },\n refillInterval: {\n type: \"number\",\n nullable: true,\n description: \"Refill interval in milliseconds\"\n },\n rateLimitEnabled: {\n type: \"boolean\",\n description: \"Whether rate limiting is enabled\"\n },\n requestCount: {\n type: \"number\",\n description: \"Current request count in window\"\n },\n permissions: {\n type: \"object\",\n nullable: true,\n additionalProperties: {\n type: \"array\",\n items: { type: \"string\" }\n },\n description: \"Permissions associated with the key\"\n }\n },\n required: [\n \"id\",\n \"createdAt\",\n \"updatedAt\",\n \"key\",\n \"enabled\",\n \"userId\",\n \"rateLimitEnabled\",\n \"requestCount\"\n ]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const {\n name,\n expiresIn,\n prefix,\n remaining,\n metadata,\n refillAmount,\n refillInterval,\n permissions,\n rateLimitMax,\n rateLimitTimeWindow,\n rateLimitEnabled\n } = ctx.body;\n const session = await getSessionFromCtx(ctx);\n const authRequired = (ctx.request || ctx.headers) && !ctx.body.userId;\n const user = session?.user ?? (authRequired ? null : { id: ctx.body.userId });\n if (!user?.id) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: ERROR_CODES.UNAUTHORIZED_SESSION\n });\n }\n if (authRequired) {\n if (refillAmount !== void 0 || refillInterval !== void 0 || rateLimitMax !== void 0 || rateLimitTimeWindow !== void 0 || rateLimitEnabled !== void 0 || permissions !== void 0 || remaining !== null) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.SERVER_ONLY_PROPERTY\n });\n }\n }\n if (metadata) {\n if (opts.enableMetadata === false) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.METADATA_DISABLED\n });\n }\n if (typeof metadata !== \"object\") {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_METADATA_TYPE\n });\n }\n }\n if (refillAmount && !refillInterval) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.REFILL_AMOUNT_AND_INTERVAL_REQUIRED\n });\n }\n if (refillInterval && !refillAmount) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.REFILL_INTERVAL_AND_AMOUNT_REQUIRED\n });\n }\n if (expiresIn) {\n if (opts.keyExpiration.disableCustomExpiresTime === true) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.KEY_DISABLED_EXPIRATION\n });\n }\n const expiresIn_in_days = expiresIn / (60 * 60 * 24);\n if (opts.keyExpiration.minExpiresIn > expiresIn_in_days) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.EXPIRES_IN_IS_TOO_SMALL\n });\n } else if (opts.keyExpiration.maxExpiresIn < expiresIn_in_days) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.EXPIRES_IN_IS_TOO_LARGE\n });\n }\n }\n if (prefix) {\n if (prefix.length < opts.minimumPrefixLength) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_PREFIX_LENGTH\n });\n }\n if (prefix.length > opts.maximumPrefixLength) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_PREFIX_LENGTH\n });\n }\n }\n if (name) {\n if (name.length < opts.minimumNameLength) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_NAME_LENGTH\n });\n }\n if (name.length > opts.maximumNameLength) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_NAME_LENGTH\n });\n }\n } else if (opts.requireName) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.NAME_REQUIRED\n });\n }\n deleteAllExpiredApiKeys(ctx.context);\n const key = await keyGenerator({\n length: opts.defaultKeyLength,\n prefix: prefix || opts.defaultPrefix\n });\n const hashed = opts.disableKeyHashing ? key : await defaultKeyHasher(key);\n let start = null;\n if (opts.startingCharactersConfig.shouldStore) {\n start = key.substring(\n 0,\n opts.startingCharactersConfig.charactersLength\n );\n }\n const defaultPermissions = opts.permissions?.defaultPermissions ? typeof opts.permissions.defaultPermissions === \"function\" ? await opts.permissions.defaultPermissions(user.id, ctx) : opts.permissions.defaultPermissions : void 0;\n const permissionsToApply = permissions ? JSON.stringify(permissions) : defaultPermissions ? JSON.stringify(defaultPermissions) : void 0;\n let data = {\n createdAt: /* @__PURE__ */ new Date(),\n updatedAt: /* @__PURE__ */ new Date(),\n name: name ?? null,\n prefix: prefix ?? opts.defaultPrefix ?? null,\n start,\n key: hashed,\n enabled: true,\n expiresAt: expiresIn ? getDate(expiresIn, \"sec\") : opts.keyExpiration.defaultExpiresIn ? getDate(opts.keyExpiration.defaultExpiresIn, \"sec\") : null,\n userId: user.id,\n lastRefillAt: null,\n lastRequest: null,\n metadata: null,\n rateLimitMax: rateLimitMax ?? opts.rateLimit.maxRequests ?? null,\n rateLimitTimeWindow: rateLimitTimeWindow ?? opts.rateLimit.timeWindow ?? null,\n remaining: remaining || refillAmount || null,\n refillAmount: refillAmount ?? null,\n refillInterval: refillInterval ?? null,\n rateLimitEnabled: rateLimitEnabled === void 0 ? opts.rateLimit.enabled ?? true : rateLimitEnabled,\n requestCount: 0,\n //@ts-ignore - we intentionally save the permissions as string on DB.\n permissions: permissionsToApply\n };\n if (metadata) {\n data.metadata = schema.apikey.fields.metadata.transform.input(metadata);\n }\n const apiKey = await ctx.context.adapter.create({\n model: API_KEY_TABLE_NAME,\n data\n });\n return ctx.json({\n ...apiKey,\n key,\n metadata: metadata ?? null,\n permissions: apiKey.permissions ? safeJSONParse(\n //@ts-ignore - from DB, this value is always a string\n apiKey.permissions\n ) : null\n });\n }\n );\n}\n\nfunction deleteApiKey({\n opts,\n schema,\n deleteAllExpiredApiKeys\n}) {\n return createAuthEndpoint(\n \"/api-key/delete\",\n {\n method: \"POST\",\n body: z.object({\n keyId: z.string().meta({\n description: \"The id of the Api Key\"\n })\n }),\n use: [sessionMiddleware],\n metadata: {\n openapi: {\n description: \"Delete an existing API key\",\n requestBody: {\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n keyId: {\n type: \"string\",\n description: \"The id of the API key to delete\"\n }\n },\n required: [\"keyId\"]\n }\n }\n }\n },\n responses: {\n \"200\": {\n description: \"API key deleted successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n success: {\n type: \"boolean\",\n description: \"Indicates if the API key was successfully deleted\"\n }\n },\n required: [\"success\"]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const { keyId } = ctx.body;\n const session = ctx.context.session;\n if (session.user.banned === true) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: ERROR_CODES.USER_BANNED\n });\n }\n const apiKey = await ctx.context.adapter.findOne({\n model: API_KEY_TABLE_NAME,\n where: [\n {\n field: \"id\",\n value: keyId\n }\n ]\n });\n if (!apiKey || apiKey.userId !== session.user.id) {\n throw new APIError(\"NOT_FOUND\", {\n message: ERROR_CODES.KEY_NOT_FOUND\n });\n }\n try {\n await ctx.context.adapter.delete({\n model: API_KEY_TABLE_NAME,\n where: [\n {\n field: \"id\",\n value: apiKey.id\n }\n ]\n });\n } catch (error) {\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n message: error?.message\n });\n }\n deleteAllExpiredApiKeys(ctx.context);\n return ctx.json({\n success: true\n });\n }\n );\n}\n\nfunction getApiKey({\n opts,\n schema,\n deleteAllExpiredApiKeys\n}) {\n return createAuthEndpoint(\n \"/api-key/get\",\n {\n method: \"GET\",\n query: z.object({\n id: z.string().meta({\n description: \"The id of the Api Key\"\n })\n }),\n use: [sessionMiddleware],\n metadata: {\n openapi: {\n description: \"Retrieve an existing API key by ID\",\n responses: {\n \"200\": {\n description: \"API key retrieved successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\",\n description: \"ID\"\n },\n name: {\n type: \"string\",\n nullable: true,\n description: \"The name of the key\"\n },\n start: {\n type: \"string\",\n nullable: true,\n description: \"Shows the first few characters of the API key, including the prefix. This allows you to show those few characters in the UI to make it easier for users to identify the API key.\"\n },\n prefix: {\n type: \"string\",\n nullable: true,\n description: \"The API Key prefix. Stored as plain text.\"\n },\n userId: {\n type: \"string\",\n description: \"The owner of the user id\"\n },\n refillInterval: {\n type: \"number\",\n nullable: true,\n description: \"The interval in which the `remaining` count is refilled by day. Example: 1 // every day\"\n },\n refillAmount: {\n type: \"number\",\n nullable: true,\n description: \"The amount to refill\"\n },\n lastRefillAt: {\n type: \"string\",\n format: \"date-time\",\n nullable: true,\n description: \"The last refill date\"\n },\n enabled: {\n type: \"boolean\",\n description: \"Sets if key is enabled or disabled\",\n default: true\n },\n rateLimitEnabled: {\n type: \"boolean\",\n description: \"Whether the key has rate limiting enabled\"\n },\n rateLimitTimeWindow: {\n type: \"number\",\n nullable: true,\n description: \"The duration in milliseconds\"\n },\n rateLimitMax: {\n type: \"number\",\n nullable: true,\n description: \"Maximum amount of requests allowed within a window\"\n },\n requestCount: {\n type: \"number\",\n description: \"The number of requests made within the rate limit time window\"\n },\n remaining: {\n type: \"number\",\n nullable: true,\n description: \"Remaining requests (every time api key is used this should updated and should be updated on refill as well)\"\n },\n lastRequest: {\n type: \"string\",\n format: \"date-time\",\n nullable: true,\n description: \"When last request occurred\"\n },\n expiresAt: {\n type: \"string\",\n format: \"date-time\",\n nullable: true,\n description: \"Expiry date of a key\"\n },\n createdAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"created at\"\n },\n updatedAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"updated at\"\n },\n metadata: {\n type: \"object\",\n nullable: true,\n additionalProperties: true,\n description: \"Extra metadata about the apiKey\"\n },\n permissions: {\n type: \"string\",\n nullable: true,\n description: \"Permissions for the api key (stored as JSON string)\"\n }\n },\n required: [\n \"id\",\n \"userId\",\n \"enabled\",\n \"rateLimitEnabled\",\n \"requestCount\",\n \"createdAt\",\n \"updatedAt\"\n ]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const { id } = ctx.query;\n const session = ctx.context.session;\n let apiKey = await ctx.context.adapter.findOne({\n model: API_KEY_TABLE_NAME,\n where: [\n {\n field: \"id\",\n value: id\n },\n {\n field: \"userId\",\n value: session.user.id\n }\n ]\n });\n if (!apiKey) {\n throw new APIError(\"NOT_FOUND\", {\n message: ERROR_CODES.KEY_NOT_FOUND\n });\n }\n deleteAllExpiredApiKeys(ctx.context);\n apiKey.metadata = schema.apikey.fields.metadata.transform.output(\n apiKey.metadata\n );\n const { key, ...returningApiKey } = apiKey;\n return ctx.json({\n ...returningApiKey,\n permissions: returningApiKey.permissions ? safeJSONParse(\n //@ts-ignore - From DB this is always a string\n returningApiKey.permissions\n ) : null\n });\n }\n );\n}\n\nfunction updateApiKey({\n opts,\n schema,\n deleteAllExpiredApiKeys\n}) {\n return createAuthEndpoint(\n \"/api-key/update\",\n {\n method: \"POST\",\n body: z.object({\n keyId: z.string().meta({\n description: \"The id of the Api Key\"\n }),\n userId: z.coerce.string().meta({\n description: 'The id of the user which the api key belongs to. server-only. Eg: \"some-user-id\"'\n }).optional(),\n name: z.string().meta({\n description: \"The name of the key\"\n }).optional(),\n enabled: z.boolean().meta({\n description: \"Whether the Api Key is enabled or not\"\n }).optional(),\n remaining: z.number().meta({\n description: \"The number of remaining requests\"\n }).min(1).optional(),\n refillAmount: z.number().meta({\n description: \"The refill amount\"\n }).optional(),\n refillInterval: z.number().meta({\n description: \"The refill interval\"\n }).optional(),\n metadata: z.any().optional(),\n expiresIn: z.number().meta({\n description: \"Expiration time of the Api Key in seconds\"\n }).min(1).optional().nullable(),\n rateLimitEnabled: z.boolean().meta({\n description: \"Whether the key has rate limiting enabled.\"\n }).optional(),\n rateLimitTimeWindow: z.number().meta({\n description: \"The duration in milliseconds where each request is counted. server-only. Eg: 1000\"\n }).optional(),\n rateLimitMax: z.number().meta({\n description: \"Maximum amount of requests allowed within a window. Once the `maxRequests` is reached, the request will be rejected until the `timeWindow` has passed, at which point the `timeWindow` will be reset. server-only. Eg: 100\"\n }).optional(),\n permissions: z.record(z.string(), z.array(z.string())).meta({\n description: \"Update the permissions on the API Key. server-only.\"\n }).optional().nullable()\n }),\n metadata: {\n openapi: {\n description: \"Update an existing API key by ID\",\n responses: {\n \"200\": {\n description: \"API key updated successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\",\n description: \"ID\"\n },\n name: {\n type: \"string\",\n nullable: true,\n description: \"The name of the key\"\n },\n start: {\n type: \"string\",\n nullable: true,\n description: \"Shows the first few characters of the API key, including the prefix. This allows you to show those few characters in the UI to make it easier for users to identify the API key.\"\n },\n prefix: {\n type: \"string\",\n nullable: true,\n description: \"The API Key prefix. Stored as plain text.\"\n },\n userId: {\n type: \"string\",\n description: \"The owner of the user id\"\n },\n refillInterval: {\n type: \"number\",\n nullable: true,\n description: \"The interval in which the `remaining` count is refilled by day. Example: 1 // every day\"\n },\n refillAmount: {\n type: \"number\",\n nullable: true,\n description: \"The amount to refill\"\n },\n lastRefillAt: {\n type: \"string\",\n format: \"date-time\",\n nullable: true,\n description: \"The last refill date\"\n },\n enabled: {\n type: \"boolean\",\n description: \"Sets if key is enabled or disabled\",\n default: true\n },\n rateLimitEnabled: {\n type: \"boolean\",\n description: \"Whether the key has rate limiting enabled\"\n },\n rateLimitTimeWindow: {\n type: \"number\",\n nullable: true,\n description: \"The duration in milliseconds\"\n },\n rateLimitMax: {\n type: \"number\",\n nullable: true,\n description: \"Maximum amount of requests allowed within a window\"\n },\n requestCount: {\n type: \"number\",\n description: \"The number of requests made within the rate limit time window\"\n },\n remaining: {\n type: \"number\",\n nullable: true,\n description: \"Remaining requests (every time api key is used this should updated and should be updated on refill as well)\"\n },\n lastRequest: {\n type: \"string\",\n format: \"date-time\",\n nullable: true,\n description: \"When last request occurred\"\n },\n expiresAt: {\n type: \"string\",\n format: \"date-time\",\n nullable: true,\n description: \"Expiry date of a key\"\n },\n createdAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"created at\"\n },\n updatedAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"updated at\"\n },\n metadata: {\n type: \"object\",\n nullable: true,\n additionalProperties: true,\n description: \"Extra metadata about the apiKey\"\n },\n permissions: {\n type: \"string\",\n nullable: true,\n description: \"Permissions for the api key (stored as JSON string)\"\n }\n },\n required: [\n \"id\",\n \"userId\",\n \"enabled\",\n \"rateLimitEnabled\",\n \"requestCount\",\n \"createdAt\",\n \"updatedAt\"\n ]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const {\n keyId,\n expiresIn,\n enabled,\n metadata,\n refillAmount,\n refillInterval,\n remaining,\n name,\n permissions,\n rateLimitEnabled,\n rateLimitTimeWindow,\n rateLimitMax\n } = ctx.body;\n const session = await getSessionFromCtx(ctx);\n const authRequired = (ctx.request || ctx.headers) && !ctx.body.userId;\n const user = session?.user ?? (authRequired ? null : { id: ctx.body.userId });\n if (!user?.id) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: ERROR_CODES.UNAUTHORIZED_SESSION\n });\n }\n if (authRequired) {\n if (refillAmount !== void 0 || refillInterval !== void 0 || rateLimitMax !== void 0 || rateLimitTimeWindow !== void 0 || rateLimitEnabled !== void 0 || remaining !== void 0 || permissions !== void 0) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.SERVER_ONLY_PROPERTY\n });\n }\n }\n const apiKey = await ctx.context.adapter.findOne({\n model: API_KEY_TABLE_NAME,\n where: [\n {\n field: \"id\",\n value: keyId\n },\n {\n field: \"userId\",\n value: user.id\n }\n ]\n });\n if (!apiKey) {\n throw new APIError(\"NOT_FOUND\", {\n message: ERROR_CODES.KEY_NOT_FOUND\n });\n }\n let newValues = {};\n if (name !== void 0) {\n if (name.length < opts.minimumNameLength) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_NAME_LENGTH\n });\n } else if (name.length > opts.maximumNameLength) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_NAME_LENGTH\n });\n }\n newValues.name = name;\n }\n if (enabled !== void 0) {\n newValues.enabled = enabled;\n }\n if (expiresIn !== void 0) {\n if (opts.keyExpiration.disableCustomExpiresTime === true) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.KEY_DISABLED_EXPIRATION\n });\n }\n if (expiresIn !== null) {\n const expiresIn_in_days = expiresIn / (60 * 60 * 24);\n if (expiresIn_in_days < opts.keyExpiration.minExpiresIn) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.EXPIRES_IN_IS_TOO_SMALL\n });\n } else if (expiresIn_in_days > opts.keyExpiration.maxExpiresIn) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.EXPIRES_IN_IS_TOO_LARGE\n });\n }\n }\n newValues.expiresAt = expiresIn ? getDate(expiresIn, \"sec\") : null;\n }\n if (metadata !== void 0) {\n if (typeof metadata !== \"object\") {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_METADATA_TYPE\n });\n }\n newValues.metadata = schema.apikey.fields.metadata.transform.input(metadata);\n }\n if (remaining !== void 0) {\n newValues.remaining = remaining;\n }\n if (refillAmount !== void 0 || refillInterval !== void 0) {\n if (refillAmount !== void 0 && refillInterval === void 0) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.REFILL_AMOUNT_AND_INTERVAL_REQUIRED\n });\n } else if (refillInterval !== void 0 && refillAmount === void 0) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.REFILL_INTERVAL_AND_AMOUNT_REQUIRED\n });\n }\n newValues.refillAmount = refillAmount;\n newValues.refillInterval = refillInterval;\n }\n if (rateLimitEnabled !== void 0) {\n newValues.rateLimitEnabled = rateLimitEnabled;\n }\n if (rateLimitTimeWindow !== void 0) {\n newValues.rateLimitTimeWindow = rateLimitTimeWindow;\n }\n if (rateLimitMax !== void 0) {\n newValues.rateLimitMax = rateLimitMax;\n }\n if (permissions !== void 0) {\n newValues.permissions = JSON.stringify(permissions);\n }\n if (Object.keys(newValues).length === 0) {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.NO_VALUES_TO_UPDATE\n });\n }\n let newApiKey = apiKey;\n try {\n let result = await ctx.context.adapter.update({\n model: API_KEY_TABLE_NAME,\n where: [\n {\n field: \"id\",\n value: apiKey.id\n }\n ],\n update: {\n lastRequest: /* @__PURE__ */ new Date(),\n remaining: apiKey.remaining === null ? null : apiKey.remaining - 1,\n ...newValues\n }\n });\n if (result) newApiKey = result;\n } catch (error) {\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n message: error?.message\n });\n }\n deleteAllExpiredApiKeys(ctx.context);\n newApiKey.metadata = schema.apikey.fields.metadata.transform.output(\n newApiKey.metadata\n );\n const { key, ...returningApiKey } = newApiKey;\n return ctx.json({\n ...returningApiKey,\n permissions: returningApiKey.permissions ? safeJSONParse(\n //@ts-ignore - from DB, this value is always a string\n returningApiKey.permissions\n ) : null\n });\n }\n );\n}\n\nfunction isRateLimited(apiKey, opts) {\n const now = /* @__PURE__ */ new Date();\n const lastRequest = apiKey.lastRequest;\n const rateLimitTimeWindow = apiKey.rateLimitTimeWindow;\n const rateLimitMax = apiKey.rateLimitMax;\n let requestCount = apiKey.requestCount;\n if (opts.rateLimit.enabled === false)\n return {\n success: true,\n message: null,\n update: { lastRequest: now },\n tryAgainIn: null\n };\n if (apiKey.rateLimitEnabled === false)\n return {\n success: true,\n message: null,\n update: { lastRequest: now },\n tryAgainIn: null\n };\n if (rateLimitTimeWindow === null || rateLimitMax === null) {\n return {\n success: true,\n message: null,\n update: null,\n tryAgainIn: null\n };\n }\n if (lastRequest === null) {\n return {\n success: true,\n message: null,\n update: { lastRequest: now, requestCount: 1 },\n tryAgainIn: null\n };\n }\n const timeSinceLastRequest = now.getTime() - lastRequest.getTime();\n if (timeSinceLastRequest > rateLimitTimeWindow) {\n return {\n success: true,\n message: null,\n update: { lastRequest: now, requestCount: 1 },\n tryAgainIn: null\n };\n }\n if (requestCount >= rateLimitMax) {\n return {\n success: false,\n message: ERROR_CODES.RATE_LIMIT_EXCEEDED,\n update: null,\n tryAgainIn: Math.ceil(rateLimitTimeWindow - timeSinceLastRequest)\n };\n }\n requestCount++;\n return {\n success: true,\n message: null,\n tryAgainIn: null,\n update: { lastRequest: now, requestCount }\n };\n}\n\nasync function validateApiKey({\n hashedKey,\n ctx,\n opts,\n schema,\n permissions\n}) {\n const apiKey = await ctx.context.adapter.findOne({\n model: API_KEY_TABLE_NAME,\n where: [\n {\n field: \"key\",\n value: hashedKey\n }\n ]\n });\n if (!apiKey) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: ERROR_CODES.INVALID_API_KEY\n });\n }\n if (apiKey.enabled === false) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: ERROR_CODES.KEY_DISABLED,\n code: \"KEY_DISABLED\"\n });\n }\n if (apiKey.expiresAt) {\n const now = (/* @__PURE__ */ new Date()).getTime();\n const expiresAt = apiKey.expiresAt.getTime();\n if (now > expiresAt) {\n try {\n ctx.context.adapter.delete({\n model: API_KEY_TABLE_NAME,\n where: [\n {\n field: \"id\",\n value: apiKey.id\n }\n ]\n });\n } catch (error) {\n ctx.context.logger.error(`Failed to delete expired API keys:`, error);\n }\n throw new APIError(\"UNAUTHORIZED\", {\n message: ERROR_CODES.KEY_EXPIRED,\n code: \"KEY_EXPIRED\"\n });\n }\n }\n if (permissions) {\n const apiKeyPermissions = apiKey.permissions ? safeJSONParse(\n //@ts-ignore - from DB, this value is always a string\n apiKey.permissions\n ) : null;\n if (!apiKeyPermissions) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: ERROR_CODES.KEY_NOT_FOUND,\n code: \"KEY_NOT_FOUND\"\n });\n }\n const r = role(apiKeyPermissions);\n const result = r.authorize(permissions);\n if (!result.success) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: ERROR_CODES.KEY_NOT_FOUND,\n code: \"KEY_NOT_FOUND\"\n });\n }\n }\n let remaining = apiKey.remaining;\n let lastRefillAt = apiKey.lastRefillAt;\n if (apiKey.remaining === 0 && apiKey.refillAmount === null) {\n try {\n ctx.context.adapter.delete({\n model: API_KEY_TABLE_NAME,\n where: [\n {\n field: \"id\",\n value: apiKey.id\n }\n ]\n });\n } catch (error) {\n ctx.context.logger.error(`Failed to delete expired API keys:`, error);\n }\n throw new APIError(\"TOO_MANY_REQUESTS\", {\n message: ERROR_CODES.USAGE_EXCEEDED,\n code: \"USAGE_EXCEEDED\"\n });\n } else if (remaining !== null) {\n let now = (/* @__PURE__ */ new Date()).getTime();\n const refillInterval = apiKey.refillInterval;\n const refillAmount = apiKey.refillAmount;\n let lastTime = (lastRefillAt ?? apiKey.createdAt).getTime();\n if (refillInterval && refillAmount) {\n const timeSinceLastRequest = (now - lastTime) / (1e3 * 60 * 60 * 24);\n if (timeSinceLastRequest > refillInterval) {\n remaining = refillAmount;\n lastRefillAt = /* @__PURE__ */ new Date();\n }\n }\n if (remaining === 0) {\n throw new APIError(\"TOO_MANY_REQUESTS\", {\n message: ERROR_CODES.USAGE_EXCEEDED,\n code: \"USAGE_EXCEEDED\"\n });\n } else {\n remaining--;\n }\n }\n const { message, success, update, tryAgainIn } = isRateLimited(apiKey, opts);\n const newApiKey = await ctx.context.adapter.update({\n model: API_KEY_TABLE_NAME,\n where: [\n {\n field: \"id\",\n value: apiKey.id\n }\n ],\n update: {\n ...update,\n remaining,\n lastRefillAt\n }\n });\n if (!newApiKey) {\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n message: ERROR_CODES.FAILED_TO_UPDATE_API_KEY,\n code: \"INTERNAL_SERVER_ERROR\"\n });\n }\n if (success === false) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: message ?? void 0,\n code: \"RATE_LIMITED\",\n details: {\n tryAgainIn\n }\n });\n }\n return newApiKey;\n}\nfunction verifyApiKey({\n opts,\n schema,\n deleteAllExpiredApiKeys\n}) {\n return createAuthEndpoint(\n \"/api-key/verify\",\n {\n method: \"POST\",\n body: z.object({\n key: z.string().meta({\n description: \"The key to verify\"\n }),\n permissions: z.record(z.string(), z.array(z.string())).meta({\n description: \"The permissions to verify.\"\n }).optional()\n }),\n metadata: {\n SERVER_ONLY: true\n }\n },\n async (ctx) => {\n const { key } = ctx.body;\n if (key.length < opts.defaultKeyLength) {\n return ctx.json({\n valid: false,\n error: {\n message: ERROR_CODES.INVALID_API_KEY,\n code: \"KEY_NOT_FOUND\"\n },\n key: null\n });\n }\n if (opts.customAPIKeyValidator) {\n const isValid = await opts.customAPIKeyValidator({ ctx, key });\n if (!isValid) {\n return ctx.json({\n valid: false,\n error: {\n message: ERROR_CODES.INVALID_API_KEY,\n code: \"KEY_NOT_FOUND\"\n },\n key: null\n });\n }\n }\n const hashed = opts.disableKeyHashing ? key : await defaultKeyHasher(key);\n let apiKey = null;\n try {\n apiKey = await validateApiKey({\n hashedKey: hashed,\n permissions: ctx.body.permissions,\n ctx,\n opts,\n schema\n });\n await deleteAllExpiredApiKeys(ctx.context);\n } catch (error) {\n if (error instanceof APIError) {\n return ctx.json({\n valid: false,\n error: {\n message: error.body?.message,\n code: error.body?.code\n },\n key: null\n });\n }\n return ctx.json({\n valid: false,\n error: {\n message: ERROR_CODES.INVALID_API_KEY,\n code: \"INVALID_API_KEY\"\n },\n key: null\n });\n }\n const { key: _, ...returningApiKey } = apiKey ?? {\n key: 1,\n permissions: void 0\n };\n if (\"metadata\" in returningApiKey) {\n returningApiKey.metadata = schema.apikey.fields.metadata.transform.output(\n returningApiKey.metadata\n );\n }\n returningApiKey.permissions = returningApiKey.permissions ? safeJSONParse(\n //@ts-ignore - from DB, this value is always a string\n returningApiKey.permissions\n ) : null;\n return ctx.json({\n valid: true,\n error: null,\n key: apiKey === null ? null : returningApiKey\n });\n }\n );\n}\n\nfunction listApiKeys({\n opts,\n schema,\n deleteAllExpiredApiKeys\n}) {\n return createAuthEndpoint(\n \"/api-key/list\",\n {\n method: \"GET\",\n use: [sessionMiddleware],\n metadata: {\n openapi: {\n description: \"List all API keys for the authenticated user\",\n responses: {\n \"200\": {\n description: \"API keys retrieved successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\",\n description: \"ID\"\n },\n name: {\n type: \"string\",\n nullable: true,\n description: \"The name of the key\"\n },\n start: {\n type: \"string\",\n nullable: true,\n description: \"Shows the first few characters of the API key, including the prefix. This allows you to show those few characters in the UI to make it easier for users to identify the API key.\"\n },\n prefix: {\n type: \"string\",\n nullable: true,\n description: \"The API Key prefix. Stored as plain text.\"\n },\n userId: {\n type: \"string\",\n description: \"The owner of the user id\"\n },\n refillInterval: {\n type: \"number\",\n nullable: true,\n description: \"The interval in which the `remaining` count is refilled by day. Example: 1 // every day\"\n },\n refillAmount: {\n type: \"number\",\n nullable: true,\n description: \"The amount to refill\"\n },\n lastRefillAt: {\n type: \"string\",\n format: \"date-time\",\n nullable: true,\n description: \"The last refill date\"\n },\n enabled: {\n type: \"boolean\",\n description: \"Sets if key is enabled or disabled\",\n default: true\n },\n rateLimitEnabled: {\n type: \"boolean\",\n description: \"Whether the key has rate limiting enabled\"\n },\n rateLimitTimeWindow: {\n type: \"number\",\n nullable: true,\n description: \"The duration in milliseconds\"\n },\n rateLimitMax: {\n type: \"number\",\n nullable: true,\n description: \"Maximum amount of requests allowed within a window\"\n },\n requestCount: {\n type: \"number\",\n description: \"The number of requests made within the rate limit time window\"\n },\n remaining: {\n type: \"number\",\n nullable: true,\n description: \"Remaining requests (every time api key is used this should updated and should be updated on refill as well)\"\n },\n lastRequest: {\n type: \"string\",\n format: \"date-time\",\n nullable: true,\n description: \"When last request occurred\"\n },\n expiresAt: {\n type: \"string\",\n format: \"date-time\",\n nullable: true,\n description: \"Expiry date of a key\"\n },\n createdAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"created at\"\n },\n updatedAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"updated at\"\n },\n metadata: {\n type: \"object\",\n nullable: true,\n additionalProperties: true,\n description: \"Extra metadata about the apiKey\"\n },\n permissions: {\n type: \"string\",\n nullable: true,\n description: \"Permissions for the api key (stored as JSON string)\"\n }\n },\n required: [\n \"id\",\n \"userId\",\n \"enabled\",\n \"rateLimitEnabled\",\n \"requestCount\",\n \"createdAt\",\n \"updatedAt\"\n ]\n }\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const session = ctx.context.session;\n let apiKeys = await ctx.context.adapter.findMany({\n model: API_KEY_TABLE_NAME,\n where: [\n {\n field: \"userId\",\n value: session.user.id\n }\n ]\n });\n deleteAllExpiredApiKeys(ctx.context);\n apiKeys = apiKeys.map((apiKey) => {\n return {\n ...apiKey,\n metadata: schema.apikey.fields.metadata.transform.output(\n apiKey.metadata\n )\n };\n });\n let returningApiKey = apiKeys.map((x) => {\n const { key, ...returningApiKey2 } = x;\n return {\n ...returningApiKey2,\n permissions: returningApiKey2.permissions ? safeJSONParse(\n //@ts-ignore - From DB this is always a string\n returningApiKey2.permissions\n ) : null\n };\n });\n return ctx.json(returningApiKey);\n }\n );\n}\n\nfunction deleteAllExpiredApiKeysEndpoint({\n deleteAllExpiredApiKeys\n}) {\n return createAuthEndpoint(\n \"/api-key/delete-all-expired-api-keys\",\n {\n method: \"POST\",\n metadata: {\n SERVER_ONLY: true,\n client: false\n }\n },\n async (ctx) => {\n try {\n await deleteAllExpiredApiKeys(ctx.context, true);\n } catch (error) {\n ctx.context.logger.error(\n \"[API KEY PLUGIN] Failed to delete expired API keys:\",\n error\n );\n return ctx.json({\n success: false,\n error\n });\n }\n return ctx.json({ success: true, error: null });\n }\n );\n}\n\nlet lastChecked = null;\nfunction deleteAllExpiredApiKeys(ctx, byPassLastCheckTime = false) {\n if (lastChecked && !byPassLastCheckTime) {\n const now = /* @__PURE__ */ new Date();\n const diff = now.getTime() - lastChecked.getTime();\n if (diff < 1e4) {\n return;\n }\n }\n lastChecked = /* @__PURE__ */ new Date();\n try {\n return ctx.adapter.deleteMany({\n model: API_KEY_TABLE_NAME,\n where: [\n {\n field: \"expiresAt\",\n operator: \"lt\",\n value: /* @__PURE__ */ new Date()\n },\n {\n field: \"expiresAt\",\n operator: \"ne\",\n value: null\n }\n ]\n });\n } catch (error) {\n ctx.logger.error(`Failed to delete expired API keys:`, error);\n }\n}\nfunction createApiKeyRoutes({\n keyGenerator,\n opts,\n schema\n}) {\n return {\n createApiKey: createApiKey({\n keyGenerator,\n opts,\n schema,\n deleteAllExpiredApiKeys\n }),\n verifyApiKey: verifyApiKey({ opts, schema, deleteAllExpiredApiKeys }),\n getApiKey: getApiKey({ opts, schema, deleteAllExpiredApiKeys }),\n updateApiKey: updateApiKey({ opts, schema, deleteAllExpiredApiKeys }),\n deleteApiKey: deleteApiKey({ opts, schema, deleteAllExpiredApiKeys }),\n listApiKeys: listApiKeys({ opts, schema, deleteAllExpiredApiKeys }),\n deleteAllExpiredApiKeys: deleteAllExpiredApiKeysEndpoint({\n deleteAllExpiredApiKeys\n })\n };\n}\n\nconst defaultKeyHasher = async (key) => {\n const hash = await createHash(\"SHA-256\").digest(\n new TextEncoder().encode(key)\n );\n const hashed = base64Url.encode(new Uint8Array(hash), {\n padding: false\n });\n return hashed;\n};\nconst ERROR_CODES = {\n INVALID_METADATA_TYPE: \"metadata must be an object or undefined\",\n REFILL_AMOUNT_AND_INTERVAL_REQUIRED: \"refillAmount is required when refillInterval is provided\",\n REFILL_INTERVAL_AND_AMOUNT_REQUIRED: \"refillInterval is required when refillAmount is provided\",\n USER_BANNED: \"User is banned\",\n UNAUTHORIZED_SESSION: \"Unauthorized or invalid session\",\n KEY_NOT_FOUND: \"API Key not found\",\n KEY_DISABLED: \"API Key is disabled\",\n KEY_EXPIRED: \"API Key has expired\",\n USAGE_EXCEEDED: \"API Key has reached its usage limit\",\n KEY_NOT_RECOVERABLE: \"API Key is not recoverable\",\n EXPIRES_IN_IS_TOO_SMALL: \"The expiresIn is smaller than the predefined minimum value.\",\n EXPIRES_IN_IS_TOO_LARGE: \"The expiresIn is larger than the predefined maximum value.\",\n INVALID_REMAINING: \"The remaining count is either too large or too small.\",\n INVALID_PREFIX_LENGTH: \"The prefix length is either too large or too small.\",\n INVALID_NAME_LENGTH: \"The name length is either too large or too small.\",\n METADATA_DISABLED: \"Metadata is disabled.\",\n RATE_LIMIT_EXCEEDED: \"Rate limit exceeded.\",\n NO_VALUES_TO_UPDATE: \"No values to update.\",\n KEY_DISABLED_EXPIRATION: \"Custom key expiration values are disabled.\",\n INVALID_API_KEY: \"Invalid API key.\",\n INVALID_USER_ID_FROM_API_KEY: \"The user id from the API key is invalid.\",\n INVALID_API_KEY_GETTER_RETURN_TYPE: \"API Key getter returned an invalid key type. Expected string.\",\n SERVER_ONLY_PROPERTY: \"The property you're trying to set can only be set from the server auth instance only.\",\n FAILED_TO_UPDATE_API_KEY: \"Failed to update API key\",\n NAME_REQUIRED: \"API Key name is required.\"\n};\nconst API_KEY_TABLE_NAME = \"apikey\";\nconst apiKey = (options) => {\n const opts = {\n ...options,\n apiKeyHeaders: options?.apiKeyHeaders ?? \"x-api-key\",\n defaultKeyLength: options?.defaultKeyLength || 64,\n maximumPrefixLength: options?.maximumPrefixLength ?? 32,\n minimumPrefixLength: options?.minimumPrefixLength ?? 1,\n maximumNameLength: options?.maximumNameLength ?? 32,\n minimumNameLength: options?.minimumNameLength ?? 1,\n enableMetadata: options?.enableMetadata ?? false,\n disableKeyHashing: options?.disableKeyHashing ?? false,\n requireName: options?.requireName ?? false,\n rateLimit: {\n enabled: options?.rateLimit?.enabled === void 0 ? true : options?.rateLimit?.enabled,\n timeWindow: options?.rateLimit?.timeWindow ?? 1e3 * 60 * 60 * 24,\n maxRequests: options?.rateLimit?.maxRequests ?? 10\n },\n keyExpiration: {\n defaultExpiresIn: options?.keyExpiration?.defaultExpiresIn ?? null,\n disableCustomExpiresTime: options?.keyExpiration?.disableCustomExpiresTime ?? false,\n maxExpiresIn: options?.keyExpiration?.maxExpiresIn ?? 365,\n minExpiresIn: options?.keyExpiration?.minExpiresIn ?? 1\n },\n startingCharactersConfig: {\n shouldStore: options?.startingCharactersConfig?.shouldStore ?? true,\n charactersLength: options?.startingCharactersConfig?.charactersLength ?? 6\n },\n disableSessionForAPIKeys: options?.disableSessionForAPIKeys ?? false\n };\n const schema = mergeSchema(\n apiKeySchema({\n rateLimitMax: opts.rateLimit.maxRequests,\n timeWindow: opts.rateLimit.timeWindow\n }),\n opts.schema\n );\n const getter = opts.customAPIKeyGetter || ((ctx) => {\n if (Array.isArray(opts.apiKeyHeaders)) {\n for (const header of opts.apiKeyHeaders) {\n const value = ctx.headers?.get(header);\n if (value) {\n return value;\n }\n }\n } else {\n return ctx.headers?.get(opts.apiKeyHeaders);\n }\n });\n const keyGenerator = opts.customKeyGenerator || (async (options2) => {\n const characters = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n let apiKey2 = `${options2.prefix || \"\"}`;\n for (let i = 0; i < options2.length; i++) {\n const randomIndex = Math.floor(Math.random() * characters.length);\n apiKey2 += characters[randomIndex];\n }\n return apiKey2;\n });\n const routes = createApiKeyRoutes({ keyGenerator, opts, schema });\n return {\n id: \"api-key\",\n $ERROR_CODES: ERROR_CODES,\n hooks: {\n before: [\n {\n matcher: (ctx) => !!getter(ctx) && opts.disableSessionForAPIKeys === false,\n handler: createAuthMiddleware(async (ctx) => {\n const key = getter(ctx);\n if (typeof key !== \"string\") {\n throw new APIError(\"BAD_REQUEST\", {\n message: ERROR_CODES.INVALID_API_KEY_GETTER_RETURN_TYPE\n });\n }\n if (key.length < opts.defaultKeyLength) {\n throw new APIError(\"FORBIDDEN\", {\n message: ERROR_CODES.INVALID_API_KEY\n });\n }\n if (opts.customAPIKeyValidator) {\n const isValid = await opts.customAPIKeyValidator({ ctx, key });\n if (!isValid) {\n throw new APIError(\"FORBIDDEN\", {\n message: ERROR_CODES.INVALID_API_KEY\n });\n }\n }\n const hashed = opts.disableKeyHashing ? key : await defaultKeyHasher(key);\n const apiKey2 = await validateApiKey({\n hashedKey: hashed,\n ctx,\n opts,\n schema\n });\n await deleteAllExpiredApiKeys(ctx.context);\n let user;\n try {\n const userResult = await ctx.context.internalAdapter.findUserById(\n apiKey2.userId\n );\n if (!userResult) {\n throw new APIError(\"UNAUTHORIZED\", {\n message: ERROR_CODES.INVALID_USER_ID_FROM_API_KEY\n });\n }\n user = userResult;\n } catch (error) {\n throw error;\n }\n const session = {\n user,\n session: {\n id: apiKey2.id,\n token: key,\n userId: user.id,\n userAgent: ctx.request?.headers.get(\"user-agent\") ?? null,\n ipAddress: ctx.request ? getIp(ctx.request, ctx.context.options) : null,\n createdAt: /* @__PURE__ */ new Date(),\n updatedAt: /* @__PURE__ */ new Date(),\n expiresAt: apiKey2.expiresAt || getDate(\n ctx.context.options.session?.expiresIn || 60 * 60 * 24 * 7,\n // 7 days\n \"ms\"\n )\n }\n };\n ctx.context.session = session;\n if (ctx.path === \"/get-session\") {\n return session;\n } else {\n return {\n context: ctx\n };\n }\n })\n }\n ]\n },\n endpoints: {\n /**\n * ### Endpoint\n *\n * POST `/api-key/create`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.createApiKey`\n *\n * **client:**\n * `authClient.apiKey.create`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/api-key#api-method-api-key-create)\n */\n createApiKey: routes.createApiKey,\n /**\n * ### Endpoint\n *\n * POST `/api-key/verify`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.verifyApiKey`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/api-key#api-method-api-key-verify)\n */\n verifyApiKey: routes.verifyApiKey,\n /**\n * ### Endpoint\n *\n * GET `/api-key/get`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.getApiKey`\n *\n * **client:**\n * `authClient.apiKey.get`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/api-key#api-method-api-key-get)\n */\n getApiKey: routes.getApiKey,\n /**\n * ### Endpoint\n *\n * POST `/api-key/update`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.updateApiKey`\n *\n * **client:**\n * `authClient.apiKey.update`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/api-key#api-method-api-key-update)\n */\n updateApiKey: routes.updateApiKey,\n /**\n * ### Endpoint\n *\n * POST `/api-key/delete`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.deleteApiKey`\n *\n * **client:**\n * `authClient.apiKey.delete`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/api-key#api-method-api-key-delete)\n */\n deleteApiKey: routes.deleteApiKey,\n /**\n * ### Endpoint\n *\n * GET `/api-key/list`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.listApiKeys`\n *\n * **client:**\n * `authClient.apiKey.list`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/api-key#api-method-api-key-list)\n */\n listApiKeys: routes.listApiKeys,\n /**\n * ### Endpoint\n *\n * POST `/api-key/delete-all-expired-api-keys`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.deleteAllExpiredApiKeys`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/api-key#api-method-api-key-delete-all-expired-api-keys)\n */\n deleteAllExpiredApiKeys: routes.deleteAllExpiredApiKeys\n },\n schema\n };\n};\n\nconst oneTimeToken = (options) => {\n const opts = {\n storeToken: \"plain\",\n ...options\n };\n async function storeToken(ctx, token) {\n if (opts.storeToken === \"hashed\") {\n return await defaultKeyHasher(token);\n }\n if (typeof opts.storeToken === \"object\" && \"type\" in opts.storeToken && opts.storeToken.type === \"custom-hasher\") {\n return await opts.storeToken.hash(token);\n }\n return token;\n }\n return {\n id: \"one-time-token\",\n endpoints: {\n /**\n * ### Endpoint\n *\n * GET `/one-time-token/generate`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.generateOneTimeToken`\n *\n * **client:**\n * `authClient.oneTimeToken.generate`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/one-time-token#api-method-one-time-token-generate)\n */\n generateOneTimeToken: createAuthEndpoint(\n \"/one-time-token/generate\",\n {\n method: \"GET\",\n use: [sessionMiddleware]\n },\n async (c) => {\n if (opts?.disableClientRequest && c.request) {\n throw c.error(\"BAD_REQUEST\", {\n message: \"Client requests are disabled\"\n });\n }\n const session = c.context.session;\n const token = opts?.generateToken ? await opts.generateToken(session, c) : generateRandomString(32);\n const expiresAt = new Date(\n Date.now() + (opts?.expiresIn ?? 3) * 60 * 1e3\n );\n const storedToken = await storeToken(c, token);\n await c.context.internalAdapter.createVerificationValue({\n value: session.session.token,\n identifier: `one-time-token:${storedToken}`,\n expiresAt\n });\n return c.json({ token });\n }\n ),\n /**\n * ### Endpoint\n *\n * POST `/one-time-token/verify`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.verifyOneTimeToken`\n *\n * **client:**\n * `authClient.oneTimeToken.verify`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/one-time-token#api-method-one-time-token-verify)\n */\n verifyOneTimeToken: createAuthEndpoint(\n \"/one-time-token/verify\",\n {\n method: \"POST\",\n body: z.object({\n token: z.string().meta({\n description: 'The token to verify. Eg: \"some-token\"'\n })\n })\n },\n async (c) => {\n const { token } = c.body;\n const storedToken = await storeToken(c, token);\n const verificationValue = await c.context.internalAdapter.findVerificationValue(\n `one-time-token:${storedToken}`\n );\n if (!verificationValue) {\n throw c.error(\"BAD_REQUEST\", {\n message: \"Invalid token\"\n });\n }\n if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) {\n await c.context.internalAdapter.deleteVerificationValue(\n verificationValue.id\n );\n throw c.error(\"BAD_REQUEST\", {\n message: \"Token expired\"\n });\n }\n await c.context.internalAdapter.deleteVerificationValue(\n verificationValue.id\n );\n const session = await c.context.internalAdapter.findSession(\n verificationValue.value\n );\n if (!session) {\n throw c.error(\"BAD_REQUEST\", {\n message: \"Session not found\"\n });\n }\n return c.json(session);\n }\n )\n }\n };\n};\n\nexport { API_KEY_TABLE_NAME as A, ERROR_CODES as E, apiKey as a, defaultKeyHasher as d, oneTimeToken as o };\n","import { APIError } from 'better-call';\nimport '../../shared/better-auth.D4HhkCZJ.mjs';\nimport 'zod/v4';\nimport '../../shared/better-auth.8zoxzg-F.mjs';\nimport '@better-auth/utils/base64';\nimport '@better-auth/utils/hmac';\nimport '@better-auth/utils/binary';\nimport '../../shared/better-auth.n2KFGwjY.mjs';\nimport '../../shared/better-auth.DBGfIDnh.mjs';\nimport 'defu';\nimport { createHash } from '@better-auth/utils/hash';\nimport { betterFetch } from '@better-fetch/fetch';\nimport '../../shared/better-auth.CW6D9eSx.mjs';\nimport '../../crypto/index.mjs';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport 'jose';\nimport '@noble/hashes/scrypt';\nimport '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport '../../shared/better-auth.B4Qoxdgc.mjs';\nimport '@better-auth/utils/random';\nimport '../../shared/better-auth.VTXNLFMT.mjs';\nimport '../../shared/better-auth.DdzSJf-n.mjs';\nimport '../../cookies/index.mjs';\nimport '../../shared/better-auth.tB5eU6EY.mjs';\nimport 'jose/errors';\n\nconst ERROR_CODES = {\n PASSWORD_COMPROMISED: \"The password you entered has been compromised. Please choose a different password.\"\n};\nasync function checkPasswordCompromise(password, customMessage) {\n if (!password) return;\n const sha1Hash = (await createHash(\"SHA-1\", \"hex\").digest(password)).toUpperCase();\n const prefix = sha1Hash.substring(0, 5);\n const suffix = sha1Hash.substring(5);\n try {\n const { data, error } = await betterFetch(\n `https://api.pwnedpasswords.com/range/${prefix}`,\n {\n headers: {\n \"Add-Padding\": \"true\",\n \"User-Agent\": \"BetterAuth Password Checker\"\n }\n }\n );\n if (error) {\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n message: `Failed to check password. Status: ${error.status}`\n });\n }\n const lines = data.split(\"\\n\");\n const found = lines.some(\n (line) => line.split(\":\")[0].toUpperCase() === suffix.toUpperCase()\n );\n if (found) {\n throw new APIError(\"BAD_REQUEST\", {\n message: customMessage || ERROR_CODES.PASSWORD_COMPROMISED,\n code: \"PASSWORD_COMPROMISED\"\n });\n }\n } catch (error) {\n if (error instanceof APIError) throw error;\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n message: \"Failed to check password. Please try again later.\"\n });\n }\n}\nconst haveIBeenPwned = (options) => ({\n id: \"haveIBeenPwned\",\n init(ctx) {\n return {\n context: {\n password: {\n ...ctx.password,\n async hash(password) {\n await checkPasswordCompromise(\n password,\n options?.customPasswordCompromisedMessage\n );\n return ctx.password.hash(password);\n }\n }\n }\n };\n },\n $ERROR_CODES: ERROR_CODES\n});\n\nexport { haveIBeenPwned };\n","export { o as organization, p as parseRoles } from '../shared/better-auth.C1Ly154X.mjs';\nexport { TWO_FACTOR_ERROR_CODES, twoFactor } from './two-factor/index.mjs';\nexport { USERNAME_ERROR_CODES, username } from './username/index.mjs';\nexport { bearer } from './bearer/index.mjs';\nimport { k as getSessionFromCtx, j as createAuthEndpoint, i as createAuthMiddleware } from '../shared/better-auth.D4HhkCZJ.mjs';\nexport { H as HIDE_METADATA, q as optionsMiddleware } from '../shared/better-auth.D4HhkCZJ.mjs';\nexport { magicLink } from './magic-link/index.mjs';\nexport { phoneNumber } from './phone-number/index.mjs';\nexport { anonymous } from './anonymous/index.mjs';\nexport { a as admin } from '../shared/better-auth.DygEm6OX.mjs';\nexport { genericOAuth } from './generic-oauth/index.mjs';\nexport { generateExportedKeyPair, getJwtToken, jwt } from './jwt/index.mjs';\nexport { multiSession } from './multi-session/index.mjs';\nexport { emailOTP } from './email-otp/index.mjs';\nexport { oneTap } from './one-tap/index.mjs';\nexport { oAuthProxy } from './oauth-proxy/index.mjs';\nexport { customSession } from './custom-session/index.mjs';\nexport { openAPI } from './open-api/index.mjs';\nimport { o as oidcProvider, s as schema } from '../shared/better-auth.CvxACSRz.mjs';\nexport { g as getClient, a as getMetadata } from '../shared/better-auth.CvxACSRz.mjs';\nexport { captcha } from './captcha/index.mjs';\nexport { A as API_KEY_TABLE_NAME, E as ERROR_CODES, a as apiKey, d as defaultKeyHasher, o as oneTimeToken } from '../shared/better-auth.Bqt8-7ls.mjs';\nexport { haveIBeenPwned } from './haveibeenpwned/index.mjs';\nimport * as z from 'zod/v4';\nimport { APIError } from 'better-call';\nimport { a as isProduction } from '../shared/better-auth.8zoxzg-F.mjs';\nimport { base64 } from '@better-auth/utils/base64';\nimport '@better-auth/utils/hmac';\nimport { a as getBaseURL } from '../shared/better-auth.VTXNLFMT.mjs';\nimport '@better-auth/utils/binary';\nimport { parseSetCookieHeader } from '../cookies/index.mjs';\nimport '../shared/better-auth.n2KFGwjY.mjs';\nimport '../shared/better-auth.DGaVMVAI.mjs';\nimport './organization/access/index.mjs';\nimport '@better-auth/utils/random';\nimport { createHash } from '@better-auth/utils/hash';\nimport '@noble/ciphers/chacha';\nimport '@noble/ciphers/utils';\nimport '@noble/ciphers/webcrypto';\nimport { SignJWT } from 'jose';\nimport '@noble/hashes/scrypt';\nimport { subtle } from '@better-auth/utils';\nimport '@better-auth/utils/hex';\nimport '@noble/hashes/utils';\nimport { g as generateRandomString } from '../shared/better-auth.B4Qoxdgc.mjs';\nimport { a as logger } from '../shared/better-auth.DBGfIDnh.mjs';\nimport 'kysely';\nimport 'defu';\nimport '@better-auth/utils/otp';\nimport './admin/access/index.mjs';\nimport '@better-fetch/fetch';\nimport '../shared/better-auth.CGrHn1Ih.mjs';\nexport { t as twoFactorClient } from '../shared/better-auth.Ddw8bVyV.mjs';\nimport '../shared/better-auth.CW6D9eSx.mjs';\nimport '../shared/better-auth.DdzSJf-n.mjs';\nimport '../shared/better-auth.ffWeg50w.mjs';\nimport '../shared/better-auth.Dt0CvI2z.mjs';\nimport '../shared/better-auth.DXqcUO8W.mjs';\nimport '../crypto/index.mjs';\nimport '../shared/better-auth.YwDQhoPc.mjs';\nimport '../shared/better-auth.tB5eU6EY.mjs';\nimport 'jose/errors';\nimport '../shared/better-auth.DQI8AD7d.mjs';\nimport '../shared/better-auth.bkwPl2G4.mjs';\nimport '../api/index.mjs';\nimport '../shared/better-auth.DcfNPS8q.mjs';\nimport '../shared/better-auth.DORkW_Ge.mjs';\nimport './access/index.mjs';\n\nfunction redirectErrorURL(url, error, description) {\n return `${url.includes(\"?\") ? \"&\" : \"?\"}error=${error}&error_description=${description}`;\n}\nasync function authorizeMCPOAuth(ctx, options) {\n ctx.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n ctx.setHeader(\"Access-Control-Allow-Methods\", \"POST, OPTIONS\");\n ctx.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization\");\n ctx.setHeader(\"Access-Control-Max-Age\", \"86400\");\n const opts = {\n codeExpiresIn: 600,\n defaultScope: \"openid\",\n ...options,\n scopes: [\n \"openid\",\n \"profile\",\n \"email\",\n \"offline_access\",\n ...options?.scopes || []\n ]\n };\n if (!ctx.request) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"request not found\",\n error: \"invalid_request\"\n });\n }\n const session = await getSessionFromCtx(ctx);\n if (!session) {\n await ctx.setSignedCookie(\n \"oidc_login_prompt\",\n JSON.stringify(ctx.query),\n ctx.context.secret,\n {\n maxAge: 600,\n path: \"/\",\n sameSite: \"lax\"\n }\n );\n const queryFromURL = ctx.request.url?.split(\"?\")[1];\n throw ctx.redirect(`${options.loginPage}?${queryFromURL}`);\n }\n const query = ctx.query;\n console.log(query);\n if (!query.client_id) {\n throw ctx.redirect(`${ctx.context.baseURL}/error?error=invalid_client`);\n }\n if (!query.response_type) {\n throw ctx.redirect(\n redirectErrorURL(\n `${ctx.context.baseURL}/error`,\n \"invalid_request\",\n \"response_type is required\"\n )\n );\n }\n const client = await ctx.context.adapter.findOne({\n model: \"oauthApplication\",\n where: [\n {\n field: \"clientId\",\n value: ctx.query.client_id\n }\n ]\n }).then((res) => {\n if (!res) {\n return null;\n }\n return {\n ...res,\n redirectURLs: res.redirectURLs.split(\",\"),\n metadata: res.metadata ? JSON.parse(res.metadata) : {}\n };\n });\n console.log(client);\n if (!client) {\n throw ctx.redirect(`${ctx.context.baseURL}/error?error=invalid_client`);\n }\n const redirectURI = client.redirectURLs.find(\n (url) => url === ctx.query.redirect_uri\n );\n if (!redirectURI || !query.redirect_uri) {\n throw new APIError(\"BAD_REQUEST\", {\n message: \"Invalid redirect URI\"\n });\n }\n if (client.disabled) {\n throw ctx.redirect(`${ctx.context.baseURL}/error?error=client_disabled`);\n }\n if (query.response_type !== \"code\") {\n throw ctx.redirect(\n `${ctx.context.baseURL}/error?error=unsupported_response_type`\n );\n }\n const requestScope = query.scope?.split(\" \").filter((s) => s) || opts.defaultScope.split(\" \");\n const invalidScopes = requestScope.filter((scope) => {\n return !opts.scopes.includes(scope);\n });\n if (invalidScopes.length) {\n throw ctx.redirect(\n redirectErrorURL(\n query.redirect_uri,\n \"invalid_scope\",\n `The following scopes are invalid: ${invalidScopes.join(\", \")}`\n )\n );\n }\n if ((!query.code_challenge || !query.code_challenge_method) && options.requirePKCE) {\n throw ctx.redirect(\n redirectErrorURL(\n query.redirect_uri,\n \"invalid_request\",\n \"pkce is required\"\n )\n );\n }\n if (!query.code_challenge_method) {\n query.code_challenge_method = \"plain\";\n }\n if (![\n \"s256\",\n options.allowPlainCodeChallengeMethod ? \"plain\" : \"s256\"\n ].includes(query.code_challenge_method?.toLowerCase() || \"\")) {\n throw ctx.redirect(\n redirectErrorURL(\n query.redirect_uri,\n \"invalid_request\",\n \"invalid code_challenge method\"\n )\n );\n }\n const code = generateRandomString(32, \"a-z\", \"A-Z\", \"0-9\");\n const codeExpiresInMs = opts.codeExpiresIn * 1e3;\n const expiresAt = new Date(Date.now() + codeExpiresInMs);\n try {\n await ctx.context.internalAdapter.createVerificationValue(\n {\n value: JSON.stringify({\n clientId: client.clientId,\n redirectURI: query.redirect_uri,\n scope: requestScope,\n userId: session.user.id,\n authTime: session.session.createdAt.getTime(),\n /**\n * If the prompt is set to `consent`, then we need\n * to require the user to consent to the scopes.\n *\n * This means the code now needs to be treated as a\n * consent request.\n *\n * once the user consents, the code will be updated\n * with the actual code. This is to prevent the\n * client from using the code before the user\n * consents.\n */\n requireConsent: query.prompt === \"consent\",\n state: query.prompt === \"consent\" ? query.state : null,\n codeChallenge: query.code_challenge,\n codeChallengeMethod: query.code_challenge_method,\n nonce: query.nonce\n }),\n identifier: code,\n expiresAt\n },\n ctx\n );\n } catch (e) {\n throw ctx.redirect(\n redirectErrorURL(\n query.redirect_uri,\n \"server_error\",\n \"An error occurred while processing the request\"\n )\n );\n }\n const redirectURIWithCode = new URL(redirectURI);\n redirectURIWithCode.searchParams.set(\"code\", code);\n redirectURIWithCode.searchParams.set(\"state\", ctx.query.state);\n if (query.prompt !== \"consent\") {\n throw ctx.redirect(redirectURIWithCode.toString());\n }\n throw ctx.redirect(redirectURIWithCode.toString());\n}\n\nconst getMCPProviderMetadata = (ctx, options) => {\n const issuer = ctx.context.options.baseURL;\n const baseURL = ctx.context.baseURL;\n if (!issuer || !baseURL) {\n throw new APIError(\"INTERNAL_SERVER_ERROR\", {\n error: \"invalid_issuer\",\n error_description: \"issuer or baseURL is not set. If you're the app developer, please make sure to set the `baseURL` in your auth config.\"\n });\n }\n return {\n issuer,\n authorization_endpoint: `${baseURL}/mcp/authorize`,\n token_endpoint: `${baseURL}/mcp/token`,\n userinfo_endpoint: `${baseURL}/mcp/userinfo`,\n jwks_uri: `${baseURL}/mcp/jwks`,\n registration_endpoint: `${baseURL}/mcp/register`,\n scopes_supported: [\"openid\", \"profile\", \"email\", \"offline_access\"],\n response_types_supported: [\"code\"],\n response_modes_supported: [\"query\"],\n grant_types_supported: [\"authorization_code\", \"refresh_token\"],\n acr_values_supported: [\n \"urn:mace:incommon:iap:silver\",\n \"urn:mace:incommon:iap:bronze\"\n ],\n subject_types_supported: [\"public\"],\n id_token_signing_alg_values_supported: [\"RS256\", \"none\"],\n token_endpoint_auth_methods_supported: [\n \"client_secret_basic\",\n \"client_secret_post\",\n \"none\"\n ],\n code_challenge_methods_supported: [\"S256\"],\n claims_supported: [\n \"sub\",\n \"iss\",\n \"aud\",\n \"exp\",\n \"nbf\",\n \"iat\",\n \"jti\",\n \"email\",\n \"email_verified\",\n \"name\"\n ],\n ...options?.metadata\n };\n};\nconst mcp = (options) => {\n const opts = {\n codeExpiresIn: 600,\n defaultScope: \"openid\",\n accessTokenExpiresIn: 3600,\n refreshTokenExpiresIn: 604800,\n allowPlainCodeChallengeMethod: true,\n ...options.oidcConfig,\n loginPage: options.loginPage,\n scopes: [\n \"openid\",\n \"profile\",\n \"email\",\n \"offline_access\",\n ...options.oidcConfig?.scopes || []\n ]\n };\n const modelName = {\n oauthClient: \"oauthApplication\",\n oauthAccessToken: \"oauthAccessToken\"};\n oidcProvider(opts);\n return {\n id: \"mcp\",\n hooks: {\n after: [\n {\n matcher() {\n return true;\n },\n handler: createAuthMiddleware(async (ctx) => {\n const cookie = await ctx.getSignedCookie(\n \"oidc_login_prompt\",\n ctx.context.secret\n );\n const cookieName = ctx.context.authCookies.sessionToken.name;\n const parsedSetCookieHeader = parseSetCookieHeader(\n ctx.context.responseHeaders?.get(\"set-cookie\") || \"\"\n );\n const hasSessionToken = parsedSetCookieHeader.has(cookieName);\n if (!cookie || !hasSessionToken) {\n return;\n }\n ctx.setCookie(\"oidc_login_prompt\", \"\", {\n maxAge: 0\n });\n const sessionCookie = parsedSetCookieHeader.get(cookieName)?.value;\n const sessionToken = sessionCookie?.split(\".\")[0];\n if (!sessionToken) {\n return;\n }\n const session = await ctx.context.internalAdapter.findSession(sessionToken);\n if (!session) {\n return;\n }\n ctx.query = JSON.parse(cookie);\n ctx.query.prompt = \"consent\";\n ctx.context.session = session;\n const response = await authorizeMCPOAuth(ctx, opts).catch((e) => {\n if (e instanceof APIError) {\n if (e.statusCode === 302) {\n return ctx.json({\n redirect: true,\n //@ts-expect-error\n url: e.headers.get(\"location\")\n });\n }\n }\n throw e;\n });\n return response;\n })\n }\n ]\n },\n endpoints: {\n getMcpOAuthConfig: createAuthEndpoint(\n \"/.well-known/oauth-authorization-server\",\n {\n method: \"GET\",\n metadata: {\n client: false\n }\n },\n async (c) => {\n try {\n const metadata = getMCPProviderMetadata(c, options);\n return c.json(metadata);\n } catch (e) {\n console.log(e);\n return c.json(null);\n }\n }\n ),\n mcpOAuthAuthroize: createAuthEndpoint(\n \"/mcp/authorize\",\n {\n method: \"GET\",\n query: z.record(z.string(), z.any()),\n metadata: {\n openapi: {\n description: \"Authorize an OAuth2 request using MCP\",\n responses: {\n \"200\": {\n description: \"Authorization response generated successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n additionalProperties: true,\n description: \"Authorization response, contents depend on the authorize function implementation\"\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n return authorizeMCPOAuth(ctx, opts);\n }\n ),\n mcpOAuthToken: createAuthEndpoint(\n \"/mcp/token\",\n {\n method: \"POST\",\n body: z.record(z.any(), z.any()),\n metadata: {\n isAction: false\n }\n },\n async (ctx) => {\n ctx.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n ctx.setHeader(\"Access-Control-Allow-Methods\", \"POST, OPTIONS\");\n ctx.setHeader(\n \"Access-Control-Allow-Headers\",\n \"Content-Type, Authorization\"\n );\n ctx.setHeader(\"Access-Control-Max-Age\", \"86400\");\n let { body } = ctx;\n if (!body) {\n throw ctx.error(\"BAD_REQUEST\", {\n error_description: \"request body not found\",\n error: \"invalid_request\"\n });\n }\n if (body instanceof FormData) {\n body = Object.fromEntries(body.entries());\n }\n if (!(body instanceof Object)) {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"request body is not an object\",\n error: \"invalid_request\"\n });\n }\n let { client_id, client_secret } = body;\n const authorization = ctx.request?.headers.get(\"authorization\") || null;\n if (authorization && !client_id && !client_secret && authorization.startsWith(\"Basic \")) {\n try {\n const encoded = authorization.replace(\"Basic \", \"\");\n const decoded = new TextDecoder().decode(base64.decode(encoded));\n if (!decoded.includes(\":\")) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid authorization header format\",\n error: \"invalid_client\"\n });\n }\n const [id, secret] = decoded.split(\":\");\n if (!id || !secret) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid authorization header format\",\n error: \"invalid_client\"\n });\n }\n client_id = id;\n client_secret = secret;\n } catch (error) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid authorization header format\",\n error: \"invalid_client\"\n });\n }\n }\n const {\n grant_type,\n code,\n redirect_uri,\n refresh_token,\n code_verifier\n } = body;\n if (grant_type === \"refresh_token\") {\n if (!refresh_token) {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"refresh_token is required\",\n error: \"invalid_request\"\n });\n }\n const token = await ctx.context.adapter.findOne({\n model: \"oauthAccessToken\",\n where: [\n {\n field: \"refreshToken\",\n value: refresh_token.toString()\n }\n ]\n });\n if (!token) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid refresh token\",\n error: \"invalid_grant\"\n });\n }\n if (token.clientId !== client_id?.toString()) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid client_id\",\n error: \"invalid_client\"\n });\n }\n if (token.refreshTokenExpiresAt < /* @__PURE__ */ new Date()) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"refresh token expired\",\n error: \"invalid_grant\"\n });\n }\n const accessToken2 = generateRandomString(32, \"a-z\", \"A-Z\");\n const newRefreshToken = generateRandomString(32, \"a-z\", \"A-Z\");\n const accessTokenExpiresAt2 = new Date(\n Date.now() + opts.accessTokenExpiresIn * 1e3\n );\n const refreshTokenExpiresAt2 = new Date(\n Date.now() + opts.refreshTokenExpiresIn * 1e3\n );\n await ctx.context.adapter.create({\n model: modelName.oauthAccessToken,\n data: {\n accessToken: accessToken2,\n refreshToken: newRefreshToken,\n accessTokenExpiresAt: accessTokenExpiresAt2,\n refreshTokenExpiresAt: refreshTokenExpiresAt2,\n clientId: client_id.toString(),\n userId: token.userId,\n scopes: token.scopes,\n createdAt: /* @__PURE__ */ new Date(),\n updatedAt: /* @__PURE__ */ new Date()\n }\n });\n return ctx.json({\n access_token: accessToken2,\n token_type: \"bearer\",\n expires_in: opts.accessTokenExpiresIn,\n refresh_token: newRefreshToken,\n scope: token.scopes\n });\n }\n if (!code) {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"code is required\",\n error: \"invalid_request\"\n });\n }\n if (opts.requirePKCE && !code_verifier) {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"code verifier is missing\",\n error: \"invalid_request\"\n });\n }\n const verificationValue = await ctx.context.internalAdapter.findVerificationValue(\n code.toString()\n );\n if (!verificationValue) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid code\",\n error: \"invalid_grant\"\n });\n }\n if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"code expired\",\n error: \"invalid_grant\"\n });\n }\n await ctx.context.internalAdapter.deleteVerificationValue(\n verificationValue.id\n );\n if (!client_id) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"client_id is required\",\n error: \"invalid_client\"\n });\n }\n if (!grant_type) {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"grant_type is required\",\n error: \"invalid_request\"\n });\n }\n if (grant_type !== \"authorization_code\") {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"grant_type must be 'authorization_code'\",\n error: \"unsupported_grant_type\"\n });\n }\n if (!redirect_uri) {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"redirect_uri is required\",\n error: \"invalid_request\"\n });\n }\n const client = await ctx.context.adapter.findOne({\n model: modelName.oauthClient,\n where: [{ field: \"clientId\", value: client_id.toString() }]\n }).then((res) => {\n if (!res) {\n return null;\n }\n return {\n ...res,\n redirectURLs: res.redirectURLs.split(\",\"),\n metadata: res.metadata ? JSON.parse(res.metadata) : {}\n };\n });\n if (!client) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid client_id\",\n error: \"invalid_client\"\n });\n }\n if (client.disabled) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"client is disabled\",\n error: \"invalid_client\"\n });\n }\n if (client.type === \"public\") {\n if (!code_verifier) {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"code verifier is required for public clients\",\n error: \"invalid_request\"\n });\n }\n } else {\n if (!client_secret) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"client_secret is required for confidential clients\",\n error: \"invalid_client\"\n });\n }\n const isValidSecret = client.clientSecret === client_secret.toString();\n if (!isValidSecret) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid client_secret\",\n error: \"invalid_client\"\n });\n }\n }\n const value = JSON.parse(\n verificationValue.value\n );\n if (value.clientId !== client_id.toString()) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid client_id\",\n error: \"invalid_client\"\n });\n }\n if (value.redirectURI !== redirect_uri.toString()) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"invalid redirect_uri\",\n error: \"invalid_client\"\n });\n }\n if (value.codeChallenge && !code_verifier) {\n throw new APIError(\"BAD_REQUEST\", {\n error_description: \"code verifier is missing\",\n error: \"invalid_request\"\n });\n }\n const challenge = value.codeChallengeMethod === \"plain\" ? code_verifier : await createHash(\"SHA-256\", \"base64urlnopad\").digest(\n code_verifier\n );\n if (challenge !== value.codeChallenge) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"code verification failed\",\n error: \"invalid_request\"\n });\n }\n const requestedScopes = value.scope;\n await ctx.context.internalAdapter.deleteVerificationValue(\n verificationValue.id\n );\n const accessToken = generateRandomString(32, \"a-z\", \"A-Z\");\n const refreshToken = generateRandomString(32, \"A-Z\", \"a-z\");\n const accessTokenExpiresAt = new Date(\n Date.now() + opts.accessTokenExpiresIn * 1e3\n );\n const refreshTokenExpiresAt = new Date(\n Date.now() + opts.refreshTokenExpiresIn * 1e3\n );\n await ctx.context.adapter.create({\n model: modelName.oauthAccessToken,\n data: {\n accessToken,\n refreshToken,\n accessTokenExpiresAt,\n refreshTokenExpiresAt,\n clientId: client_id.toString(),\n userId: value.userId,\n scopes: requestedScopes.join(\" \"),\n createdAt: /* @__PURE__ */ new Date(),\n updatedAt: /* @__PURE__ */ new Date()\n }\n });\n const user = await ctx.context.internalAdapter.findUserById(\n value.userId\n );\n if (!user) {\n throw new APIError(\"UNAUTHORIZED\", {\n error_description: \"user not found\",\n error: \"invalid_grant\"\n });\n }\n let secretKey = {\n alg: \"HS256\",\n key: await subtle.generateKey(\n {\n name: \"HMAC\",\n hash: \"SHA-256\"\n },\n true,\n [\"sign\", \"verify\"]\n )\n };\n const profile = {\n given_name: user.name.split(\" \")[0],\n family_name: user.name.split(\" \")[1],\n name: user.name,\n profile: user.image,\n updated_at: user.updatedAt.toISOString()\n };\n const email = {\n email: user.email,\n email_verified: user.emailVerified\n };\n const userClaims = {\n ...requestedScopes.includes(\"profile\") ? profile : {},\n ...requestedScopes.includes(\"email\") ? email : {}\n };\n const additionalUserClaims = opts.getAdditionalUserInfoClaim ? opts.getAdditionalUserInfoClaim(user, requestedScopes) : {};\n const idToken = await new SignJWT({\n sub: user.id,\n aud: client_id.toString(),\n iat: Date.now(),\n auth_time: ctx.context.session?.session.createdAt.getTime(),\n nonce: value.nonce,\n acr: \"urn:mace:incommon:iap:silver\",\n // default to silver - ⚠︎ this should be configurable and should be validated against the client's metadata\n ...userClaims,\n ...additionalUserClaims\n }).setProtectedHeader({ alg: secretKey.alg }).setIssuedAt().setExpirationTime(\n Math.floor(Date.now() / 1e3) + opts.accessTokenExpiresIn\n ).sign(secretKey.key);\n return ctx.json(\n {\n access_token: accessToken,\n token_type: \"Bearer\",\n expires_in: opts.accessTokenExpiresIn,\n refresh_token: requestedScopes.includes(\"offline_access\") ? refreshToken : void 0,\n scope: requestedScopes.join(\" \"),\n id_token: requestedScopes.includes(\"openid\") ? idToken : void 0\n },\n {\n headers: {\n \"Cache-Control\": \"no-store\",\n Pragma: \"no-cache\"\n }\n }\n );\n }\n ),\n registerMcpClient: createAuthEndpoint(\n \"/mcp/register\",\n {\n method: \"POST\",\n body: z.object({\n redirect_uris: z.array(z.string()),\n token_endpoint_auth_method: z.enum([\"none\", \"client_secret_basic\", \"client_secret_post\"]).default(\"client_secret_basic\").optional(),\n grant_types: z.array(\n z.enum([\n \"authorization_code\",\n \"implicit\",\n \"password\",\n \"client_credentials\",\n \"refresh_token\",\n \"urn:ietf:params:oauth:grant-type:jwt-bearer\",\n \"urn:ietf:params:oauth:grant-type:saml2-bearer\"\n ])\n ).default([\"authorization_code\"]).optional(),\n response_types: z.array(z.enum([\"code\", \"token\"])).default([\"code\"]).optional(),\n client_name: z.string().optional(),\n client_uri: z.string().optional(),\n logo_uri: z.string().optional(),\n scope: z.string().optional(),\n contacts: z.array(z.string()).optional(),\n tos_uri: z.string().optional(),\n policy_uri: z.string().optional(),\n jwks_uri: z.string().optional(),\n jwks: z.record(z.string(), z.any()).optional(),\n metadata: z.record(z.any(), z.any()).optional(),\n software_id: z.string().optional(),\n software_version: z.string().optional(),\n software_statement: z.string().optional()\n }),\n metadata: {\n openapi: {\n description: \"Register an OAuth2 application\",\n responses: {\n \"200\": {\n description: \"OAuth2 application registered successfully\",\n content: {\n \"application/json\": {\n schema: {\n type: \"object\",\n properties: {\n name: {\n type: \"string\",\n description: \"Name of the OAuth2 application\"\n },\n icon: {\n type: \"string\",\n nullable: true,\n description: \"Icon URL for the application\"\n },\n metadata: {\n type: \"object\",\n additionalProperties: true,\n nullable: true,\n description: \"Additional metadata for the application\"\n },\n clientId: {\n type: \"string\",\n description: \"Unique identifier for the client\"\n },\n clientSecret: {\n type: \"string\",\n description: \"Secret key for the client. Not included for public clients.\"\n },\n redirectURLs: {\n type: \"array\",\n items: { type: \"string\", format: \"uri\" },\n description: \"List of allowed redirect URLs\"\n },\n type: {\n type: \"string\",\n description: \"Type of the client\",\n enum: [\"web\", \"public\"]\n },\n authenticationScheme: {\n type: \"string\",\n description: \"Authentication scheme used by the client\",\n enum: [\"client_secret\", \"none\"]\n },\n disabled: {\n type: \"boolean\",\n description: \"Whether the client is disabled\",\n enum: [false]\n },\n userId: {\n type: \"string\",\n nullable: true,\n description: \"ID of the user who registered the client, null if registered anonymously\"\n },\n createdAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Creation timestamp\"\n },\n updatedAt: {\n type: \"string\",\n format: \"date-time\",\n description: \"Last update timestamp\"\n }\n },\n required: [\n \"name\",\n \"clientId\",\n \"redirectURLs\",\n \"type\",\n \"authenticationScheme\",\n \"disabled\",\n \"createdAt\",\n \"updatedAt\"\n ]\n }\n }\n }\n }\n }\n }\n }\n },\n async (ctx) => {\n const body = ctx.body;\n const session = await getSessionFromCtx(ctx);\n ctx.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n ctx.setHeader(\"Access-Control-Allow-Methods\", \"POST, OPTIONS\");\n ctx.setHeader(\n \"Access-Control-Allow-Headers\",\n \"Content-Type, Authorization\"\n );\n ctx.setHeader(\"Access-Control-Max-Age\", \"86400\");\n ctx.headers?.set(\"Access-Control-Max-Age\", \"86400\");\n if ((!body.grant_types || body.grant_types.includes(\"authorization_code\") || body.grant_types.includes(\"implicit\")) && (!body.redirect_uris || body.redirect_uris.length === 0)) {\n throw new APIError(\"BAD_REQUEST\", {\n error: \"invalid_redirect_uri\",\n error_description: \"Redirect URIs are required for authorization_code and implicit grant types\"\n });\n }\n if (body.grant_types && body.response_types) {\n if (body.grant_types.includes(\"authorization_code\") && !body.response_types.includes(\"code\")) {\n throw new APIError(\"BAD_REQUEST\", {\n error: \"invalid_client_metadata\",\n error_description: \"When 'authorization_code' grant type is used, 'code' response type must be included\"\n });\n }\n if (body.grant_types.includes(\"implicit\") && !body.response_types.includes(\"token\")) {\n throw new APIError(\"BAD_REQUEST\", {\n error: \"invalid_client_metadata\",\n error_description: \"When 'implicit' grant type is used, 'token' response type must be included\"\n });\n }\n }\n const clientId = opts.generateClientId?.() || generateRandomString(32, \"a-z\", \"A-Z\");\n const clientSecret = opts.generateClientSecret?.() || generateRandomString(32, \"a-z\", \"A-Z\");\n const clientType = body.token_endpoint_auth_method === \"none\" ? \"public\" : \"web\";\n const finalClientSecret = clientType === \"public\" ? \"\" : clientSecret;\n await ctx.context.adapter.create({\n model: modelName.oauthClient,\n data: {\n name: body.client_name,\n icon: body.logo_uri,\n metadata: body.metadata ? JSON.stringify(body.metadata) : null,\n clientId,\n clientSecret: finalClientSecret,\n redirectURLs: body.redirect_uris.join(\",\"),\n type: clientType,\n authenticationScheme: body.token_endpoint_auth_method || \"client_secret_basic\",\n disabled: false,\n userId: session?.session.userId,\n createdAt: /* @__PURE__ */ new Date(),\n updatedAt: /* @__PURE__ */ new Date()\n }\n });\n const responseData = {\n client_id: clientId,\n client_id_issued_at: Math.floor(Date.now() / 1e3),\n redirect_uris: body.redirect_uris,\n token_endpoint_auth_method: body.token_endpoint_auth_method || \"client_secret_basic\",\n grant_types: body.grant_types || [\"authorization_code\"],\n response_types: body.response_types || [\"code\"],\n client_name: body.client_name,\n client_uri: body.client_uri,\n logo_uri: body.logo_uri,\n scope: body.scope,\n contacts: body.contacts,\n tos_uri: body.tos_uri,\n policy_uri: body.policy_uri,\n jwks_uri: body.jwks_uri,\n jwks: body.jwks,\n software_id: body.software_id,\n software_version: body.software_version,\n software_statement: body.software_statement,\n metadata: body.metadata,\n ...clientType !== \"public\" ? {\n client_secret: finalClientSecret,\n client_secret_expires_at: 0\n // 0 means it doesn't expire\n } : {}\n };\n return ctx.json(responseData, {\n status: 201,\n headers: {\n \"Cache-Control\": \"no-store\",\n Pragma: \"no-cache\"\n }\n });\n }\n ),\n getMcpSession: createAuthEndpoint(\n \"/mcp/get-session\",\n {\n method: \"GET\",\n requireHeaders: true\n },\n async (c) => {\n const accessToken = c.headers?.get(\"Authorization\")?.replace(\"Bearer \", \"\");\n if (!accessToken) {\n c.headers?.set(\"WWW-Authenticate\", \"Bearer\");\n return c.json(null);\n }\n const accessTokenData = await c.context.adapter.findOne({\n model: modelName.oauthAccessToken,\n where: [\n {\n field: \"accessToken\",\n value: accessToken\n }\n ]\n });\n if (!accessTokenData) {\n return c.json(null);\n }\n return c.json(accessTokenData);\n }\n )\n },\n schema\n };\n};\nconst withMcpAuth = (auth, handler) => {\n return async (req) => {\n const baseURL = getBaseURL(auth.options.baseURL, auth.options.basePath);\n if (!baseURL && !isProduction) {\n logger.warn(\"Unable to get the baseURL, please check your config!\");\n }\n const session = await auth.api.getMcpSession({\n headers: req.headers\n });\n const wwwAuthenticateValue = `Bearer resource_metadata=${baseURL}/api/auth/.well-known/oauth-authorization-server`;\n if (!session) {\n return Response.json(\n {\n jsonrpc: \"2.0\",\n error: {\n code: -32e3,\n message: \"Unauthorized: Authentication required\",\n \"www-authenticate\": wwwAuthenticateValue\n },\n id: null\n },\n {\n status: 401,\n headers: {\n \"WWW-Authenticate\": wwwAuthenticateValue\n }\n }\n );\n }\n return handler(req, session);\n };\n};\nconst oAuthDiscoveryMetadata = (auth) => {\n return async (request) => {\n const res = await auth.api.getMcpOAuthConfig();\n return new Response(JSON.stringify(res), {\n status: 200,\n headers: {\n \"Content-Type\": \"application/json\",\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type, Authorization\",\n \"Access-Control-Max-Age\": \"86400\"\n }\n });\n };\n};\n\nexport { createAuthEndpoint, createAuthMiddleware, getMCPProviderMetadata, mcp, oAuthDiscoveryMetadata, oidcProvider, withMcpAuth };\n"],"mappings":";;;;;;;;AACA,YAAYA,QAAO;;;ACMnB,IAAM,iBAAiB;AAAA,EACrB,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AAAA,EACN,WAAW;AAAA,EACX,KAAK,OAAO;AAAA,EACZ,UAAU,OAAO;AAAA,EACjB,aAAa,OAAO;AACtB;;;ACbA,YAAY,OAAO;AAmBnB,IAAM,gBAAgB,qBAAqB,YAAY;AACrD,SAAO,CAAC;AACV,CAAC;AACD,IAAM,uBAAuB;AAAA,EAC3B;AAAA,IACE,KAAK,CAAC,iBAAiB;AAAA,EACzB;AAAA,EACA,OAAO,QAAQ;AACb,UAAM,UAAU,IAAI,QAAQ;AAC5B,WAAO;AAAA,MACL;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAM,OAAS,SAAO;AACtB,IAAM,mBAAqB,OAAK,CAAC,WAAW,YAAY,YAAY,UAAU,CAAC,EAAE,QAAQ,SAAS;AAChG,SAAO;AAAA,EACP,IAAM,SAAO,EAAE,QAAQ,UAAU;AAAA,EACjC,MAAQ,SAAO;AAAA,EACf,MAAQ,SAAO;AAAA,EACf,MAAQ,SAAO,EAAE,QAAQ,EAAE,SAAS;AAAA,EACpC,UAAY,SAAS,SAAO,GAAK,UAAQ,CAAC,EAAE,GAAK,SAAO,EAAE,UAAU,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS;AAAA,EACpG,WAAa,OAAK;AACpB,CAAC;AACC,SAAO;AAAA,EACP,IAAM,SAAO,EAAE,QAAQ,UAAU;AAAA,EACjC,gBAAkB,SAAO;AAAA,EACzB,QAAU,SAAO,OAAO;AAAA,EACxB;AAAA,EACA,WAAa,OAAK,EAAE,QAAQ,MAAsB,oBAAI,KAAK,CAAC;AAC9D,CAAC;AACC,SAAO;AAAA,EACP,IAAM,SAAO,EAAE,QAAQ,UAAU;AAAA,EACjC,gBAAkB,SAAO;AAAA,EACzB,OAAS,SAAO;AAAA,EAChB;AAAA,EACA,QAAQ;AAAA,EACR,QAAU,SAAO,EAAE,SAAS;AAAA,EAC5B,WAAa,SAAO;AAAA,EACpB,WAAa,OAAK;AACpB,CAAC;AACD,IAAM,aAAe,SAAO;AAAA,EAC1B,IAAM,SAAO,EAAE,QAAQ,UAAU;AAAA,EACjC,MAAQ,SAAO,EAAE,IAAI,CAAC;AAAA,EACtB,gBAAkB,SAAO;AAAA,EACzB,WAAa,OAAK;AAAA,EAClB,WAAa,OAAK,EAAE,SAAS;AAC/B,CAAC;AACC,SAAO;AAAA,EACP,IAAM,SAAO,EAAE,QAAQ,UAAU;AAAA,EACjC,QAAU,SAAO;AAAA,EACjB,QAAU,SAAO;AAAA,EACjB,WAAa,OAAK,EAAE,QAAQ,MAAsB,oBAAI,KAAK,CAAC;AAC9D,CAAC;AACD,IAAM,eAAe,CAAC,SAAS,UAAU,OAAO;AAC9C,QAAM;AAAA,EACJ,OAAK,YAAY;AAAA,EACjB,QAAQ,OAAK,YAAY,CAAC;AAC9B,CAAC;;;AC9ED,SAASC,MAAK,YAAY;AACxB,SAAO;AAAA,IACL,UAAU,SAAS,YAAY,OAAO;AACpC,UAAI,UAAU;AACd,iBAAW,CAAC,mBAAmB,gBAAgB,KAAK,OAAO;AAAA,QACzD;AAAA,MACF,GAAG;AACD,cAAM,iBAAiB,WAAW,iBAAiB;AACnD,YAAI,CAAC,gBAAgB;AACnB,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,OAAO,2CAA2C,iBAAiB;AAAA,UACrE;AAAA,QACF;AACA,YAAI,MAAM,QAAQ,gBAAgB,GAAG;AACnC,oBAAU,iBAAiB;AAAA,YACzB,CAAC,oBAAoB,eAAe,SAAS,eAAe;AAAA,UAC9D;AAAA,QACF,OAAO;AACL,cAAI,OAAO,qBAAqB,UAAU;AACxC,kBAAM,UAAU;AAChB,gBAAI,QAAQ,cAAc,MAAM;AAC9B,wBAAU,QAAQ,QAAQ;AAAA,gBACxB,CAAC,oBAAoB,eAAe,SAAS,eAAe;AAAA,cAC9D;AAAA,YACF,OAAO;AACL,wBAAU,QAAQ,QAAQ;AAAA,gBACxB,CAAC,oBAAoB,eAAe,SAAS,eAAe;AAAA,cAC9D;AAAA,YACF;AAAA,UACF,OAAO;AACL,kBAAM,IAAI,gBAAgB,gCAAgC;AAAA,UAC5D;AAAA,QACF;AACA,YAAI,WAAW,cAAc,MAAM;AACjC,iBAAO,EAAE,QAAQ;AAAA,QACnB;AACA,YAAI,CAAC,WAAW,cAAc,OAAO;AACnC,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,OAAO,oCAAoC,iBAAiB;AAAA,UAC9D;AAAA,QACF;AAAA,MACF;AACA,UAAI,SAAS;AACX,eAAO;AAAA,UACL;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACF;AACA,SAAS,oBAAoB,GAAG;AAC9B,SAAO;AAAA,IACL,QAAQ,YAAY;AAClB,aAAOA,MAAK,UAAU;AAAA,IACxB;AAAA,IACA,YAAY;AAAA,EACd;AACF;;;AC/DA,IAAM,oBAAoB;AAAA,EACxB,cAAc,CAAC,UAAU,QAAQ;AAAA,EACjC,QAAQ,CAAC,UAAU,UAAU,QAAQ;AAAA,EACrC,YAAY,CAAC,UAAU,QAAQ;AAAA,EAC/B,MAAM,CAAC,UAAU,UAAU,QAAQ;AACrC;AACA,IAAM,YAAY,oBAAoB,iBAAiB;AACvD,IAAM,UAAU,UAAU,QAAQ;AAAA,EAChC,cAAc,CAAC,QAAQ;AAAA,EACvB,YAAY,CAAC,UAAU,QAAQ;AAAA,EAC/B,QAAQ,CAAC,UAAU,UAAU,QAAQ;AAAA,EACrC,MAAM,CAAC,UAAU,UAAU,QAAQ;AACrC,CAAC;AACD,IAAM,UAAU,UAAU,QAAQ;AAAA,EAChC,cAAc,CAAC,UAAU,QAAQ;AAAA,EACjC,QAAQ,CAAC,UAAU,UAAU,QAAQ;AAAA,EACrC,YAAY,CAAC,UAAU,QAAQ;AAAA,EAC/B,MAAM,CAAC,UAAU,UAAU,QAAQ;AACrC,CAAC;AACD,IAAM,WAAW,UAAU,QAAQ;AAAA,EACjC,cAAc,CAAC;AAAA,EACf,QAAQ,CAAC;AAAA,EACT,YAAY,CAAC;AAAA,EACb,MAAM,CAAC;AACT,CAAC;;;AC3BD,YAAYC,QAAO;;;ACCnB,YAAYC,QAAO;;;ACDnB,YAAYC,QAAO;;;ACOnB,OAAO;;;ACPP,YAAYC,QAAO;;;ACAnB,YAAYC,QAAO;;;ACEnB,OAAO;;;ACFP,YAAYC,QAAO;;;ACGnB,IAAMC,qBAAoB;AAAA,EACxB,MAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,SAAS,CAAC,QAAQ,UAAU,QAAQ;AACtC;AACA,IAAMC,aAAY,oBAAoBD,kBAAiB;AACvD,IAAME,WAAUD,WAAU,QAAQ;AAAA,EAChC,MAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,SAAS,CAAC,QAAQ,UAAU,QAAQ;AACtC,CAAC;AACD,IAAM,SAASA,WAAU,QAAQ;AAAA,EAC/B,MAAM,CAAC;AAAA,EACP,SAAS,CAAC;AACZ,CAAC;;;AC9BD,YAAYE,QAAO;;;ACHnB,YAAYC,SAAO;AAoBjB,WAAO;AAAA,EACP,IAAM,WAAO;AAAA,EACb,WAAa,WAAO;AAAA,EACpB,YAAc,WAAO;AAAA,EACrB,WAAa,SAAK;AACpB,CAAC;;;ACnBD,OAAO;;;ACNP,YAAYC,SAAO;;;ACAnB,YAAYC,SAAO;;;ACAnB,YAAYC,SAAO;;;ACAnB,YAAYC,SAAO;;;ACAnB,YAAYC,SAAO;;;ACAnB,SAAS,WAAW,SAAS,aAAa,KAAAC,KAAG,WAAW,WAAW,YAAY,gBAAgB;;;ACA/F,YAAYC,SAAO;;;ACOnB,IAAM,YAAY;AAAA,EAChB,sBAAsB;AAAA,EACtB,kBAAkB;AAAA,EAClB,UAAU;AACZ;AACA,IAAM,gBAAgB;AAAA,EACpB,CAAC,UAAU,oBAAoB,GAAG;AAAA,EAClC,CAAC,UAAU,gBAAgB,GAAG;AAAA,EAC9B,CAAC,UAAU,QAAQ,GAAG;AACxB;;;AChBA,YAAYC,SAAO;;;ACEnB,OAAO;;;ACqBP,YAAYC,SAAO;","names":["z","role","z","z","z","z","z","z","defaultStatements","defaultAc","adminAc","z","z","z","z","z","z","z","z","z","z","z"]}