better-auth
Version:
The most comprehensive authentication framework for TypeScript.
855 lines (854 loc) • 22 kB
JavaScript
import { getSessionFromCtx } from "../../api/routes/session.mjs";
import { PACKAGE_VERSION } from "../../version.mjs";
import { defaultRoles } from "./access/statement.mjs";
import { ORGANIZATION_ERROR_CODES } from "./error-codes.mjs";
import { getOrgAdapter } from "./adapter.mjs";
import { shimContext } from "../../utils/shim.mjs";
import { orgSessionMiddleware } from "./call.mjs";
import { hasPermission } from "./has-permission.mjs";
import { createOrgRole, deleteOrgRole, getOrgRole, listOrgRoles, updateOrgRole } from "./routes/crud-access-control.mjs";
import { acceptInvitation, cancelInvitation, createInvitation, getInvitation, listInvitations, listUserInvitations, rejectInvitation } from "./routes/crud-invites.mjs";
import { addMember, getActiveMember, getActiveMemberRole, leaveOrganization, listMembers, removeMember, updateMemberRole } from "./routes/crud-members.mjs";
import { checkOrganizationSlug, createOrganization, deleteOrganization, getFullOrganization, listOrganizations, setActiveOrganization, updateOrganization } from "./routes/crud-org.mjs";
import { addTeamMember, createTeam, listOrganizationTeams, listTeamMembers, listUserTeams, removeTeam, removeTeamMember, setActiveTeam, updateTeam } from "./routes/crud-team.mjs";
import { APIError } from "@better-auth/core/error";
import { createAuthEndpoint } from "@better-auth/core/api";
import * as z from "zod";
//#region src/plugins/organization/organization.ts
function parseRoles(roles) {
return Array.isArray(roles) ? roles.join(",") : roles;
}
const createHasPermissionBodySchema = z.object({ organizationId: z.string().optional() }).and(z.xor([z.object({ permission: z.record(z.string(), z.array(z.string())) }), z.object({ permissions: z.record(z.string(), z.array(z.string())) })]));
const createHasPermission = (options) => {
return createAuthEndpoint("/organization/has-permission", {
method: "POST",
requireHeaders: true,
body: createHasPermissionBodySchema,
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 APIError.from("BAD_REQUEST", ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION);
const member = await getOrgAdapter(ctx.context, options).findMemberByOrgId({
userId: ctx.context.session.user.id,
organizationId: activeOrganizationId
});
if (!member) throw APIError.from("UNAUTHORIZED", ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION);
const result = await hasPermission({
role: member.role,
options,
permissions: ctx.body.permissions,
organizationId: activeOrganizationId
}, ctx);
return ctx.json({
error: null,
success: result
});
});
};
function organization(options) {
const opts = options || {};
let endpoints = {
/**
* ### Endpoint
*
* POST `/organization/create`
*
* ### API Methods
*
* **server:**
* `auth.api.createOrganization`
*
* **client:**
* `authClient.organization.create`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-create)
*/
createOrganization: createOrganization(opts),
/**
* ### Endpoint
*
* POST `/organization/update`
*
* ### API Methods
*
* **server:**
* `auth.api.updateOrganization`
*
* **client:**
* `authClient.organization.update`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-update)
*/
updateOrganization: updateOrganization(opts),
/**
* ### Endpoint
*
* POST `/organization/delete`
*
* ### API Methods
*
* **server:**
* `auth.api.deleteOrganization`
*
* **client:**
* `authClient.organization.delete`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-delete)
*/
deleteOrganization: deleteOrganization(opts),
/**
* ### Endpoint
*
* POST `/organization/set-active`
*
* ### API Methods
*
* **server:**
* `auth.api.setActiveOrganization`
*
* **client:**
* `authClient.organization.setActive`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-set-active)
*/
setActiveOrganization: setActiveOrganization(opts),
/**
* ### Endpoint
*
* GET `/organization/get-full-organization`
*
* ### API Methods
*
* **server:**
* `auth.api.getFullOrganization`
*
* **client:**
* `authClient.organization.getFullOrganization`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-get-full-organization)
*/
getFullOrganization: getFullOrganization(opts),
/**
* ### Endpoint
*
* GET `/organization/list`
*
* ### API Methods
*
* **server:**
* `auth.api.listOrganizations`
*
* **client:**
* `authClient.organization.list`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-list)
*/
listOrganizations: listOrganizations(opts),
/**
* ### Endpoint
*
* POST `/organization/invite-member`
*
* ### API Methods
*
* **server:**
* `auth.api.createInvitation`
*
* **client:**
* `authClient.organization.inviteMember`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-invite-member)
*/
createInvitation: createInvitation(opts),
/**
* ### Endpoint
*
* POST `/organization/cancel-invitation`
*
* ### API Methods
*
* **server:**
* `auth.api.cancelInvitation`
*
* **client:**
* `authClient.organization.cancelInvitation`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-cancel-invitation)
*/
cancelInvitation: cancelInvitation(opts),
/**
* ### Endpoint
*
* POST `/organization/accept-invitation`
*
* ### API Methods
*
* **server:**
* `auth.api.acceptInvitation`
*
* **client:**
* `authClient.organization.acceptInvitation`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-accept-invitation)
*/
acceptInvitation: acceptInvitation(opts),
/**
* ### Endpoint
*
* GET `/organization/get-invitation`
*
* ### API Methods
*
* **server:**
* `auth.api.getInvitation`
*
* **client:**
* `authClient.organization.getInvitation`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-get-invitation)
*/
getInvitation: getInvitation(opts),
/**
* ### Endpoint
*
* POST `/organization/reject-invitation`
*
* ### API Methods
*
* **server:**
* `auth.api.rejectInvitation`
*
* **client:**
* `authClient.organization.rejectInvitation`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-reject-invitation)
*/
rejectInvitation: rejectInvitation(opts),
/**
* ### Endpoint
*
* GET `/organization/list-invitations`
*
* ### API Methods
*
* **server:**
* `auth.api.listInvitations`
*
* **client:**
* `authClient.organization.listInvitations`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-list-invitations)
*/
listInvitations: listInvitations(opts),
/**
* ### Endpoint
*
* GET `/organization/get-active-member`
*
* ### API Methods
*
* **server:**
* `auth.api.getActiveMember`
*
* **client:**
* `authClient.organization.getActiveMember`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-get-active-member)
*/
getActiveMember: getActiveMember(opts),
/**
* ### Endpoint
*
* POST `/organization/check-slug`
*
* ### API Methods
*
* **server:**
* `auth.api.checkOrganizationSlug`
*
* **client:**
* `authClient.organization.checkSlug`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-check-slug)
*/
checkOrganizationSlug: checkOrganizationSlug(opts),
/**
* Add a member to an organization directly, bypassing the invitation flow.
*
* **Server-only:** callable as `auth.api.addMember` from trusted server
* code. It is not registered as an HTTP route and has no client method, so
* it runs no session or permission check of its own; the caller is
* responsible for authorizing the request.
*
* ### API Methods
*
* **server:**
* `auth.api.addMember`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-add-member)
*/
addMember: addMember(opts),
/**
* ### Endpoint
*
* POST `/organization/remove-member`
*
* ### API Methods
*
* **server:**
* `auth.api.removeMember`
*
* **client:**
* `authClient.organization.removeMember`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-remove-member)
*/
removeMember: removeMember(opts),
/**
* ### Endpoint
*
* POST `/organization/update-member-role`
*
* ### API Methods
*
* **server:**
* `auth.api.updateMemberRole`
*
* **client:**
* `authClient.organization.updateMemberRole`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-update-member-role)
*/
updateMemberRole: updateMemberRole(opts),
/**
* ### Endpoint
*
* POST `/organization/leave`
*
* ### API Methods
*
* **server:**
* `auth.api.leaveOrganization`
*
* **client:**
* `authClient.organization.leave`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-leave)
*/
leaveOrganization: leaveOrganization(opts),
listUserInvitations: listUserInvitations(opts),
/**
* ### Endpoint
*
* GET `/organization/list-members`
*
* ### API Methods
*
* **server:**
* `auth.api.listMembers`
*
* **client:**
* `authClient.organization.listMembers`
*/
listMembers: listMembers(opts),
/**
* ### Endpoint
*
* GET `/organization/get-active-member-role`
*
* ### API Methods
*
* **server:**
* `auth.api.getActiveMemberRole`
*
* **client:**
* `authClient.organization.getActiveMemberRole`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-get-active-member-role)
*/
getActiveMemberRole: getActiveMemberRole(opts)
};
const teamSupport = opts.teams?.enabled;
const teamEndpoints = {
/**
* ### Endpoint
*
* POST `/organization/create-team`
*
* ### API Methods
*
* **server:**
* `auth.api.createTeam`
*
* **client:**
* `authClient.organization.createTeam`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-create-team)
*/
createTeam: createTeam(opts),
/**
* ### Endpoint
*
* GET `/organization/list-teams`
*
* ### API Methods
*
* **server:**
* `auth.api.listOrganizationTeams`
*
* **client:**
* `authClient.organization.listTeams`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-list-teams)
*/
listOrganizationTeams: listOrganizationTeams(opts),
/**
* ### Endpoint
*
* POST `/organization/remove-team`
*
* ### API Methods
*
* **server:**
* `auth.api.removeTeam`
*
* **client:**
* `authClient.organization.removeTeam`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-remove-team)
*/
removeTeam: removeTeam(opts),
/**
* ### Endpoint
*
* POST `/organization/update-team`
*
* ### API Methods
*
* **server:**
* `auth.api.updateTeam`
*
* **client:**
* `authClient.organization.updateTeam`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-update-team)
*/
updateTeam: updateTeam(opts),
/**
* ### Endpoint
*
* POST `/organization/set-active-team`
*
* ### API Methods
*
* **server:**
* `auth.api.setActiveTeam`
*
* **client:**
* `authClient.organization.setActiveTeam`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-set-active-team)
*/
setActiveTeam: setActiveTeam(opts),
/**
* ### Endpoint
*
* GET `/organization/list-user-teams`
*
* ### API Methods
*
* **server:**
* `auth.api.listUserTeams`
*
* **client:**
* `authClient.organization.listUserTeams`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-set-active-team)
*/
listUserTeams: listUserTeams(opts),
/**
* ### Endpoint
*
* POST `/organization/list-team-members`
*
* ### API Methods
*
* **server:**
* `auth.api.listTeamMembers`
*
* **client:**
* `authClient.organization.listTeamMembers`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-set-active-team)
*/
listTeamMembers: listTeamMembers(opts),
/**
* ### Endpoint
*
* POST `/organization/add-team-member`
*
* ### API Methods
*
* **server:**
* `auth.api.addTeamMember`
*
* **client:**
* `authClient.organization.addTeamMember`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-add-team-member)
*/
addTeamMember: addTeamMember(opts),
/**
* ### Endpoint
*
* POST `/organization/remove-team-member`
*
* ### API Methods
*
* **server:**
* `auth.api.removeTeamMember`
*
* **client:**
* `authClient.organization.removeTeamMember`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-remove-team-member)
*/
removeTeamMember: removeTeamMember(opts)
};
if (teamSupport) endpoints = {
...endpoints,
...teamEndpoints
};
const dynamicAccessControlEndpoints = {
createOrgRole: createOrgRole(opts),
deleteOrgRole: deleteOrgRole(opts),
listOrgRoles: listOrgRoles(opts),
getOrgRole: getOrgRole(opts),
updateOrgRole: updateOrgRole(opts)
};
if (opts.dynamicAccessControl?.enabled) endpoints = {
...endpoints,
...dynamicAccessControlEndpoints
};
const roles = {
...defaultRoles,
...opts.roles
};
const teamSchema = teamSupport ? {
team: {
modelName: opts.schema?.team?.modelName,
fields: {
name: {
type: "string",
required: true,
fieldName: opts.schema?.team?.fields?.name
},
organizationId: {
type: "string",
required: true,
references: {
model: "organization",
field: "id"
},
fieldName: opts.schema?.team?.fields?.organizationId,
index: true
},
createdAt: {
type: "date",
required: true,
fieldName: opts.schema?.team?.fields?.createdAt
},
updatedAt: {
type: "date",
required: false,
fieldName: opts.schema?.team?.fields?.updatedAt,
onUpdate: () => /* @__PURE__ */ new Date()
},
...opts.schema?.team?.additionalFields || {}
}
},
teamMember: {
modelName: opts.schema?.teamMember?.modelName,
fields: {
teamId: {
type: "string",
required: true,
references: {
model: "team",
field: "id"
},
fieldName: opts.schema?.teamMember?.fields?.teamId,
index: true
},
userId: {
type: "string",
required: true,
references: {
model: "user",
field: "id"
},
fieldName: opts.schema?.teamMember?.fields?.userId,
index: true
},
createdAt: {
type: "date",
required: false,
fieldName: opts.schema?.teamMember?.fields?.createdAt
}
}
}
} : {};
const organizationRoleSchema = opts.dynamicAccessControl?.enabled ? { organizationRole: {
fields: {
organizationId: {
type: "string",
required: true,
references: {
model: "organization",
field: "id"
},
fieldName: opts.schema?.organizationRole?.fields?.organizationId,
index: true
},
role: {
type: "string",
required: true,
fieldName: opts.schema?.organizationRole?.fields?.role,
index: true
},
permission: {
type: "string",
required: true,
fieldName: opts.schema?.organizationRole?.fields?.permission
},
createdAt: {
type: "date",
required: true,
defaultValue: () => /* @__PURE__ */ new Date(),
fieldName: opts.schema?.organizationRole?.fields?.createdAt
},
updatedAt: {
type: "date",
required: false,
fieldName: opts.schema?.organizationRole?.fields?.updatedAt,
onUpdate: () => /* @__PURE__ */ new Date()
},
...opts.schema?.organizationRole?.additionalFields || {}
},
modelName: opts.schema?.organizationRole?.modelName
} } : {};
const schema = {
organization: {
modelName: opts.schema?.organization?.modelName,
fields: {
name: {
type: "string",
required: true,
sortable: true,
fieldName: opts.schema?.organization?.fields?.name
},
slug: {
type: "string",
required: true,
unique: true,
sortable: true,
fieldName: opts.schema?.organization?.fields?.slug,
index: true
},
logo: {
type: "string",
required: false,
fieldName: opts.schema?.organization?.fields?.logo
},
createdAt: {
type: "date",
required: true,
fieldName: opts.schema?.organization?.fields?.createdAt
},
metadata: {
type: "string",
required: false,
fieldName: opts.schema?.organization?.fields?.metadata
},
...opts.schema?.organization?.additionalFields || {}
}
},
...organizationRoleSchema,
...teamSchema,
member: {
modelName: opts.schema?.member?.modelName,
fields: {
organizationId: {
type: "string",
required: true,
references: {
model: "organization",
field: "id"
},
fieldName: opts.schema?.member?.fields?.organizationId,
index: true
},
userId: {
type: "string",
required: true,
fieldName: opts.schema?.member?.fields?.userId,
references: {
model: "user",
field: "id"
},
index: true
},
role: {
type: "string",
required: true,
sortable: true,
defaultValue: "member",
fieldName: opts.schema?.member?.fields?.role
},
createdAt: {
type: "date",
required: true,
fieldName: opts.schema?.member?.fields?.createdAt
},
...opts.schema?.member?.additionalFields || {}
}
},
invitation: {
modelName: opts.schema?.invitation?.modelName,
fields: {
organizationId: {
type: "string",
required: true,
references: {
model: "organization",
field: "id"
},
fieldName: opts.schema?.invitation?.fields?.organizationId,
index: true
},
email: {
type: "string",
required: true,
sortable: true,
fieldName: opts.schema?.invitation?.fields?.email,
index: true
},
role: {
type: "string",
required: false,
sortable: true,
fieldName: opts.schema?.invitation?.fields?.role
},
...teamSupport ? { teamId: {
type: "string",
required: false,
sortable: true,
fieldName: opts.schema?.invitation?.fields?.teamId
} } : {},
status: {
type: "string",
required: true,
sortable: true,
defaultValue: "pending",
fieldName: opts.schema?.invitation?.fields?.status
},
expiresAt: {
type: "date",
required: true,
fieldName: opts.schema?.invitation?.fields?.expiresAt
},
createdAt: {
type: "date",
required: true,
fieldName: opts.schema?.invitation?.fields?.createdAt,
defaultValue: () => /* @__PURE__ */ new Date()
},
inviterId: {
type: "string",
references: {
model: "user",
field: "id"
},
fieldName: opts.schema?.invitation?.fields?.inviterId,
required: true
},
...opts.schema?.invitation?.additionalFields || {}
}
}
};
return {
id: "organization",
version: PACKAGE_VERSION,
endpoints: {
...shimContext(endpoints, {
orgOptions: opts,
roles,
getSession: async (context) => {
return await getSessionFromCtx(context);
}
}),
hasPermission: createHasPermission(opts)
},
schema: {
...schema,
session: { fields: {
activeOrganizationId: {
type: "string",
required: false,
input: false,
fieldName: opts.schema?.session?.fields?.activeOrganizationId
},
...teamSupport ? { activeTeamId: {
type: "string",
required: false,
input: false,
fieldName: opts.schema?.session?.fields?.activeTeamId
} } : {}
} }
},
$Infer: {
Organization: {},
Invitation: {},
Member: {},
Team: teamSupport ? {} : {},
TeamMember: teamSupport ? {} : {},
ActiveOrganization: {}
},
$ERROR_CODES: ORGANIZATION_ERROR_CODES,
options: opts
};
}
//#endregion
export { hasPermission, organization, parseRoles };