fix(authz): tighten policy scope enforcement
This commit is contained in:
@@ -9,6 +9,28 @@ function getBearerToken(header) {
|
||||
return token;
|
||||
}
|
||||
|
||||
function buildBypassActor() {
|
||||
let policyContext = {
|
||||
user: { userId: 'test-user' },
|
||||
tenant: { tenantId: '*' },
|
||||
staff: { staffId: '*', workforceId: '*' },
|
||||
};
|
||||
|
||||
if (process.env.AUTH_BYPASS_CONTEXT) {
|
||||
try {
|
||||
policyContext = JSON.parse(process.env.AUTH_BYPASS_CONTEXT);
|
||||
} catch (_error) {
|
||||
policyContext = {
|
||||
user: { userId: 'test-user' },
|
||||
tenant: { tenantId: '*' },
|
||||
staff: { staffId: '*', workforceId: '*' },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { uid: 'test-user', email: 'test@krow.local', role: 'TEST', policyContext };
|
||||
}
|
||||
|
||||
export async function requireAuth(req, _res, next) {
|
||||
try {
|
||||
const token = getBearerToken(req.get('Authorization'));
|
||||
@@ -17,7 +39,7 @@ export async function requireAuth(req, _res, next) {
|
||||
}
|
||||
|
||||
if (process.env.AUTH_BYPASS === 'true') {
|
||||
req.actor = { uid: 'test-user', email: 'test@krow.local', role: 'TEST' };
|
||||
req.actor = buildBypassActor();
|
||||
return next();
|
||||
}
|
||||
|
||||
@@ -36,10 +58,14 @@ export async function requireAuth(req, _res, next) {
|
||||
}
|
||||
|
||||
export function requirePolicy(action, resource) {
|
||||
return (req, _res, next) => {
|
||||
if (!can(action, resource, req.actor)) {
|
||||
return next(new AppError('FORBIDDEN', 'Not allowed to perform this action', 403));
|
||||
return async (req, _res, next) => {
|
||||
try {
|
||||
if (!(await can(action, resource, req.actor, req))) {
|
||||
return next(new AppError('FORBIDDEN', 'Not allowed to perform this action', 403));
|
||||
}
|
||||
return next();
|
||||
} catch (error) {
|
||||
return next(error);
|
||||
}
|
||||
return next();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,46 @@
|
||||
export function can(action, resource, actor) {
|
||||
void action;
|
||||
void resource;
|
||||
return Boolean(actor?.uid);
|
||||
import { loadActorContext } from './actor-context.js';
|
||||
|
||||
function normalize(value) {
|
||||
return `${value || ''}`.trim();
|
||||
}
|
||||
|
||||
function requestField(req, field) {
|
||||
return normalize(
|
||||
req?.params?.[field]
|
||||
?? req?.body?.[field]
|
||||
?? req?.query?.[field]
|
||||
);
|
||||
}
|
||||
|
||||
async function resolveActorContext(actor) {
|
||||
if (!actor?.uid) {
|
||||
return null;
|
||||
}
|
||||
if (actor.policyContext) {
|
||||
return actor.policyContext;
|
||||
}
|
||||
const context = await loadActorContext(actor.uid);
|
||||
actor.policyContext = context;
|
||||
return context;
|
||||
}
|
||||
|
||||
export async function can(action, resource, actor, req) {
|
||||
void resource;
|
||||
if (!action.startsWith('core.')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const context = await resolveActorContext(actor);
|
||||
if (!context?.user || !context?.tenant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tenantId = requestField(req, 'tenantId');
|
||||
if (!tenantId) {
|
||||
return true;
|
||||
}
|
||||
if (context.tenant.tenantId === '*') {
|
||||
return true;
|
||||
}
|
||||
return context.tenant.tenantId === tenantId;
|
||||
}
|
||||
|
||||
33
backend/core-api/test/policy.test.js
Normal file
33
backend/core-api/test/policy.test.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { can } from '../src/services/policy.js';
|
||||
|
||||
test('core actions require tenant scope', async () => {
|
||||
const allowed = await can(
|
||||
'core.verification.read',
|
||||
'verification',
|
||||
{
|
||||
uid: 'user-1',
|
||||
policyContext: {
|
||||
user: { userId: 'user-1' },
|
||||
tenant: { tenantId: 'tenant-1' },
|
||||
},
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const denied = await can(
|
||||
'core.verification.read',
|
||||
'verification',
|
||||
{
|
||||
uid: 'user-1',
|
||||
policyContext: {
|
||||
user: { userId: 'user-1' },
|
||||
},
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
assert.equal(allowed, true);
|
||||
assert.equal(denied, false);
|
||||
});
|
||||
Reference in New Issue
Block a user