Skip to content
96 changes: 96 additions & 0 deletions backend/src/authorization/constants/ownership.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Resource ownership rules
*
* Defines who owns what resources and how ownership affects permissions
*/

import { Permission } from './permissions';

// Resource types in the system
export enum ResourceType {
USER = 'user',
BOOKING = 'booking',
LAB = 'lab',
TIME_SLOT = 'time_slot',
WAITLIST = 'waitlist',
NOTIFICATION = 'notification',
ORGANIZATION = 'organization',
ADMIN = 'admin',
}

// Ownership verification functions
export interface OwnershipVerifier {
/**
* Check if a user owns a resource
* @param userId The ID of the user
* @param resourceId The ID of the resource
* @returns Promise resolving to boolean indicating ownership
*/
verifyOwnership: (userId: string, resourceId: string) => Promise<boolean>;

/**
* Check if a user belongs to the same organization as a resource
* @param userId The ID of the user
* @param resourceId The ID of the resource
* @returns Promise resolving to boolean indicating same organization
*/
verifySameOrganization?: (userId: string, resourceId: string) => Promise<boolean>;
}

// Permissions that bypass ownership checks
export const OWNERSHIP_BYPASS_PERMISSIONS: Record<ResourceType, Permission[]> = {
[ResourceType.USER]: ['users:read', 'users:update', 'users:delete', 'users:reset_password'],
[ResourceType.BOOKING]: ['bookings:read_all', 'bookings:update_any', 'bookings:cancel_any'],
[ResourceType.LAB]: ['labs:update', 'labs:delete'],
[ResourceType.TIME_SLOT]: ['time_slots:update', 'time_slots:delete'],
[ResourceType.WAITLIST]: ['waitlists:read_all', 'waitlists:manage'],
[ResourceType.NOTIFICATION]: ['notifications:send'],
[ResourceType.ORGANIZATION]: ['organizations:update', 'organizations:delete'],
[ResourceType.ADMIN]: ['admins:update', 'admins:delete'],
};

/**
* Ownership rules for different resource types
* Describes who can own a resource and what constitutes ownership
*/
export const OWNERSHIP_RULES = {
[ResourceType.USER]: {
description: 'A user owns their own profile and data',
ownerField: 'id', // The field that identifies the owner in the User model
},

[ResourceType.BOOKING]: {
description: 'A user owns bookings they have created',
ownerField: 'user_id', // The field that identifies the owner in the Booking model
},

[ResourceType.LAB]: {
description: 'An admin owns labs they are assigned to manage',
ownerField: 'adminId', // The field that identifies the owner in the Lab model
},

[ResourceType.TIME_SLOT]: {
description: 'A lab admin owns time slots for their labs',
ownerField: null, // Requires special logic - check if user is admin of the lab
},

[ResourceType.WAITLIST]: {
description: 'A user owns their own waitlist entries',
ownerField: 'user_id', // The field that identifies the owner in the Waitlist model
},

[ResourceType.NOTIFICATION]: {
description: 'A user owns notifications addressed to them',
ownerField: 'user_id', // The field that identifies the owner in the Notification model
},

[ResourceType.ORGANIZATION]: {
description: 'A super admin manages organizations',
ownerField: 'superAdminId', // The field that identifies the owner in the Organization model
},

[ResourceType.ADMIN]: {
description: 'A super admin manages admin accounts',
ownerField: null, // Requires special logic - only super admins can manage admins
},
};
108 changes: 108 additions & 0 deletions backend/src/authorization/constants/permissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* Permission constants for the Time-Booking Application
*
* Format: [RESOURCE]:[ACTION]
* This provides a granular and consistent permission structure
*/

// User permissions
export const USER_PERMISSIONS = {
// Self management
READ_SELF: 'users:read_self',
UPDATE_SELF: 'users:update_self',
DELETE_SELF: 'users:delete_self',

// Organization access
READ_ORGANIZATION: 'organizations:read',

// Lab access
READ_LABS: 'labs:read',

// Time slot access
READ_TIME_SLOTS: 'time_slots:read',

// Booking permissions
CREATE_BOOKING: 'bookings:create',
READ_OWN_BOOKINGS: 'bookings:read_own',
UPDATE_OWN_BOOKING: 'bookings:update_own',
CANCEL_OWN_BOOKING: 'bookings:cancel_own',

// Waitlist permissions
JOIN_WAITLIST: 'waitlists:join',
LEAVE_WAITLIST: 'waitlists:leave',
READ_OWN_WAITLIST: 'waitlists:read_own',

// Notification permissions
READ_OWN_NOTIFICATIONS: 'notifications:read_own',
UPDATE_OWN_NOTIFICATION: 'notifications:update_own',
};

// Admin permissions
export const ADMIN_PERMISSIONS = {
// User management
READ_USERS: 'users:read',
UPDATE_USERS: 'users:update',
DELETE_USERS: 'users:delete',
RESET_USER_PASSWORD: 'users:reset_password',

// Organization management
READ_ORGANIZATION_DETAILS: 'organizations:read_details',

// Lab management
CREATE_LAB: 'labs:create',
UPDATE_LAB: 'labs:update',
DELETE_LAB: 'labs:delete',

// Time slot management
CREATE_TIME_SLOT: 'time_slots:create',
UPDATE_TIME_SLOT: 'time_slots:update',
DELETE_TIME_SLOT: 'time_slots:delete',

// Booking management
READ_ALL_BOOKINGS: 'bookings:read_all',
UPDATE_ANY_BOOKING: 'bookings:update_any',
CANCEL_ANY_BOOKING: 'bookings:cancel_any',

// Waitlist management
READ_WAITLISTS: 'waitlists:read_all',
MANAGE_WAITLISTS: 'waitlists:manage',

// Reporting
VIEW_LAB_REPORTS: 'reports:view_lab',

// Notification management
SEND_NOTIFICATIONS: 'notifications:send',
};

// Super Admin permissions
export const SUPER_ADMIN_PERMISSIONS = {
// Organization management
CREATE_ORGANIZATION: 'organizations:create',
UPDATE_ORGANIZATION: 'organizations:update',
DELETE_ORGANIZATION: 'organizations:delete',

// Admin management
CREATE_ADMIN: 'admins:create',
READ_ADMINS: 'admins:read',
UPDATE_ADMIN: 'admins:update',
DELETE_ADMIN: 'admins:delete',

// System-wide permissions
SYSTEM_CONFIGURATION: 'system:configure',
VIEW_SYSTEM_REPORTS: 'reports:view_system',
SEND_SYSTEM_ANNOUNCEMENTS: 'announcements:send',
};

// Combine all permissions for easier reference
export const ALL_PERMISSIONS = {
...USER_PERMISSIONS,
...ADMIN_PERMISSIONS,
...SUPER_ADMIN_PERMISSIONS,
};

// Export types for TypeScript
export type Permission = (typeof ALL_PERMISSIONS)[keyof typeof ALL_PERMISSIONS];
export type UserPermission = (typeof USER_PERMISSIONS)[keyof typeof USER_PERMISSIONS];
export type AdminPermission = (typeof ADMIN_PERMISSIONS)[keyof typeof ADMIN_PERMISSIONS];
export type SuperAdminPermission =
(typeof SUPER_ADMIN_PERMISSIONS)[keyof typeof SUPER_ADMIN_PERMISSIONS];
74 changes: 74 additions & 0 deletions backend/src/authorization/constants/roles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { UserRole } from '@prisma/client';
import {
USER_PERMISSIONS,
ADMIN_PERMISSIONS,
SUPER_ADMIN_PERMISSIONS,
Permission,
} from './permissions';

/**
* Role-based permission mappings
*
* Each role is assigned a set of permissions. Roles higher in the hierarchy
* inherit all permissions from roles below them.
*/

// Define permissions for each role
const ROLE_PERMISSIONS: Record<UserRole, Permission[]> = {
USER: Object.values(USER_PERMISSIONS),

ADMIN: [...Object.values(USER_PERMISSIONS), ...Object.values(ADMIN_PERMISSIONS)],

SUPER_ADMIN: [
...Object.values(USER_PERMISSIONS),
...Object.values(ADMIN_PERMISSIONS),
...Object.values(SUPER_ADMIN_PERMISSIONS),
],
};

/**
* Get all permissions for a specific role
* @param role The user role to get permissions for
* @returns Array of permission strings
*/
export const getPermissionsForRole = (role: UserRole): Permission[] => {
return ROLE_PERMISSIONS[role] || [];
};

/**
* Check if a role has a specific permission
* @param role The user role to check
* @param permission The permission to check for
* @returns Boolean indicating if the role has the permission
*/
export const roleHasPermission = (role: UserRole, permission: Permission): boolean => {
return ROLE_PERMISSIONS[role]?.includes(permission) || false;
};

/**
* Role hierarchy definition for inheritance checks
* Roles can access resources that roles below them can access
*/
export const ROLE_HIERARCHY: Record<UserRole, UserRole[]> = {
USER: [],
ADMIN: ['USER'],
SUPER_ADMIN: ['USER', 'ADMIN'],
};

/**
* Check if one role is higher than another in the hierarchy
* @param role The role to check
* @param thanRole The role to compare against
* @returns Boolean indicating if role is higher than thanRole
*/
export const isRoleHigherThan = (role: UserRole, thanRole: UserRole): boolean => {
return ROLE_HIERARCHY[role]?.includes(thanRole) || false;
};

// Export role descriptions for documentation
export const ROLE_DESCRIPTIONS = {
USER: 'Regular user who can book, reschedule, and cancel lab time slots',
ADMIN: 'Lab administrator who manages labs, time slots, and users within their organization',
SUPER_ADMIN:
'System administrator with complete control over all organizations and system settings',
};
Loading