@spinajs/rbac-http-admin 2.0.485 → 2.0.487
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -5
- package/lib/cjs/config/rbac-http-admin.d.ts +25 -0
- package/lib/cjs/config/rbac-http-admin.d.ts.map +1 -1
- package/lib/cjs/config/rbac-http-admin.js +25 -0
- package/lib/cjs/config/rbac-http-admin.js.map +1 -1
- package/lib/cjs/controllers/Users/Profile.d.ts.map +1 -1
- package/lib/cjs/controllers/Users/Profile.js +3 -1
- package/lib/cjs/controllers/Users/Profile.js.map +1 -1
- package/lib/cjs/controllers/Users/Roles.d.ts +15 -8
- package/lib/cjs/controllers/Users/Roles.d.ts.map +1 -1
- package/lib/cjs/controllers/Users/Roles.js +34 -16
- package/lib/cjs/controllers/Users/Roles.js.map +1 -1
- package/lib/cjs/controllers/Users/Security.d.ts +151 -11
- package/lib/cjs/controllers/Users/Security.d.ts.map +1 -1
- package/lib/cjs/controllers/Users/Security.js +303 -30
- package/lib/cjs/controllers/Users/Security.js.map +1 -1
- package/lib/cjs/controllers/Users/Users.d.ts +97 -14
- package/lib/cjs/controllers/Users/Users.d.ts.map +1 -1
- package/lib/cjs/controllers/Users/Users.js +278 -73
- package/lib/cjs/controllers/Users/Users.js.map +1 -1
- package/lib/cjs/index.d.ts +6 -1
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +5 -0
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/interfaces.d.ts +81 -0
- package/lib/cjs/interfaces.d.ts.map +1 -0
- package/lib/cjs/interfaces.js +20 -0
- package/lib/cjs/interfaces.js.map +1 -0
- package/lib/cjs/services/RoleGuard.d.ts +96 -0
- package/lib/cjs/services/RoleGuard.d.ts.map +1 -0
- package/lib/cjs/services/RoleGuard.js +258 -0
- package/lib/cjs/services/RoleGuard.js.map +1 -0
- package/lib/mjs/config/rbac-http-admin.d.ts +25 -0
- package/lib/mjs/config/rbac-http-admin.d.ts.map +1 -1
- package/lib/mjs/config/rbac-http-admin.js +25 -0
- package/lib/mjs/config/rbac-http-admin.js.map +1 -1
- package/lib/mjs/controllers/Users/Profile.d.ts.map +1 -1
- package/lib/mjs/controllers/Users/Profile.js +4 -2
- package/lib/mjs/controllers/Users/Profile.js.map +1 -1
- package/lib/mjs/controllers/Users/Roles.d.ts +15 -8
- package/lib/mjs/controllers/Users/Roles.d.ts.map +1 -1
- package/lib/mjs/controllers/Users/Roles.js +35 -17
- package/lib/mjs/controllers/Users/Roles.js.map +1 -1
- package/lib/mjs/controllers/Users/Security.d.ts +151 -11
- package/lib/mjs/controllers/Users/Security.d.ts.map +1 -1
- package/lib/mjs/controllers/Users/Security.js +304 -31
- package/lib/mjs/controllers/Users/Security.js.map +1 -1
- package/lib/mjs/controllers/Users/Users.d.ts +97 -14
- package/lib/mjs/controllers/Users/Users.d.ts.map +1 -1
- package/lib/mjs/controllers/Users/Users.js +279 -74
- package/lib/mjs/controllers/Users/Users.js.map +1 -1
- package/lib/mjs/index.d.ts +6 -1
- package/lib/mjs/index.d.ts.map +1 -1
- package/lib/mjs/index.js +6 -1
- package/lib/mjs/index.js.map +1 -1
- package/lib/mjs/interfaces.d.ts +81 -0
- package/lib/mjs/interfaces.d.ts.map +1 -0
- package/lib/mjs/interfaces.js +16 -0
- package/lib/mjs/interfaces.js.map +1 -0
- package/lib/mjs/services/RoleGuard.d.ts +96 -0
- package/lib/mjs/services/RoleGuard.d.ts.map +1 -0
- package/lib/mjs/services/RoleGuard.js +252 -0
- package/lib/mjs/services/RoleGuard.js.map +1 -0
- package/lib/tsconfig.cjs.tsbuildinfo +1 -1
- package/lib/tsconfig.mjs.tsbuildinfo +1 -1
- package/package.json +14 -12
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../../src/interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAEhB,+EAA+E;IAC/E,gBAAgB,EAAE,OAAO,CAAC;IAE1B,gFAAgF;IAChF,iBAAiB,EAAE,OAAO,CAAC;IAE3B;;;;OAIG;IACH,iBAAiB,EAAE,OAAO,CAAC;IAE3B;;;OAGG;IACH,kBAAkB,EAAE,OAAO,CAAC;IAE5B;;;OAGG;IACH,4BAA4B,EAAE,OAAO,CAAC;IAEtC;;;;OAIG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,8BAAsB,SAAS;IAC7B;;;;;;OAMG;aACa,oBAAoB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAEtG;;OAEG;aACa,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAE3F;;OAEG;aACa,uBAAuB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAE/G;;;;OAIG;aACa,eAAe,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,EAAE;CACvD;AAED,uDAAuD;AACvD,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,QAAQ,GAAG,KAAK,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RoleGuard = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Role and account-state guard for the admin API.
|
|
6
|
+
*
|
|
7
|
+
* The checks live here rather than in the controllers because the same rule has
|
|
8
|
+
* to hold on every path that can change what an account may do: granting a role,
|
|
9
|
+
* revoking one, rewriting the whole role list through `PATCH /users/:uuid`, and
|
|
10
|
+
* taking an account out of service. A rule implemented per-controller is a rule
|
|
11
|
+
* that will be missing from the next controller.
|
|
12
|
+
*
|
|
13
|
+
* All methods THROW on refusal ( `Forbidden` / `InvalidArgument`, which the http
|
|
14
|
+
* error map turns into 403 / 400 ) and return normally when the operation is
|
|
15
|
+
* allowed.
|
|
16
|
+
*/
|
|
17
|
+
class RoleGuard {
|
|
18
|
+
}
|
|
19
|
+
exports.RoleGuard = RoleGuard;
|
|
20
|
+
//# sourceMappingURL=interfaces.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../../src/interfaces.ts"],"names":[],"mappings":";;;AAiDA;;;;;;;;;;;;GAYG;AACH,MAAsB,SAAS;CA0B9B;AA1BD,8BA0BC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Log } from '@spinajs/log';
|
|
2
|
+
import { AccessControl, User } from '@spinajs/rbac';
|
|
3
|
+
import { AccountDisableAction, IRoleGuardConfig, RoleGuard } from '../interfaces.js';
|
|
4
|
+
/** A resolved grant map: resource -> action -> attribute list. */
|
|
5
|
+
type GrantMap = {
|
|
6
|
+
[resource: string]: {
|
|
7
|
+
[action: string]: string[];
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* The shipped {@link RoleGuard}.
|
|
12
|
+
*
|
|
13
|
+
* Every check reads its switch from `rbac.admin.roleGuard`, so an application
|
|
14
|
+
* turns one off in configuration instead of replacing the whole service — and
|
|
15
|
+
* an application that needs a different rule entirely registers its own class
|
|
16
|
+
* under {@link RoleGuard} and names it in `rbac.admin.roleGuard.service`.
|
|
17
|
+
*/
|
|
18
|
+
export declare class DefaultRoleGuard extends RoleGuard {
|
|
19
|
+
protected Log: Log;
|
|
20
|
+
protected Options: IRoleGuardConfig;
|
|
21
|
+
protected SystemRole: string;
|
|
22
|
+
/**
|
|
23
|
+
* Declared roles, which is NOT the same set as the roles accesscontrol knows
|
|
24
|
+
* about: a role may be declared for assignment and carry no grants yet.
|
|
25
|
+
*/
|
|
26
|
+
protected DeclaredRoles: Array<{
|
|
27
|
+
Name: string;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Resolved late, never cached in a field: the container entry is replaced
|
|
31
|
+
* whenever the rbac bootstrapper runs ( every test case does ), and a guard
|
|
32
|
+
* holding the stale instance would answer from grants nobody configured.
|
|
33
|
+
*/
|
|
34
|
+
protected get AC(): AccessControl;
|
|
35
|
+
assertCanAssignRoles(actor: User, target: User | null, roles: string[]): Promise<void>;
|
|
36
|
+
assertCanRevokeRole(actor: User, target: User, role: string): Promise<void>;
|
|
37
|
+
assertCanDisableAccount(actor: User, target: User, action: AccountDisableAction): Promise<void>;
|
|
38
|
+
assignableRoles(actor: User): string[];
|
|
39
|
+
/**
|
|
40
|
+
* A missing actor is a programming error on a route that must be behind
|
|
41
|
+
* `AuthorizedPolicy` — refused rather than waved through, because every
|
|
42
|
+
* escalation check below is meaningless without one.
|
|
43
|
+
*/
|
|
44
|
+
protected assertActor(actor: User): void;
|
|
45
|
+
protected assertKnownRole(role: string): void;
|
|
46
|
+
protected assertNotSystemRole(role: string, verb: string): void;
|
|
47
|
+
protected isSelf(actor: User, target: User): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Throws when `target` is the last account that can still act with `role`.
|
|
50
|
+
*
|
|
51
|
+
* Counts accounts that could actually log in — inactive and soft-deleted rows
|
|
52
|
+
* are not a way back into the installation.
|
|
53
|
+
*/
|
|
54
|
+
protected assertNotLastHolder(role: string, action: string): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Active accounts holding `role`.
|
|
57
|
+
*
|
|
58
|
+
* Counted in the database. This used to narrow with LIKE and finish the job in
|
|
59
|
+
* memory, because `withRole` compiled to `FIND_IN_SET` — MySQL only — and threw
|
|
60
|
+
* a driver error on SQLite, turning "is this the last administrator" into a 500
|
|
61
|
+
* on every deployment that is not MySQL. Set membership is a per-dialect
|
|
62
|
+
* statement now, so the scope answers everywhere and the workaround is gone.
|
|
63
|
+
*/
|
|
64
|
+
protected countActiveHolders(role: string): Promise<number>;
|
|
65
|
+
protected isPrivileged(role: string): boolean;
|
|
66
|
+
protected rolesWithGrants(): string[];
|
|
67
|
+
protected allGrants(): GrantMap;
|
|
68
|
+
/** Grants of one role, with its `$extend` chain resolved. */
|
|
69
|
+
protected grantsOfRole(role: string): GrantMap;
|
|
70
|
+
/**
|
|
71
|
+
* The caller's ceiling: the union of every role they hold.
|
|
72
|
+
*
|
|
73
|
+
* Deliberately the union and not the session's active role — an administrator
|
|
74
|
+
* who can switch to a stronger role could otherwise be blocked from an
|
|
75
|
+
* operation they can perform by switching first, which teaches people to
|
|
76
|
+
* switch rather than to stay in the weaker role.
|
|
77
|
+
*/
|
|
78
|
+
protected grantsOf(actor: User): GrantMap;
|
|
79
|
+
/**
|
|
80
|
+
* True when `holder` permits everything `wanted` does.
|
|
81
|
+
*
|
|
82
|
+
* Attributes are compared as sets, with `*` covering anything. Negated
|
|
83
|
+
* attributes ( `!Password` ) are treated as ordinary members: a holder that
|
|
84
|
+
* lists the same negation still covers, one that does not is reported as not
|
|
85
|
+
* covering. That errs towards refusing an assignment, which is the safe
|
|
86
|
+
* direction for a guard.
|
|
87
|
+
*
|
|
88
|
+
* `:any` covers the matching `:own`, exactly as accesscontrol resolves them at
|
|
89
|
+
* enforcement time. Without that an administrator holding `read:any` on users
|
|
90
|
+
* would be told they cannot hand out a role holding `read:own` — a role
|
|
91
|
+
* strictly weaker than their own.
|
|
92
|
+
*/
|
|
93
|
+
protected covers(holder: GrantMap, wanted: GrantMap): boolean;
|
|
94
|
+
}
|
|
95
|
+
export {};
|
|
96
|
+
//# sourceMappingURL=RoleGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RoleGuard.d.ts","sourceRoot":"","sources":["../../../src/services/RoleGuard.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAU,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,aAAa,EAAE,IAAI,EAAiC,MAAM,eAAe,CAAC;AAGnF,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAErF,kEAAkE;AAClE,KAAK,QAAQ,GAAG;IAAE,CAAC,QAAQ,EAAE,MAAM,GAAG;QAAE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;KAAE,CAAA;CAAE,CAAC;AAEvE;;;;;;;GAOG;AACH,qBACa,gBAAiB,SAAQ,SAAS;IAE7C,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;IAGnB,SAAS,CAAC,OAAO,EAAE,gBAAgB,CAAC;IAGpC,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IAEH,SAAS,CAAC,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEjD;;;;OAIG;IACH,SAAS,KAAK,EAAE,IAAI,aAAa,CAEhC;IAEY,oBAAoB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BtF,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2B3E,uBAAuB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBrG,eAAe,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,EAAE;IAa7C;;;;OAIG;IACH,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI;IAMxC,SAAS,CAAC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAY7C,SAAS,CAAC,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAM/D,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAG,OAAO;IAUpD;;;;;OAKG;cACa,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUhF;;;;;;;;OAQG;cACa,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIjE,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAO7C,SAAS,CAAC,eAAe,IAAI,MAAM,EAAE;IASrC,SAAS,CAAC,SAAS,IAAI,QAAQ;IAQ/B,6DAA6D;IAC7D,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ;IAI9C;;;;;;;OAOG;IACH,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,GAAG,QAAQ;IAKzC;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO;CA2B9D"}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.DefaultRoleGuard = void 0;
|
|
16
|
+
const di_1 = require("@spinajs/di");
|
|
17
|
+
const configuration_1 = require("@spinajs/configuration");
|
|
18
|
+
const log_1 = require("@spinajs/log");
|
|
19
|
+
const exceptions_1 = require("@spinajs/exceptions");
|
|
20
|
+
const rbac_1 = require("@spinajs/rbac");
|
|
21
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
22
|
+
const interfaces_js_1 = require("../interfaces.js");
|
|
23
|
+
/**
|
|
24
|
+
* The shipped {@link RoleGuard}.
|
|
25
|
+
*
|
|
26
|
+
* Every check reads its switch from `rbac.admin.roleGuard`, so an application
|
|
27
|
+
* turns one off in configuration instead of replacing the whole service — and
|
|
28
|
+
* an application that needs a different rule entirely registers its own class
|
|
29
|
+
* under {@link RoleGuard} and names it in `rbac.admin.roleGuard.service`.
|
|
30
|
+
*/
|
|
31
|
+
let DefaultRoleGuard = class DefaultRoleGuard extends interfaces_js_1.RoleGuard {
|
|
32
|
+
/**
|
|
33
|
+
* Resolved late, never cached in a field: the container entry is replaced
|
|
34
|
+
* whenever the rbac bootstrapper runs ( every test case does ), and a guard
|
|
35
|
+
* holding the stale instance would answer from grants nobody configured.
|
|
36
|
+
*/
|
|
37
|
+
get AC() {
|
|
38
|
+
return di_1.DI.get('AccessControl');
|
|
39
|
+
}
|
|
40
|
+
async assertCanAssignRoles(actor, target, roles) {
|
|
41
|
+
const wanted = lodash_1.default.uniq((roles ?? []).map((r) => String(r ?? '').trim()).filter((r) => r.length > 0));
|
|
42
|
+
if (wanted.length === 0) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.assertActor(actor);
|
|
46
|
+
for (const role of wanted) {
|
|
47
|
+
this.assertKnownRole(role);
|
|
48
|
+
this.assertNotSystemRole(role, 'assigned');
|
|
49
|
+
if (this.Options?.preventEscalation !== false && !this.covers(this.grantsOf(actor), this.grantsOfRole(role))) {
|
|
50
|
+
throw new exceptions_1.Forbidden(`Role '${role}' grants more than the caller holds and cannot be assigned`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// The guard passed — the assignment itself is the caller's next step. Logged
|
|
54
|
+
// with the actor because the rbac events carry the TARGET only, so this line
|
|
55
|
+
// is the one record of who handed out which role.
|
|
56
|
+
this.Log.info(`Role assignment allowed`, {
|
|
57
|
+
Actor: actor?.Uuid,
|
|
58
|
+
Target: target?.Uuid ?? null,
|
|
59
|
+
Roles: wanted,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async assertCanRevokeRole(actor, target, role) {
|
|
63
|
+
const wanted = String(role ?? '').trim();
|
|
64
|
+
if (wanted.length === 0) {
|
|
65
|
+
throw new exceptions_1.InvalidArgument('role cannot be empty');
|
|
66
|
+
}
|
|
67
|
+
this.assertActor(actor);
|
|
68
|
+
this.assertKnownRole(wanted);
|
|
69
|
+
this.assertNotSystemRole(wanted, 'revoked');
|
|
70
|
+
if (!this.isPrivileged(wanted)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Taking a privileged role off YOUR OWN account through the admin API is
|
|
74
|
+
// almost always a mistake and is never recoverable through the same API —
|
|
75
|
+
// the request that would put the role back needs the role.
|
|
76
|
+
if (this.Options?.preventSelfLockout !== false && this.isSelf(actor, target)) {
|
|
77
|
+
throw new exceptions_1.Forbidden(`Cannot revoke your own '${wanted}' role`);
|
|
78
|
+
}
|
|
79
|
+
if (this.Options?.preventLastPrivilegedRemoval !== false && target?.Role?.includes(wanted)) {
|
|
80
|
+
await this.assertNotLastHolder(wanted, `revoke '${wanted}'`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async assertCanDisableAccount(actor, target, action) {
|
|
84
|
+
this.assertActor(actor);
|
|
85
|
+
if (this.Options?.preventSelfLockout !== false && this.isSelf(actor, target)) {
|
|
86
|
+
throw new exceptions_1.Forbidden(`Cannot ${action} your own account`);
|
|
87
|
+
}
|
|
88
|
+
if (this.Options?.preventLastPrivilegedRemoval === false) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
for (const role of target?.Role ?? []) {
|
|
92
|
+
if (this.isPrivileged(role)) {
|
|
93
|
+
await this.assertNotLastHolder(role, action);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
assignableRoles(actor) {
|
|
98
|
+
const all = lodash_1.default.uniq([...(this.DeclaredRoles ?? []).map((r) => r?.Name).filter(Boolean), ...this.rolesWithGrants()]);
|
|
99
|
+
const withoutSystem = this.Options?.protectSystemRole === false ? all : all.filter((r) => r !== this.SystemRole);
|
|
100
|
+
if (this.Options?.preventEscalation === false) {
|
|
101
|
+
return withoutSystem;
|
|
102
|
+
}
|
|
103
|
+
const actorGrants = this.grantsOf(actor);
|
|
104
|
+
return withoutSystem.filter((r) => this.covers(actorGrants, this.grantsOfRole(r)));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* A missing actor is a programming error on a route that must be behind
|
|
108
|
+
* `AuthorizedPolicy` — refused rather than waved through, because every
|
|
109
|
+
* escalation check below is meaningless without one.
|
|
110
|
+
*/
|
|
111
|
+
assertActor(actor) {
|
|
112
|
+
if (!actor) {
|
|
113
|
+
throw new exceptions_1.Forbidden('Role changes require an authenticated caller');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
assertKnownRole(role) {
|
|
117
|
+
if (this.Options?.requireKnownRole === false) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const known = lodash_1.default.uniq([...(this.DeclaredRoles ?? []).map((r) => r?.Name).filter(Boolean), ...this.rolesWithGrants(), this.SystemRole]);
|
|
121
|
+
if (!known.includes(role)) {
|
|
122
|
+
throw new exceptions_1.InvalidArgument(`Role '${role}' is not declared in rbac configuration`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
assertNotSystemRole(role, verb) {
|
|
126
|
+
if (this.Options?.protectSystemRole !== false && role === this.SystemRole) {
|
|
127
|
+
throw new exceptions_1.Forbidden(`The system role cannot be ${verb} through the API`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
isSelf(actor, target) {
|
|
131
|
+
if (!actor || !target) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
// Uuid first: an actor resolved from a session and a target loaded by route
|
|
135
|
+
// parameter are two different model instances of the same row.
|
|
136
|
+
return (actor.Uuid && actor.Uuid === target.Uuid) || (!!actor.Id && actor.Id === target.Id);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Throws when `target` is the last account that can still act with `role`.
|
|
140
|
+
*
|
|
141
|
+
* Counts accounts that could actually log in — inactive and soft-deleted rows
|
|
142
|
+
* are not a way back into the installation.
|
|
143
|
+
*/
|
|
144
|
+
async assertNotLastHolder(role, action) {
|
|
145
|
+
const holders = await this.countActiveHolders(role);
|
|
146
|
+
// The target itself is one of them ( it is active and holds the role ), so
|
|
147
|
+
// one holder means the operation empties the role.
|
|
148
|
+
if (holders <= 1) {
|
|
149
|
+
throw new exceptions_1.Forbidden(`Cannot ${action}: it would leave no active account holding '${role}'`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Active accounts holding `role`.
|
|
154
|
+
*
|
|
155
|
+
* Counted in the database. This used to narrow with LIKE and finish the job in
|
|
156
|
+
* memory, because `withRole` compiled to `FIND_IN_SET` — MySQL only — and threw
|
|
157
|
+
* a driver error on SQLite, turning "is this the last administrator" into a 500
|
|
158
|
+
* on every deployment that is not MySQL. Set membership is a per-dialect
|
|
159
|
+
* statement now, so the scope answers everywhere and the workaround is gone.
|
|
160
|
+
*/
|
|
161
|
+
async countActiveHolders(role) {
|
|
162
|
+
return rbac_1.User.query().isActiveUser().withRole([role]).selectCount();
|
|
163
|
+
}
|
|
164
|
+
isPrivileged(role) {
|
|
165
|
+
const resource = this.Options?.privilegedResource ?? 'users';
|
|
166
|
+
const action = this.Options?.privilegedAction ?? 'update:any';
|
|
167
|
+
return Boolean(this.grantsOfRole(role)?.[resource]?.[action]);
|
|
168
|
+
}
|
|
169
|
+
rolesWithGrants() {
|
|
170
|
+
try {
|
|
171
|
+
return this.AC?.getRoles() ?? [];
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// accesscontrol throws when no grants were ever set
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
allGrants() {
|
|
179
|
+
try {
|
|
180
|
+
return (this.AC?.getGrants() ?? {});
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return {};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** Grants of one role, with its `$extend` chain resolved. */
|
|
187
|
+
grantsOfRole(role) {
|
|
188
|
+
return (0, rbac_1._unwindGrants)(role, this.allGrants());
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The caller's ceiling: the union of every role they hold.
|
|
192
|
+
*
|
|
193
|
+
* Deliberately the union and not the session's active role — an administrator
|
|
194
|
+
* who can switch to a stronger role could otherwise be blocked from an
|
|
195
|
+
* operation they can perform by switching first, which teaches people to
|
|
196
|
+
* switch rather than to stay in the weaker role.
|
|
197
|
+
*/
|
|
198
|
+
grantsOf(actor) {
|
|
199
|
+
const grants = this.allGrants();
|
|
200
|
+
return (0, rbac_1._combineGrants)(...(actor?.Role ?? []).map((r) => (0, rbac_1._unwindGrants)(r, grants)));
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* True when `holder` permits everything `wanted` does.
|
|
204
|
+
*
|
|
205
|
+
* Attributes are compared as sets, with `*` covering anything. Negated
|
|
206
|
+
* attributes ( `!Password` ) are treated as ordinary members: a holder that
|
|
207
|
+
* lists the same negation still covers, one that does not is reported as not
|
|
208
|
+
* covering. That errs towards refusing an assignment, which is the safe
|
|
209
|
+
* direction for a guard.
|
|
210
|
+
*
|
|
211
|
+
* `:any` covers the matching `:own`, exactly as accesscontrol resolves them at
|
|
212
|
+
* enforcement time. Without that an administrator holding `read:any` on users
|
|
213
|
+
* would be told they cannot hand out a role holding `read:own` — a role
|
|
214
|
+
* strictly weaker than their own.
|
|
215
|
+
*/
|
|
216
|
+
covers(holder, wanted) {
|
|
217
|
+
for (const [resource, actions] of Object.entries(wanted ?? {})) {
|
|
218
|
+
const held = holder?.[resource];
|
|
219
|
+
if (!held) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
for (const [action, attributes] of Object.entries(actions ?? {})) {
|
|
223
|
+
const heldAttributes = held[action] ?? (action.endsWith(':own') ? held[action.replace(/:own$/, ':any')] : undefined);
|
|
224
|
+
if (!heldAttributes) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
if (heldAttributes.includes('*')) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if ((attributes ?? []).some((a) => !heldAttributes.includes(a))) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
exports.DefaultRoleGuard = DefaultRoleGuard;
|
|
239
|
+
__decorate([
|
|
240
|
+
(0, log_1.Logger)('rbac-admin'),
|
|
241
|
+
__metadata("design:type", log_1.Log)
|
|
242
|
+
], DefaultRoleGuard.prototype, "Log", void 0);
|
|
243
|
+
__decorate([
|
|
244
|
+
(0, configuration_1.Config)('rbac.admin.roleGuard'),
|
|
245
|
+
__metadata("design:type", Object)
|
|
246
|
+
], DefaultRoleGuard.prototype, "Options", void 0);
|
|
247
|
+
__decorate([
|
|
248
|
+
(0, configuration_1.Config)('rbac.systemRole', { defaultValue: 'system' }),
|
|
249
|
+
__metadata("design:type", String)
|
|
250
|
+
], DefaultRoleGuard.prototype, "SystemRole", void 0);
|
|
251
|
+
__decorate([
|
|
252
|
+
(0, configuration_1.Config)('rbac.roles', { defaultValue: [] }),
|
|
253
|
+
__metadata("design:type", Array)
|
|
254
|
+
], DefaultRoleGuard.prototype, "DeclaredRoles", void 0);
|
|
255
|
+
exports.DefaultRoleGuard = DefaultRoleGuard = __decorate([
|
|
256
|
+
(0, di_1.Injectable)(interfaces_js_1.RoleGuard)
|
|
257
|
+
], DefaultRoleGuard);
|
|
258
|
+
//# sourceMappingURL=RoleGuard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RoleGuard.js","sourceRoot":"","sources":["../../../src/services/RoleGuard.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,31 @@ declare const rbacHttpAdmin: {
|
|
|
6
6
|
views: string[];
|
|
7
7
|
};
|
|
8
8
|
};
|
|
9
|
+
rbac: {
|
|
10
|
+
admin: {
|
|
11
|
+
/**
|
|
12
|
+
* Guards applied to every admin operation that changes what an account may
|
|
13
|
+
* do. Defaults are the strict end — see IRoleGuardConfig for what each one
|
|
14
|
+
* refuses and why. Turn individual checks off here rather than replacing
|
|
15
|
+
* the service.
|
|
16
|
+
*/
|
|
17
|
+
roleGuard: {
|
|
18
|
+
service: string;
|
|
19
|
+
requireKnownRole: boolean;
|
|
20
|
+
protectSystemRole: boolean;
|
|
21
|
+
preventEscalation: boolean;
|
|
22
|
+
preventSelfLockout: boolean;
|
|
23
|
+
preventLastPrivilegedRemoval: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* What makes a role "privileged" for the self-lockout and last-holder
|
|
26
|
+
* checks. Action uses accesscontrol grant notation, exactly as written
|
|
27
|
+
* in `rbac.grants`.
|
|
28
|
+
*/
|
|
29
|
+
privilegedResource: string;
|
|
30
|
+
privilegedAction: string;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
};
|
|
9
34
|
};
|
|
10
35
|
export default rbacHttpAdmin;
|
|
11
36
|
//# sourceMappingURL=rbac-http-admin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rbac-http-admin.d.ts","sourceRoot":"","sources":["../../../src/config/rbac-http-admin.ts"],"names":[],"mappings":"AAOA,QAAA,MAAM,aAAa;;;;;;;;
|
|
1
|
+
{"version":3,"file":"rbac-http-admin.d.ts","sourceRoot":"","sources":["../../../src/config/rbac-http-admin.ts"],"names":[],"mappings":"AAOA,QAAA,MAAM,aAAa;;;;;;;;;;YAWb;;;;;eAKG;;;;;;;;gBAUD;;;;mBAIG;;;;;;CAMV,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
|
@@ -11,6 +11,31 @@ const rbacHttpAdmin = {
|
|
|
11
11
|
views: [dir('views')],
|
|
12
12
|
},
|
|
13
13
|
},
|
|
14
|
+
rbac: {
|
|
15
|
+
admin: {
|
|
16
|
+
/**
|
|
17
|
+
* Guards applied to every admin operation that changes what an account may
|
|
18
|
+
* do. Defaults are the strict end — see IRoleGuardConfig for what each one
|
|
19
|
+
* refuses and why. Turn individual checks off here rather than replacing
|
|
20
|
+
* the service.
|
|
21
|
+
*/
|
|
22
|
+
roleGuard: {
|
|
23
|
+
service: 'DefaultRoleGuard',
|
|
24
|
+
requireKnownRole: true,
|
|
25
|
+
protectSystemRole: true,
|
|
26
|
+
preventEscalation: true,
|
|
27
|
+
preventSelfLockout: true,
|
|
28
|
+
preventLastPrivilegedRemoval: true,
|
|
29
|
+
/**
|
|
30
|
+
* What makes a role "privileged" for the self-lockout and last-holder
|
|
31
|
+
* checks. Action uses accesscontrol grant notation, exactly as written
|
|
32
|
+
* in `rbac.grants`.
|
|
33
|
+
*/
|
|
34
|
+
privilegedResource: 'users',
|
|
35
|
+
privilegedAction: 'update:any',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
14
39
|
};
|
|
15
40
|
export default rbacHttpAdmin;
|
|
16
41
|
//# sourceMappingURL=rbac-http-admin.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rbac-http-admin.js","sourceRoot":"","sources":["../../../src/config/rbac-http-admin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEhD,SAAS,GAAG,CAAC,IAAY;IACvB,MAAM,UAAU,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC;IACjD,OAAO,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,UAAU,EAAE,iBAAiB,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAC5K,CAAC;AAED,MAAM,aAAa,GAAG;IACpB,MAAM,EAAE;QACN,IAAI,EAAE;YACJ,WAAW,EAAE,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;YACjC,OAAO,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACzB,KAAK,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;SACtB;KACF;CACF,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"rbac-http-admin.js","sourceRoot":"","sources":["../../../src/config/rbac-http-admin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEhD,SAAS,GAAG,CAAC,IAAY;IACvB,MAAM,UAAU,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC;IACjD,OAAO,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,UAAU,EAAE,iBAAiB,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAC5K,CAAC;AAED,MAAM,aAAa,GAAG;IACpB,MAAM,EAAE;QACN,IAAI,EAAE;YACJ,WAAW,EAAE,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;YACjC,OAAO,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACzB,KAAK,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;SACtB;KACF;IAED,IAAI,EAAE;QACJ,KAAK,EAAE;YACL;;;;;eAKG;YACH,SAAS,EAAE;gBACT,OAAO,EAAE,kBAAkB;gBAE3B,gBAAgB,EAAE,IAAI;gBACtB,iBAAiB,EAAE,IAAI;gBACvB,iBAAiB,EAAE,IAAI;gBACvB,kBAAkB,EAAE,IAAI;gBACxB,4BAA4B,EAAE,IAAI;gBAElC;;;;mBAIG;gBACH,kBAAkB,EAAE,OAAO;gBAC3B,gBAAgB,EAAE,YAAY;aAC/B;SACF;KACF;CACF,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Profile.d.ts","sourceRoot":"","sources":["../../../../src/controllers/Users/Profile.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAiB,EAAE,EAAiB,MAAM,eAAe,CAAC;AACjF,OAAO,EAAS,mBAAmB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"Profile.d.ts","sourceRoot":"","sources":["../../../../src/controllers/Users/Profile.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAiB,EAAE,EAAiB,MAAM,eAAe,CAAC;AACjF,OAAO,EAAS,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAG3D;;;;GAIG;AACH,qBAGa,OAAQ,SAAQ,cAAc;IAOzC,SAAS,CAAC,cAAc,EAAE,mBAAmB,CAAC;IAE9C;;;;;;;;;;OAUG;IAGU,cAAc,CAAU,KAAK,EAAE,MAAM;CAKnD"}
|
|
@@ -13,7 +13,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
13
13
|
import { AutoinjectService } from '@spinajs/configuration';
|
|
14
14
|
import { BaseController, BasePath, Get, Ok, Param, Policy } from '@spinajs/http';
|
|
15
15
|
import { UserProfileProvider } from '@spinajs/rbac';
|
|
16
|
-
import { AuthorizedPolicy, Permission, Resource } from
|
|
16
|
+
import { AuthorizedPolicy, Permission, Resource } from '@spinajs/rbac-http';
|
|
17
17
|
/**
|
|
18
18
|
* User profile management (admin).
|
|
19
19
|
* Provides administrative access to user profile data via the configured UserProfileProvider.
|
|
@@ -32,7 +32,9 @@ let Profile = class Profile extends BaseController {
|
|
|
32
32
|
* @response 404 User not found
|
|
33
33
|
*/
|
|
34
34
|
async getUserProfile(login) {
|
|
35
|
-
|
|
35
|
+
// NOTE: awaited. Handing the pending promise to `Ok` serialized as an
|
|
36
|
+
// empty object for every client that got JSON back.
|
|
37
|
+
return new Ok(await this.ProfileService.retrieve(login));
|
|
36
38
|
}
|
|
37
39
|
};
|
|
38
40
|
__decorate([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Profile.js","sourceRoot":"","sources":["../../../../src/controllers/Users/Profile.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,EAAS,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"Profile.js","sourceRoot":"","sources":["../../../../src/controllers/Users/Profile.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,EAAS,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE5E;;;;GAIG;AAII,IAAM,OAAO,GAAb,MAAM,OAAQ,SAAQ,cAAc;IASzC;;;;;;;;;;OAUG;IAGU,AAAN,KAAK,CAAC,cAAc,CAAU,KAAa;QAChD,sEAAsE;QACtE,oDAAoD;QACpD,OAAO,IAAI,EAAE,CAAC,MAAM,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3D,CAAC;CACF,CAAA;AApBW;IADT,iBAAiB,CAAC,mBAAmB,CAAC;8BACb,mBAAmB;+CAAC;AAejC;IAFZ,GAAG,CAAC,QAAQ,CAAC;IACb,UAAU,CAAC,CAAC,SAAS,CAAC,CAAC;IACK,WAAA,KAAK,EAAE,CAAA;;;;6CAInC;AA1BU,OAAO;IAHnB,QAAQ,CAAC,eAAe,CAAC;IACzB,MAAM,CAAC,gBAAgB,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC;GACL,OAAO,CA2BnB"}
|
|
@@ -1,38 +1,45 @@
|
|
|
1
1
|
import { BaseController, Ok } from '@spinajs/http';
|
|
2
|
-
|
|
2
|
+
import { User } from '@spinajs/rbac';
|
|
3
|
+
import { RoleGuard } from '../../interfaces.js';
|
|
4
|
+
import '../../services/RoleGuard.js';
|
|
5
|
+
export declare class RoleDto {
|
|
3
6
|
role: string;
|
|
4
7
|
constructor(data: Partial<RoleDto>);
|
|
5
8
|
}
|
|
6
9
|
/**
|
|
7
10
|
* User role management (admin).
|
|
8
11
|
* Grants and revokes RBAC roles for user accounts.
|
|
12
|
+
*
|
|
13
|
+
* Both routes run the configured {@link RoleGuard} before touching anything: a
|
|
14
|
+
* role is the one thing in this API that can grant MORE than the caller has, so
|
|
15
|
+
* "may update users" is not by itself an answer to "may hand out this role".
|
|
9
16
|
* @tags Admin Users
|
|
10
17
|
*/
|
|
11
18
|
export declare class Roles extends BaseController {
|
|
19
|
+
protected RoleGuard: RoleGuard;
|
|
12
20
|
/**
|
|
13
21
|
* Grant role to user (admin)
|
|
14
22
|
* Assigns the specified RBAC role to the user identified by login name.
|
|
15
23
|
* @security cookieAuth
|
|
16
24
|
* @param login User login name
|
|
17
25
|
* @response 200 Role granted successfully
|
|
18
|
-
* @response 400
|
|
26
|
+
* @response 400 Unknown role name
|
|
19
27
|
* @response 401 Unauthorized — valid session required
|
|
20
|
-
* @response 403 Forbidden — updateAny permission required
|
|
28
|
+
* @response 403 Forbidden — updateAny permission required, or the role grants more than the caller holds
|
|
21
29
|
* @response 404 User not found
|
|
22
30
|
*/
|
|
23
|
-
addRole(
|
|
31
|
+
addRole(actor: User, user: User, roleDto: RoleDto): Promise<Ok<any>>;
|
|
24
32
|
/**
|
|
25
33
|
* Revoke role from user (admin)
|
|
26
34
|
* Removes the specified RBAC role from the user identified by login name.
|
|
27
35
|
* @security cookieAuth
|
|
28
36
|
* @param login User login name
|
|
29
37
|
* @response 200 Role revoked successfully
|
|
30
|
-
* @response 400
|
|
38
|
+
* @response 400 Unknown role name
|
|
31
39
|
* @response 401 Unauthorized — valid session required
|
|
32
|
-
* @response 403 Forbidden — updateAny permission required
|
|
40
|
+
* @response 403 Forbidden — updateAny permission required, or the revocation would lock the caller or the installation out
|
|
33
41
|
* @response 404 User not found
|
|
34
42
|
*/
|
|
35
|
-
revokeRole(
|
|
43
|
+
revokeRole(actor: User, user: User, roleDto: RoleDto): Promise<Ok<any>>;
|
|
36
44
|
}
|
|
37
|
-
export {};
|
|
38
45
|
//# sourceMappingURL=Roles.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Roles.d.ts","sourceRoot":"","sources":["../../../../src/controllers/Users/Roles.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Roles.d.ts","sourceRoot":"","sources":["../../../../src/controllers/Users/Roles.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAkB,EAAE,EAAiB,MAAM,eAAe,CAAC;AAElF,OAAO,EAAiB,IAAI,EAAE,MAAM,eAAe,CAAC;AAIpD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGhD,OAAO,6BAA6B,CAAC;AAErC,qBAQa,OAAO;IACX,IAAI,EAAE,MAAM,CAAC;gBAER,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC;CAGnC;AAED;;;;;;;;GAQG;AACH,qBAGa,KAAM,SAAQ,cAAc;IAEvC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC;IAE/B;;;;;;;;;;OAUG;IAGU,OAAO,CAAgB,KAAK,EAAE,IAAI,EAAkF,IAAI,EAAE,IAAI,EAAU,OAAO,EAAE,OAAO;IAOrK;;;;;;;;;;OAUG;IAGU,UAAU,CAAgB,KAAK,EAAE,IAAI,EAAkF,IAAI,EAAE,IAAI,EAAU,OAAO,EAAE,OAAO;CAMzK"}
|