navidocs/server/routes/permission.routes.js
ggq-admin fd403323bb feat: Phase 2 - Authorization & multi-tenancy permissions
Implement granular permission system with organization and entity-level access control:

Services:
- authorization.service.js: Permission management (grant, revoke, check entity permissions, organization membership)
- organization.service.js: Organization CRUD operations and statistics

Routes:
- organization.routes.js: 9 organization endpoints (create, list, update, delete, members management, stats)
- permission.routes.js: 5 permission endpoints (grant, revoke, list, check permissions)

Features:
- Multi-tenancy with organizations
- 4-tier permission hierarchy (viewer < editor < manager < admin)
- Entity-level granular permissions
- Organization role-based access control
- Permission expiration support
- Super admin delegation (org admins can grant permissions to users for entities)

Middleware Enhancements:
- requireOrganizationMember: Verify org membership
- requireOrganizationRole: Check role level
- requireEntityPermission: Verify entity access

Use Case:
Agency admins can grant specific boat access to technicians, captains, and office staff with different permission levels

Cross-Vertical Compatible:
Works for marine (boats), aviation (aircraft), vehicles, or any entity type

🤖 Generated with Claude Code
2025-10-21 10:11:51 +02:00

165 lines
3.8 KiB
JavaScript

/**
* Permission Routes
*
* POST /api/permissions/entities/:entityId - Grant entity permission
* DELETE /api/permissions/entities/:entityId/users/:userId - Revoke entity permission
* GET /api/permissions/entities/:entityId - List entity permissions
* GET /api/permissions/users/:userId/entities - List user's entity permissions
* GET /api/permissions/check/entities/:entityId - Check user's permission for entity
*/
import express from 'express';
import * as authzService from '../services/authorization.service.js';
import { authenticateToken, requireEntityPermission } from '../middleware/auth.middleware.js';
const router = express.Router();
/**
* Grant entity permission to user
*/
router.post('/entities/:entityId', authenticateToken, requireEntityPermission('manager'), async (req, res) => {
try {
const { userId, permissionLevel, expiresAt } = req.body;
if (!userId) {
return res.status(400).json({
success: false,
error: 'User ID is required'
});
}
if (!permissionLevel) {
return res.status(400).json({
success: false,
error: 'Permission level is required (viewer, editor, manager, admin)'
});
}
const permission = await authzService.grantEntityPermission({
userId,
entityId: req.params.entityId,
permissionLevel,
grantedBy: req.user.userId,
expiresAt: expiresAt || null
});
res.json({
success: true,
permission
});
} catch (error) {
res.status(400).json({
success: false,
error: error.message
});
}
});
/**
* Revoke entity permission from user
*/
router.delete('/entities/:entityId/users/:userId', authenticateToken, requireEntityPermission('manager'), async (req, res) => {
try {
const result = await authzService.revokeEntityPermission({
userId: req.params.userId,
entityId: req.params.entityId,
revokedBy: req.user.userId
});
res.json({
success: true,
...result
});
} catch (error) {
res.status(400).json({
success: false,
error: error.message
});
}
});
/**
* List all permissions for an entity
*/
router.get('/entities/:entityId', authenticateToken, requireEntityPermission('viewer'), async (req, res) => {
try {
const includeExpired = req.query.includeExpired === 'true';
const permissions = authzService.getEntityPermissions(req.params.entityId, {
includeExpired
});
res.json({
success: true,
permissions
});
} catch (error) {
res.status(400).json({
success: false,
error: error.message
});
}
});
/**
* List user's entity permissions
*/
router.get('/users/:userId/entities', authenticateToken, async (req, res) => {
try {
// Users can only view their own permissions unless they're querying as admin
if (req.params.userId !== req.user.userId) {
return res.status(403).json({
success: false,
error: 'You can only view your own permissions'
});
}
const includeExpired = req.query.includeExpired === 'true';
const permissions = authzService.getUserEntityPermissions(req.params.userId, {
includeExpired
});
res.json({
success: true,
permissions
});
} catch (error) {
res.status(400).json({
success: false,
error: error.message
});
}
});
/**
* Check if current user has permission for entity
*/
router.get('/check/entities/:entityId', authenticateToken, async (req, res) => {
try {
const minimumPermission = req.query.level || 'viewer';
const check = await authzService.checkEntityPermission(
req.user.userId,
req.params.entityId,
minimumPermission
);
res.json({
success: true,
...check
});
} catch (error) {
res.status(400).json({
success: false,
error: error.message
});
}
});
export default router;