better-auth
Version:
The most comprehensive authentication library for TypeScript.
3,079 lines • 93.4 kB
JavaScript
'use strict';
const betterCall = require('better-call');
const zod = require('zod');
const account = require('./better-auth.BzwQYq5t.cjs');
require('./better-auth.DiSjtgs9.cjs');
require('@better-auth/utils/base64');
require('@better-auth/utils/hmac');
require('@better-auth/utils/binary');
require('./better-auth.DcWKCjjf.cjs');
const date = require('./better-auth.C1hdVENX.cjs');
const index = require('./better-auth.ANpbi45u.cjs');
const parser = require('./better-auth.DhsGZ30Q.cjs');
require('./better-auth.GpOOav9x.cjs');
require('defu');
const hasPermission = require('./better-auth.DSVbLSt7.cjs');
const cookies_index = require('../cookies/index.cjs');
const id = require('./better-auth.Bg6iw3ig.cjs');
require('@better-auth/utils/hash');
require('@noble/ciphers/chacha');
require('@noble/ciphers/utils');
require('@noble/ciphers/webcrypto');
require('jose');
require('@noble/hashes/scrypt');
require('@better-auth/utils');
require('@better-auth/utils/hex');
require('@noble/hashes/utils');
require('./better-auth.CYeOI8C-.cjs');
const plugins_organization_access_index = require('../plugins/organization/access/index.cjs');
const shimContext = (originalObject, newContext) => {
const shimmedObj = {};
for (const [key, value] of Object.entries(originalObject)) {
shimmedObj[key] = (ctx) => {
return value({
...ctx,
context: {
...newContext,
...ctx.context
}
});
};
shimmedObj[key].path = value.path;
shimmedObj[key].method = value.method;
shimmedObj[key].options = value.options;
shimmedObj[key].headers = value.headers;
}
return shimmedObj;
};
const getOrgAdapter = (context, options) => {
const adapter = context.adapter;
return {
findOrganizationBySlug: async (slug) => {
const organization = await adapter.findOne({
model: "organization",
where: [
{
field: "slug",
value: slug
}
]
});
return organization;
},
createOrganization: async (data) => {
const organization = await adapter.create({
model: "organization",
data: {
...data.organization,
metadata: data.organization.metadata ? JSON.stringify(data.organization.metadata) : void 0
}
});
return {
...organization,
metadata: organization.metadata ? JSON.parse(organization.metadata) : void 0
};
},
findMemberByEmail: async (data) => {
const user = await adapter.findOne({
model: "user",
where: [
{
field: "email",
value: data.email
}
]
});
if (!user) {
return null;
}
const member = await adapter.findOne({
model: "member",
where: [
{
field: "organizationId",
value: data.organizationId
},
{
field: "userId",
value: user.id
}
]
});
if (!member) {
return null;
}
return {
...member,
user: {
id: user.id,
name: user.name,
email: user.email,
image: user.image
}
};
},
listMembers: async (data) => {
const members = await adapter.findMany({
model: "member",
where: [
{
field: "organizationId",
value: data.organizationId
}
],
limit: options?.membershipLimit || 100
});
return members;
},
findMemberByOrgId: async (data) => {
const [member, user] = await Promise.all([
await adapter.findOne({
model: "member",
where: [
{
field: "userId",
value: data.userId
},
{
field: "organizationId",
value: data.organizationId
}
]
}),
await adapter.findOne({
model: "user",
where: [
{
field: "id",
value: data.userId
}
]
})
]);
if (!user || !member) {
return null;
}
return {
...member,
user: {
id: user.id,
name: user.name,
email: user.email,
image: user.image
}
};
},
findMemberById: async (memberId) => {
const member = await adapter.findOne({
model: "member",
where: [
{
field: "id",
value: memberId
}
]
});
if (!member) {
return null;
}
const user = await adapter.findOne({
model: "user",
where: [
{
field: "id",
value: member.userId
}
]
});
if (!user) {
return null;
}
return {
...member,
user: {
id: user.id,
name: user.name,
email: user.email,
image: user.image
}
};
},
createMember: async (data) => {
const member = await adapter.create({
model: "member",
data: {
...data,
createdAt: /* @__PURE__ */ new Date()
}
});
return member;
},
updateMember: async (memberId, role) => {
const member = await adapter.update({
model: "member",
where: [
{
field: "id",
value: memberId
}
],
update: {
role
}
});
return member;
},
deleteMember: async (memberId) => {
const member = await adapter.delete({
model: "member",
where: [
{
field: "id",
value: memberId
}
]
});
return member;
},
updateOrganization: async (organizationId, data) => {
const organization = await adapter.update({
model: "organization",
where: [
{
field: "id",
value: organizationId
}
],
update: {
...data,
metadata: typeof data.metadata === "object" ? JSON.stringify(data.metadata) : data.metadata
}
});
if (!organization) {
return null;
}
return {
...organization,
metadata: organization.metadata ? parser.parseJSON(organization.metadata) : void 0
};
},
deleteOrganization: async (organizationId) => {
await adapter.delete({
model: "member",
where: [
{
field: "organizationId",
value: organizationId
}
]
});
await adapter.delete({
model: "invitation",
where: [
{
field: "organizationId",
value: organizationId
}
]
});
await adapter.delete({
model: "organization",
where: [
{
field: "id",
value: organizationId
}
]
});
return organizationId;
},
setActiveOrganization: async (sessionToken, organizationId) => {
const session = await context.internalAdapter.updateSession(
sessionToken,
{
activeOrganizationId: organizationId
}
);
return session;
},
findOrganizationById: async (organizationId) => {
const organization = await adapter.findOne({
model: "organization",
where: [
{
field: "id",
value: organizationId
}
]
});
return organization;
},
/**
* @requires db
*/
findFullOrganization: async ({
organizationId,
isSlug,
includeTeams
}) => {
const org = await adapter.findOne({
model: "organization",
where: [{ field: isSlug ? "slug" : "id", value: organizationId }]
});
if (!org) {
return null;
}
const [invitations, members, teams] = await Promise.all([
adapter.findMany({
model: "invitation",
where: [{ field: "organizationId", value: org.id }]
}),
adapter.findMany({
model: "member",
where: [{ field: "organizationId", value: org.id }],
limit: options?.membershipLimit || 100
}),
includeTeams ? adapter.findMany({
model: "team",
where: [{ field: "organizationId", value: org.id }]
}) : null
]);
if (!org) return null;
const userIds = members.map((member) => member.userId);
const users = userIds.length > 0 ? await adapter.findMany({
model: "user",
where: [{ field: "id", value: userIds, operator: "in" }],
limit: options?.membershipLimit || 100
}) : [];
const userMap = new Map(users.map((user) => [user.id, user]));
const membersWithUsers = members.map((member) => {
const user = userMap.get(member.userId);
if (!user) {
throw new index.BetterAuthError(
"Unexpected error: User not found for member"
);
}
return {
...member,
user: {
id: user.id,
name: user.name,
email: user.email,
image: user.image
}
};
});
return {
...org,
invitations,
members: membersWithUsers,
teams
};
},
listOrganizations: async (userId) => {
const members = await adapter.findMany({
model: "member",
where: [
{
field: "userId",
value: userId
}
]
});
if (!members || members.length === 0) {
return [];
}
const organizationIds = members.map((member) => member.organizationId);
const organizations = await adapter.findMany({
model: "organization",
where: [
{
field: "id",
value: organizationIds,
operator: "in"
}
]
});
return organizations;
},
createTeam: async (data) => {
const team = await adapter.create({
model: "team",
data
});
return team;
},
findTeamById: async ({
teamId,
organizationId,
includeTeamMembers
}) => {
const team = await adapter.findOne({
model: "team",
where: [
{
field: "id",
value: teamId
},
...organizationId ? [
{
field: "organizationId",
value: organizationId
}
] : []
]
});
if (!team) {
return null;
}
let members = [];
if (includeTeamMembers) {
members = await adapter.findMany({
model: "member",
where: [
{
field: "teamId",
value: teamId
}
],
limit: options?.membershipLimit || 100
});
return {
...team,
members
};
}
return team;
},
updateTeam: async (teamId, data) => {
const team = await adapter.update({
model: "team",
where: [
{
field: "id",
value: teamId
}
],
update: {
...data
}
});
return team;
},
deleteTeam: async (teamId) => {
const team = await adapter.delete({
model: "team",
where: [
{
field: "id",
value: teamId
}
]
});
return team;
},
listTeams: async (organizationId) => {
const teams = await adapter.findMany({
model: "team",
where: [
{
field: "organizationId",
value: organizationId
}
]
});
return teams;
},
createTeamInvitation: async ({
email,
role,
teamId,
organizationId,
inviterId,
expiresIn = 1e3 * 60 * 60 * 48
// Default expiration: 48 hours
}) => {
const expiresAt = date.getDate(expiresIn);
const invitation = await adapter.create({
model: "invitation",
data: {
email,
role,
organizationId,
teamId,
inviterId,
status: "pending",
expiresAt
}
});
return invitation;
},
findInvitationsByTeamId: async (teamId) => {
const invitations = await adapter.findMany({
model: "invitation",
where: [
{
field: "teamId",
value: teamId
}
]
});
return invitations;
},
createInvitation: async ({
invitation,
user
}) => {
const defaultExpiration = 60 * 60 * 48;
const expiresAt = date.getDate(
options?.invitationExpiresIn || defaultExpiration,
"sec"
);
const invite = await adapter.create({
model: "invitation",
data: {
status: "pending",
expiresAt,
inviterId: user.id,
...invitation
}
});
return invite;
},
findInvitationById: async (id) => {
const invitation = await adapter.findOne({
model: "invitation",
where: [
{
field: "id",
value: id
}
]
});
return invitation;
},
findPendingInvitation: async (data) => {
const invitation = await adapter.findMany({
model: "invitation",
where: [
{
field: "email",
value: data.email
},
{
field: "organizationId",
value: data.organizationId
},
{
field: "status",
value: "pending"
}
]
});
return invitation.filter(
(invite) => new Date(invite.expiresAt) > /* @__PURE__ */ new Date()
);
},
findPendingInvitations: async (data) => {
const invitations = await adapter.findMany({
model: "invitation",
where: [
{
field: "organizationId",
value: data.organizationId
},
{
field: "status",
value: "pending"
}
]
});
return invitations.filter(
(invite) => new Date(invite.expiresAt) > /* @__PURE__ */ new Date()
);
},
listInvitations: async (data) => {
const invitations = await adapter.findMany({
model: "invitation",
where: [
{
field: "organizationId",
value: data.organizationId
}
]
});
return invitations;
},
updateInvitation: async (data) => {
const invitation = await adapter.update({
model: "invitation",
where: [
{
field: "id",
value: data.invitationId
}
],
update: {
status: data.status
}
});
return invitation;
}
};
};
const orgMiddleware = account.createAuthMiddleware(async (ctx) => {
return {};
});
const orgSessionMiddleware = account.createAuthMiddleware(
{
use: [account.sessionMiddleware]
},
async (ctx) => {
const session = ctx.context.session;
return {
session
};
}
);
const ORGANIZATION_ERROR_CODES = {
YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION: "You are not allowed to create a new organization",
YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS: "You have reached the maximum number of organizations",
ORGANIZATION_ALREADY_EXISTS: "Organization already exists",
ORGANIZATION_NOT_FOUND: "Organization not found",
USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION: "User is not a member of the organization",
YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION: "You are not allowed to update this organization",
YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION: "You are not allowed to delete this organization",
NO_ACTIVE_ORGANIZATION: "No active organization",
USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION: "User is already a member of this organization",
MEMBER_NOT_FOUND: "Member not found",
ROLE_NOT_FOUND: "Role not found",
YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM: "You are not allowed to create a new team",
TEAM_ALREADY_EXISTS: "Team already exists",
TEAM_NOT_FOUND: "Team not found",
YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER: "You cannot leave the organization as the only owner",
YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_MEMBER: "You are not allowed to delete this member",
YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION: "You are not allowed to invite users to this organization",
USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION: "User is already invited to this organization",
INVITATION_NOT_FOUND: "Invitation not found",
YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION: "You are not the recipient of the invitation",
YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION: "You are not allowed to cancel this invitation",
INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION: "Inviter is no longer a member of the organization",
YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE: "you are not allowed to invite user with this role",
FAILED_TO_RETRIEVE_INVITATION: "Failed to retrieve invitation",
YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS: "You have reached the maximum number of teams",
UNABLE_TO_REMOVE_LAST_TEAM: "Unable to remove last team",
YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER: "You are not allowed to update this member",
ORGANIZATION_MEMBERSHIP_LIMIT_REACHED: "Organization membership limit reached",
YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION: "You are not allowed to create teams in this organization",
YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION: "You are not allowed to delete teams in this organization",
YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM: "You are not allowed to update this team",
YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM: "You are not allowed to delete this team",
INVITATION_LIMIT_REACHED: "Invitation limit reached",
TEAM_MEMBER_LIMIT_REACHED: "Team member limit reached"
};
const createInvitation = (option) => account.createAuthEndpoint(
"/organization/invite-member",
{
method: "POST",
use: [orgMiddleware, orgSessionMiddleware],
body: zod.z.object({
email: zod.z.string({
description: "The email address of the user to invite"
}),
role: zod.z.union([
zod.z.string({
description: "The role to assign to the user"
}),
zod.z.array(
zod.z.string({
description: "The roles to assign to the user"
})
)
]),
organizationId: zod.z.string({
description: "The organization ID to invite the user to"
}).optional(),
resend: zod.z.boolean({
description: "Resend the invitation email, if the user is already invited"
}).optional(),
teamId: zod.z.string({
description: "The team ID to invite the user to"
}).optional()
}),
metadata: {
$Infer: {
body: {}
},
openapi: {
description: "Invite a user to an organization",
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
properties: {
id: {
type: "string"
},
email: {
type: "string"
},
role: {
type: "string"
},
organizationId: {
type: "string"
},
inviterId: {
type: "string"
},
status: {
type: "string"
},
expiresAt: {
type: "string"
}
},
required: [
"id",
"email",
"role",
"organizationId",
"inviterId",
"status",
"expiresAt"
]
}
}
}
}
}
}
}
},
async (ctx) => {
const session = ctx.context.session;
const organizationId = ctx.body.organizationId || session.session.activeOrganizationId;
if (!organizationId) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND
});
}
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const member = await adapter.findMemberByOrgId({
userId: session.user.id,
organizationId
});
if (!member) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND
});
}
const canInvite = hasPermission.hasPermission({
role: member.role,
options: ctx.context.orgOptions,
permissions: {
invitation: ["create"]
}
});
if (!canInvite) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION
});
}
const creatorRole = ctx.context.orgOptions.creatorRole || "owner";
const roles = parseRoles(ctx.body.role);
if (member.role !== creatorRole && roles.split(",").includes(creatorRole)) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE
});
}
const alreadyMember = await adapter.findMemberByEmail({
email: ctx.body.email,
organizationId
});
if (alreadyMember) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION
});
}
const alreadyInvited = await adapter.findPendingInvitation({
email: ctx.body.email,
organizationId
});
if (alreadyInvited.length && !ctx.body.resend) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION
});
}
if (alreadyInvited.length && ctx.context.orgOptions.cancelPendingInvitationsOnReInvite) {
await adapter.updateInvitation({
invitationId: alreadyInvited[0].id,
status: "canceled"
});
}
const organization = await adapter.findOrganizationById(organizationId);
if (!organization) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND
});
}
const invitationLimit = typeof ctx.context.orgOptions.invitationLimit === "function" ? await ctx.context.orgOptions.invitationLimit(
{
user: session.user,
organization,
member
},
ctx.context
) : ctx.context.orgOptions.invitationLimit ?? 100;
const pendingInvitations = await adapter.findPendingInvitations({
organizationId
});
if (pendingInvitations.length >= invitationLimit) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.INVITATION_LIMIT_REACHED
});
}
if (ctx.context.orgOptions.teams && ctx.context.orgOptions.teams.enabled && typeof ctx.context.orgOptions.teams.maximumMembersPerTeam !== "undefined" && "teamId" in ctx.body && ctx.body.teamId) {
const team = await adapter.findTeamById({
teamId: ctx.body.teamId,
organizationId,
includeTeamMembers: true
});
if (!team) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.TEAM_NOT_FOUND
});
}
const maximumMembersPerTeam = typeof ctx.context.orgOptions.teams.maximumMembersPerTeam === "function" ? await ctx.context.orgOptions.teams.maximumMembersPerTeam({
teamId: ctx.body.teamId,
session,
organizationId
}) : ctx.context.orgOptions.teams.maximumMembersPerTeam;
if (team.members.length >= maximumMembersPerTeam) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.TEAM_MEMBER_LIMIT_REACHED
});
}
}
const invitation = await adapter.createInvitation({
invitation: {
role: roles,
email: ctx.body.email.toLowerCase(),
organizationId,
..."teamId" in ctx.body ? {
teamId: ctx.body.teamId
} : {}
},
user: session.user
});
await ctx.context.orgOptions.sendInvitationEmail?.(
{
id: invitation.id,
role: invitation.role,
email: invitation.email.toLowerCase(),
organization,
inviter: {
...member,
user: session.user
},
invitation
},
ctx.request
);
return ctx.json(invitation);
}
);
const acceptInvitation = account.createAuthEndpoint(
"/organization/accept-invitation",
{
method: "POST",
body: zod.z.object({
invitationId: zod.z.string({
description: "The ID of the invitation to accept"
})
}),
use: [orgMiddleware, orgSessionMiddleware],
metadata: {
openapi: {
description: "Accept an invitation to an organization",
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
properties: {
invitation: {
type: "object"
},
member: {
type: "object"
}
}
}
}
}
}
}
}
}
},
async (ctx) => {
const session = ctx.context.session;
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const invitation = await adapter.findInvitationById(ctx.body.invitationId);
if (!invitation || invitation.expiresAt < /* @__PURE__ */ new Date() || invitation.status !== "pending") {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.INVITATION_NOT_FOUND
});
}
if (invitation.email.toLowerCase() !== session.user.email.toLowerCase()) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION
});
}
const membershipLimit = ctx.context.orgOptions?.membershipLimit || 100;
const members = await adapter.listMembers({
organizationId: invitation.organizationId
});
if (members.length >= membershipLimit) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.ORGANIZATION_MEMBERSHIP_LIMIT_REACHED
});
}
const acceptedI = await adapter.updateInvitation({
invitationId: ctx.body.invitationId,
status: "accepted"
});
if (!acceptedI) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.FAILED_TO_RETRIEVE_INVITATION
});
}
if (ctx.context.orgOptions.teams && ctx.context.orgOptions.teams.enabled && typeof ctx.context.orgOptions.teams.maximumMembersPerTeam !== "undefined" && "teamId" in acceptedI && acceptedI.teamId) {
const team = await adapter.findTeamById({
teamId: acceptedI.teamId,
organizationId: invitation.organizationId,
includeTeamMembers: true
});
if (!team) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.TEAM_NOT_FOUND
});
}
const maximumMembersPerTeam = typeof ctx.context.orgOptions.teams.maximumMembersPerTeam === "function" ? await ctx.context.orgOptions.teams.maximumMembersPerTeam({
teamId: acceptedI.teamId,
session,
organizationId: invitation.organizationId
}) : ctx.context.orgOptions.teams.maximumMembersPerTeam;
if (team.members.length >= maximumMembersPerTeam) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.TEAM_MEMBER_LIMIT_REACHED
});
}
}
const member = await adapter.createMember({
organizationId: invitation.organizationId,
userId: session.user.id,
role: invitation.role,
createdAt: /* @__PURE__ */ new Date(),
..."teamId" in acceptedI ? {
teamId: acceptedI.teamId
} : {}
});
await adapter.setActiveOrganization(
session.session.token,
invitation.organizationId
);
if (!acceptedI) {
return ctx.json(null, {
status: 400,
body: {
message: ORGANIZATION_ERROR_CODES.INVITATION_NOT_FOUND
}
});
}
return ctx.json({
invitation: acceptedI,
member
});
}
);
const rejectInvitation = account.createAuthEndpoint(
"/organization/reject-invitation",
{
method: "POST",
body: zod.z.object({
invitationId: zod.z.string({
description: "The ID of the invitation to reject"
})
}),
use: [orgMiddleware, orgSessionMiddleware],
metadata: {
openapi: {
description: "Reject an invitation to an organization",
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
properties: {
invitation: {
type: "object"
},
member: {
type: "null"
}
}
}
}
}
}
}
}
}
},
async (ctx) => {
const session = ctx.context.session;
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const invitation = await adapter.findInvitationById(ctx.body.invitationId);
if (!invitation || invitation.expiresAt < /* @__PURE__ */ new Date() || invitation.status !== "pending") {
throw new betterCall.APIError("BAD_REQUEST", {
message: "Invitation not found!"
});
}
if (invitation.email.toLowerCase() !== session.user.email.toLowerCase()) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION
});
}
const rejectedI = await adapter.updateInvitation({
invitationId: ctx.body.invitationId,
status: "rejected"
});
return ctx.json({
invitation: rejectedI,
member: null
});
}
);
const cancelInvitation = account.createAuthEndpoint(
"/organization/cancel-invitation",
{
method: "POST",
body: zod.z.object({
invitationId: zod.z.string({
description: "The ID of the invitation to cancel"
})
}),
use: [orgMiddleware, orgSessionMiddleware],
openapi: {
description: "Cancel an invitation to an organization",
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
properties: {
invitation: {
type: "object"
}
}
}
}
}
}
}
}
},
async (ctx) => {
const session = ctx.context.session;
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const invitation = await adapter.findInvitationById(ctx.body.invitationId);
if (!invitation) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.INVITATION_NOT_FOUND
});
}
const member = await adapter.findMemberByOrgId({
userId: session.user.id,
organizationId: invitation.organizationId
});
if (!member) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND
});
}
const canCancel = hasPermission.hasPermission({
role: member.role,
options: ctx.context.orgOptions,
permissions: {
invitation: ["cancel"]
}
});
if (!canCancel) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION
});
}
const canceledI = await adapter.updateInvitation({
invitationId: ctx.body.invitationId,
status: "canceled"
});
return ctx.json(canceledI);
}
);
const getInvitation = account.createAuthEndpoint(
"/organization/get-invitation",
{
method: "GET",
use: [orgMiddleware],
requireHeaders: true,
query: zod.z.object({
id: zod.z.string({
description: "The ID of the invitation to get"
})
}),
metadata: {
openapi: {
description: "Get an invitation by ID",
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
properties: {
id: {
type: "string"
},
email: {
type: "string"
},
role: {
type: "string"
},
organizationId: {
type: "string"
},
inviterId: {
type: "string"
},
status: {
type: "string"
},
expiresAt: {
type: "string"
},
organizationName: {
type: "string"
},
organizationSlug: {
type: "string"
},
inviterEmail: {
type: "string"
}
},
required: [
"id",
"email",
"role",
"organizationId",
"inviterId",
"status",
"expiresAt",
"organizationName",
"organizationSlug",
"inviterEmail"
]
}
}
}
}
}
}
}
},
async (ctx) => {
const session = await account.getSessionFromCtx(ctx);
if (!session) {
throw new betterCall.APIError("UNAUTHORIZED", {
message: "Not authenticated"
});
}
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const invitation = await adapter.findInvitationById(ctx.query.id);
if (!invitation || invitation.status !== "pending" || invitation.expiresAt < /* @__PURE__ */ new Date()) {
throw new betterCall.APIError("BAD_REQUEST", {
message: "Invitation not found!"
});
}
if (invitation.email.toLowerCase() !== session.user.email.toLowerCase()) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION
});
}
const organization = await adapter.findOrganizationById(
invitation.organizationId
);
if (!organization) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND
});
}
const member = await adapter.findMemberByOrgId({
userId: invitation.inviterId,
organizationId: invitation.organizationId
});
if (!member) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION
});
}
return ctx.json({
...invitation,
organizationName: organization.name,
organizationSlug: organization.slug,
inviterEmail: member.user.email
});
}
);
const listInvitations = account.createAuthEndpoint(
"/organization/list-invitations",
{
method: "GET",
use: [orgMiddleware, orgSessionMiddleware],
query: zod.z.object({
organizationId: zod.z.string({
description: "The ID of the organization to list invitations for"
}).optional()
}).optional()
},
async (ctx) => {
const session = await account.getSessionFromCtx(ctx);
if (!session) {
throw new betterCall.APIError("UNAUTHORIZED", {
message: "Not authenticated"
});
}
const orgId = ctx.query?.organizationId || session.session.activeOrganizationId;
if (!orgId) {
throw new betterCall.APIError("BAD_REQUEST", {
message: "Organization ID is required"
});
}
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const isMember = await adapter.findMemberByOrgId({
userId: session.user.id,
organizationId: orgId
});
if (!isMember) {
throw new betterCall.APIError("FORBIDDEN", {
message: "You are not a member of this organization"
});
}
const invitations = await adapter.listInvitations({
organizationId: orgId
});
return ctx.json(invitations);
}
);
const addMember = () => account.createAuthEndpoint(
"/organization/add-member",
{
method: "POST",
body: zod.z.object({
userId: zod.z.coerce.string(),
role: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]),
organizationId: zod.z.string().optional()
}),
use: [orgMiddleware],
metadata: {
SERVER_ONLY: true,
$Infer: {
body: {}
}
}
},
async (ctx) => {
const session = ctx.body.userId ? await account.getSessionFromCtx(ctx).catch((e) => null) : null;
const orgId = ctx.body.organizationId || session?.session.activeOrganizationId;
if (!orgId) {
return ctx.json(null, {
status: 400,
body: {
message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION
}
});
}
const teamId = "teamId" in ctx.body ? ctx.body.teamId : void 0;
if (teamId && !ctx.context.orgOptions.teams?.enabled) {
ctx.context.logger.error("Teams are not enabled");
throw new betterCall.APIError("BAD_REQUEST", {
message: "Teams are not enabled"
});
}
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const user = await ctx.context.internalAdapter.findUserById(
ctx.body.userId
);
if (!user) {
throw new betterCall.APIError("BAD_REQUEST", {
message: account.BASE_ERROR_CODES.USER_NOT_FOUND
});
}
const alreadyMember = await adapter.findMemberByEmail({
email: user.email,
organizationId: orgId
});
if (alreadyMember) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION
});
}
if (teamId) {
const team = await adapter.findTeamById({
teamId,
organizationId: orgId
});
if (!team || team.organizationId !== orgId) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.TEAM_NOT_FOUND
});
}
}
const membershipLimit = ctx.context.orgOptions?.membershipLimit || 100;
const members = await adapter.listMembers({ organizationId: orgId });
if (members.length >= membershipLimit) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.ORGANIZATION_MEMBERSHIP_LIMIT_REACHED
});
}
const createdMember = await adapter.createMember({
organizationId: orgId,
userId: user.id,
role: parseRoles(ctx.body.role),
createdAt: /* @__PURE__ */ new Date(),
...teamId ? { teamId } : {}
});
return ctx.json(createdMember);
}
);
const removeMember = account.createAuthEndpoint(
"/organization/remove-member",
{
method: "POST",
body: zod.z.object({
memberIdOrEmail: zod.z.string({
description: "The ID or email of the member to remove"
}),
/**
* If not provided, the active organization will be used
*/
organizationId: zod.z.string({
description: "The ID of the organization to remove the member from. If not provided, the active organization will be used"
}).optional()
}),
use: [orgMiddleware, orgSessionMiddleware],
metadata: {
openapi: {
description: "Remove a member from an organization",
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
properties: {
member: {
type: "object",
properties: {
id: {
type: "string"
},
userId: {
type: "string"
},
organizationId: {
type: "string"
},
role: {
type: "string"
}
},
required: ["id", "userId", "organizationId", "role"]
}
},
required: ["member"]
}
}
}
}
}
}
}
},
async (ctx) => {
const session = ctx.context.session;
const organizationId = ctx.body.organizationId || session.session.activeOrganizationId;
if (!organizationId) {
return ctx.json(null, {
status: 400,
body: {
message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION
}
});
}
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const member = await adapter.findMemberByOrgId({
userId: session.user.id,
organizationId
});
if (!member) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND
});
}
let toBeRemovedMember = null;
if (ctx.body.memberIdOrEmail.includes("@")) {
toBeRemovedMember = await adapter.findMemberByEmail({
email: ctx.body.memberIdOrEmail,
organizationId
});
} else {
toBeRemovedMember = await adapter.findMemberById(
ctx.body.memberIdOrEmail
);
}
if (!toBeRemovedMember) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND
});
}
const roles = toBeRemovedMember.role.split(",");
const creatorRole = ctx.context.orgOptions?.creatorRole || "owner";
const isOwner = roles.includes(creatorRole);
if (isOwner) {
if (member.role !== creatorRole) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER
});
}
const members = await adapter.listMembers({
organizationId
});
const owners = members.filter((member2) => {
const roles2 = member2.role.split(",");
return roles2.includes(creatorRole);
});
if (owners.length <= 1) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER
});
}
}
const canDeleteMember = hasPermission.hasPermission({
role: member.role,
options: ctx.context.orgOptions,
permissions: {
member: ["delete"]
}
});
if (!canDeleteMember) {
throw new betterCall.APIError("UNAUTHORIZED", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_MEMBER
});
}
if (toBeRemovedMember?.organizationId !== organizationId) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND
});
}
await adapter.deleteMember(toBeRemovedMember.id);
if (session.user.id === toBeRemovedMember.userId && session.session.activeOrganizationId === toBeRemovedMember.organizationId) {
await adapter.setActiveOrganization(session.session.token, null);
}
return ctx.json({
member: toBeRemovedMember
});
}
);
const updateMemberRole = (option) => account.createAuthEndpoint(
"/organization/update-member-role",
{
method: "POST",
body: zod.z.object({
role: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]),
memberId: zod.z.string(),
organizationId: zod.z.string().optional()
}),
use: [orgMiddleware, orgSessionMiddleware],
metadata: {
$Infer: {
body: {}
},
openapi: {
description: "Update the role of a member in an organization",
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
properties: {
member: {
type: "object",
properties: {
id: {
type: "string"
},
userId: {
type: "string"
},
organizationId: {
type: "string"
},
role: {
type: "string"
}
},
required: ["id", "userId", "organizationId", "role"]
}
},
required: ["member"]
}
}
}
}
}
}
}
},
async (ctx) => {
const session = ctx.context.session;
if (!ctx.body.role) {
throw new betterCall.APIError("BAD_REQUEST");
}
const organizationId = ctx.body.organizationId || session.session.activeOrganizationId;
if (!organizationId) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION
});
}
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const roleToSet = Array.isArray(ctx.body.role) ? ctx.body.role : ctx.body.role ? [ctx.body.role] : [];
const member = await adapter.findMemberByOrgId({
userId: session.user.id,
organizationId
});
if (!member) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND
});
}
const toBeUpdatedMember = member.id !== ctx.body.memberId ? await adapter.findMemberById(ctx.body.memberId) : member;
if (!toBeUpdatedMember) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND
});
}
const toBeUpdatedMemberRoles = toBeUpdatedMember.role.split(",");
const updatingMemberRoles = member.role.split(",");
const creatorRole = ctx.context.orgOptions?.creatorRole || "owner";
if (toBeUpdatedMemberRoles.includes(creatorRole) && !updatingMemberRoles.includes(creatorRole) || roleToSet.includes(creatorRole) && !updatingMemberRoles.includes(creatorRole)) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER
});
}
const canUpdateMember = hasPermission.hasPermission({
role: member.role,
options: ctx.context.orgOptions,
permissions: {
member: ["update"]
}
});
if (!canUpdateMember) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER
});
}
const updatedMember = await adapter.updateMember(
ctx.body.memberId,
parseRoles(ctx.body.role)
);
if (!updatedMember) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND
});
}
return ctx.json(updatedMember);
}
);
const getActiveMember = account.createAuthEndpoint(
"/organization/get-active-member",
{
method: "GET",
use: [orgMiddleware, orgSessionMiddleware],
metadata: {
openapi: {
description: "Get the member details of the active organization",
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
properties: {
id: {
type: "string"
},
userId: {
type: "string"
},
organizationId: {
type: "string"
},
role: {
type: "string"
}
},
required: ["id", "userId", "organizationId", "role"]
}
}
}
}
}
}
}
},
async (ctx) => {
const session = ctx.context.session;
const organizationId = session.session.activeOrganizationId;
if (!organizationId) {
return ctx.json(null, {
status: 400,
body: {
message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION
}
});
}
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const member = await adapter.findMemberByOrgId({
userId: session.user.id,
organizationId
});
if (!member) {
return ctx.json(null, {
status: 400,
body: {
message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND
}
});
}
return ctx.json(member);
}
);
const leaveOrganization = account.createAuthEndpoint(
"/organization/leave",
{
method: "POST",
body: zod.z.object({
organizationId: zod.z.string()
}),
use: [account.sessionMiddleware, orgMiddleware]
},
async (ctx) => {
const session = ctx.context.session;
const adapter = getOrgAdapter(ctx.context);
const member = await adapter.findMemberByOrgId({
userId: session.user.id,
organizationId: ctx.body.organizationId
});
if (!member) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND
});
}
const isOwnerLeaving = member.role === (ctx.context.orgOptions?.creatorRole || "owner");
if (isOwnerLeaving) {
const members = await ctx.context.adapter.findMany({
model: "member",
where: [
{
field: "organizationId",
value: ctx.body.organizationId
}
]
});
const owners = members.filter(
(member2) => member2.role === (ctx.context.orgOptions?.creatorRole || "owner")
);
if (owners.length <= 1) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER
});
}
}
await adapter.deleteMember(member.id);
if (session.session.activeOrganizationId === ctx.body.organizationId) {
await adapter.setActiveOrganization(session.session.token, null);
}
return ctx.json(member);
}
);
const createOrganization = account.createAuthEndpoint(
"/organization/create",
{
method: "POST",
body: zod.z.object({
name: zod.z.string({
description: "The name of the organization"
}),
slug: zod.z.string({
description: "The slug of the organization"
}),
userId: zod.z.coerce.string({
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."
}).optional(),
logo: zod.z.string({
description: "The logo of the organization"
}).optional(),
metadata: zod.z.record(zod.z.string(), zod.z.any(), {
description: "The metadata of the organization"
}).optional(),
keepCurrentActiveOrganization: zod.z.boolean({
description: "Whether to keep the current active organization active after creating a new one"
}).optional()
}),
use: [orgMiddleware],
metadata: {
openapi: {
description: "Create an organization",
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
description: "The organization that was created",
$ref: "#/components/schemas/Organization"
}
}
}
}
}
}
}
},
async (ctx) => {
const session = await account.getSessionFromCtx(ctx);
if (!session && (ctx.request || ctx.headers)) {
throw new betterCall.APIError("UNAUTHORIZED");
}
let user = session?.user || null;
if (!user) {
if (!ctx.body.userId) {
throw new betterCall.APIError("UNAUTHORIZED");
}
user = await ctx.context.internalAdapter.findUserById(ctx.body.userId);
}
if (!user) {
return ctx.json(null, {
status: 401
});
}
const options = ctx.context.orgOptions;
const canCreateOrg = typeof options?.allowUserToCreateOrganization === "function" ? await options.allowUserToCreateOrganization(user) : options?.allowUserToCreateOrganization === void 0 ? true : options.allowUserToCreateOrganization;
if (!canCreateOrg) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION
});
}
const adapter = getOrgAdapter(ctx.context, options);
const userOrganizations = await adapter.listOrganizations(user.id);
const hasReachedOrgLimit = typeof options.organizationLimit === "number" ? userOrganizations.length >= options.organizationLimit : typeof options.organizationLimit === "function" ? await options.organizationLimit(user) : false;
if (hasReachedOrgLimit) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS
});
}
const existingOrganization = await adapter.findOrganizationBySlug(
ctx.body.slug
);
if (existingOrganization) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.ORGANIZATION_ALREADY_EXISTS
});
}
let hookResponse = void 0;
if (options.organizationCreation?.beforeCreate) {
const response = await options.organizationCreation.beforeCreate(
{
organization: {
slug: ctx.body.slug,
name: ctx.body.name,
logo: ctx.body.logo,
createdAt: /* @__PURE__ */ new Date(),
metadata: ctx.body.metadata
},
user
},
ctx.request
);
if (response && typeof response === "object" && "data" in response) {
hookResponse = response;
}
}
const organization = await adapter.createOrganization({
organization: {
slug: ctx.body.slug,
name: ctx.body.name,
logo: ctx.body.logo,
createdAt: /* @__PURE__ */ new Date(),
metadata: ctx.body.metadata,
...hookResponse?.data || {}
}
});
let member;
if (options?.teams?.enabled && options.teams.defaultTeam?.enabled !== false) {
const defaultTeam = await options.teams.defaultTeam?.customCreateDefaultTeam?.(
organization,
ctx.request
) || await adapter.createTeam({
organizationId: organization.id,
name: `${organization.name}`,
createdAt: /* @__PURE__ */ new Date()
});
member = await adapter.createMember({
teamId: defaultTeam.id,
userId: user.id,
organizationId: organization.id,
role: ctx.context.orgOptions.creatorRole || "owner"
});
} else {
member = await adapter.createMember({
userId: user.id,
organizationId: organization.id,
role: ctx.context.orgOptions.creatorRole || "owner"
});
}
if (options.organizationCreation?.afterCreate) {
await options.organizationCreation.afterCreate(
{
organization,
user,
member
},
ctx.request
);
}
if (ctx.context.session && !ctx.body.keepCurrentActiveOrganization) {
await adapter.setActiveOrganization(
ctx.context.session.session.token,
organization.id
);
}
return ctx.json({
...organization,
metadata: ctx.body.metadata,
members: [member]
});
}
);
const checkOrganizationSlug = account.createAuthEndpoint(
"/organization/check-slug",
{
method: "POST",
body: zod.z.object({
slug: zod.z.string()
}),
use: [account.requestOnlySessionMiddleware, orgMiddleware]
},
async (ctx) => {
const orgAdapter = getOrgAdapter(ctx.context);
const org = await orgAdapter.findOrganizationBySlug(ctx.body.slug);
if (!org) {
return ctx.json({
status: true
});
}
throw new betterCall.APIError("BAD_REQUEST", {
message: "slug is taken"
});
}
);
const updateOrganization = account.createAuthEndpoint(
"/organization/update",
{
method: "POST",
body: zod.z.object({
data: zod.z.object({
name: zod.z.string({
description: "The name of the organization"
}).optional(),
slug: zod.z.string({
description: "The slug of the organization"
}).optional(),
logo: zod.z.string({
description: "The logo of the organization"
}).optional(),
metadata: zod.z.record(zod.z.string(), zod.z.any(), {
description: "The metadata of the organization"
}).optional()
}).partial(),
organizationId: zod.z.string().optional()
}),
requireHeaders: true,
use: [orgMiddleware],
metadata: {
openapi: {
description: "Update an organization",
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
description: "The updated organization",
$ref: "#/components/schemas/Organization"
}
}
}
}
}
}
}
},
async (ctx) => {
const session = await ctx.context.getSession(ctx);
if (!session) {
throw new betterCall.APIError("UNAUTHORIZED", {
message: "User not found"
});
}
const organizationId = ctx.body.organizationId || session.session.activeOrganizationId;
if (!organizationId) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND
});
}
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const member = await adapter.findMemberByOrgId({
userId: session.user.id,
organizationId
});
if (!member) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION
});
}
const canUpdateOrg = hasPermission.hasPermission({
permissions: {
organization: ["update"]
},
role: member.role,
options: ctx.context.orgOptions
});
if (!canUpdateOrg) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION
});
}
const updatedOrg = await adapter.updateOrganization(
organizationId,
ctx.body.data
);
return ctx.json(updatedOrg);
}
);
const deleteOrganization = account.createAuthEndpoint(
"/organization/delete",
{
method: "POST",
body: zod.z.object({
organizationId: zod.z.string({
description: "The organization id to delete"
})
}),
requireHeaders: true,
use: [orgMiddleware],
metadata: {
openapi: {
description: "Delete an organization",
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "string",
description: "The organization id that was deleted"
}
}
}
}
}
}
}
},
async (ctx) => {
const session = await ctx.context.getSession(ctx);
if (!session) {
return ctx.json(null, {
status: 401
});
}
const organizationId = ctx.body.organizationId;
if (!organizationId) {
return ctx.json(null, {
status: 400,
body: {
message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND
}
});
}
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const member = await adapter.findMemberByOrgId({
userId: session.user.id,
organizationId
});
if (!member) {
return ctx.json(null, {
status: 400,
body: {
message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION
}
});
}
const canDeleteOrg = hasPermission.hasPermission({
role: member.role,
permissions: {
organization: ["delete"]
},
options: ctx.context.orgOptions
});
if (!canDeleteOrg) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION
});
}
if (organizationId === session.session.activeOrganizationId) {
await adapter.setActiveOrganization(session.session.token, null);
}
const option = ctx.context.orgOptions.organizationDeletion;
if (option?.disabled) {
throw new betterCall.APIError("FORBIDDEN");
}
const org = await adapter.findOrganizationById(organizationId);
if (!org) {
throw new betterCall.APIError("BAD_REQUEST");
}
if (option?.beforeDelete) {
await option.beforeDelete({
organization: org,
user: session.user
});
}
await adapter.deleteOrganization(organizationId);
if (option?.afterDelete) {
await option.afterDelete({
organization: org,
user: session.user
});
}
return ctx.json(org);
}
);
const getFullOrganization = () => account.createAuthEndpoint(
"/organization/get-full-organization",
{
method: "GET",
query: zod.z.optional(
zod.z.object({
organizationId: zod.z.string({
description: "The organization id to get"
}).optional(),
organizationSlug: zod.z.string({
description: "The organization slug to get"
}).optional()
})
),
requireHeaders: true,
use: [orgMiddleware, orgSessionMiddleware],
metadata: {
openapi: {
description: "Get the full organization",
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
description: "The organization",
$ref: "#/components/schemas/Organization"
}
}
}
}
}
}
}
},
async (ctx) => {
const session = ctx.context.session;
const organizationId = ctx.query?.organizationSlug || ctx.query?.organizationId || session.session.activeOrganizationId;
if (!organizationId) {
return ctx.json(null, {
status: 200
});
}
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const organization = await adapter.findFullOrganization({
organizationId,
isSlug: !!ctx.query?.organizationSlug,
includeTeams: ctx.context.orgOptions.teams?.enabled
});
const isMember = organization?.members.find(
(member) => member.userId === session.user.id
);
if (!isMember) {
await adapter.setActiveOrganization(session.session.token, null);
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION
});
}
if (!organization) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND
});
}
return ctx.json(organization);
}
);
const setActiveOrganization = () => {
return account.createAuthEndpoint(
"/organization/set-active",
{
method: "POST",
body: zod.z.object({
organizationId: zod.z.string({
description: "The organization id to set as active. It can be null to unset the active organization"
}).nullable().optional(),
organizationSlug: zod.z.string({
description: "The organization slug to set as active. It can be null to unset the active organization if organizationId is not provided"
}).optional()
}),
use: [orgSessionMiddleware, orgMiddleware],
metadata: {
openapi: {
description: "Set the active organization",
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
description: "The organization",
$ref: "#/components/schemas/Organization"
}
}
}
}
}
}
}
},
async (ctx) => {
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const session = ctx.context.session;
let organizationId = ctx.body.organizationSlug || ctx.body.organizationId;
if (organizationId === null) {
const sessionOrgId = session.session.activeOrganizationId;
if (!sessionOrgId) {
return ctx.json(null);
}
const updatedSession2 = await adapter.setActiveOrganization(
session.session.token,
null
);
await cookies_index.setSessionCookie(ctx, {
session: updatedSession2,
user: session.user
});
return ctx.json(null);
}
if (!organizationId) {
const sessionOrgId = session.session.activeOrganizationId;
if (!sessionOrgId) {
return ctx.json(null);
}
organizationId = sessionOrgId;
}
const organization = await adapter.findFullOrganization({
organizationId,
isSlug: !!ctx.body.organizationSlug
});
const isMember = organization?.members.find(
(member) => member.userId === session.user.id
);
if (!isMember) {
await adapter.setActiveOrganization(session.session.token, null);
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION
});
}
if (!organization) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND
});
}
const updatedSession = await adapter.setActiveOrganization(
session.session.token,
organization.id
);
await cookies_index.setSessionCookie(ctx, {
session: updatedSession,
user: session.user
});
return ctx.json(organization);
}
);
};
const listOrganizations = account.createAuthEndpoint(
"/organization/list",
{
method: "GET",
use: [orgMiddleware, orgSessionMiddleware],
metadata: {
openapi: {
description: "List all organizations",
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "array",
items: {
$ref: "#/components/schemas/Organization"
}
}
}
}
}
}
}
}
},
async (ctx) => {
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const organizations = await adapter.listOrganizations(
ctx.context.session.user.id
);
return ctx.json(organizations);
}
);
const role = zod.z.string();
const invitationStatus = zod.z.enum(["pending", "accepted", "rejected", "canceled"]).default("pending");
zod.z.object({
id: zod.z.string().default(id.generateId),
name: zod.z.string(),
slug: zod.z.string(),
logo: zod.z.string().nullish().optional(),
metadata: zod.z.record(zod.z.string()).or(zod.z.string().transform((v) => JSON.parse(v))).optional(),
createdAt: zod.z.date()
});
zod.z.object({
id: zod.z.string().default(id.generateId),
organizationId: zod.z.string(),
userId: zod.z.coerce.string(),
role,
createdAt: zod.z.date().default(() => /* @__PURE__ */ new Date()),
teamId: zod.z.string().optional()
});
zod.z.object({
id: zod.z.string().default(id.generateId),
organizationId: zod.z.string(),
email: zod.z.string(),
role,
status: invitationStatus,
teamId: zod.z.string().optional(),
inviterId: zod.z.string(),
expiresAt: zod.z.date()
});
const teamSchema = zod.z.object({
id: zod.z.string().default(id.generateId),
name: zod.z.string().min(1),
organizationId: zod.z.string(),
createdAt: zod.z.date(),
updatedAt: zod.z.date().optional()
});
const createTeam = (options) => account.createAuthEndpoint(
"/organization/create-team",
{
method: "POST",
body: zod.z.object({
organizationId: zod.z.string().optional(),
name: zod.z.string()
}),
use: [orgMiddleware],
metadata: {
openapi: {
description: "Create a new team within an organization",
responses: {
"200": {
description: "Team created successfully",
content: {
"application/json": {
schema: {
type: "object",
properties: {
id: {
type: "string",
description: "Unique identifier of the created team"
},
name: {
type: "string",
description: "Name of the team"
},
organizationId: {
type: "string",
description: "ID of the organization the team belongs to"
},
createdAt: {
type: "string",
format: "date-time",
description: "Timestamp when the team was created"
},
updatedAt: {
type: "string",
format: "date-time",
description: "Timestamp when the team was last updated"
}
},
required: [
"id",
"name",
"organizationId",
"createdAt",
"updatedAt"
]
}
}
}
}
}
}
}
},
async (ctx) => {
const session = await account.getSessionFromCtx(ctx);
const organizationId = ctx.body.organizationId || session?.session.activeOrganizationId;
if (!session && (ctx.request || ctx.headers)) {
throw new betterCall.APIError("UNAUTHORIZED");
}
if (!organizationId) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION
});
}
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
if (session) {
const member = await adapter.findMemberByOrgId({
userId: session.user.id,
organizationId
});
if (!member) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION
});
}
const canCreate = hasPermission.hasPermission({
role: member.role,
options: ctx.context.orgOptions,
permissions: {
team: ["create"]
}
});
if (!canCreate) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION
});
}
}
const existingTeams = await adapter.listTeams(organizationId);
const maximum = typeof ctx.context.orgOptions.teams?.maximumTeams === "function" ? await ctx.context.orgOptions.teams?.maximumTeams(
{
organizationId,
session
},
ctx.request
) : ctx.context.orgOptions.teams?.maximumTeams;
const maxTeamsReached = maximum ? existingTeams.length >= maximum : false;
if (maxTeamsReached) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS
});
}
const createdTeam = await adapter.createTeam({
name: ctx.body.name,
organizationId,
createdAt: /* @__PURE__ */ new Date(),
updatedAt: /* @__PURE__ */ new Date()
});
return ctx.json(createdTeam);
}
);
const removeTeam = account.createAuthEndpoint(
"/organization/remove-team",
{
method: "POST",
body: zod.z.object({
teamId: zod.z.string(),
organizationId: zod.z.string().optional()
}),
use: [orgMiddleware],
metadata: {
openapi: {
description: "Remove a team from an organization",
responses: {
"200": {
description: "Team removed successfully",
content: {
"application/json": {
schema: {
type: "object",
properties: {
message: {
type: "string",
description: "Confirmation message indicating successful removal",
enum: ["Team removed successfully."]
}
},
required: ["message"]
}
}
}
}
}
}
}
},
async (ctx) => {
const session = await account.getSessionFromCtx(ctx);
const organizationId = ctx.body.organizationId || session?.session.activeOrganizationId;
if (!organizationId) {
return ctx.json(null, {
status: 400,
body: {
message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION
}
});
}
if (!session && (ctx.request || ctx.headers)) {
throw new betterCall.APIError("UNAUTHORIZED");
}
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
if (session) {
const member = await adapter.findMemberByOrgId({
userId: session.user.id,
organizationId
});
if (!member || member.teamId === ctx.body.teamId) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM
});
}
const canRemove = hasPermission.hasPermission({
role: member.role,
options: ctx.context.orgOptions,
permissions: {
team: ["delete"]
}
});
if (!canRemove) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION
});
}
}
const team = await adapter.findTeamById({
teamId: ctx.body.teamId,
organizationId
});
if (!team || team.organizationId !== organizationId) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.TEAM_NOT_FOUND
});
}
if (!ctx.context.orgOptions.teams?.allowRemovingAllTeams) {
const teams = await adapter.listTeams(organizationId);
if (teams.length <= 1) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.UNABLE_TO_REMOVE_LAST_TEAM
});
}
}
await adapter.deleteTeam(team.id);
return ctx.json({ message: "Team removed successfully." });
}
);
const updateTeam = account.createAuthEndpoint(
"/organization/update-team",
{
method: "POST",
body: zod.z.object({
teamId: zod.z.string(),
data: teamSchema.partial()
}),
use: [orgMiddleware, orgSessionMiddleware],
metadata: {
openapi: {
description: "Update an existing team in an organization",
responses: {
"200": {
description: "Team updated successfully",
content: {
"application/json": {
schema: {
type: "object",
properties: {
id: {
type: "string",
description: "Unique identifier of the updated team"
},
name: {
type: "string",
description: "Updated name of the team"
},
organizationId: {
type: "string",
description: "ID of the organization the team belongs to"
},
createdAt: {
type: "string",
format: "date-time",
description: "Timestamp when the team was created"
},
updatedAt: {
type: "string",
format: "date-time",
description: "Timestamp when the team was last updated"
}
},
required: [
"id",
"name",
"organizationId",
"createdAt",
"updatedAt"
]
}
}
}
}
}
}
}
},
async (ctx) => {
const session = ctx.context.session;
const organizationId = ctx.body.data.organizationId || session.session.activeOrganizationId;
if (!organizationId) {
return ctx.json(null, {
status: 400,
body: {
message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION
}
});
}
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const member = await adapter.findMemberByOrgId({
userId: session.user.id,
organizationId
});
if (!member) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM
});
}
const canUpdate = hasPermission.hasPermission({
role: member.role,
options: ctx.context.orgOptions,
permissions: {
team: ["update"]
}
});
if (!canUpdate) {
throw new betterCall.APIError("FORBIDDEN", {
message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM
});
}
const team = await adapter.findTeamById({
teamId: ctx.body.teamId,
organizationId
});
if (!team || team.organizationId !== organizationId) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.TEAM_NOT_FOUND
});
}
const updatedTeam = await adapter.updateTeam(team.id, {
name: ctx.body.data.name
});
return ctx.json(updatedTeam);
}
);
const listOrganizationTeams = account.createAuthEndpoint(
"/organization/list-teams",
{
method: "GET",
query: zod.z.optional(
zod.z.object({
organizationId: zod.z.string().optional()
})
),
metadata: {
openapi: {
description: "List all teams in an organization",
responses: {
"200": {
description: "Teams retrieved successfully",
content: {
"application/json": {
schema: {
type: "array",
items: {
type: "object",
properties: {
id: {
type: "string",
description: "Unique identifier of the team"
},
name: {
type: "string",
description: "Name of the team"
},
organizationId: {
type: "string",
description: "ID of the organization the team belongs to"
},
createdAt: {
type: "string",
format: "date-time",
description: "Timestamp when the team was created"
},
updatedAt: {
type: "string",
format: "date-time",
description: "Timestamp when the team was last updated"
}
},
required: [
"id",
"name",
"organizationId",
"createdAt",
"updatedAt"
]
},
description: "Array of team objects within the organization"
}
}
}
}
}
}
},
use: [orgMiddleware, orgSessionMiddleware]
},
async (ctx) => {
const session = ctx.context.session;
const organizationId = session.session.activeOrganizationId || ctx.query?.organizationId;
if (!organizationId) {
return ctx.json(null, {
status: 400,
body: {
message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION
}
});
}
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const member = await adapter.findMemberByOrgId({
userId: session?.user.id,
organizationId: organizationId || ""
});
if (!member) {
throw new betterCall.APIError("FORBIDDEN");
}
const teams = await adapter.listTeams(organizationId);
return ctx.json(teams);
}
);
function parseRoles(roles) {
return Array.isArray(roles) ? roles.join(",") : roles;
}
const organization = (options) => {
let endpoints = {
createOrganization,
updateOrganization,
deleteOrganization,
setActiveOrganization: setActiveOrganization(),
getFullOrganization: getFullOrganization(),
listOrganizations,
createInvitation: createInvitation(),
cancelInvitation,
acceptInvitation,
getInvitation,
rejectInvitation,
checkOrganizationSlug,
addMember: addMember(),
removeMember,
updateMemberRole: updateMemberRole(),
getActiveMember,
leaveOrganization,
listInvitations
};
const teamSupport = options?.teams?.enabled;
const teamEndpoints = {
createTeam: createTeam(),
listOrganizationTeams,
removeTeam,
updateTeam
};
if (teamSupport) {
endpoints = {
...endpoints,
...teamEndpoints
};
}
const roles = {
...plugins_organization_access_index.defaultRoles,
...options?.roles
};
const teamSchema = teamSupport ? {
team: {
modelName: options?.schema?.team?.modelName,
fields: {
name: {
type: "string",
required: true,
fieldName: options?.schema?.team?.fields?.name
},
organizationId: {
type: "string",
required: true,
references: {
model: "organization",
field: "id"
},
fieldName: options?.schema?.team?.fields?.organizationId
},
createdAt: {
type: "date",
required: true,
fieldName: options?.schema?.team?.fields?.createdAt
},
updatedAt: {
type: "date",
required: false,
fieldName: options?.schema?.team?.fields?.updatedAt
}
}
}
} : void 0;
const api = shimContext(endpoints, {
orgOptions: options || {},
roles,
getSession: async (context) => {
return await account.getSessionFromCtx(context);
}
});
return {
id: "organization",
endpoints: {
...api,
hasPermission: account.createAuthEndpoint(
"/organization/has-permission",
{
method: "POST",
requireHeaders: true,
body: zod.z.object({
organizationId: zod.z.string().optional()
}).and(
zod.z.union([
zod.z.object({
permission: zod.z.record(zod.z.string(), zod.z.array(zod.z.string())),
permissions: zod.z.undefined()
}),
zod.z.object({
permission: zod.z.undefined(),
permissions: zod.z.record(zod.z.string(), zod.z.array(zod.z.string()))
})
])
),
use: [orgSessionMiddleware],
metadata: {
$Infer: {
body: {}
},
openapi: {
description: "Check if the user has permission",
requestBody: {
content: {
"application/json": {
schema: {
type: "object",
properties: {
permission: {
type: "object",
description: "The permission to check",
deprecated: true
},
permissions: {
type: "object",
description: "The permission to check"
}
},
required: ["permissions"]
}
}
}
},
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
properties: {
error: {
type: "string"
},
success: {
type: "boolean"
}
},
required: ["success"]
}
}
}
}
}
}
}
},
async (ctx) => {
const activeOrganizationId = ctx.body.organizationId || ctx.context.session.session.activeOrganizationId;
if (!activeOrganizationId) {
throw new betterCall.APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION
});
}
const adapter = getOrgAdapter(ctx.context);
const member = await adapter.findMemberByOrgId({
userId: ctx.context.session.user.id,
organizationId: activeOrganizationId
});
if (!member) {
throw new betterCall.APIError("UNAUTHORIZED", {
message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION
});
}
const result = hasPermission.hasPermission({
role: member.role,
options: options || {},
permissions: ctx.body.permissions ?? ctx.body.permission
});
return ctx.json({
error: null,
success: result
});
}
)
},
schema: {
session: {
fields: {
activeOrganizationId: {
type: "string",
required: false,
fieldName: options?.schema?.session?.fields?.activeOrganizationId
}
}
},
organization: {
modelName: options?.schema?.organization?.modelName,
fields: {
name: {
type: "string",
required: true,
sortable: true,
fieldName: options?.schema?.organization?.fields?.name
},
slug: {
type: "string",
unique: true,
sortable: true,
fieldName: options?.schema?.organization?.fields?.slug
},
logo: {
type: "string",
required: false,
fieldName: options?.schema?.organization?.fields?.logo
},
createdAt: {
type: "date",
required: true,
fieldName: options?.schema?.organization?.fields?.createdAt
},
metadata: {
type: "string",
required: false,
fieldName: options?.schema?.organization?.fields?.metadata
}
}
},
member: {
modelName: options?.schema?.member?.modelName,
fields: {
organizationId: {
type: "string",
required: true,
references: {
model: "organization",
field: "id"
},
fieldName: options?.schema?.member?.fields?.organizationId
},
userId: {
type: "string",
required: true,
fieldName: options?.schema?.member?.fields?.userId,
references: {
model: "user",
field: "id"
}
},
role: {
type: "string",
required: true,
sortable: true,
defaultValue: "member",
fieldName: options?.schema?.member?.fields?.role
},
...teamSupport ? {
teamId: {
type: "string",
required: false,
sortable: true,
fieldName: options?.schema?.member?.fields?.teamId
}
} : {},
createdAt: {
type: "date",
required: true,
fieldName: options?.schema?.member?.fields?.createdAt
}
}
},
invitation: {
modelName: options?.schema?.invitation?.modelName,
fields: {
organizationId: {
type: "string",
required: true,
references: {
model: "organization",
field: "id"
},
fieldName: options?.schema?.invitation?.fields?.organizationId
},
email: {
type: "string",
required: true,
sortable: true,
fieldName: options?.schema?.invitation?.fields?.email
},
role: {
type: "string",
required: false,
sortable: true,
fieldName: options?.schema?.invitation?.fields?.role
},
...teamSupport ? {
teamId: {
type: "string",
required: false,
sortable: true,
fieldName: options?.schema?.invitation?.fields?.teamId
}
} : {},
status: {
type: "string",
required: true,
sortable: true,
defaultValue: "pending",
fieldName: options?.schema?.invitation?.fields?.status
},
expiresAt: {
type: "date",
required: true,
fieldName: options?.schema?.invitation?.fields?.expiresAt
},
inviterId: {
type: "string",
references: {
model: "user",
field: "id"
},
fieldName: options?.schema?.invitation?.fields?.inviterId,
required: true
}
}
},
...teamSupport ? teamSchema : {}
},
$Infer: {
Organization: {},
Invitation: {},
Member: {},
Team: teamSupport ? {} : {},
ActiveOrganization: {}
},
$ERROR_CODES: ORGANIZATION_ERROR_CODES
};
};
exports.organization = organization;
exports.parseRoles = parseRoles;