@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
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The optional `"permissions"` config namespace.
3
+ *
4
+ * Every field has a default, so an app that never writes
5
+ * `config/permissions.ts` gets a working package: roles scoped to the
6
+ * app's default auth guard, a day-long cache in the default store, and
7
+ * the gate hook registered.
8
+ *
9
+ * NOTHING HERE IMPORTS A MODEL. A `config/*.ts` is loaded before
10
+ * `app.bootstrap()`, so importing a model would pull the ORM into
11
+ * config-load time.
12
+ */
13
+ export interface PermissionsConfig {
14
+ /**
15
+ * The guard name stamped on new roles and permissions, and used by any
16
+ * check that doesn't name one.
17
+ *
18
+ * Defaults to `auth.default`, which is what a single-guard app wants
19
+ * and never has to think about. A multi-guard app that wants `api`
20
+ * roles passes `{ guard: "api" }` per call; this is only the fallback.
21
+ *
22
+ * There is no wildcard guard. See the migration for why the column
23
+ * cannot be nullable.
24
+ */
25
+ guard?: string;
26
+ cache?: PermissionsCacheConfig;
27
+ /**
28
+ * Register the `Gate.before()` hook that makes `can("posts.edit")`
29
+ * consult permissions.
30
+ *
31
+ * On by default: it is the headline integration, and an app that
32
+ * installs this package and then finds `can()` ignores it has been
33
+ * surprised in the expensive direction. Set `false` to check only
34
+ * through this package's own API and middleware.
35
+ */
36
+ gate?: boolean;
37
+ }
38
+ export interface PermissionsCacheConfig {
39
+ /**
40
+ * The single key holding the whole role/permission map.
41
+ *
42
+ * One key, not one per entity, because `@mahiframework/cache` has no
43
+ * tags: there is no way to flush by pattern, so every key this package
44
+ * writes is a key it must be able to name later. One is nameable.
45
+ */
46
+ key?: string;
47
+ /** A named cache store, else the default one. */
48
+ store?: string;
49
+ /**
50
+ * How long the map survives, in seconds. Defaults to 24 hours.
51
+ *
52
+ * A TTL rather than no-expiry specifically BECAUSE there are no cache
53
+ * tags. Invalidation here is explicit `forget()` on write plus model-
54
+ * event listeners, and if some path ever escapes both, a `null` TTL
55
+ * would make the stale map permanent. A day is short enough that a
56
+ * missed invalidation is an incident with an end, and long enough that
57
+ * the map is effectively always warm.
58
+ */
59
+ ttlSeconds?: number;
60
+ }
61
+ /** The config with every default applied, built once at provider boot. */
62
+ export interface ResolvedPermissionsConfig {
63
+ /** Null when neither config nor `auth.default` named one; resolved lazily so boot doesn't fail. */
64
+ guard: string | null;
65
+ cacheKey: string;
66
+ cacheStore: string | undefined;
67
+ cacheTtlSeconds: number;
68
+ gate: boolean;
69
+ }
70
+ /**
71
+ * Normalise a config block once, at provider boot.
72
+ *
73
+ * Every default is applied here with `??`, which is both the single place
74
+ * to read them and immune to merge-order surprises: contributing them via
75
+ * `ConfigRepository.merge()` would deep-merge the INCOMING values last
76
+ * and silently overwrite the app's own config rather than layering under
77
+ * it.
78
+ */
79
+ export declare function resolveConfig(config?: PermissionsConfig): ResolvedPermissionsConfig;
80
+ //# sourceMappingURL=permissions-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions-config.d.ts","sourceRoot":"","sources":["../src/permissions-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;;;OAUG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAE/B;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,0EAA0E;AAC1E,MAAM,WAAW,yBAAyB;IACxC,mGAAmG;IACnG,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;CACf;AAKD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,MAAM,GAAE,iBAAsB,GAAG,yBAAyB,CAUvF"}
@@ -0,0 +1,22 @@
1
+ const DEFAULT_CACHE_KEY = "mahi.permissions";
2
+ const DEFAULT_TTL_SECONDS = 86_400;
3
+ /**
4
+ * Normalise a config block once, at provider boot.
5
+ *
6
+ * Every default is applied here with `??`, which is both the single place
7
+ * to read them and immune to merge-order surprises: contributing them via
8
+ * `ConfigRepository.merge()` would deep-merge the INCOMING values last
9
+ * and silently overwrite the app's own config rather than layering under
10
+ * it.
11
+ */
12
+ export function resolveConfig(config = {}) {
13
+ const cache = config.cache ?? {};
14
+ return {
15
+ guard: config.guard ?? null,
16
+ cacheKey: cache.key ?? DEFAULT_CACHE_KEY,
17
+ cacheStore: cache.store,
18
+ cacheTtlSeconds: cache.ttlSeconds ?? DEFAULT_TTL_SECONDS,
19
+ gate: config.gate ?? true,
20
+ };
21
+ }
22
+ //# sourceMappingURL=permissions-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions-config.js","sourceRoot":"","sources":["../src/permissions-config.ts"],"names":[],"mappings":"AA4EA,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAC7C,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEnC;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,SAA4B,EAAE;IAC1D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;IAEjC,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,IAAI;QAC3B,QAAQ,EAAE,KAAK,CAAC,GAAG,IAAI,iBAAiB;QACxC,UAAU,EAAE,KAAK,CAAC,KAAK;QACvB,eAAe,EAAE,KAAK,CAAC,UAAU,IAAI,mBAAmB;QACxD,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,IAAI;KAC1B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,74 @@
1
+ import type { Assignee } from "./assignee.js";
2
+ import type { Permission } from "./models/permission.model.js";
3
+ import type { Role } from "./models/role.model.js";
4
+ import type { GuardOption, PermissionRef, PermissionRegistrar, RoleRef } from "./permission-registrar.js";
5
+ declare const Permissions_base: {
6
+ new (): {};
7
+ _swapped?: PermissionRegistrar | undefined;
8
+ instance(): PermissionRegistrar;
9
+ swap<S extends Partial<PermissionRegistrar>>(fake: S): S;
10
+ restore(): void;
11
+ isSwapped(): boolean;
12
+ };
13
+ /**
14
+ * Thin facade over the `PermissionRegistrar` singleton bound at
15
+ * `PERMISSIONS_TOKEN`.
16
+ *
17
+ * await Permissions.assignRole(user, "admin");
18
+ * if (await Permissions.hasPermissionTo(user, "posts.edit")) { ... }
19
+ *
20
+ * Every method takes the subject explicitly. Laravel's spatie package
21
+ * reads `$user->assignRole(...)` off a trait, and this framework has no
22
+ * traits and no package that touches the app's `User` model — behaviour
23
+ * is attached through a container singleton, never by extending a model
24
+ * the app owns. The explicit subject also works where a method could
25
+ * not: on any polymorphic entity uniformly, and in a queue job holding
26
+ * only a `{ type, id }` descriptor.
27
+ *
28
+ * Every call is `async`. There is no synchronous variant, and there
29
+ * cannot be: the first check of a process reads the database.
30
+ */
31
+ export declare class Permissions extends Permissions_base {
32
+ static createRole(name: string, options?: GuardOption): Promise<Role>;
33
+ static createPermission(name: string, options?: GuardOption): Promise<Permission>;
34
+ static findOrCreateRole(name: string, options?: GuardOption): Promise<Role>;
35
+ static findOrCreatePermission(name: string, options?: GuardOption): Promise<Permission>;
36
+ static findRole(name: string, options?: GuardOption): Promise<Role>;
37
+ static findPermission(name: string, options?: GuardOption): Promise<Permission>;
38
+ static deleteRole(role: RoleRef, options?: GuardOption): Promise<void>;
39
+ static deletePermission(permission: PermissionRef, options?: GuardOption): Promise<void>;
40
+ static givePermissionToRole(role: RoleRef, permissions: PermissionRef | PermissionRef[], options?: GuardOption): Promise<void>;
41
+ static revokePermissionFromRole(role: RoleRef, permissions: PermissionRef | PermissionRef[], options?: GuardOption): Promise<void>;
42
+ static syncRolePermissions(role: RoleRef, permissions: PermissionRef | PermissionRef[], options?: GuardOption): Promise<void>;
43
+ static assignRole(assignee: Assignee, roles: RoleRef | RoleRef[], options?: GuardOption): Promise<void>;
44
+ static removeRole(assignee: Assignee, roles: RoleRef | RoleRef[], options?: GuardOption): Promise<void>;
45
+ /** Makes the subject's roles exactly `roles`. An empty array removes all of them. */
46
+ static syncRoles(assignee: Assignee, roles: RoleRef | RoleRef[], options?: GuardOption): Promise<void>;
47
+ static givePermissionTo(assignee: Assignee, permissions: PermissionRef | PermissionRef[], options?: GuardOption): Promise<void>;
48
+ static revokePermissionTo(assignee: Assignee, permissions: PermissionRef | PermissionRef[], options?: GuardOption): Promise<void>;
49
+ /** Makes the subject's DIRECT permissions exactly `permissions`. Roles are untouched. */
50
+ static syncPermissions(assignee: Assignee, permissions: PermissionRef | PermissionRef[], options?: GuardOption): Promise<void>;
51
+ static hasRole(assignee: Assignee, role: string | bigint, options?: GuardOption): Promise<boolean>;
52
+ static hasAnyRole(assignee: Assignee, roles: Array<string | bigint>, options?: GuardOption): Promise<boolean>;
53
+ static hasAllRoles(assignee: Assignee, roles: Array<string | bigint>, options?: GuardOption): Promise<boolean>;
54
+ /** True if the subject holds this permission by ANY route: a role, or directly. */
55
+ static hasPermissionTo(assignee: Assignee, permission: string | bigint, options?: GuardOption): Promise<boolean>;
56
+ static hasAnyPermission(assignee: Assignee, permissions: Array<string | bigint>, options?: GuardOption): Promise<boolean>;
57
+ static hasAllPermissions(assignee: Assignee, permissions: Array<string | bigint>, options?: GuardOption): Promise<boolean>;
58
+ /** True only if granted directly. A permission held via a role answers false here. */
59
+ static hasDirectPermission(assignee: Assignee, permission: string | bigint, options?: GuardOption): Promise<boolean>;
60
+ static getRoleNames(assignee: Assignee, options?: GuardOption): Promise<Set<string>>;
61
+ static getAllPermissions(assignee: Assignee, options?: GuardOption): Promise<Set<string>>;
62
+ static getDirectPermissions(assignee: Assignee, options?: GuardOption): Promise<Set<string>>;
63
+ static getPermissionsViaRoles(assignee: Assignee, options?: GuardOption): Promise<Set<string>>;
64
+ /** Drop the cached role/permission map and every memoised assignment. */
65
+ static forgetCache(): Promise<void>;
66
+ /**
67
+ * Run `callback` with a per-subject assignment memo, for a job or
68
+ * command that makes several checks. HTTP requests already have one,
69
+ * opened by the provider's middleware.
70
+ */
71
+ static withCache<T>(callback: () => T | Promise<T>): Promise<T>;
72
+ }
73
+ export {};
74
+ //# sourceMappingURL=permissions-facade.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions-facade.d.ts","sourceRoot":"","sources":["../src/permissions-facade.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,131 @@
1
+ import { Facade } from "@mahiframework/facades";
2
+ import { withPermissionCache } from "./request-cache.js";
3
+ import { PERMISSIONS_TOKEN } from "./tokens.js";
4
+ /**
5
+ * Thin facade over the `PermissionRegistrar` singleton bound at
6
+ * `PERMISSIONS_TOKEN`.
7
+ *
8
+ * await Permissions.assignRole(user, "admin");
9
+ * if (await Permissions.hasPermissionTo(user, "posts.edit")) { ... }
10
+ *
11
+ * Every method takes the subject explicitly. Laravel's spatie package
12
+ * reads `$user->assignRole(...)` off a trait, and this framework has no
13
+ * traits and no package that touches the app's `User` model — behaviour
14
+ * is attached through a container singleton, never by extending a model
15
+ * the app owns. The explicit subject also works where a method could
16
+ * not: on any polymorphic entity uniformly, and in a queue job holding
17
+ * only a `{ type, id }` descriptor.
18
+ *
19
+ * Every call is `async`. There is no synchronous variant, and there
20
+ * cannot be: the first check of a process reads the database.
21
+ */
22
+ export class Permissions extends Facade(() => PERMISSIONS_TOKEN) {
23
+ // --------------------------------------------------------- roles/permissions
24
+ static createRole(name, options) {
25
+ return this.instance().createRole(name, options);
26
+ }
27
+ static createPermission(name, options) {
28
+ return this.instance().createPermission(name, options);
29
+ }
30
+ static findOrCreateRole(name, options) {
31
+ return this.instance().findOrCreateRole(name, options);
32
+ }
33
+ static findOrCreatePermission(name, options) {
34
+ return this.instance().findOrCreatePermission(name, options);
35
+ }
36
+ static findRole(name, options) {
37
+ return this.instance().findRole(name, options);
38
+ }
39
+ static findPermission(name, options) {
40
+ return this.instance().findPermission(name, options);
41
+ }
42
+ static deleteRole(role, options) {
43
+ return this.instance().deleteRole(role, options);
44
+ }
45
+ static deletePermission(permission, options) {
46
+ return this.instance().deletePermission(permission, options);
47
+ }
48
+ // ------------------------------------------------------ role -> permissions
49
+ static givePermissionToRole(role, permissions, options) {
50
+ return this.instance().givePermissionToRole(role, permissions, options);
51
+ }
52
+ static revokePermissionFromRole(role, permissions, options) {
53
+ return this.instance().revokePermissionFromRole(role, permissions, options);
54
+ }
55
+ static syncRolePermissions(role, permissions, options) {
56
+ return this.instance().syncRolePermissions(role, permissions, options);
57
+ }
58
+ // ---------------------------------------------------------- subject -> roles
59
+ static assignRole(assignee, roles, options) {
60
+ return this.instance().assignRole(assignee, roles, options);
61
+ }
62
+ static removeRole(assignee, roles, options) {
63
+ return this.instance().removeRole(assignee, roles, options);
64
+ }
65
+ /** Makes the subject's roles exactly `roles`. An empty array removes all of them. */
66
+ static syncRoles(assignee, roles, options) {
67
+ return this.instance().syncRoles(assignee, roles, options);
68
+ }
69
+ // ---------------------------------------------------- subject -> permissions
70
+ static givePermissionTo(assignee, permissions, options) {
71
+ return this.instance().givePermissionTo(assignee, permissions, options);
72
+ }
73
+ static revokePermissionTo(assignee, permissions, options) {
74
+ return this.instance().revokePermissionTo(assignee, permissions, options);
75
+ }
76
+ /** Makes the subject's DIRECT permissions exactly `permissions`. Roles are untouched. */
77
+ static syncPermissions(assignee, permissions, options) {
78
+ return this.instance().syncPermissions(assignee, permissions, options);
79
+ }
80
+ // --------------------------------------------------------------- the checks
81
+ static hasRole(assignee, role, options) {
82
+ return this.instance().hasRole(assignee, role, options);
83
+ }
84
+ static hasAnyRole(assignee, roles, options) {
85
+ return this.instance().hasAnyRole(assignee, roles, options);
86
+ }
87
+ static hasAllRoles(assignee, roles, options) {
88
+ return this.instance().hasAllRoles(assignee, roles, options);
89
+ }
90
+ /** True if the subject holds this permission by ANY route: a role, or directly. */
91
+ static hasPermissionTo(assignee, permission, options) {
92
+ return this.instance().hasPermissionTo(assignee, permission, options);
93
+ }
94
+ static hasAnyPermission(assignee, permissions, options) {
95
+ return this.instance().hasAnyPermission(assignee, permissions, options);
96
+ }
97
+ static hasAllPermissions(assignee, permissions, options) {
98
+ return this.instance().hasAllPermissions(assignee, permissions, options);
99
+ }
100
+ /** True only if granted directly. A permission held via a role answers false here. */
101
+ static hasDirectPermission(assignee, permission, options) {
102
+ return this.instance().hasDirectPermission(assignee, permission, options);
103
+ }
104
+ // ------------------------------------------------------------ introspection
105
+ static getRoleNames(assignee, options) {
106
+ return this.instance().getRoleNames(assignee, options);
107
+ }
108
+ static getAllPermissions(assignee, options) {
109
+ return this.instance().getAllPermissions(assignee, options);
110
+ }
111
+ static getDirectPermissions(assignee, options) {
112
+ return this.instance().getDirectPermissions(assignee, options);
113
+ }
114
+ static getPermissionsViaRoles(assignee, options) {
115
+ return this.instance().getPermissionsViaRoles(assignee, options);
116
+ }
117
+ // -------------------------------------------------------------------- cache
118
+ /** Drop the cached role/permission map and every memoised assignment. */
119
+ static forgetCache() {
120
+ return this.instance().forgetCache();
121
+ }
122
+ /**
123
+ * Run `callback` with a per-subject assignment memo, for a job or
124
+ * command that makes several checks. HTTP requests already have one,
125
+ * opened by the provider's middleware.
126
+ */
127
+ static withCache(callback) {
128
+ return withPermissionCache(callback);
129
+ }
130
+ }
131
+ //# sourceMappingURL=permissions-facade.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions-facade.js","sourceRoot":"","sources":["../src/permissions-facade.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ import { ServiceProvider } from "@mahiframework/core";
2
+ import type { AnyModelClass, RegisteredMigration } from "@mahiframework/database";
3
+ import type { GateRegistry } from "@mahiframework/authorization";
4
+ import type { ListenerRegistration } from "@mahiframework/events";
5
+ import type { HttpPipe } from "@mahiframework/http";
6
+ import { PermissionsShowCommand } from "./commands/permissions-show.js";
7
+ import { PERMISSIONS_TOKEN } from "./tokens.js";
8
+ export { PERMISSIONS_TOKEN };
9
+ /**
10
+ * Registers the `PermissionRegistrar` singleton, the gate hook that makes
11
+ * `can()` consult permissions, the per-request assignment memo, and the
12
+ * listeners that keep the cache honest.
13
+ *
14
+ * ORDERING: list this provider AFTER `DatabaseServiceProvider` (it owns
15
+ * five tables and two models), AFTER `CacheServiceProvider` (the map
16
+ * lives in a cache store), AFTER `AuthServiceProvider` (so its memo pipe
17
+ * runs inside the ambient auth scope and the subject is resolvable), and
18
+ * BEFORE `HttpServiceProvider` (so its pipe is collected before routes
19
+ * are). `AuthorizationServiceProvider` may come on either side:
20
+ * its own `boot()` walks every registered provider, so `gates()` is
21
+ * collected whichever order they appear in. You cannot enforce any of
22
+ * that; the app's `config/app.ts` decides, and this docstring is the
23
+ * whole mechanism.
24
+ */
25
+ export declare class PermissionsServiceProvider extends ServiceProvider {
26
+ register(): void;
27
+ /**
28
+ * Teach the gate that a bare ability may be a permission name.
29
+ *
30
+ * This is what makes `can("posts.edit")`, `Gate.authorize("posts.edit")`
31
+ * and the `can()` route middleware consult permissions without any of
32
+ * them knowing this package exists. spatie registers the same hook.
33
+ *
34
+ * THREE THINGS HERE ARE LOAD-BEARING:
35
+ *
36
+ * 1. It returns `true` or `null`, NEVER `false`. A `false` from a
37
+ * `before()` hook hard-denies and skips policy resolution entirely,
38
+ * so denying on a permission miss would make every policy in the app
39
+ * unreachable — the single worst bug this package could ship, and
40
+ * the reason there is a test asserting a policy still grants after a
41
+ * miss.
42
+ *
43
+ * 2. It abstains the moment the check carries an argument
44
+ * (`args.length > 0`), which is a DELIBERATE DIVERGENCE FROM SPATIE.
45
+ * There, `Gate::allows("update", $post)` also routes through the
46
+ * permission check, so an app with both a permission named `update`
47
+ * and a `PostPolicy.update` grants update on EVERY post to anyone
48
+ * holding that permission. Abstaining splits the two cleanly: bare
49
+ * abilities are permission names, model-scoped abilities are the
50
+ * policy's business, and a policy that wants a permission check calls
51
+ * `Permissions.hasPermissionTo()` explicitly — which is where that
52
+ * decision belongs anyway, next to the row it concerns.
53
+ *
54
+ * 3. A non-model user abstains too. The pivots need a morph alias and a
55
+ * `bigint` key; a token-guard adapter or a plain object has neither,
56
+ * and `resolveAssignee()` would throw. A hook that throws turns every
57
+ * authorization check in the app into a 500.
58
+ *
59
+ * `gates()` is synchronous, so nothing is loaded here. The closure is
60
+ * async and the first check of the process populates the cache.
61
+ *
62
+ * Hooks run in registration order and the first non-null wins, so a
63
+ * permission grant beats a later hook that would have denied. That
64
+ * ordering is the app's `config/app.ts` choice.
65
+ */
66
+ gates(gate: GateRegistry): void;
67
+ /**
68
+ * Open a per-request assignment memo.
69
+ *
70
+ * Not an optimisation so much as a correction: the gate hook above runs
71
+ * on EVERY authorization check, so a controller making five `can()`
72
+ * calls would be ten queries without this. See `request-cache.ts`.
73
+ */
74
+ middleware(): HttpPipe[];
75
+ /**
76
+ * Forget the cached map when a `Role` or `Permission` is written
77
+ * outside the registrar — a seeder, a migration, an admin screen.
78
+ * See the listener for why these three events and not a pattern.
79
+ */
80
+ listeners(): ReadonlyArray<ListenerRegistration>;
81
+ /**
82
+ * Static rather than a `migrations()` directory path, so it resolves
83
+ * inside a bundled binary. One migration for all five tables; see the
84
+ * file for why they are not five.
85
+ */
86
+ migrationSources(): RegisteredMigration[];
87
+ /** Registered so a queued job can carry a `Role` or a `Permission`. */
88
+ models(): AnyModelClass[];
89
+ commands(): (typeof PermissionsShowCommand)[];
90
+ }
91
+ //# sourceMappingURL=permissions-service-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions-service-provider.d.ts","sourceRoot":"","sources":["../src/permissions-service-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAElF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAQpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAGxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAE7B;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,0BAA2B,SAAQ,eAAe;IAC7D,QAAQ,IAAI,IAAI;IAiBhB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAsCG;IACH,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAkB/B;;;;;;OAMG;IACH,UAAU,IAAI,QAAQ,EAAE;IAIxB;;;;OAIG;IACH,SAAS,IAAI,aAAa,CAAC,oBAAoB,CAAC;IAQhD;;;;OAIG;IACH,gBAAgB,IAAI,mBAAmB,EAAE;IAIzC,uEAAuE;IACvE,MAAM,IAAI,aAAa,EAAE;IAIzB,QAAQ;CAGT"}
@@ -0,0 +1,137 @@
1
+ import { ServiceProvider } from "@mahiframework/core";
2
+ import { BaseModel, ModelCreated, ModelDeleted, ModelUpdated } from "@mahiframework/database";
3
+ import { PermissionRegistrar } from "./permission-registrar.js";
4
+ import { resolveConfig } from "./permissions-config.js";
5
+ import { Permission } from "./models/permission.model.js";
6
+ import { Role } from "./models/role.model.js";
7
+ import { InvalidatePermissionCacheListener } from "./listeners/invalidate-permission-cache.listener.js";
8
+ import { PermissionsCacheResetCommand } from "./commands/permissions-cache-reset.js";
9
+ import { PermissionsCheckCommand } from "./commands/permissions-check.js";
10
+ import { PermissionsShowCommand } from "./commands/permissions-show.js";
11
+ import createPermissionTables from "./migrations/0001_create_permission_tables.js";
12
+ import { runWithPermissionCache } from "./request-cache.js";
13
+ import { PERMISSIONS_TOKEN } from "./tokens.js";
14
+ export { PERMISSIONS_TOKEN };
15
+ /**
16
+ * Registers the `PermissionRegistrar` singleton, the gate hook that makes
17
+ * `can()` consult permissions, the per-request assignment memo, and the
18
+ * listeners that keep the cache honest.
19
+ *
20
+ * ORDERING: list this provider AFTER `DatabaseServiceProvider` (it owns
21
+ * five tables and two models), AFTER `CacheServiceProvider` (the map
22
+ * lives in a cache store), AFTER `AuthServiceProvider` (so its memo pipe
23
+ * runs inside the ambient auth scope and the subject is resolvable), and
24
+ * BEFORE `HttpServiceProvider` (so its pipe is collected before routes
25
+ * are). `AuthorizationServiceProvider` may come on either side:
26
+ * its own `boot()` walks every registered provider, so `gates()` is
27
+ * collected whichever order they appear in. You cannot enforce any of
28
+ * that; the app's `config/app.ts` decides, and this docstring is the
29
+ * whole mechanism.
30
+ */
31
+ export class PermissionsServiceProvider extends ServiceProvider {
32
+ register() {
33
+ // No `config.merge()` of defaults. Every default is applied in
34
+ // `resolveConfig()` with `??`, which is both the single place to read
35
+ // them and immune to merge-order surprises: `ConfigRepository.merge()`
36
+ // deep-merges the INCOMING values last, so contributing defaults that
37
+ // way would silently overwrite the app's own config rather than
38
+ // layering under it.
39
+ this.app.singleton(PERMISSIONS_TOKEN, (app) => {
40
+ // `get`, not `require`: an app that installs the package and
41
+ // configures nothing gets working defaults (the auth default
42
+ // guard, a day-long cache) rather than a boot failure.
43
+ const config = app.config.get("permissions") ?? {};
44
+ return new PermissionRegistrar(app, resolveConfig(config));
45
+ });
46
+ }
47
+ /**
48
+ * Teach the gate that a bare ability may be a permission name.
49
+ *
50
+ * This is what makes `can("posts.edit")`, `Gate.authorize("posts.edit")`
51
+ * and the `can()` route middleware consult permissions without any of
52
+ * them knowing this package exists. spatie registers the same hook.
53
+ *
54
+ * THREE THINGS HERE ARE LOAD-BEARING:
55
+ *
56
+ * 1. It returns `true` or `null`, NEVER `false`. A `false` from a
57
+ * `before()` hook hard-denies and skips policy resolution entirely,
58
+ * so denying on a permission miss would make every policy in the app
59
+ * unreachable — the single worst bug this package could ship, and
60
+ * the reason there is a test asserting a policy still grants after a
61
+ * miss.
62
+ *
63
+ * 2. It abstains the moment the check carries an argument
64
+ * (`args.length > 0`), which is a DELIBERATE DIVERGENCE FROM SPATIE.
65
+ * There, `Gate::allows("update", $post)` also routes through the
66
+ * permission check, so an app with both a permission named `update`
67
+ * and a `PostPolicy.update` grants update on EVERY post to anyone
68
+ * holding that permission. Abstaining splits the two cleanly: bare
69
+ * abilities are permission names, model-scoped abilities are the
70
+ * policy's business, and a policy that wants a permission check calls
71
+ * `Permissions.hasPermissionTo()` explicitly — which is where that
72
+ * decision belongs anyway, next to the row it concerns.
73
+ *
74
+ * 3. A non-model user abstains too. The pivots need a morph alias and a
75
+ * `bigint` key; a token-guard adapter or a plain object has neither,
76
+ * and `resolveAssignee()` would throw. A hook that throws turns every
77
+ * authorization check in the app into a 500.
78
+ *
79
+ * `gates()` is synchronous, so nothing is loaded here. The closure is
80
+ * async and the first check of the process populates the cache.
81
+ *
82
+ * Hooks run in registration order and the first non-null wins, so a
83
+ * permission grant beats a later hook that would have denied. That
84
+ * ordering is the app's `config/app.ts` choice.
85
+ */
86
+ gates(gate) {
87
+ const config = resolveConfig(this.app.config.get("permissions") ?? {});
88
+ if (!config.gate) {
89
+ return;
90
+ }
91
+ gate.before(async (user, ability, ...args) => {
92
+ if (args.length > 0 || !(user instanceof BaseModel)) {
93
+ return null;
94
+ }
95
+ const registrar = this.app.make(PERMISSIONS_TOKEN);
96
+ return (await registrar.hasPermissionTo(user, ability)) ? true : null;
97
+ });
98
+ }
99
+ /**
100
+ * Open a per-request assignment memo.
101
+ *
102
+ * Not an optimisation so much as a correction: the gate hook above runs
103
+ * on EVERY authorization check, so a controller making five `can()`
104
+ * calls would be ten queries without this. See `request-cache.ts`.
105
+ */
106
+ middleware() {
107
+ return [(request, next) => runWithPermissionCache(() => next(request))];
108
+ }
109
+ /**
110
+ * Forget the cached map when a `Role` or `Permission` is written
111
+ * outside the registrar — a seeder, a migration, an admin screen.
112
+ * See the listener for why these three events and not a pattern.
113
+ */
114
+ listeners() {
115
+ return [
116
+ [ModelCreated, InvalidatePermissionCacheListener],
117
+ [ModelUpdated, InvalidatePermissionCacheListener],
118
+ [ModelDeleted, InvalidatePermissionCacheListener],
119
+ ];
120
+ }
121
+ /**
122
+ * Static rather than a `migrations()` directory path, so it resolves
123
+ * inside a bundled binary. One migration for all five tables; see the
124
+ * file for why they are not five.
125
+ */
126
+ migrationSources() {
127
+ return [{ name: "0001_create_permission_tables", migration: createPermissionTables }];
128
+ }
129
+ /** Registered so a queued job can carry a `Role` or a `Permission`. */
130
+ models() {
131
+ return [Role, Permission];
132
+ }
133
+ commands() {
134
+ return [PermissionsCacheResetCommand, PermissionsCheckCommand, PermissionsShowCommand];
135
+ }
136
+ }
137
+ //# sourceMappingURL=permissions-service-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions-service-provider.js","sourceRoot":"","sources":["../src/permissions-service-provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import { Permission } from "./models/permission.model.js";
2
+ import { Role } from "./models/role.model.js";
3
+ /**
4
+ * The `roles` relation, for an app model that wants to eager-load them.
5
+ *
6
+ * Opt-in per model rather than shipped on anything, because the package
7
+ * owns neither the app's models nor its attribute interfaces:
8
+ *
9
+ * export interface UserAttributes {
10
+ * // ...
11
+ * roles: MorphToMany<Role>;
12
+ * }
13
+ *
14
+ * export class User extends Model<UserAttributes>()({ ... }) {
15
+ * static override relationships = {
16
+ * roles: rolesRelation(),
17
+ * };
18
+ * }
19
+ *
20
+ * That buys `User.query().with("roles.permissions")` and
21
+ * `whereHas("roles", (q) => q.where("name", "admin"))`.
22
+ *
23
+ * READ-ONLY, in practice. `Permissions.assignRole()` writes the pivot
24
+ * directly (it takes a subject, not a relation handle, so it can also
25
+ * work from a `{ type, id }` descriptor in a job), so a collection
26
+ * loaded by `with("roles")` will not reflect an assignment made later in
27
+ * the same request. Re-`load()` it if that matters.
28
+ *
29
+ * `type` is deliberately omitted so it defaults to the DECLARING model's
30
+ * `morphAlias()` — which is what a `morphToMany` wants, and the opposite
31
+ * of what `morphedByMany` would. Note that `morphAlias()` falls back to
32
+ * the table name, so an app without a `Relation.morphMap()` entry has
33
+ * made its assignment rows depend on its table name.
34
+ */
35
+ export declare function rolesRelation(): {
36
+ readonly type: "morphToMany";
37
+ readonly related: () => import("@mahiframework/database").ModelLike;
38
+ readonly options: import("@mahiframework/database").MorphToManyOptions<any, any>;
39
+ readonly __brand?: {
40
+ kind: "morphToMany";
41
+ related: Role;
42
+ } | undefined;
43
+ };
44
+ /**
45
+ * The `permissions` relation: permissions granted to this model
46
+ * DIRECTLY, never those inherited from its roles.
47
+ *
48
+ * The inherited ones have no relation to declare — they are two hops
49
+ * through `model_has_roles` and `role_has_permissions`, which is a
50
+ * `hasManyThrough` the ORM cannot express across a polymorphic pivot.
51
+ * `Permissions.getAllPermissions(user)` is the union, and it answers
52
+ * from the cached map rather than a join.
53
+ */
54
+ export declare function permissionsRelation(): {
55
+ readonly type: "morphToMany";
56
+ readonly related: () => import("@mahiframework/database").ModelLike;
57
+ readonly options: import("@mahiframework/database").MorphToManyOptions<any, any>;
58
+ readonly __brand?: {
59
+ kind: "morphToMany";
60
+ related: Permission;
61
+ } | undefined;
62
+ };
63
+ //# sourceMappingURL=relations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relations.d.ts","sourceRoot":"","sources":["../src/relations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,aAAa;;;;;;;;EAO5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB;;;;;;;;EAOlC"}