86 lines
2.8 KiB
TypeScript
86 lines
2.8 KiB
TypeScript
import { ForbiddenException, type ExecutionContext } from '@nestjs/common';
|
|||
|
|
import { Reflector } from '@nestjs/core';
|
||
|
|
import { PermissionsGuard, RequirePermission, SuperAdminOnly } from './guards';
|
||
|
|
import type { AuthenticatedAdmin } from './jwt.strategy';
|
||
|
|
import { SUPER_ADMIN_MOBILE, withSuperAdminRids } from './super-admin';
|
||
|
|
|
||
|
|
class Routes {
|
||
|
|
@SuperAdminOnly()
|
||
|
|
@RequirePermission('config.wallet')
|
||
|
|
superOnly() {}
|
||
|
|
|
||
|
|
@SuperAdminOnly({ allowBranch: true })
|
||
|
|
@RequirePermission('order.list')
|
||
|
|
superOrBranch() {}
|
||
|
|
|
||
|
|
@RequirePermission('order.list', 'order.omde', 'order.tamin')
|
||
|
|
anyOf() {}
|
||
|
|
|
||
|
|
open() {}
|
||
|
|
}
|
||
|
|
|
||
|
|
function admin(over: Partial<AuthenticatedAdmin> = {}): AuthenticatedAdmin {
|
||
|
|
return {
|
||
|
|
id: 'a1',
|
||
|
|
fullName: 'x',
|
||
|
|
mobile: '09120000000',
|
||
|
|
role: 'SUPER_ADMIN',
|
||
|
|
userTypeId: 85,
|
||
|
|
branchId: null,
|
||
|
|
permissions: ['config.wallet', 'order.list', 'order.omde'],
|
||
|
|
...over,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function run(handler: keyof Routes, user: AuthenticatedAdmin) {
|
||
|
|
const ctx = {
|
||
|
|
getHandler: () => Routes.prototype[handler],
|
||
|
|
getClass: () => Routes,
|
||
|
|
switchToHttp: () => ({ getRequest: () => ({ user }) }),
|
||
|
|
} as unknown as ExecutionContext;
|
||
|
|
return new PermissionsGuard(new Reflector()).canActivate(ctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('PermissionsGuard — super-admin-only routes', () => {
|
||
|
|
it('admits the super admin', () => {
|
||
|
|
expect(run('superOnly', admin({ mobile: SUPER_ADMIN_MOBILE }))).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('refuses any other admin, even one holding the rid and the SUPER_ADMIN role', () => {
|
||
|
|
expect(() => run('superOnly', admin())).toThrow(ForbiddenException);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('refuses a branch admin unless the route allows branches', () => {
|
||
|
|
expect(() => run('superOnly', admin({ branchId: 'b1' }))).toThrow(ForbiddenException);
|
||
|
|
expect(run('superOrBranch', admin({ branchId: 'b1' }))).toBe(true);
|
||
|
|
expect(() => run('superOrBranch', admin())).toThrow(ForbiddenException);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('still requires the rid from a branch admin', () => {
|
||
|
|
expect(() => run('superOrBranch', admin({ branchId: 'b1', permissions: [] }))).toThrow(
|
||
|
|
ForbiddenException,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('leaves routes without the decorator alone', () => {
|
||
|
|
expect(run('open', admin({ permissions: [] }))).toBe(true);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('PermissionsGuard — several rids', () => {
|
||
|
|
it('admits a holder of any one of them', () => {
|
||
|
|
expect(run('anyOf', admin({ permissions: ['order.tamin'] }))).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('refuses a holder of none', () => {
|
||
|
|
expect(() => run('anyOf', admin({ permissions: ['product.list'] }))).toThrow(ForbiddenException);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('withSuperAdminRids', () => {
|
||
|
|
it('adds the super-admin rids for the super admin only', () => {
|
||
|
|
expect(withSuperAdminRids(SUPER_ADMIN_MOBILE, [])).toContain('config.wallet');
|
||
|
|
expect(withSuperAdminRids('09120000000', ['product.list'])).toEqual(['product.list']);
|
||
|
|
});
|
||
|
|
});
|