@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.
Files changed (66) hide show
  1. package/README.md +104 -5
  2. package/lib/cjs/config/rbac-http-admin.d.ts +25 -0
  3. package/lib/cjs/config/rbac-http-admin.d.ts.map +1 -1
  4. package/lib/cjs/config/rbac-http-admin.js +25 -0
  5. package/lib/cjs/config/rbac-http-admin.js.map +1 -1
  6. package/lib/cjs/controllers/Users/Profile.d.ts.map +1 -1
  7. package/lib/cjs/controllers/Users/Profile.js +3 -1
  8. package/lib/cjs/controllers/Users/Profile.js.map +1 -1
  9. package/lib/cjs/controllers/Users/Roles.d.ts +15 -8
  10. package/lib/cjs/controllers/Users/Roles.d.ts.map +1 -1
  11. package/lib/cjs/controllers/Users/Roles.js +34 -16
  12. package/lib/cjs/controllers/Users/Roles.js.map +1 -1
  13. package/lib/cjs/controllers/Users/Security.d.ts +151 -11
  14. package/lib/cjs/controllers/Users/Security.d.ts.map +1 -1
  15. package/lib/cjs/controllers/Users/Security.js +303 -30
  16. package/lib/cjs/controllers/Users/Security.js.map +1 -1
  17. package/lib/cjs/controllers/Users/Users.d.ts +97 -14
  18. package/lib/cjs/controllers/Users/Users.d.ts.map +1 -1
  19. package/lib/cjs/controllers/Users/Users.js +278 -73
  20. package/lib/cjs/controllers/Users/Users.js.map +1 -1
  21. package/lib/cjs/index.d.ts +6 -1
  22. package/lib/cjs/index.d.ts.map +1 -1
  23. package/lib/cjs/index.js +5 -0
  24. package/lib/cjs/index.js.map +1 -1
  25. package/lib/cjs/interfaces.d.ts +81 -0
  26. package/lib/cjs/interfaces.d.ts.map +1 -0
  27. package/lib/cjs/interfaces.js +20 -0
  28. package/lib/cjs/interfaces.js.map +1 -0
  29. package/lib/cjs/services/RoleGuard.d.ts +96 -0
  30. package/lib/cjs/services/RoleGuard.d.ts.map +1 -0
  31. package/lib/cjs/services/RoleGuard.js +258 -0
  32. package/lib/cjs/services/RoleGuard.js.map +1 -0
  33. package/lib/mjs/config/rbac-http-admin.d.ts +25 -0
  34. package/lib/mjs/config/rbac-http-admin.d.ts.map +1 -1
  35. package/lib/mjs/config/rbac-http-admin.js +25 -0
  36. package/lib/mjs/config/rbac-http-admin.js.map +1 -1
  37. package/lib/mjs/controllers/Users/Profile.d.ts.map +1 -1
  38. package/lib/mjs/controllers/Users/Profile.js +4 -2
  39. package/lib/mjs/controllers/Users/Profile.js.map +1 -1
  40. package/lib/mjs/controllers/Users/Roles.d.ts +15 -8
  41. package/lib/mjs/controllers/Users/Roles.d.ts.map +1 -1
  42. package/lib/mjs/controllers/Users/Roles.js +35 -17
  43. package/lib/mjs/controllers/Users/Roles.js.map +1 -1
  44. package/lib/mjs/controllers/Users/Security.d.ts +151 -11
  45. package/lib/mjs/controllers/Users/Security.d.ts.map +1 -1
  46. package/lib/mjs/controllers/Users/Security.js +304 -31
  47. package/lib/mjs/controllers/Users/Security.js.map +1 -1
  48. package/lib/mjs/controllers/Users/Users.d.ts +97 -14
  49. package/lib/mjs/controllers/Users/Users.d.ts.map +1 -1
  50. package/lib/mjs/controllers/Users/Users.js +279 -74
  51. package/lib/mjs/controllers/Users/Users.js.map +1 -1
  52. package/lib/mjs/index.d.ts +6 -1
  53. package/lib/mjs/index.d.ts.map +1 -1
  54. package/lib/mjs/index.js +6 -1
  55. package/lib/mjs/index.js.map +1 -1
  56. package/lib/mjs/interfaces.d.ts +81 -0
  57. package/lib/mjs/interfaces.d.ts.map +1 -0
  58. package/lib/mjs/interfaces.js +16 -0
  59. package/lib/mjs/interfaces.js.map +1 -0
  60. package/lib/mjs/services/RoleGuard.d.ts +96 -0
  61. package/lib/mjs/services/RoleGuard.d.ts.map +1 -0
  62. package/lib/mjs/services/RoleGuard.js +252 -0
  63. package/lib/mjs/services/RoleGuard.js.map +1 -0
  64. package/lib/tsconfig.cjs.tsbuildinfo +1 -1
  65. package/lib/tsconfig.mjs.tsbuildinfo +1 -1
  66. 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;;;;;;;;CAQlB,CAAC;AAEF,eAAe,aAAa,CAAC"}
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;AAI3D;;;;GAIG;AACH,qBAGa,OAAQ,SAAQ,cAAc;IASvC,SAAS,CAAC,cAAc,EAAE,mBAAmB,CAAC;IAE9C;;;;;;;;;;OAUG;IAGU,cAAc,CAAU,KAAK,EAAE,MAAM;CAGrD"}
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 "@spinajs/rbac-http";
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
- return new Ok(this.ProfileService.retrieve(login));
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;AAG5E;;;;GAIG;AAII,IAAM,OAAO,GAAb,MAAM,OAAQ,SAAQ,cAAc;IAWvC;;;;;;;;;;OAUG;IAGU,AAAN,KAAK,CAAC,cAAc,CAAU,KAAa;QAC7C,OAAO,IAAI,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IACxD,CAAC;CACJ,CAAA;AAlBa;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;;;;6CAEnC;AA1BQ,OAAO;IAHnB,QAAQ,CAAC,eAAe,CAAC;IACzB,MAAM,CAAC,gBAAgB,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC;GACL,OAAO,CA2BnB"}
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
- declare class RoleDto {
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 Invalid role name
26
+ * @response 400 Unknown role name
19
27
  * @response 401 Unauthorized — valid session required
20
- * @response 403 Forbidden — updateAny permission required on users resource
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(login: string, roleDto: RoleDto): Promise<Ok<any>>;
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 Invalid role name
38
+ * @response 400 Unknown role name
31
39
  * @response 401 Unauthorized — valid session required
32
- * @response 403 Forbidden — updateAny permission required on users resource
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(login: string, roleDto: RoleDto): Promise<Ok<any>>;
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":"AAAA,OAAO,EAAE,cAAc,EAAkB,EAAE,EAAwB,MAAM,eAAe,CAAC;AAMzF,cAQM,OAAO;IACF,IAAI,EAAE,MAAM,CAAC;gBAER,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC;CAGrC;AAGD;;;;GAIG;AACH,qBAGa,KAAM,SAAQ,cAAc;IACrC;;;;;;;;;;OAUG;IAGU,OAAO,CAAU,KAAK,EAAE,MAAM,EAAU,OAAO,EAAE,OAAO;IAKrE;;;;;;;;;;OAUG;IAGU,UAAU,CAAU,KAAK,EAAE,MAAM,EAAU,OAAO,EAAE,OAAO;CAI3E"}
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"}