# Salesforce-Style Authorization System ## Overview Implemented a comprehensive authorization system based on Salesforce's model with: - **Org-Wide Defaults (OWD)** for record visibility - **Role-based permissions** for object and field access - **Record sharing** for granular access control - **CASL** for flexible permission evaluation ## Architecture ### 1. Org-Wide Defaults (OWD) Controls baseline record visibility for each object: - `private`: Only owner can see records - `public_read`: Everyone can see, only owner can edit/delete - `public_read_write`: Everyone can see and modify all records ### 2. Role-Based Object Permissions Table: `role_object_permissions` - `canCreate`: Can create new records - `canRead`: Can read records (subject to OWD) - `canEdit`: Can edit records (subject to OWD) - `canDelete`: Can delete records (subject to OWD) - `canViewAll`: Override OWD to see ALL records - `canModifyAll`: Override OWD to edit ALL records ### 3. Field-Level Security Table: `role_field_permissions` - `canRead`: Can view field value - `canEdit`: Can modify field value ### 4. Record Sharing Table: `record_shares` Grants specific users access to individual records with: ```json { "canRead": boolean, "canEdit": boolean, "canDelete": boolean } ``` ## Permission Evaluation Flow ``` 1. Check role_object_permissions ├─ Does user have canCreate/Read/Edit/Delete? │ └─ NO → Deny │ └─ YES → Continue │ 2. Check canViewAll / canModifyAll ├─ Does user have special "all" permissions? │ └─ YES → Grant access │ └─ NO → Continue │ 3. Check OWD (orgWideDefault) ├─ public_read_write → Grant access ├─ public_read → Grant read, check ownership for write └─ private → Check ownership or sharing 4. Check Ownership ├─ Is user the record owner? │ └─ YES → Grant access │ └─ NO → Continue │ 5. Check Record Shares └─ Is record explicitly shared with user? └─ Check accessLevel permissions ``` ## Field-Level Security Fields are filtered after record access is granted: 1. User queries records → Apply record-level scope 2. System filters readable fields based on `role_field_permissions` 3. User updates records → System filters editable fields ## Key Features ### Multiple Role Support - Users can have multiple roles - Permissions are **unioned** (any role grants = user has it) - More flexible than Salesforce's single profile model ### Active Share Detection - Shares can expire (`expiresAt`) - Shares can be revoked (`revokedAt`) - Only active shares are evaluated ### CASL Integration - Dynamic ability building per request - Condition-based rules - Field-level permission support ## Usage Example ```typescript // In a controller/service constructor( private authService: AuthorizationService, private tenantDbService: TenantDatabaseService, ) {} async getRecords(tenantId: string, objectApiName: string, userId: string) { const knex = await this.tenantDbService.getTenantKnex(tenantId); // Get user with roles const user = await User.query(knex) .findById(userId) .withGraphFetched('[roles.[objectPermissions, fieldPermissions]]'); // Get object definition const objectDef = await ObjectDefinition.query(knex) .findOne({ apiName: objectApiName }); // Build query with authorization scope let query = knex(objectApiName.toLowerCase()); query = await this.authService.applyScopeToQuery( query, objectDef, user, 'read', knex, ); const records = await query; // Get field definitions const fields = await FieldDefinition.query(knex) .where('objectDefinitionId', objectDef.id); // Filter fields user can read const filteredRecords = await Promise.all( records.map(record => this.authService.filterReadableFields(record, fields, user) ) ); return filteredRecords; } async updateRecord(tenantId: string, objectApiName: string, recordId: string, data: any, userId: string) { const knex = await this.tenantDbService.getTenantKnex(tenantId); const user = await User.query(knex) .findById(userId) .withGraphFetched('[roles.[objectPermissions, fieldPermissions]]'); const objectDef = await ObjectDefinition.query(knex) .findOne({ apiName: objectApiName }); // Get existing record const record = await knex(objectApiName.toLowerCase()) .where({ id: recordId }) .first(); if (!record) { throw new NotFoundException('Record not found'); } // Check if user can update this record await this.authService.assertCanPerformAction( 'update', objectDef, record, user, knex, ); // Get field definitions const fields = await FieldDefinition.query(knex) .where('objectDefinitionId', objectDef.id); // Filter to only editable fields const editableData = await this.authService.filterEditableFields( data, fields, user, ); // Perform update await knex(objectApiName.toLowerCase()) .where({ id: recordId }) .update(editableData); return knex(objectApiName.toLowerCase()) .where({ id: recordId }) .first(); } ``` ## Migration Run the migration to add authorization tables: ```bash npm run knex migrate:latest ``` The migration creates: - `orgWideDefault` column in `object_definitions` - `role_object_permissions` table - `role_field_permissions` table - `record_shares` table ## Next Steps 1. **Migrate existing data**: Set default `orgWideDefault` values for existing objects 2. **Create default roles**: Create Admin, Standard User, etc. with appropriate permissions 3. **Update API endpoints**: Integrate authorization service into all CRUD operations 4. **UI for permission management**: Build admin interface to manage role permissions 5. **Sharing UI**: Build interface for users to share records with others