Files
neo/IMPLEMENTATION_SUMMARY.md
Francisco Gaona 88f656c3f5 WIP - permissions
2025-12-28 05:43:03 +01:00

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.vue component:
    • 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.vue component:
    • Configure CRUD permissions per object
    • Apply conditions (e.g., own records only)
    • Visual permission matrix

6. Record Sharing

  • RecordShareDialog.vue component:
    • 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

  1. Index Usage: The migration creates proper indexes on foreign keys and commonly queried columns
  2. Query Scoping: Uses SQL EXISTS subqueries for efficient filtering
  3. Ability Caching: Consider caching abilities per request (already done via middleware)
  4. 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:

  1. Check the documentation in docs/AUTHORIZATION_SYSTEM.md
  2. Review example usage in the controllers
  3. Examine the test cases (when added)