7.3 KiB
7.3 KiB
Authorization System Implementation Summary
✅ Implementation Complete
A comprehensive polymorphic record sharing and authorization system has been implemented with CASL, Objection.js, and NestJS.
What Was Built
Backend (NestJS + Objection.js + CASL)
1. Database Layer
- ✅ Migration for authorization tables (
20250128000001_add_authorization_system.js) - ✅ Updated Prisma schema with new models
- ✅ Objection.js models:
ObjectField,RoleRule,RecordShare - ✅ Updated existing models with new relations
2. Authorization Core
- ✅
AbilityFactory- Builds CASL abilities from 3 layers (global, role, share) - ✅ Query scoping utilities for SQL-level authorization
- ✅ Guards and decorators (
AbilitiesGuard,@CheckAbility(),@CurrentUser()) - ✅ Middleware for attaching abilities to requests
3. API Endpoints
-
✅ ShareController - CRUD for record shares
- POST /shares - Create share
- GET /shares/record/:objectDefinitionId/:recordId - List shares
- GET /shares/granted - Shares granted by user
- GET /shares/received - Shares received by user
- PATCH /shares/:id - Update share
- DELETE /shares/:id - Revoke share
-
✅ RoleController - Role management
- Standard CRUD for roles
- RoleRuleController for CASL rules
-
✅ ObjectAccessController - Object-level permissions
- GET/PUT /setup/objects/:apiName/access
- POST /setup/objects/:apiName/fields/:fieldKey/permissions
- PUT /setup/objects/:apiName/field-permissions
Frontend (Nuxt 3 + Vue 3)
4. Object Management Enhancement
- ✅ Added "Access & Permissions" tab to object setup page
- ✅
ObjectAccessSettings.vuecomponent:- Configure access model (public/owner/mixed)
- Set public CRUD permissions
- Configure owner field
- Set field-level read/write permissions
5. Role Management
- ✅ New page:
/setup/roles - ✅
RolePermissionsEditor.vuecomponent:- Configure CRUD permissions per object
- Apply conditions (e.g., own records only)
- Visual permission matrix
6. Record Sharing
- ✅
RecordShareDialog.vuecomponent:- List current shares
- Add new shares with permissions
- Field-level scoping
- Expiration dates
- Revoke shares
Key Features
🌍 Global Object Policies
- Public/private access models
- Default CRUD permissions per object
- Configurable owner field
- Field-level default permissions
👥 Role-Based Access
- CASL rules stored in database
- Per-object permissions
- Condition-based rules (e.g., ownerId matching)
- Multiple actions per rule
🔗 Per-Record Sharing
- Polymorphic design (works with any object type)
- Grant read/update access to specific users
- Optional field-level scoping
- Expiration and revocation support
- Track who granted each share
🔒 SQL Query Scoping
- Critical for list endpoints
- Ensures users only see authorized records
- Combines ownership + sharing logic
- Works with public access flags
File Structure
backend/
├── migrations/tenant/
│ └── 20250128000001_add_authorization_system.js
├── src/
│ ├── auth/
│ │ ├── ability.factory.ts (CASL ability builder)
│ │ ├── query-scope.util.ts (SQL scoping utilities)
│ │ ├── guards/
│ │ │ └── abilities.guard.ts
│ │ ├── decorators/
│ │ │ ├── auth.decorators.ts
│ │ │ └── check-ability.decorator.ts
│ │ └── middleware/
│ │ └── ability.middleware.ts
│ ├── models/
│ │ ├── object-field.model.ts
│ │ ├── role-rule.model.ts
│ │ └── record-share.model.ts
│ ├── rbac/
│ │ ├── share.controller.ts
│ │ └── role.controller.ts
│ └── object/
│ └── object-access.controller.ts
frontend/
├── components/
│ ├── ObjectAccessSettings.vue
│ ├── RecordShareDialog.vue
│ └── RolePermissionsEditor.vue
└── pages/
├── setup/
│ ├── objects/[apiName].vue (enhanced with access tab)
│ └── roles.vue
└── ...
docs/
└── AUTHORIZATION_SYSTEM.md (comprehensive documentation)
Next Steps
1. Run the Migration
cd backend
npm run migrate:latest
2. Initialize Existing Objects
Set default access models for existing object definitions:
UPDATE object_definitions
SET
access_model = 'owner',
public_read = false,
public_create = false,
public_update = false,
public_delete = false,
owner_field = 'ownerId'
WHERE access_model IS NULL;
3. Apply Query Scoping
Update existing controllers to use query scoping:
import { applyReadScope } from '@/auth/query-scope.util';
// In your list endpoint
async findAll(@CurrentUser() user: User) {
const objectDef = await ObjectDefinition.query(this.knex)
.findOne({ apiName: 'YourObject' });
let query = YourModel.query(this.knex);
query = applyReadScope(query, user, objectDef, this.knex);
return query;
}
4. Add Route Protection
Use guards on sensitive endpoints:
@UseGuards(JwtAuthGuard, AbilitiesGuard)
@CheckAbility({ action: 'update', subject: 'Post' })
async update(@Body() data: any) {
// Only users with 'update' permission on 'Post' can access
}
5. Frontend Integration
Add sharing button to record detail pages:
<template>
<div>
<!-- Your record details -->
<Button @click="showShareDialog = true">
<Share class="w-4 h-4 mr-2" />
Share
</Button>
<RecordShareDialog
:open="showShareDialog"
:object-definition-id="objectDefinition.id"
:record-id="record.id"
:fields="objectDefinition.fields"
@close="showShareDialog = false"
/>
</div>
</template>
Testing Checklist
- Run database migration successfully
- Create a test role with permissions
- Configure object access settings via UI
- Share a record with another user
- Verify shared record appears in grantee's list
- Verify query scoping filters unauthorized records
- Test field-level permissions
- Test share expiration
- Test share revocation
- Test role-based access with conditions
Performance Considerations
- Index Usage: The migration creates proper indexes on foreign keys and commonly queried columns
- Query Scoping: Uses SQL EXISTS subqueries for efficient filtering
- Ability Caching: Consider caching abilities per request (already done via middleware)
- Batch Loading: When checking multiple records, batch the share lookups
Security Notes
⚠️ Important: Always use SQL query scoping for list endpoints. Never fetch all records and filter in application code.
✅ Best Practices:
- Share creation requires ownership verification
- Only grantors can update/revoke shares
- Expired/revoked shares are excluded from queries
- Field-level permissions are enforced on write operations
Documentation
Full documentation available in:
- AUTHORIZATION_SYSTEM.md - Comprehensive guide
- Inline code comments in all new files
- JSDoc comments on key functions
Support
For questions or issues:
- Check the documentation in
docs/AUTHORIZATION_SYSTEM.md - Review example usage in the controllers
- Examine the test cases (when added)