The roles and permissions system provides fine-grained access control for your music platform. You can create custom roles, assign specific permissions, and control who can perform which actions.
Overview
The access control system enables you to:
Create roles Define custom roles for different user types
Assign permissions Grant specific capabilities to each role
User management Control user access across the platform
Audit trail Track who created roles and permissions
Architecture
The system uses a many-to-many relationship between roles and permissions through a join entity:
User → Role → RolePermission ← Permission
This architecture allows:
One role to have multiple permissions
One permission to be assigned to multiple roles
Flexible permission combinations
Easy role updates without affecting users
Role entity structure
The Role entity represents user roles:
@ Entity ( 'roles' )
@ Unique ([ 'name' ])
export class Role extends AbstractEntity {
// NAME
@ Column ({ name: 'name' , nullable: false })
name !: string ;
// DESCRIPTION
@ Column ({ name: 'description' , nullable: true })
description : string ;
// CREATED BY ID
@ Column ({ name: 'created_by_id' , nullable: false , type: 'uuid' })
createdById : UUID ;
// Relations
@ ManyToOne (() => User , ( user ) => user . createdRoles )
@ JoinColumn ({ name: 'created_by_id' })
createdBy : User ;
@ OneToMany (() => RolePermission , ( rolePermission ) => rolePermission . role )
permissions : RolePermission [];
}
Role fields
Field Type Required Description idUUID Yes Unique identifier (inherited from AbstractEntity) namestring Yes Unique role name descriptionstring No Optional role description createdByIdUUID Yes Reference to user who created the role createdAttimestamp Yes Record creation time (inherited) updatedAttimestamp Yes Last update time (inherited)
Role names must be unique. Attempting to create a role with an existing name will fail.
Permission entity structure
The Permission entity represents individual capabilities:
@ Entity ( 'permissions' )
@ Unique ([ 'name' ])
export class Permission extends AbstractEntity {
// NAME
@ Column ({ name: 'name' , nullable: false })
name !: string ;
// DESCRIPTION
@ Column ({ name: 'description' , nullable: true })
description : string ;
// Relations
@ OneToMany (() => RolePermission , ( rolePermission ) => rolePermission . permission )
roles : RolePermission [];
}
Permission fields
Field Type Required Description idUUID Yes Unique identifier (inherited from AbstractEntity) namestring Yes Unique permission name descriptionstring No Optional permission description createdAttimestamp Yes Record creation time (inherited) updatedAttimestamp Yes Last update time (inherited)
System permissions
The system defines these core permissions:
export enum PERMISSIONS {
CREATE_USER = 'CREATE_USER' ,
READ_USER = 'READ_USER' ,
UPDATE_USER = 'UPDATE_USER' ,
DELETE_USER = 'DELETE_USER' ,
}
CREATE_USER Ability to create new user accounts
READ_USER Ability to view user information
UPDATE_USER Ability to modify user details
DELETE_USER Ability to delete user accounts
You can extend the permissions system by adding more permission constants for other resources like artists, labels, and releases.
RolePermission join entity
The RolePermission entity connects roles to permissions:
@ Entity ( 'role_permissions' )
@ Unique ([ 'roleId' , 'permissionId' ])
export class RolePermission extends AbstractEntity {
// ROLE ID
@ Column ({ name: 'role_id' , nullable: false , type: 'uuid' })
roleId !: UUID ;
// PERMISSION ID
@ Column ({ name: 'permission_id' , nullable: false , type: 'uuid' })
permissionId !: UUID ;
// CREATED BY ID
@ Column ({ name: 'created_by_id' , nullable: true , type: 'uuid' })
createdById : UUID ;
// Relations
@ ManyToOne (() => Role , ( role ) => role . permissions )
@ JoinColumn ({ name: 'role_id' })
role : Role ;
@ ManyToOne (() => Permission , ( permission ) => permission . roles )
@ JoinColumn ({ name: 'permission_id' })
permission : Permission ;
@ ManyToOne (() => User , ( user ) => user . createdRolePermissions )
@ JoinColumn ({ name: 'created_by_id' })
createdBy : User ;
}
Join entity fields
Field Type Required Description idUUID Yes Unique identifier (inherited from AbstractEntity) roleIdUUID Yes Reference to role permissionIdUUID Yes Reference to permission createdByIdUUID No Reference to user who created the assignment createdAttimestamp Yes Record creation time (inherited) updatedAttimestamp Yes Last update time (inherited)
The combination of roleId and permissionId must be unique. You cannot assign the same permission to a role twice.
Common workflows
Create a custom role
Define a new role for your platform:
const role = await createRole ({
name: 'Label Manager' ,
description: 'Can manage label artists and releases' ,
createdById: currentUser . id
});
Assign permissions to role
Grant specific permissions to a role:
// Give the Label Manager role user read access
await createRolePermission ({
roleId: role . id ,
permissionId: permissions . READ_USER ,
createdById: currentUser . id
});
// Add more permissions
await createRolePermission ({
roleId: role . id ,
permissionId: permissions . UPDATE_USER ,
createdById: currentUser . id
});
Check user permissions
Verify if a user has specific permissions:
function hasPermission ( user : User , permissionName : string ) : boolean {
return user . role . permissions . some (
rp => rp . permission . name === permissionName
);
}
// Usage
if ( hasPermission ( user , 'CREATE_USER' )) {
// Allow user creation
}
Pre-defined roles
The system includes a built-in role structure:
Administrators have unrestricted access to all resources: // From auth.constant.ts
export const ROLES = {
ADMIN: 'admin' ,
USER: 'user'
};
Admin capabilities:
View all artists regardless of status
Access any user’s data
Bypass ownership checks
Manage system-wide settings
Regular users have restricted access:
Can only view their own data
Can only see active artists
Cannot access other users’ resources
Limited to standard CRUD operations on owned entities
Use cases
Artist manager role
Create a role for users who manage artists:
// 1. Create the role
const artistManager = await createRole ({
name: 'Artist Manager' ,
description: 'Manages artists and their releases'
});
// 2. Assign relevant permissions
await assignPermissions ( artistManager . id , [
'READ_USER' ,
'CREATE_ARTIST' ,
'UPDATE_ARTIST' ,
'READ_ARTIST' ,
'CREATE_RELEASE' ,
'UPDATE_RELEASE'
]);
Label administrator role
Create a role for label admins:
const labelAdmin = await createRole ({
name: 'Label Administrator' ,
description: 'Full control over label and its releases'
});
await assignPermissions ( labelAdmin . id , [
'CREATE_LABEL' ,
'UPDATE_LABEL' ,
'DELETE_LABEL' ,
'CREATE_RELEASE' ,
'UPDATE_RELEASE' ,
'DELETE_RELEASE' ,
'CREATE_ARTIST' ,
'UPDATE_ARTIST'
]);
Read-only analyst role
Create a role for users who can view but not modify:
const analyst = await createRole ({
name: 'Analyst' ,
description: 'Read-only access to all data'
});
await assignPermissions ( analyst . id , [
'READ_USER' ,
'READ_ARTIST' ,
'READ_LABEL' ,
'READ_RELEASE' ,
'READ_LYRICS'
]);
Temporary permissions
Grant time-limited access by creating and revoking role permissions:
// Grant permission for collaboration
const tempPermission = await createRolePermission ({
roleId: collaboratorRole . id ,
permissionId: permissions . UPDATE_RELEASE ,
createdById: admin . id
});
// Later, revoke the permission
await deleteRolePermission ( tempPermission . id );
Guard implementation
The system uses guards to enforce permissions:
JWT Auth Guard
Validates authentication:
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard' ;
@ Controller ( 'artists' )
@ UseGuards ( JwtAuthGuard )
export class ArtistsController {
// All endpoints require authentication
}
Roles Guard
Validates role-based access:
import { RolesGuard } from '../../common/guards/roles.guard' ;
import { Roles } from '../../common/decorators/roles.decorator' ;
@ Controller ( 'admin' )
@ UseGuards ( JwtAuthGuard , RolesGuard )
export class AdminController {
@ Get ( 'users' )
@ Roles ( 'admin' )
async getAllUsers () {
// Only admin users can access
}
}
Current User Decorator
Extract authenticated user from request:
import { CurrentUser , AuthUser } from '../../common/decorators/current-user.decorator' ;
@ Get ( 'my-artists' )
async getMyArtists (@ CurrentUser () user : AuthUser ) {
return this . artistService . fetchArtists ({
condition: { userId: user . id }
});
}
Best practices
Principle of least privilege Grant only the minimum permissions necessary for each role
Role naming Use clear, descriptive names that indicate the role’s purpose
Permission groups Group related permissions together for easier management
Regular audits Periodically review and update role permissions
Document roles Maintain clear documentation of what each role can do
Test permissions Always test role changes in a non-production environment
Extending the system
You can extend the permissions system for other resources:
// Add new permissions for artists
export enum ARTIST_PERMISSIONS {
CREATE_ARTIST = 'CREATE_ARTIST' ,
READ_ARTIST = 'READ_ARTIST' ,
UPDATE_ARTIST = 'UPDATE_ARTIST' ,
DELETE_ARTIST = 'DELETE_ARTIST' ,
ACTIVATE_ARTIST = 'ACTIVATE_ARTIST' ,
DEACTIVATE_ARTIST = 'DEACTIVATE_ARTIST' ,
}
// Add new permissions for labels
export enum LABEL_PERMISSIONS {
CREATE_LABEL = 'CREATE_LABEL' ,
READ_LABEL = 'READ_LABEL' ,
UPDATE_LABEL = 'UPDATE_LABEL' ,
DELETE_LABEL = 'DELETE_LABEL' ,
}
// Add new permissions for releases
export enum RELEASE_PERMISSIONS {
CREATE_RELEASE = 'CREATE_RELEASE' ,
READ_RELEASE = 'READ_RELEASE' ,
UPDATE_RELEASE = 'UPDATE_RELEASE' ,
DELETE_RELEASE = 'DELETE_RELEASE' ,
PUBLISH_RELEASE = 'PUBLISH_RELEASE' ,
}
Artists Role-based access control for artist management
Labels Permission control for label operations
Releases Secure release management with roles