Skip to main content
Glama
permissions.ts12.8 kB
import type { Dictionary, DictionaryAPI } from '@/types/dictionary.types'; import type { Organization, OrganizationAPI } from '@/types/organization.types'; import type { Project, ProjectAPI } from '@/types/project.types'; import type { SessionContext } from '@/types/session.types'; import type { Tag, TagAPI } from '@/types/tag.types'; import type { User, UserAPI } from '@/types/user.types'; /** * A named grouping of privileges (e.g. `"org_admin"`). * Users are *granted* one or more Roles. */ export type Roles = | 'user' | 'admin' | 'org_admin' | 'org_user' | 'project_admin' | 'project_user' | 'project_reviewer'; /** * An atomic operation that can be performed on a resource. * - **read**: view or list * - **write**: create or update * - **admin**: delete or change permissions */ export type Action = 'read' | 'write' | 'admin'; /** * A first‑class entity in your domain model that you want to protect. */ export type Resource = { organization: Organization; project: Project; dictionary: Dictionary; tag: Tag; user: User; }; /** * A literal string combining a Resource and an Action, e.g. `"project:write"`. * This is the *unit* checked at runtime in your middleware. */ export type Permission = `${keyof Resource}:${Action}`; type CheckPrivilege = ( args: any ) => boolean | undefined | Promise<boolean> | Promise<undefined>; type RolePolicy = Record<Roles, Partial<Record<Permission, CheckPrivilege>>>; export const ROLE_POLICY = { admin: { 'organization:read': () => true, 'organization:write': () => true, 'organization:admin': () => true, 'project:read': () => true, 'project:write': () => true, 'project:admin': () => true, 'dictionary:read': () => true, 'dictionary:write': () => true, 'dictionary:admin': () => true, 'tag:read': () => true, 'tag:write': () => true, 'tag:admin': () => true, 'user:read': () => true, 'user:write': () => true, 'user:admin': () => true, }, user: { 'user:read': ({ user, targetUsers, }: SessionContext & { targetUsers: (User | UserAPI)[] }) => targetUsers.every( (targetUser) => String(targetUser.id) === String(user?.id) && targetUser.email === user?.email ), 'user:write': ({ user, targetUsers, }: SessionContext & { targetUsers: (User | UserAPI)[] }) => targetUsers.every( (targetUser) => String(targetUser.id) === String(user?.id) && targetUser.email === user?.email ), 'user:admin': ({ user, targetUsers, }: SessionContext & { targetUsers: (User | UserAPI)[] }) => targetUsers.every( (targetUser) => String(targetUser.id) === String(user?.id) && targetUser.email === user?.email ), 'organization:read': ({ user, targetOrganizations, }: SessionContext & { targetOrganizations: (Organization | OrganizationAPI)[]; }) => targetOrganizations.every((organization) => organization?.membersIds?.map(String).includes(String(user?.id)) ), }, org_admin: { 'organization:read': ({ organization, targetOrganizations, }: SessionContext & { targetOrganizations: (Organization | OrganizationAPI)[]; }) => targetOrganizations.every( (targetOrg) => String(targetOrg.id) === String(organization?.id) ), 'organization:write': ({ organization, targetOrganizations, }: SessionContext & { targetOrganizations: (Organization | OrganizationAPI)[]; }) => targetOrganizations.every( (targetOrg) => String(targetOrg.id) === String(organization?.id) ), 'organization:admin': ({ organization, targetOrganizations, }: SessionContext & { targetOrganizations: (Organization | OrganizationAPI)[]; }) => targetOrganizations.every( (targetOrg) => String(targetOrg.id) === String(organization?.id) ), 'project:read': ({ organization, project }: SessionContext) => String(project?.organizationId) === String(organization?.id), 'project:write': ({ organization, project }: SessionContext) => String(project?.organizationId) === String(organization?.id), 'project:admin': ({ organization, project }: SessionContext) => String(project?.organizationId) === String(organization?.id), 'tag:read': ({ organization, project }: SessionContext) => String(project?.organizationId) === String(organization?.id), 'tag:write': ({ organization, project }: SessionContext) => String(project?.organizationId) === String(organization?.id), 'tag:admin': ({ organization, project }: SessionContext) => String(project?.organizationId) === String(organization?.id), 'user:write': ({ organization, targetUsers, }: SessionContext & { targetUsers: (User | UserAPI)[] }) => targetUsers.every((targetUser) => organization?.membersIds?.map(String).includes(String(targetUser?.id)) ), }, org_user: { 'organization:read': ({ organization, targetOrganizations, }: SessionContext & { targetOrganizations: (Organization | OrganizationAPI)[]; }) => targetOrganizations.every( (targetOrg) => String(targetOrg.id) === String(organization?.id) ), 'project:read': ({ user, targetProjects, }: SessionContext & { targetProjects: (Project | ProjectAPI)[]; }) => targetProjects?.every((project) => project?.membersIds?.map(String).includes(String(user?.id)) ), 'user:read': ({ organization, targetUsers, }: SessionContext & { targetUsers: (User | UserAPI)[] }) => targetUsers.every((targetUser) => organization?.membersIds?.map(String).includes(String(targetUser?.id)) ), }, project_admin: { 'project:read': ({ project, targetProjectIds, }: SessionContext & { targetProjectIds?: (Project | ProjectAPI)['id'][]; }) => targetProjectIds?.every( (projectId) => String(project?.id) === String(projectId) ), 'project:write': ({ project, targetProjectIds, }: SessionContext & { targetProjectIds?: (Project | ProjectAPI)['id'][]; }) => targetProjectIds?.every( (projectId) => String(project?.id) === String(projectId) ), 'project:admin': ({ project, targetProjectIds, }: SessionContext & { targetProjectIds?: (Project | ProjectAPI)['id'][]; }) => targetProjectIds?.every( (projectId) => String(project?.id) === String(projectId) ), 'tag:read': ({ project, targetTags, }: SessionContext & { targetTags: (Tag | TagAPI)[] }) => targetTags.every((tag) => String(project?.id) === String(tag?.projectId)), 'tag:write': ({ project, targetTags, }: SessionContext & { targetTags: (Tag | TagAPI)[] }) => targetTags.every((tag) => String(project?.id) === String(tag?.projectId)), 'tag:admin': ({ project, targetTags, }: SessionContext & { targetTags: (Tag | TagAPI)[] }) => targetTags.every((tag) => String(project?.id) === String(tag?.projectId)), 'user:write': ({ project, targetUsers, }: SessionContext & { targetUsers: (User | UserAPI)[] }) => targetUsers.every((targetUser) => project?.membersIds?.map(String).includes(String(targetUser?.id)) ), 'dictionary:read': ({ project, user }: SessionContext) => project?.adminsIds?.map(String).includes(String(user?.id)), 'dictionary:write': ({ project, user }: SessionContext) => project?.adminsIds?.map(String).includes(String(user?.id)), 'dictionary:admin': ({ project, user }: SessionContext) => project?.adminsIds?.map(String).includes(String(user?.id)), }, project_user: { 'project:read': ({ user, organization, project }: SessionContext) => String(organization?.id) === String(project?.organizationId) && organization?.membersIds?.map(String).includes(String(user?.id)) && project?.membersIds?.map(String).includes(String(user?.id)), 'dictionary:read': ({ user, project, targetDictionaries, }: SessionContext & { targetDictionaries: (Dictionary | DictionaryAPI)[]; }) => project?.membersIds?.map(String).includes(String(user?.id)) && targetDictionaries.every((dictionary) => dictionary?.projectIds?.map(String).includes(String(project?.id)) ), 'dictionary:write': ({ user, project }: SessionContext) => project?.adminsIds?.map(String).includes(String(user?.id)), 'tag:read': ({ project, targetTags, user, }: SessionContext & { targetTags: (Tag | TagAPI)[]; }) => project?.membersIds?.map(String).includes(String(user?.id)) && targetTags.every((tag) => String(project?.id) === String(tag?.projectId)), 'tag:write': ({ project, targetTags, user, }: SessionContext & { targetTags: (Tag | TagAPI)[]; }) => project?.membersIds?.map(String).includes(String(user?.id)) && targetTags.every((tag) => String(project?.id) === String(tag?.projectId)), 'tag:admin': ({ user, project, targetTags, }: SessionContext & { targetTags: (Tag | TagAPI)[]; }) => project?.adminsIds?.map(String).includes(String(user?.id)) && targetTags.every((tag) => String(project?.id) === String(tag?.projectId)), 'user:read': ({ project, targetUsers, }: SessionContext & { targetUsers: (User | UserAPI)[] }) => targetUsers.every((targetUser) => project?.membersIds?.map(String).includes(String(targetUser?.id)) ), }, project_reviewer: { 'dictionary:read': ({ user, project }: SessionContext) => project?.membersIds?.map(String).includes(String(user?.id)), 'dictionary:write': ({ user, project }: SessionContext) => project?.membersIds?.map(String).includes(String(user?.id)), 'tag:read': () => true, }, } as const satisfies RolePolicy; export const getSessionRoles = ({ user, organization, project, }: SessionContext): Roles[] => { const roles: Roles[] = []; if (!user) { return roles; } roles.push('user'); const isUserAdmin = user.role === 'admin'; if (isUserAdmin) { roles.push('admin'); } if (!organization) { return roles; } const isOrganizationAdmin = organization.adminsIds ?.map(String) .includes(String(user?.id)); if (isOrganizationAdmin) { roles.push('org_admin'); } const isOrganizationMember = organization.membersIds ?.map(String) .includes(String(user?.id)); if (isOrganizationMember) { roles.push('org_user'); } if (!project) { return roles; } const isProjectAdmin = project.adminsIds ?.map(String) .includes(String(user?.id)); if (isProjectAdmin) { roles.push('project_admin'); } const isProjectMember = project?.membersIds ?.map(String) .includes(String(user?.id)); if (isProjectMember) { roles.push('project_user'); } // const isProjectReviewer = // session.project?.reviewersIds?.includes(session.user!.id); // if (isProjectReviewer) { // roles.push('project_reviewer'); // } return roles; }; export const computeEffectivePermission = (roles: Roles[]): Permission[] => Array.from( new Set( roles.flatMap((role) => Object.keys(ROLE_POLICY[role]) as Permission[]) ) ); /** * Intersect two permission lists * @param permissionList1 - The first permission list * @param permissionList2 - The second permission list * @returns The intersection of the two permission lists (only permissions present in both) */ export const intersectPermissions = ( permissions1: Permission[], permissions2: Permission[] ): Permission[] => permissions1.filter((permission) => permissions2.includes(permission)); type PermissionResult< R extends Roles, P extends Permission, > = (typeof ROLE_POLICY)[R] extends infer RolePerms ? RolePerms extends Record<string, (args: any) => any> ? P extends keyof RolePerms ? RolePerms[P] extends undefined ? never : RolePerms[P] : never : never : never; export const hasPermission = <P extends Permission>( roles: Roles[], permission: P ): PermissionResult<Roles, P> => { const rolesCheck: any = roles.map( (role) => ROLE_POLICY[role]?.[ permission as keyof (typeof ROLE_POLICY)[typeof role] ] ?? (() => false) ) as unknown as PermissionResult<Roles, P>[]; return ((args: any) => rolesCheck.some((check: any) => check(args))) as any; };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server