Files
registry/ts/services/permission.service.ts
Juergen Kunz ab88ac896f feat: implement account settings and API tokens management
- Added SettingsComponent for user profile management, including display name and password change functionality.
- Introduced TokensComponent for managing API tokens, including creation and revocation.
- Created LayoutComponent for consistent application layout with navigation and user information.
- Established main application structure in index.html and main.ts.
- Integrated Tailwind CSS for styling and responsive design.
- Configured TypeScript settings for strict type checking and module resolution.
2025-11-27 22:15:38 +00:00

308 lines
8.3 KiB
TypeScript

/**
* PermissionService - RBAC resolution across org → team → repo hierarchy
*/
import type {
TOrganizationRole,
TTeamRole,
TRepositoryRole,
TRegistryProtocol,
} from '../interfaces/auth.interfaces.ts';
import {
User,
Organization,
OrganizationMember,
Team,
TeamMember,
Repository,
RepositoryPermission,
} from '../models/index.ts';
export type TAction = 'read' | 'write' | 'delete' | 'admin';
export interface IPermissionContext {
userId: string;
organizationId?: string;
repositoryId?: string;
protocol?: TRegistryProtocol;
}
export interface IResolvedPermissions {
canRead: boolean;
canWrite: boolean;
canDelete: boolean;
canAdmin: boolean;
effectiveRole: TRepositoryRole | null;
organizationRole: TOrganizationRole | null;
teamRoles: Array<{ teamId: string; role: TTeamRole }>;
repositoryRole: TRepositoryRole | null;
}
export class PermissionService {
/**
* Resolve all permissions for a user in a specific context
*/
public async resolvePermissions(context: IPermissionContext): Promise<IResolvedPermissions> {
const result: IResolvedPermissions = {
canRead: false,
canWrite: false,
canDelete: false,
canAdmin: false,
effectiveRole: null,
organizationRole: null,
teamRoles: [],
repositoryRole: null,
};
// Get user
const user = await User.findById(context.userId);
if (!user || !user.isActive) return result;
// System admins have full access
if (user.isSystemAdmin) {
result.canRead = true;
result.canWrite = true;
result.canDelete = true;
result.canAdmin = true;
result.effectiveRole = 'admin';
return result;
}
if (!context.organizationId) return result;
// Get organization membership
const orgMember = await OrganizationMember.findMembership(context.organizationId, context.userId);
if (orgMember) {
result.organizationRole = orgMember.role;
// Organization owners have full access to everything in the org
if (orgMember.role === 'owner') {
result.canRead = true;
result.canWrite = true;
result.canDelete = true;
result.canAdmin = true;
result.effectiveRole = 'admin';
return result;
}
// Organization admins have admin access to all repos
if (orgMember.role === 'admin') {
result.canRead = true;
result.canWrite = true;
result.canDelete = true;
result.canAdmin = true;
result.effectiveRole = 'admin';
return result;
}
}
// If no repository specified, check org-level permissions
if (!context.repositoryId) {
if (orgMember) {
result.canRead = true; // Members can read org info
result.effectiveRole = 'reader';
}
return result;
}
// Get repository
const repository = await Repository.findById(context.repositoryId);
if (!repository) return result;
// Check if repository is public
if (repository.isPublic) {
result.canRead = true;
}
// Get team memberships that grant access to this repository
if (orgMember) {
const teams = await Team.getOrgTeams(context.organizationId);
for (const team of teams) {
const teamMember = await TeamMember.findMembership(team.id, context.userId);
if (teamMember) {
result.teamRoles.push({ teamId: team.id, role: teamMember.role });
// Check if team has access to this repository
if (team.repositoryIds.includes(context.repositoryId)) {
// Team maintainers get maintainer access to repos
if (teamMember.role === 'maintainer') {
this.applyRole(result, 'maintainer');
} else {
// Team members get developer access
this.applyRole(result, 'developer');
}
}
}
}
}
// Get direct repository permission (highest priority)
const repoPerm = await RepositoryPermission.findPermission(context.repositoryId, context.userId);
if (repoPerm) {
result.repositoryRole = repoPerm.role;
this.applyRole(result, repoPerm.role);
}
return result;
}
/**
* Check if user can perform a specific action
*/
public async checkPermission(
context: IPermissionContext,
action: TAction
): Promise<boolean> {
const permissions = await this.resolvePermissions(context);
switch (action) {
case 'read':
return permissions.canRead;
case 'write':
return permissions.canWrite;
case 'delete':
return permissions.canDelete;
case 'admin':
return permissions.canAdmin;
default:
return false;
}
}
/**
* Check if user can access a package
*/
public async canAccessPackage(
userId: string,
organizationId: string,
repositoryId: string,
action: 'read' | 'write' | 'delete'
): Promise<boolean> {
return await this.checkPermission(
{ userId, organizationId, repositoryId },
action
);
}
/**
* Check if user can manage organization
*/
public async canManageOrganization(userId: string, organizationId: string): Promise<boolean> {
const user = await User.findById(userId);
if (!user || !user.isActive) return false;
if (user.isSystemAdmin) return true;
const orgMember = await OrganizationMember.findMembership(organizationId, userId);
return orgMember?.role === 'owner' || orgMember?.role === 'admin';
}
/**
* Check if user can manage repository
*/
public async canManageRepository(
userId: string,
organizationId: string,
repositoryId: string
): Promise<boolean> {
const permissions = await this.resolvePermissions({
userId,
organizationId,
repositoryId,
});
return permissions.canAdmin;
}
/**
* Get all repositories a user can access in an organization
*/
public async getAccessibleRepositories(
userId: string,
organizationId: string
): Promise<Repository[]> {
const user = await User.findById(userId);
if (!user || !user.isActive) return [];
// System admins and org owners/admins can access all repos
if (user.isSystemAdmin) {
return await Repository.getOrgRepositories(organizationId);
}
const orgMember = await OrganizationMember.findMembership(organizationId, userId);
if (orgMember?.role === 'owner' || orgMember?.role === 'admin') {
return await Repository.getOrgRepositories(organizationId);
}
const allRepos = await Repository.getOrgRepositories(organizationId);
const accessibleRepos: Repository[] = [];
for (const repo of allRepos) {
// Public repos are always accessible
if (repo.isPublic) {
accessibleRepos.push(repo);
continue;
}
// Check direct permission
const directPerm = await RepositoryPermission.findPermission(repo.id, userId);
if (directPerm) {
accessibleRepos.push(repo);
continue;
}
// Check team access
const teams = await Team.getOrgTeams(organizationId);
for (const team of teams) {
if (team.repositoryIds.includes(repo.id)) {
const teamMember = await TeamMember.findMembership(team.id, userId);
if (teamMember) {
accessibleRepos.push(repo);
break;
}
}
}
}
return accessibleRepos;
}
/**
* Apply a role's permissions to the result
*/
private applyRole(result: IResolvedPermissions, role: TRepositoryRole): void {
const roleHierarchy: Record<TRepositoryRole, number> = {
reader: 1,
developer: 2,
maintainer: 3,
admin: 4,
};
const currentLevel = result.effectiveRole ? roleHierarchy[result.effectiveRole] : 0;
const newLevel = roleHierarchy[role];
if (newLevel > currentLevel) {
result.effectiveRole = role;
}
switch (role) {
case 'admin':
result.canRead = true;
result.canWrite = true;
result.canDelete = true;
result.canAdmin = true;
break;
case 'maintainer':
result.canRead = true;
result.canWrite = true;
result.canDelete = true;
break;
case 'developer':
result.canRead = true;
result.canWrite = true;
break;
case 'reader':
result.canRead = true;
break;
}
}
}