refacto: cleanup
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import type { Pool } from 'pg';
|
||||
import { getUser, getOrgScope, orgScopeExpr, hasPermission, SYSTEM_ORG_ID } from '../permissions.js';
|
||||
import { crossOrgRuleResponse, notFound, forbidden, unauthorized, badRequest } from '../schemas.js';
|
||||
|
||||
export function createCrossOrgRuleRoutes(db: Pool) {
|
||||
return async function crossOrgRuleRoutes(app: FastifyInstance) {
|
||||
app.get('/cross-org-rules', {
|
||||
schema: {
|
||||
tags: ['Management'],
|
||||
summary: 'List cross-org rules',
|
||||
description: "Rules that grant a role from a source org access to a target org's resources.",
|
||||
security: [{ oauth2: [] }],
|
||||
response: {
|
||||
200: { type: 'array', items: crossOrgRuleResponse },
|
||||
400: badRequest, 401: unauthorized, 403: forbidden,
|
||||
},
|
||||
},
|
||||
}, async (req, reply) => {
|
||||
const user = getUser(req);
|
||||
if (!user) return reply.code(401).send({ error: 'Authentication required' });
|
||||
|
||||
const orgIds = getOrgScope(req, reply, false);
|
||||
if (orgIds === null) return reply;
|
||||
|
||||
if (!await hasPermission(db, req, reply, orgIds[0] ?? null, 'read', 'cross_org_rules')) return reply;
|
||||
|
||||
const { expr: srcExpr, params: srcParams } = orgScopeExpr(orgIds, 'cor.source_org_id');
|
||||
const { expr: tgtExpr, params: tgtParams } = orgScopeExpr(orgIds, 'cor.target_org_id', srcParams.length);
|
||||
const where = srcExpr && tgtExpr ? `WHERE (${srcExpr} OR ${tgtExpr})`
|
||||
: srcExpr ? `WHERE ${srcExpr}`
|
||||
: tgtExpr ? `WHERE ${tgtExpr}`
|
||||
: '';
|
||||
|
||||
const { rows } = await db.query(
|
||||
`SELECT cor.id,
|
||||
cor.source_org_id, cor.role_id AS source_role_id, cor.target_org_id,
|
||||
so.name AS source_org_name,
|
||||
sr.name AS source_role_name,
|
||||
"to".name AS target_org_name,
|
||||
cor.activity, cor.view, cor.modality, cor.priority
|
||||
FROM morbac.cross_org_rules cor
|
||||
JOIN morbac.orgs so ON so.id = cor.source_org_id
|
||||
JOIN morbac.roles sr ON sr.id = cor.role_id
|
||||
JOIN morbac.orgs "to" ON "to".id = cor.target_org_id
|
||||
${where}
|
||||
ORDER BY so.name, sr.name, "to".name, cor.activity, cor.view`,
|
||||
[...srcParams, ...tgtParams],
|
||||
);
|
||||
return rows;
|
||||
});
|
||||
|
||||
app.post('/cross-org-rules', {
|
||||
schema: {
|
||||
tags: ['Management'],
|
||||
summary: 'Create a cross-org rule',
|
||||
description: 'Grants a role from source_org access to target_org resources. Requires `create`/`cross_org_rules` in the source org.',
|
||||
security: [{ oauth2: [] }],
|
||||
body: {
|
||||
type: 'object',
|
||||
required: ['source_org_id', 'source_role_id', 'target_org_id', 'activity', 'view', 'modality'],
|
||||
properties: {
|
||||
source_org_id: { type: 'string', format: 'uuid' },
|
||||
source_role_id: { type: 'string', format: 'uuid' },
|
||||
target_org_id: { type: 'string', format: 'uuid' },
|
||||
activity: { type: 'string' },
|
||||
view: { type: 'string' },
|
||||
modality: { type: 'string', enum: ['permission', 'prohibition'] },
|
||||
priority: { type: 'integer' },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
201: crossOrgRuleResponse,
|
||||
400: badRequest, 401: unauthorized, 403: forbidden,
|
||||
409: { description: 'Rule already exists.' },
|
||||
},
|
||||
},
|
||||
}, async (req, reply) => {
|
||||
const user = getUser(req);
|
||||
if (!user) return reply.code(401).send({ error: 'Authentication required' });
|
||||
|
||||
const { source_org_id, source_role_id, target_org_id, activity, view, modality, priority } = req.body as {
|
||||
source_org_id: string; source_role_id: string; target_org_id: string;
|
||||
activity: string; view: string; modality: 'permission' | 'prohibition'; priority?: number;
|
||||
};
|
||||
|
||||
if (source_org_id === SYSTEM_ORG_ID) return reply.code(403).send({ error: 'System entities are immutable' });
|
||||
if (!await hasPermission(db, req, reply, source_org_id, 'create', 'cross_org_rules')) return reply;
|
||||
|
||||
try {
|
||||
const { rows } = await db.query(
|
||||
`INSERT INTO morbac.cross_org_rules
|
||||
(source_org_id, target_org_id, role_id, activity, view, context_id, modality, priority)
|
||||
VALUES (
|
||||
$1, $2, $3, $4, $5,
|
||||
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
||||
$6::morbac.modality, $7
|
||||
)
|
||||
RETURNING id,
|
||||
$1::UUID AS source_org_id, $3::UUID AS source_role_id, $2::UUID AS target_org_id,
|
||||
(SELECT name FROM morbac.orgs WHERE id = $1) AS source_org_name,
|
||||
(SELECT name FROM morbac.roles WHERE id = $3) AS source_role_name,
|
||||
(SELECT name FROM morbac.orgs WHERE id = $2) AS target_org_name,
|
||||
activity, view, modality, priority`,
|
||||
[source_org_id, target_org_id, source_role_id, activity, view, modality, priority ?? null],
|
||||
);
|
||||
return reply.code(201).send(rows[0]);
|
||||
} catch (e: unknown) {
|
||||
if ((e as { code?: string }).code === '23505') return reply.code(409).send({ error: 'Rule already exists' });
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/cross-org-rules/:id', {
|
||||
schema: {
|
||||
tags: ['Management'],
|
||||
summary: 'Delete a cross-org rule',
|
||||
description: 'Removes a cross-org rule. Requires `delete`/`cross_org_rules` in the source org.',
|
||||
security: [{ oauth2: [] }],
|
||||
params: { type: 'object', properties: { id: { type: 'string', format: 'uuid' } } },
|
||||
response: {
|
||||
204: { type: 'null', description: 'Rule deleted.' },
|
||||
400: badRequest, 401: unauthorized, 403: forbidden, 404: notFound,
|
||||
},
|
||||
},
|
||||
}, async (req, reply) => {
|
||||
const user = getUser(req);
|
||||
if (!user) return reply.code(401).send({ error: 'Authentication required' });
|
||||
|
||||
const { id } = req.params as { id: string };
|
||||
const { rows: [cor] } = await db.query<{ source_org_id: string }>(
|
||||
'SELECT source_org_id FROM morbac.cross_org_rules WHERE id = $1', [id],
|
||||
);
|
||||
if (!cor) return reply.code(404).send({ error: 'Not found' });
|
||||
if (cor.source_org_id === SYSTEM_ORG_ID) return reply.code(403).send({ error: 'System entities are immutable' });
|
||||
if (!await hasPermission(db, req, reply, cor.source_org_id, 'delete', 'cross_org_rules')) return reply;
|
||||
|
||||
await db.query('DELETE FROM morbac.cross_org_rules WHERE id = $1', [id]);
|
||||
return reply.code(204).send();
|
||||
});
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user