Functions for converting and managing roles across organization, project, and space contexts.
This module provides the following functionality:
/**
* Convert an organization role to the equivalent project role
* @param role - Organization member role
* @returns Corresponding project member role
*/
function convertOrganizationRoleToProjectRole(
role: OrganizationMemberRole
): ProjectMemberRole;
/**
* Convert a project role to the equivalent organization role
* @param role - Project member role
* @returns Corresponding organization member role
*/
function convertProjectRoleToOrganizationRole(
role: ProjectMemberRole
): OrganizationMemberRole;
/**
* Convert a project role to the equivalent space role
* @param role - Project member role
* @returns Corresponding space member role
*/
function convertProjectRoleToSpaceRole(
role: ProjectMemberRole
): SpaceMemberRole;
/**
* Convert a space role to the equivalent project role
* @param role - Space member role
* @returns Corresponding project member role
*/
function convertSpaceRoleToProjectRole(
role: SpaceMemberRole
): ProjectMemberRole;
/**
* Get the highest role from multiple inherited roles
* @param inheritedRoles - Array of inherited roles from different sources
* @returns The highest role or undefined
*/
function getHighestProjectRole(
inheritedRoles: Array<OrganizationRole | ProjectRole | GroupRole | SpaceGroupAccessRole>
): InheritedProjectRole | undefined;import {
convertOrganizationRoleToProjectRole,
convertProjectRoleToOrganizationRole,
convertProjectRoleToSpaceRole,
convertSpaceRoleToProjectRole,
OrganizationMemberRole,
ProjectMemberRole,
SpaceMemberRole,
} from '@lightdash/common';
// Convert organization role to project role
const projectRole = convertOrganizationRoleToProjectRole(
OrganizationMemberRole.EDITOR
);
// Returns: ProjectMemberRole.EDITOR
// Convert project role to organization role
const orgRole = convertProjectRoleToOrganizationRole(
ProjectMemberRole.VIEWER
);
// Returns: OrganizationMemberRole.VIEWER
// Convert project role to space role
const spaceRole = convertProjectRoleToSpaceRole(
ProjectMemberRole.DEVELOPER
);
// Returns: SpaceMemberRole.EDITOR
// Convert space role to project role
const projectRole2 = convertSpaceRoleToProjectRole(
SpaceMemberRole.VIEWER
);
// Returns: ProjectMemberRole.VIEWERimport {
getHighestProjectRole,
type OrganizationRole,
type ProjectRole,
type GroupRole,
} from '@lightdash/common';
// Get highest role from multiple sources
const highestRole = getHighestProjectRole([
{ type: 'organization', role: ProjectMemberRole.VIEWER },
{ type: 'project', role: ProjectMemberRole.EDITOR },
{ type: 'group', role: ProjectMemberRole.ADMIN },
]);
// Returns: { type: 'group', role: ProjectMemberRole.ADMIN }
// User with only organization role
const orgOnlyRole = getHighestProjectRole([
{ type: 'organization', role: ProjectMemberRole.DEVELOPER },
]);
// Returns: { type: 'organization', role: ProjectMemberRole.DEVELOPER }
// No roles
const noRole = getHighestProjectRole([]);
// Returns: undefinedimport {
getHighestProjectRole,
convertOrganizationRoleToProjectRole,
ProjectMemberRole,
} from '@lightdash/common';
function getUserProjectPermissions(user: User, projectId: string) {
const roles = [];
// Add organization role
if (user.organizationRole) {
const projectRole = convertOrganizationRoleToProjectRole(
user.organizationRole
);
roles.push({
type: 'organization',
role: projectRole,
});
}
// Add direct project role
const projectMembership = user.projectMemberships.find(
m => m.projectId === projectId
);
if (projectMembership) {
roles.push({
type: 'project',
role: projectMembership.role,
});
}
// Add group roles
for (const group of user.groups) {
const groupProjectAccess = group.projectAccess.find(
a => a.projectId === projectId
);
if (groupProjectAccess) {
roles.push({
type: 'group',
role: groupProjectAccess.role,
});
}
}
// Get highest role
return getHighestProjectRole(roles);
}
// Usage
const userRole = getUserProjectPermissions(currentUser, 'project-123');
if (userRole?.role === ProjectMemberRole.ADMIN) {
// User has admin permissions
}import { getHighestProjectRole, ProjectMemberRole } from '@lightdash/common';
function canEditProject(user: User, project: Project): boolean {
const roles = getUserInheritedRoles(user, project);
const highestRole = getHighestProjectRole(roles);
if (!highestRole) {
return false;
}
// Editor and above can edit
return [
ProjectMemberRole.EDITOR,
ProjectMemberRole.DEVELOPER,
ProjectMemberRole.ADMIN,
].includes(highestRole.role);
}
function canDeleteProject(user: User, project: Project): boolean {
const roles = getUserInheritedRoles(user, project);
const highestRole = getHighestProjectRole(roles);
// Only admins can delete
return highestRole?.role === ProjectMemberRole.ADMIN;
}import {
convertOrganizationRoleToProjectRole,
convertProjectRoleToSpaceRole,
} from '@lightdash/common';
async function migrateUserRoles() {
const users = await database.users.findMany();
for (const user of users) {
// Convert organization role to project role
const projectRole = convertOrganizationRoleToProjectRole(
user.organizationRole
);
// Convert project role to space role
const spaceRole = convertProjectRoleToSpaceRole(projectRole);
// Update user's default space role
await database.users.update(user.id, {
defaultSpaceRole: spaceRole,
});
}
}import { getHighestProjectRole, ProjectMemberRole } from '@lightdash/common';
// Middleware to check project access
function requireProjectRole(minimumRole: ProjectMemberRole) {
return async (req: Request, res: Response, next: NextFunction) => {
const projectId = req.params.projectId;
const user = req.user;
const roles = getUserInheritedRoles(user, projectId);
const highestRole = getHighestProjectRole(roles);
if (!highestRole) {
return res.status(403).json({ error: 'No access to this project' });
}
// Check if user has sufficient role
const roleHierarchy = [
ProjectMemberRole.VIEWER,
ProjectMemberRole.INTERACTIVE_VIEWER,
ProjectMemberRole.EDITOR,
ProjectMemberRole.DEVELOPER,
ProjectMemberRole.ADMIN,
];
const userRoleIndex = roleHierarchy.indexOf(highestRole.role);
const requiredRoleIndex = roleHierarchy.indexOf(minimumRole);
if (userRoleIndex < requiredRoleIndex) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// Usage in routes
app.get(
'/api/projects/:projectId',
requireProjectRole(ProjectMemberRole.VIEWER)
);
app.put(
'/api/projects/:projectId',
requireProjectRole(ProjectMemberRole.EDITOR)
);
app.delete(
'/api/projects/:projectId',
requireProjectRole(ProjectMemberRole.ADMIN)
);import {
getHighestProjectRole,
type InheritedProjectRole,
} from '@lightdash/common';
function getRoleDisplayInfo(inheritedRole: InheritedProjectRole) {
const sourceLabels = {
organization: 'Organization',
project: 'Direct',
group: 'Group',
space: 'Space',
};
const roleLabels = {
[ProjectMemberRole.VIEWER]: 'Viewer',
[ProjectMemberRole.INTERACTIVE_VIEWER]: 'Interactive Viewer',
[ProjectMemberRole.EDITOR]: 'Editor',
[ProjectMemberRole.DEVELOPER]: 'Developer',
[ProjectMemberRole.ADMIN]: 'Admin',
};
return {
role: roleLabels[inheritedRole.role],
source: sourceLabels[inheritedRole.type],
description: `${roleLabels[inheritedRole.role]} via ${sourceLabels[inheritedRole.type]}`,
};
}
// Usage in UI
const userRoles = getUserInheritedRoles(user, project);
const highestRole = getHighestProjectRole(userRoles);
if (highestRole) {
const displayInfo = getRoleDisplayInfo(highestRole);
console.log(displayInfo.description);
// Output: "Admin via Group"
}import { convertProjectRoleToSpaceRole } from '@lightdash/common';
async function assignGroupToSpace(
groupId: string,
spaceId: string,
projectRole: ProjectMemberRole
) {
// Convert project role to appropriate space role
const spaceRole = convertProjectRoleToSpaceRole(projectRole);
await database.spaceGroupAccess.create({
groupId,
spaceId,
role: spaceRole,
});
console.log(`Group assigned to space with role: ${spaceRole}`);
}import {
convertOrganizationRoleToProjectRole,
convertSpaceRoleToProjectRole,
getHighestProjectRole,
OrganizationMemberRole,
ProjectMemberRole,
SpaceMemberRole,
} from '@lightdash/common';
describe('Role utilities', () => {
describe('role conversion', () => {
it('should convert organization role to project role', () => {
const role = convertOrganizationRoleToProjectRole(
OrganizationMemberRole.EDITOR
);
expect(role).toBe(ProjectMemberRole.EDITOR);
});
it('should convert space role to project role', () => {
const role = convertSpaceRoleToProjectRole(SpaceMemberRole.VIEWER);
expect(role).toBe(ProjectMemberRole.VIEWER);
});
});
describe('getHighestProjectRole', () => {
it('should return highest role', () => {
const roles = [
{ type: 'organization', role: ProjectMemberRole.VIEWER },
{ type: 'project', role: ProjectMemberRole.ADMIN },
];
const highest = getHighestProjectRole(roles);
expect(highest?.role).toBe(ProjectMemberRole.ADMIN);
});
it('should return undefined for empty array', () => {
const highest = getHighestProjectRole([]);
expect(highest).toBeUndefined();
});
});
});| Organization Role | Project Role | Space Role |
|---|---|---|
| Admin | Admin | Admin |
| Developer | Developer | Editor |
| Editor | Editor | Editor |
| Interactive Viewer | Interactive Viewer | Viewer |
| Viewer | Viewer | Viewer |
| Member | Viewer | Viewer |