@mahiframework/permissions 0.1.4

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 (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -0
  3. package/dist/assignee.d.ts +57 -0
  4. package/dist/assignee.d.ts.map +1 -0
  5. package/dist/assignee.js +46 -0
  6. package/dist/assignee.js.map +1 -0
  7. package/dist/commands/permissions-cache-reset.d.ts +20 -0
  8. package/dist/commands/permissions-cache-reset.d.ts.map +1 -0
  9. package/dist/commands/permissions-cache-reset.js +24 -0
  10. package/dist/commands/permissions-cache-reset.js.map +1 -0
  11. package/dist/commands/permissions-check.d.ts +37 -0
  12. package/dist/commands/permissions-check.d.ts.map +1 -0
  13. package/dist/commands/permissions-check.js +92 -0
  14. package/dist/commands/permissions-check.js.map +1 -0
  15. package/dist/commands/permissions-show.d.ts +20 -0
  16. package/dist/commands/permissions-show.d.ts.map +1 -0
  17. package/dist/commands/permissions-show.js +49 -0
  18. package/dist/commands/permissions-show.js.map +1 -0
  19. package/dist/errors.d.ts +77 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/errors.js +106 -0
  22. package/dist/errors.js.map +1 -0
  23. package/dist/index.d.ts +25 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +18 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/listeners/invalidate-permission-cache.listener.d.ts +39 -0
  28. package/dist/listeners/invalidate-permission-cache.listener.d.ts.map +1 -0
  29. package/dist/listeners/invalidate-permission-cache.listener.js +50 -0
  30. package/dist/listeners/invalidate-permission-cache.listener.js.map +1 -0
  31. package/dist/middleware/current-subject.d.ts +23 -0
  32. package/dist/middleware/current-subject.d.ts.map +1 -0
  33. package/dist/middleware/current-subject.js +31 -0
  34. package/dist/middleware/current-subject.js.map +1 -0
  35. package/dist/middleware/permission.d.ts +19 -0
  36. package/dist/middleware/permission.d.ts.map +1 -0
  37. package/dist/middleware/permission.js +34 -0
  38. package/dist/middleware/permission.js.map +1 -0
  39. package/dist/middleware/role-or-permission.d.ts +29 -0
  40. package/dist/middleware/role-or-permission.d.ts.map +1 -0
  41. package/dist/middleware/role-or-permission.js +45 -0
  42. package/dist/middleware/role-or-permission.js.map +1 -0
  43. package/dist/middleware/role.d.ts +21 -0
  44. package/dist/middleware/role.d.ts.map +1 -0
  45. package/dist/middleware/role.js +36 -0
  46. package/dist/middleware/role.js.map +1 -0
  47. package/dist/migrations/0001_create_permission_tables.d.ts +63 -0
  48. package/dist/migrations/0001_create_permission_tables.d.ts.map +1 -0
  49. package/dist/migrations/0001_create_permission_tables.js +113 -0
  50. package/dist/migrations/0001_create_permission_tables.js.map +1 -0
  51. package/dist/models/permission.model.d.ts +58 -0
  52. package/dist/models/permission.model.d.ts.map +1 -0
  53. package/dist/models/permission.model.js +18 -0
  54. package/dist/models/permission.model.js.map +1 -0
  55. package/dist/models/role.model.d.ts +60 -0
  56. package/dist/models/role.model.d.ts.map +1 -0
  57. package/dist/models/role.model.js +18 -0
  58. package/dist/models/role.model.js.map +1 -0
  59. package/dist/permission-map.d.ts +97 -0
  60. package/dist/permission-map.d.ts.map +1 -0
  61. package/dist/permission-map.js +93 -0
  62. package/dist/permission-map.js.map +1 -0
  63. package/dist/permission-registrar.d.ts +183 -0
  64. package/dist/permission-registrar.d.ts.map +1 -0
  65. package/dist/permission-registrar.js +573 -0
  66. package/dist/permission-registrar.js.map +1 -0
  67. package/dist/permissions-config.d.ts +80 -0
  68. package/dist/permissions-config.d.ts.map +1 -0
  69. package/dist/permissions-config.js +22 -0
  70. package/dist/permissions-config.js.map +1 -0
  71. package/dist/permissions-facade.d.ts +74 -0
  72. package/dist/permissions-facade.d.ts.map +1 -0
  73. package/dist/permissions-facade.js +131 -0
  74. package/dist/permissions-facade.js.map +1 -0
  75. package/dist/permissions-service-provider.d.ts +91 -0
  76. package/dist/permissions-service-provider.d.ts.map +1 -0
  77. package/dist/permissions-service-provider.js +137 -0
  78. package/dist/permissions-service-provider.js.map +1 -0
  79. package/dist/relations.d.ts +63 -0
  80. package/dist/relations.d.ts.map +1 -0
  81. package/dist/relations.js +62 -0
  82. package/dist/relations.js.map +1 -0
  83. package/dist/request-cache.d.ts +50 -0
  84. package/dist/request-cache.d.ts.map +1 -0
  85. package/dist/request-cache.js +40 -0
  86. package/dist/request-cache.js.map +1 -0
  87. package/dist/tokens.d.ts +15 -0
  88. package/dist/tokens.d.ts.map +1 -0
  89. package/dist/tokens.js +15 -0
  90. package/dist/tokens.js.map +1 -0
  91. package/package.json +69 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bradie Tilley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # @mahiframework/permissions
2
+
3
+ Permissions — roles, permissions, and cached authorization checks for Mahi.
4
+
5
+ Part of the [Mahi](https://github.com/mahiframework/mahi) framework.
6
+
7
+ See the [documentation](https://github.com/mahiframework/mahi/tree/main/docs/permissions).
@@ -0,0 +1,57 @@
1
+ import { BaseModel } from "@mahiframework/database";
2
+ /**
3
+ * Anything that can hold a role or a permission.
4
+ *
5
+ * Either a model instance (the ordinary case: a `User` from
6
+ * `Auth.user()`), or an explicit `{ type, id }` descriptor. The explicit
7
+ * form is not a convenience — it is what lets a queue job or a CLI
8
+ * command assign a role without hydrating a model, and what lets a
9
+ * caller name a subject whose class isn't loaded.
10
+ *
11
+ * Deliberately NOT a plain `Record` with an `id`: a bare attributes
12
+ * object carries no `morphAlias()`, so the discriminant would have to be
13
+ * guessed. A row that came back from `Auth.user()` IS a hydrated model
14
+ * instance in this framework, so the common path still takes the first
15
+ * form.
16
+ */
17
+ export type Assignee = BaseModel | AssigneeRef;
18
+ /** A subject named without a model instance: `{ type: "User", id: 123n }`. */
19
+ export interface AssigneeRef {
20
+ /** The morph alias, i.e. whatever `Model.morphAlias()` returns for the class. */
21
+ type: string;
22
+ id: bigint;
23
+ }
24
+ /** A resolved subject, the shape every pivot read and write actually uses. */
25
+ export interface ResolvedAssignee {
26
+ morphType: string;
27
+ key: bigint;
28
+ }
29
+ /**
30
+ * Narrow an `Assignee` to the `(model_type, model_id)` pair the pivots
31
+ * store.
32
+ *
33
+ * The key MUST be a `bigint`. `model_has_roles.model_id` is a
34
+ * `bigInteger` column (see the migration for why it can't be text), so a
35
+ * uuid- or string-keyed model cannot hold a role. Caught here rather
36
+ * than at the database because the two engines disagree about how badly:
37
+ * Postgres raises `operator does not exist: bigint = character varying`,
38
+ * a 500 that names no model, while SQLite happily stores the string and
39
+ * then never matches it — a permission check that silently returns false
40
+ * forever. A `number` is rejected for the same reason the snowflake
41
+ * package returns `bigint`: a 64-bit id does not survive the round trip.
42
+ *
43
+ * `morphAlias()` resolves through a `Relation.morphMap()` entry, then
44
+ * `static morphName`, then the TABLE NAME. That last fallback is why the
45
+ * docs recommend `Relation.enforceMorphMap()`: without a map, renaming a
46
+ * table silently orphans every assignment row that named it.
47
+ */
48
+ export declare function resolveAssignee(assignee: Assignee): ResolvedAssignee;
49
+ /**
50
+ * A stable string for one subject, used only as a per-request memo key.
51
+ *
52
+ * Never stored. The separator is `:` and both halves are already
53
+ * constrained (a morph alias is an identifier, a key is digits), so no
54
+ * escaping is needed to keep two different subjects from colliding.
55
+ */
56
+ export declare function assigneeCacheKey(assignee: ResolvedAssignee): string;
57
+ //# sourceMappingURL=assignee.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assignee.d.ts","sourceRoot":"","sources":["../src/assignee.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAGpD;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;AAE/C,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,GAAG,gBAAgB,CASpE;AAUD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,CAEnE"}
@@ -0,0 +1,46 @@
1
+ import { BaseModel } from "@mahiframework/database";
2
+ import { UnsupportedAssigneeKeyError } from "./errors.js";
3
+ /**
4
+ * Narrow an `Assignee` to the `(model_type, model_id)` pair the pivots
5
+ * store.
6
+ *
7
+ * The key MUST be a `bigint`. `model_has_roles.model_id` is a
8
+ * `bigInteger` column (see the migration for why it can't be text), so a
9
+ * uuid- or string-keyed model cannot hold a role. Caught here rather
10
+ * than at the database because the two engines disagree about how badly:
11
+ * Postgres raises `operator does not exist: bigint = character varying`,
12
+ * a 500 that names no model, while SQLite happily stores the string and
13
+ * then never matches it — a permission check that silently returns false
14
+ * forever. A `number` is rejected for the same reason the snowflake
15
+ * package returns `bigint`: a 64-bit id does not survive the round trip.
16
+ *
17
+ * `morphAlias()` resolves through a `Relation.morphMap()` entry, then
18
+ * `static morphName`, then the TABLE NAME. That last fallback is why the
19
+ * docs recommend `Relation.enforceMorphMap()`: without a map, renaming a
20
+ * table silently orphans every assignment row that named it.
21
+ */
22
+ export function resolveAssignee(assignee) {
23
+ if (assignee instanceof BaseModel) {
24
+ const modelClass = assignee.constructor;
25
+ const key = assignee.getRawAttribute(modelClass.primaryKeyColumn);
26
+ return { morphType: modelClass.morphAlias(), key: requireBigint(modelClass.morphAlias(), key) };
27
+ }
28
+ return { morphType: assignee.type, key: requireBigint(assignee.type, assignee.id) };
29
+ }
30
+ function requireBigint(morphType, key) {
31
+ if (typeof key !== "bigint") {
32
+ throw new UnsupportedAssigneeKeyError(morphType, key);
33
+ }
34
+ return key;
35
+ }
36
+ /**
37
+ * A stable string for one subject, used only as a per-request memo key.
38
+ *
39
+ * Never stored. The separator is `:` and both halves are already
40
+ * constrained (a morph alias is an identifier, a key is digits), so no
41
+ * escaping is needed to keep two different subjects from colliding.
42
+ */
43
+ export function assigneeCacheKey(assignee) {
44
+ return `${assignee.morphType}:${assignee.key}`;
45
+ }
46
+ //# sourceMappingURL=assignee.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assignee.js","sourceRoot":"","sources":["../src/assignee.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAgC1D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,eAAe,CAAC,QAAkB;IAChD,IAAI,QAAQ,YAAY,SAAS,EAAE,CAAC;QAClC,MAAM,UAAU,GAAG,QAAQ,CAAC,WAA+B,CAAC;QAC5D,MAAM,GAAG,GAAG,QAAQ,CAAC,eAAe,CAAC,UAAU,CAAC,gBAAgB,CAAY,CAAC;QAE7E,OAAO,EAAE,SAAS,EAAE,UAAU,CAAC,UAAU,EAAE,EAAE,GAAG,EAAE,aAAa,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC;IAClG,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,aAAa,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;AACtF,CAAC;AAED,SAAS,aAAa,CAAC,SAAiB,EAAE,GAAY;IACpD,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,IAAI,2BAA2B,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IACxD,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAA0B;IACzD,OAAO,GAAG,QAAQ,CAAC,SAAS,IAAI,QAAQ,CAAC,GAAG,EAAE,CAAC;AACjD,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { Command } from "@mahiframework/cli";
2
+ /**
3
+ * Forget the cached role/permission map.
4
+ *
5
+ * The registrar already does this on every write it performs, and the
6
+ * model-event listeners cover writes that bypass it, so reaching for this
7
+ * command means something escaped both: a raw `DB.table("roles")` insert,
8
+ * a migration, a restored database dump.
9
+ *
10
+ * It forgets exactly ONE key and touches nothing else.
11
+ * `@mahiframework/cache` has no tags, so there is no flush-by-pattern,
12
+ * and `cache:clear` would take out the app's entire cache to fix a
13
+ * problem with five tables.
14
+ */
15
+ export declare class PermissionsCacheResetCommand extends Command {
16
+ signature: string;
17
+ description: string;
18
+ handle(): Promise<void>;
19
+ }
20
+ //# sourceMappingURL=permissions-cache-reset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions-cache-reset.d.ts","sourceRoot":"","sources":["../../src/commands/permissions-cache-reset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAG7C;;;;;;;;;;;;GAYG;AACH,qBAAa,4BAA6B,SAAQ,OAAO;IACvD,SAAS,SAA6B;IACtC,WAAW,SAAgD;IAErD,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAK9B"}
@@ -0,0 +1,24 @@
1
+ import { Command } from "@mahiframework/cli";
2
+ import { Permissions } from "../permissions-facade.js";
3
+ /**
4
+ * Forget the cached role/permission map.
5
+ *
6
+ * The registrar already does this on every write it performs, and the
7
+ * model-event listeners cover writes that bypass it, so reaching for this
8
+ * command means something escaped both: a raw `DB.table("roles")` insert,
9
+ * a migration, a restored database dump.
10
+ *
11
+ * It forgets exactly ONE key and touches nothing else.
12
+ * `@mahiframework/cache` has no tags, so there is no flush-by-pattern,
13
+ * and `cache:clear` would take out the app's entire cache to fix a
14
+ * problem with five tables.
15
+ */
16
+ export class PermissionsCacheResetCommand extends Command {
17
+ signature = "permissions:cache-reset";
18
+ description = "Forget the cached role and permission map.";
19
+ async handle() {
20
+ await Permissions.forgetCache();
21
+ this.app.logger.info("permissions:cache-reset forgot the cached role/permission map.");
22
+ }
23
+ }
24
+ //# sourceMappingURL=permissions-cache-reset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions-cache-reset.js","sourceRoot":"","sources":["../../src/commands/permissions-cache-reset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,4BAA6B,SAAQ,OAAO;IACvD,SAAS,GAAG,yBAAyB,CAAC;IACtC,WAAW,GAAG,4CAA4C,CAAC;IAE3D,KAAK,CAAC,MAAM;QACV,MAAM,WAAW,CAAC,WAAW,EAAE,CAAC;QAEhC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,gEAAgE,CAAC,CAAC;IACzF,CAAC;CACF"}
@@ -0,0 +1,37 @@
1
+ import { Command } from "@mahiframework/cli";
2
+ /**
3
+ * Validate stored assignment rows against the models and guards that
4
+ * exist.
5
+ *
6
+ * Two things can rot without anything noticing:
7
+ *
8
+ * 1. A `model_type` that names no model. Assignment rows store a morph
9
+ * alias, and `morphAlias()` falls back to the TABLE NAME when no
10
+ * `Relation.morphMap()` entry exists — so renaming a table orphans
11
+ * every row that named the old one. The rows stay, the checks stop
12
+ * matching, and nothing errors. This is the whole argument for
13
+ * `Relation.enforceMorphMap()`, and this command is how an app
14
+ * without one finds out.
15
+ *
16
+ * 2. A `guard_name` naming no configured guard. `guard_name` is NOT NULL
17
+ * with no wildcard, so a role created under a guard that was later
18
+ * renamed in `config/auth.ts` can never again satisfy a check.
19
+ *
20
+ * A CHECK, not a boot failure. A model can legitimately be registered by
21
+ * a provider this command never loads, so an unmatched alias is reported
22
+ * rather than fatal at runtime. Exits non-zero so CI can gate on it.
23
+ */
24
+ export declare class PermissionsCheckCommand extends Command {
25
+ signature: string;
26
+ description: string;
27
+ handle(): Promise<void>;
28
+ /** Every distinct `model_type` across both assignment tables, with a row count. */
29
+ private assignedAliases;
30
+ /**
31
+ * The app's configured guard names, or null if auth isn't configured at
32
+ * all — in which case there is nothing to validate against and the
33
+ * guard check is skipped rather than reporting every guard as unknown.
34
+ */
35
+ private configuredGuards;
36
+ }
37
+ //# sourceMappingURL=permissions-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions-check.d.ts","sourceRoot":"","sources":["../../src/commands/permissions-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAW7C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,uBAAwB,SAAQ,OAAO;IAClD,SAAS,SAAuB;IAChC,WAAW,SAA4E;IAEjF,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IA4D7B,mFAAmF;YACrE,eAAe;IAc7B;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;CAKzB"}
@@ -0,0 +1,92 @@
1
+ import { Command } from "@mahiframework/cli";
2
+ import { DB, Relation } from "@mahiframework/database";
3
+ import { PERMISSIONS_TOKEN } from "../tokens.js";
4
+ /**
5
+ * Validate stored assignment rows against the models and guards that
6
+ * exist.
7
+ *
8
+ * Two things can rot without anything noticing:
9
+ *
10
+ * 1. A `model_type` that names no model. Assignment rows store a morph
11
+ * alias, and `morphAlias()` falls back to the TABLE NAME when no
12
+ * `Relation.morphMap()` entry exists — so renaming a table orphans
13
+ * every row that named the old one. The rows stay, the checks stop
14
+ * matching, and nothing errors. This is the whole argument for
15
+ * `Relation.enforceMorphMap()`, and this command is how an app
16
+ * without one finds out.
17
+ *
18
+ * 2. A `guard_name` naming no configured guard. `guard_name` is NOT NULL
19
+ * with no wildcard, so a role created under a guard that was later
20
+ * renamed in `config/auth.ts` can never again satisfy a check.
21
+ *
22
+ * A CHECK, not a boot failure. A model can legitimately be registered by
23
+ * a provider this command never loads, so an unmatched alias is reported
24
+ * rather than fatal at runtime. Exits non-zero so CI can gate on it.
25
+ */
26
+ export class PermissionsCheckCommand extends Command {
27
+ signature = "permissions:check";
28
+ description = "Validate stored role/permission assignments against models and guards.";
29
+ async handle() {
30
+ const registrar = this.app.make(PERMISSIONS_TOKEN);
31
+ const map = await registrar.map();
32
+ const errors = [];
33
+ for (const alias of await this.assignedAliases()) {
34
+ if (Relation.getMorphedModel(alias.model_type) !== undefined) {
35
+ continue;
36
+ }
37
+ errors.push(`"${alias.model_type}" names no registered model, but ${alias.total} assignment row(s) ` +
38
+ `use it. Morph aliases come from a Relation.morphMap() entry, a static morphName, or ` +
39
+ `the table name — a renamed table orphans rows that named the old one. Register the ` +
40
+ `alias with Relation.morphMap(), or delete the rows.`);
41
+ }
42
+ // Read straight off the map rather than the tables: it is already
43
+ // loaded, and a guard present in the cache but not in config is the
44
+ // same defect as one present in the table.
45
+ const guards = new Set();
46
+ for (const role of map.roles) {
47
+ guards.add(role.guardName);
48
+ }
49
+ for (const permission of map.permissions) {
50
+ guards.add(permission.guardName);
51
+ }
52
+ const configured = this.configuredGuards();
53
+ if (configured !== null) {
54
+ for (const guard of guards) {
55
+ if (!configured.has(guard)) {
56
+ errors.push(`guard "${guard}" is used by a role or permission but is not configured in ` +
57
+ `config/auth.ts. Nothing can ever check against it.`);
58
+ }
59
+ }
60
+ }
61
+ for (const error of errors) {
62
+ this.app.logger.error(`permissions:check: ${error}`);
63
+ }
64
+ if (errors.length > 0) {
65
+ process.exitCode = 1;
66
+ return;
67
+ }
68
+ this.app.logger.info(`permissions:check: ${map.roles.length} role(s), ${map.permissions.length} permission(s), ` +
69
+ `${guards.size} guard(s), no errors.`);
70
+ }
71
+ /** Every distinct `model_type` across both assignment tables, with a row count. */
72
+ async assignedAliases() {
73
+ const tally = new Map();
74
+ for (const table of ["model_has_roles", "model_has_permissions"]) {
75
+ const rows = await DB.table(table).select("model_type").get();
76
+ for (const row of rows) {
77
+ tally.set(row.model_type, (tally.get(row.model_type) ?? 0) + 1);
78
+ }
79
+ }
80
+ return [...tally].map(([model_type, total]) => ({ model_type, total }));
81
+ }
82
+ /**
83
+ * The app's configured guard names, or null if auth isn't configured at
84
+ * all — in which case there is nothing to validate against and the
85
+ * guard check is skipped rather than reporting every guard as unknown.
86
+ */
87
+ configuredGuards() {
88
+ const guards = this.app.config.get("auth.guards");
89
+ return guards === undefined ? null : new Set(Object.keys(guards));
90
+ }
91
+ }
92
+ //# sourceMappingURL=permissions-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions-check.js","sourceRoot":"","sources":["../../src/commands/permissions-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { Command as CommanderCommand } from "commander";
2
+ import { Command } from "@mahiframework/cli";
3
+ /**
4
+ * Print every role and what it grants.
5
+ *
6
+ * The answer to "why can this user do that", which is otherwise five
7
+ * tables and a join. Reads the same cached map the checks read, so it
8
+ * shows what the application currently BELIEVES rather than what the
9
+ * tables say — which is the more useful answer when the suspicion is a
10
+ * stale cache. Follow with `permissions:cache-reset` to compare.
11
+ */
12
+ export declare class PermissionsShowCommand extends Command {
13
+ signature: string;
14
+ description: string;
15
+ configure(program: CommanderCommand): void;
16
+ handle(options?: {
17
+ guard?: string;
18
+ }): Promise<void>;
19
+ }
20
+ //# sourceMappingURL=permissions-show.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions-show.d.ts","sourceRoot":"","sources":["../../src/commands/permissions-show.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAI7C;;;;;;;;GAQG;AACH,qBAAa,sBAAuB,SAAQ,OAAO;IACjD,SAAS,SAAsB;IAC/B,WAAW,SAAiE;IAE5E,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAIpC,MAAM,CAAC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CA6C9D"}
@@ -0,0 +1,49 @@
1
+ import { Command } from "@mahiframework/cli";
2
+ import { PERMISSIONS_TOKEN } from "../tokens.js";
3
+ /**
4
+ * Print every role and what it grants.
5
+ *
6
+ * The answer to "why can this user do that", which is otherwise five
7
+ * tables and a join. Reads the same cached map the checks read, so it
8
+ * shows what the application currently BELIEVES rather than what the
9
+ * tables say — which is the more useful answer when the suspicion is a
10
+ * stale cache. Follow with `permissions:cache-reset` to compare.
11
+ */
12
+ export class PermissionsShowCommand extends Command {
13
+ signature = "permissions:show";
14
+ description = "Print every role, its guard, and the permissions it grants.";
15
+ configure(program) {
16
+ program.option("--guard <guard>", "Only show roles and permissions for this guard");
17
+ }
18
+ async handle(options = {}) {
19
+ const map = await this.app.make(PERMISSIONS_TOKEN).map();
20
+ const guard = options.guard;
21
+ const roles = guard === undefined ? map.roles : map.roles.filter((r) => r.guardName === guard);
22
+ const permissions = guard === undefined ? map.permissions : map.permissions.filter((p) => p.guardName === guard);
23
+ if (roles.length === 0 && permissions.length === 0) {
24
+ this.line(guard === undefined
25
+ ? "No roles or permissions are defined."
26
+ : `No roles or permissions are defined for guard "${guard}".`);
27
+ return;
28
+ }
29
+ this.table(["Role", "Guard", "Permissions"], roles.map((role) => [
30
+ role.name,
31
+ role.guardName,
32
+ role.permissionIds
33
+ .map((id) => map.permissionById.get(id)?.name ?? `<missing ${id}>`)
34
+ .sort()
35
+ .join(", ") || "—",
36
+ ]));
37
+ // Listed separately because a permission attached to no role is
38
+ // invisible above, and an unattached permission is usually either a
39
+ // direct grant or a typo — both worth seeing.
40
+ const attached = new Set(roles.flatMap((role) => role.permissionIds.map(String)));
41
+ const orphans = permissions.filter((p) => !attached.has(String(p.id)));
42
+ if (orphans.length > 0) {
43
+ this.line("");
44
+ this.line("Permissions not granted by any role (direct grants only):");
45
+ this.table(["Permission", "Guard"], orphans.map((permission) => [permission.name, permission.guardName]));
46
+ }
47
+ }
48
+ }
49
+ //# sourceMappingURL=permissions-show.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions-show.js","sourceRoot":"","sources":["../../src/commands/permissions-show.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD;;;;;;;;GAQG;AACH,MAAM,OAAO,sBAAuB,SAAQ,OAAO;IACjD,SAAS,GAAG,kBAAkB,CAAC;IAC/B,WAAW,GAAG,6DAA6D,CAAC;IAE5E,SAAS,CAAC,OAAyB;QACjC,OAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,gDAAgD,CAAC,CAAC;IACtF,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,UAA8B,EAAE;QAC3C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAsB,iBAAiB,CAAC,CAAC,GAAG,EAAE,CAAC;QAC9E,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAE5B,MAAM,KAAK,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC;QAC/F,MAAM,WAAW,GACf,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC;QAE/F,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnD,IAAI,CAAC,IAAI,CACP,KAAK,KAAK,SAAS;gBACjB,CAAC,CAAC,sCAAsC;gBACxC,CAAC,CAAC,kDAAkD,KAAK,IAAI,CAChE,CAAC;YAEF,OAAO;QACT,CAAC;QAED,IAAI,CAAC,KAAK,CACR,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,EAChC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAClB,IAAI,CAAC,IAAI;YACT,IAAI,CAAC,SAAS;YACd,IAAI,CAAC,aAAa;iBACf,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,IAAI,YAAY,EAAE,GAAG,CAAC;iBAClE,IAAI,EAAE;iBACN,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG;SACrB,CAAC,CACH,CAAC;QAEF,gEAAgE;QAChE,oEAAoE;QACpE,8CAA8C;QAC9C,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAClF,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAEvE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACd,IAAI,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;YACvE,IAAI,CAAC,KAAK,CACR,CAAC,YAAY,EAAE,OAAO,CAAC,EACvB,OAAO,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,CACrE,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Base class for every error this package throws, so an app can catch
3
+ * the whole family in one `catch` when it would rather render a generic
4
+ * failure than discriminate.
5
+ */
6
+ export declare class PermissionsError extends Error {
7
+ constructor(message: string);
8
+ }
9
+ /**
10
+ * A role was looked up by name and does not exist.
11
+ *
12
+ * Thrown rather than returning null because every caller is a write
13
+ * (`assignRole("admni")`) or an explicit `findRole`. A silent no-op on a
14
+ * typo'd role name is the failure mode an authorization system can least
15
+ * afford: it reads as "assigned" and grants nothing.
16
+ */
17
+ export declare class RoleNotFoundError extends PermissionsError {
18
+ readonly roleName: string;
19
+ readonly guard: string;
20
+ constructor(roleName: string, guard: string);
21
+ }
22
+ /** A permission was looked up by name and does not exist. See `RoleNotFoundError`. */
23
+ export declare class PermissionNotFoundError extends PermissionsError {
24
+ readonly permissionName: string;
25
+ readonly guard: string;
26
+ constructor(permissionName: string, guard: string);
27
+ }
28
+ /**
29
+ * A role or permission with this name already exists for this guard.
30
+ *
31
+ * `(name, guard_name)` is unique in the database, so this is also a
32
+ * `UniqueConstraintViolationException` waiting to happen; raising it by
33
+ * name first means the message says which name, rather than which index.
34
+ */
35
+ export declare class DuplicateNameError extends PermissionsError {
36
+ readonly kind: "role" | "permission";
37
+ readonly name: string;
38
+ readonly guard: string;
39
+ constructor(kind: "role" | "permission", name: string, guard: string);
40
+ }
41
+ /**
42
+ * The assignee's primary key is not a `bigint`.
43
+ *
44
+ * `model_has_roles.model_id` is a `bigInteger` column, so only
45
+ * snowflake-keyed models can hold roles. Thrown here rather than letting
46
+ * the value reach SQL: Postgres rejects a non-numeric string against a
47
+ * `bigint` with `operator does not exist`, which is a 500 that says
48
+ * nothing about why, and SQLite would accept it and simply never match.
49
+ */
50
+ export declare class UnsupportedAssigneeKeyError extends PermissionsError {
51
+ readonly morphType: string;
52
+ readonly key: unknown;
53
+ constructor(morphType: string, key: unknown);
54
+ }
55
+ /**
56
+ * No guard name could be resolved.
57
+ *
58
+ * Every role and permission is scoped to a guard, and the column is NOT
59
+ * NULL with no wildcard, so there is nothing sensible to fall back to.
60
+ * Failing here beats stamping `""` and silently creating a guard nobody
61
+ * will ever check against.
62
+ */
63
+ export declare class UnresolvedGuardError extends PermissionsError {
64
+ constructor();
65
+ }
66
+ /**
67
+ * An assignee's `model_type` names no registered model.
68
+ *
69
+ * Only raised by `permissions:check`, never during a check: a stale
70
+ * discriminant is data, and the framework's own morph resolution treats
71
+ * an unknown one as a dangling foreign key rather than crashing a read.
72
+ */
73
+ export declare class UnknownMorphAliasError extends PermissionsError {
74
+ readonly alias: string;
75
+ constructor(alias: string);
76
+ }
77
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;GAOG;AACH,qBAAa,iBAAkB,SAAQ,gBAAgB;IAEnD,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM;gBADb,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM;CAIzB;AAED,sFAAsF;AACtF,qBAAa,uBAAwB,SAAQ,gBAAgB;IAEzD,QAAQ,CAAC,cAAc,EAAE,MAAM;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM;gBADb,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,MAAM;CAIzB;AAED;;;;;;GAMG;AACH,qBAAa,kBAAmB,SAAQ,gBAAgB;IAEpD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM;gBAFb,IAAI,EAAE,MAAM,GAAG,YAAY,EAC3B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM;CAIzB;AAED;;;;;;;;GAQG;AACH,qBAAa,2BAA4B,SAAQ,gBAAgB;IAE7D,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,QAAQ,CAAC,GAAG,EAAE,OAAO;gBADZ,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,OAAO;CAQxB;AAED;;;;;;;GAOG;AACH,qBAAa,oBAAqB,SAAQ,gBAAgB;;CAOzD;AAED;;;;;;GAMG;AACH,qBAAa,sBAAuB,SAAQ,gBAAgB;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM;gBAAb,KAAK,EAAE,MAAM;CAMnC"}
package/dist/errors.js ADDED
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Base class for every error this package throws, so an app can catch
3
+ * the whole family in one `catch` when it would rather render a generic
4
+ * failure than discriminate.
5
+ */
6
+ export class PermissionsError extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = new.target.name;
10
+ }
11
+ }
12
+ /**
13
+ * A role was looked up by name and does not exist.
14
+ *
15
+ * Thrown rather than returning null because every caller is a write
16
+ * (`assignRole("admni")`) or an explicit `findRole`. A silent no-op on a
17
+ * typo'd role name is the failure mode an authorization system can least
18
+ * afford: it reads as "assigned" and grants nothing.
19
+ */
20
+ export class RoleNotFoundError extends PermissionsError {
21
+ roleName;
22
+ guard;
23
+ constructor(roleName, guard) {
24
+ super(`There is no role named "${roleName}" for guard "${guard}".`);
25
+ this.roleName = roleName;
26
+ this.guard = guard;
27
+ }
28
+ }
29
+ /** A permission was looked up by name and does not exist. See `RoleNotFoundError`. */
30
+ export class PermissionNotFoundError extends PermissionsError {
31
+ permissionName;
32
+ guard;
33
+ constructor(permissionName, guard) {
34
+ super(`There is no permission named "${permissionName}" for guard "${guard}".`);
35
+ this.permissionName = permissionName;
36
+ this.guard = guard;
37
+ }
38
+ }
39
+ /**
40
+ * A role or permission with this name already exists for this guard.
41
+ *
42
+ * `(name, guard_name)` is unique in the database, so this is also a
43
+ * `UniqueConstraintViolationException` waiting to happen; raising it by
44
+ * name first means the message says which name, rather than which index.
45
+ */
46
+ export class DuplicateNameError extends PermissionsError {
47
+ kind;
48
+ name;
49
+ guard;
50
+ constructor(kind, name, guard) {
51
+ super(`A ${kind} named "${name}" already exists for guard "${guard}".`);
52
+ this.kind = kind;
53
+ this.name = name;
54
+ this.guard = guard;
55
+ }
56
+ }
57
+ /**
58
+ * The assignee's primary key is not a `bigint`.
59
+ *
60
+ * `model_has_roles.model_id` is a `bigInteger` column, so only
61
+ * snowflake-keyed models can hold roles. Thrown here rather than letting
62
+ * the value reach SQL: Postgres rejects a non-numeric string against a
63
+ * `bigint` with `operator does not exist`, which is a 500 that says
64
+ * nothing about why, and SQLite would accept it and simply never match.
65
+ */
66
+ export class UnsupportedAssigneeKeyError extends PermissionsError {
67
+ morphType;
68
+ key;
69
+ constructor(morphType, key) {
70
+ super(`"${morphType}" keys on ${typeof key}, but model_has_roles.model_id is a bigInteger, ` +
71
+ `so only snowflake-keyed models can hold roles or permissions. ` +
72
+ `Give the model \`keyType: snowflake()\`, or assign to a model that has one.`);
73
+ this.morphType = morphType;
74
+ this.key = key;
75
+ }
76
+ }
77
+ /**
78
+ * No guard name could be resolved.
79
+ *
80
+ * Every role and permission is scoped to a guard, and the column is NOT
81
+ * NULL with no wildcard, so there is nothing sensible to fall back to.
82
+ * Failing here beats stamping `""` and silently creating a guard nobody
83
+ * will ever check against.
84
+ */
85
+ export class UnresolvedGuardError extends PermissionsError {
86
+ constructor() {
87
+ super(`No guard name could be resolved. Pass one explicitly ({ guard: "web" }), set ` +
88
+ `\`guard\` in config/permissions.ts, or configure \`auth.default\`.`);
89
+ }
90
+ }
91
+ /**
92
+ * An assignee's `model_type` names no registered model.
93
+ *
94
+ * Only raised by `permissions:check`, never during a check: a stale
95
+ * discriminant is data, and the framework's own morph resolution treats
96
+ * an unknown one as a dangling foreign key rather than crashing a read.
97
+ */
98
+ export class UnknownMorphAliasError extends PermissionsError {
99
+ alias;
100
+ constructor(alias) {
101
+ super(`"${alias}" matches no registered model. Morph aliases come from a Relation.morphMap() ` +
102
+ `entry, a model's \`static morphName\`, or its table name.`);
103
+ this.alias = alias;
104
+ }
105
+ }
106
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACzC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;IAC9B,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,iBAAkB,SAAQ,gBAAgB;IAE1C;IACA;IAFX,YACW,QAAgB,EAChB,KAAa;QAEtB,KAAK,CAAC,2BAA2B,QAAQ,gBAAgB,KAAK,IAAI,CAAC,CAAC;QAH3D,aAAQ,GAAR,QAAQ,CAAQ;QAChB,UAAK,GAAL,KAAK,CAAQ;IAGxB,CAAC;CACF;AAED,sFAAsF;AACtF,MAAM,OAAO,uBAAwB,SAAQ,gBAAgB;IAEhD;IACA;IAFX,YACW,cAAsB,EACtB,KAAa;QAEtB,KAAK,CAAC,iCAAiC,cAAc,gBAAgB,KAAK,IAAI,CAAC,CAAC;QAHvE,mBAAc,GAAd,cAAc,CAAQ;QACtB,UAAK,GAAL,KAAK,CAAQ;IAGxB,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,OAAO,kBAAmB,SAAQ,gBAAgB;IAE3C;IACA;IACA;IAHX,YACW,IAA2B,EAC3B,IAAY,EACZ,KAAa;QAEtB,KAAK,CAAC,KAAK,IAAI,WAAW,IAAI,+BAA+B,KAAK,IAAI,CAAC,CAAC;QAJ/D,SAAI,GAAJ,IAAI,CAAuB;QAC3B,SAAI,GAAJ,IAAI,CAAQ;QACZ,UAAK,GAAL,KAAK,CAAQ;IAGxB,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,OAAO,2BAA4B,SAAQ,gBAAgB;IAEpD;IACA;IAFX,YACW,SAAiB,EACjB,GAAY;QAErB,KAAK,CACH,IAAI,SAAS,aAAa,OAAO,GAAG,kDAAkD;YACpF,gEAAgE;YAChE,6EAA6E,CAChF,CAAC;QAPO,cAAS,GAAT,SAAS,CAAQ;QACjB,QAAG,GAAH,GAAG,CAAS;IAOvB,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,oBAAqB,SAAQ,gBAAgB;IACxD;QACE,KAAK,CACH,+EAA+E;YAC7E,oEAAoE,CACvE,CAAC;IACJ,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,OAAO,sBAAuB,SAAQ,gBAAgB;IACrC;IAArB,YAAqB,KAAa;QAChC,KAAK,CACH,IAAI,KAAK,+EAA+E;YACtF,2DAA2D,CAC9D,CAAC;QAJiB,UAAK,GAAL,KAAK,CAAQ;IAKlC,CAAC;CACF"}
@@ -0,0 +1,25 @@
1
+ export { PermissionsServiceProvider, PERMISSIONS_TOKEN } from "./permissions-service-provider.js";
2
+ export { Permissions } from "./permissions-facade.js";
3
+ export { PermissionRegistrar } from "./permission-registrar.js";
4
+ export type { GuardOption, RoleRef, PermissionRef } from "./permission-registrar.js";
5
+ export { Role } from "./models/role.model.js";
6
+ export type { RoleAttributes } from "./models/role.model.js";
7
+ export { Permission } from "./models/permission.model.js";
8
+ export type { PermissionAttributes } from "./models/permission.model.js";
9
+ export { rolesRelation, permissionsRelation } from "./relations.js";
10
+ export { resolveAssignee, assigneeCacheKey } from "./assignee.js";
11
+ export type { Assignee, AssigneeRef, ResolvedAssignee } from "./assignee.js";
12
+ export { resolveConfig } from "./permissions-config.js";
13
+ export type { PermissionsConfig, PermissionsCacheConfig, ResolvedPermissionsConfig, } from "./permissions-config.js";
14
+ export { role } from "./middleware/role.js";
15
+ export { permission } from "./middleware/permission.js";
16
+ export { roleOrPermission } from "./middleware/role-or-permission.js";
17
+ export type { RoleOrPermissionOptions } from "./middleware/role-or-permission.js";
18
+ export { withPermissionCache } from "./request-cache.js";
19
+ export { InvalidatePermissionCacheListener } from "./listeners/invalidate-permission-cache.listener.js";
20
+ export { PermissionsCacheResetCommand } from "./commands/permissions-cache-reset.js";
21
+ export { PermissionsCheckCommand } from "./commands/permissions-check.js";
22
+ export { PermissionsShowCommand } from "./commands/permissions-show.js";
23
+ export type { PermissionMap, MappedRole, MappedPermission, SerializedPermissionMap, } from "./permission-map.js";
24
+ export { PermissionsError, RoleNotFoundError, PermissionNotFoundError, DuplicateNameError, UnsupportedAssigneeKeyError, UnresolvedGuardError, UnknownMorphAliasError, } from "./errors.js";
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAClG,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAErF,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,YAAY,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEzE,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAClE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAE7E,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,YAAY,EACV,iBAAiB,EACjB,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AACtE,YAAY,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAC;AAElF,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,EAAE,iCAAiC,EAAE,MAAM,qDAAqD,CAAC;AAExG,OAAO,EAAE,4BAA4B,EAAE,MAAM,uCAAuC,CAAC;AACrF,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAKxE,YAAY,EACV,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,uBAAuB,EACvB,kBAAkB,EAClB,2BAA2B,EAC3B,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,aAAa,CAAC"}