@beechcms/core 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/antivirus/antivirus.interface.d.ts +12 -0
- package/dist/antivirus/antivirus.interface.d.ts.map +1 -0
- package/dist/antivirus/antivirus.interface.js +3 -0
- package/dist/antivirus/noop-antivirus.provider.d.ts +6 -0
- package/dist/antivirus/noop-antivirus.provider.d.ts.map +1 -0
- package/dist/antivirus/noop-antivirus.provider.js +8 -0
- package/dist/antivirus/virustotal-antivirus.provider.d.ts +8 -0
- package/dist/antivirus/virustotal-antivirus.provider.d.ts.map +1 -0
- package/dist/antivirus/virustotal-antivirus.provider.js +48 -0
- package/dist/automations-grammar-words.d.ts +2 -0
- package/dist/automations-grammar-words.d.ts.map +1 -0
- package/dist/automations-grammar-words.js +8 -0
- package/dist/automations.repository.interface.d.ts +19 -0
- package/dist/automations.repository.interface.d.ts.map +1 -0
- package/dist/automations.repository.interface.js +3 -0
- package/dist/automations.runner.interface.d.ts +10 -0
- package/dist/automations.runner.interface.d.ts.map +1 -0
- package/dist/automations.runner.interface.js +3 -0
- package/dist/automations.runner.stub.d.ts +5 -0
- package/dist/automations.runner.stub.d.ts.map +1 -0
- package/dist/automations.runner.stub.js +7 -0
- package/dist/automations.types.d.ts +75 -0
- package/dist/automations.types.d.ts.map +1 -0
- package/dist/automations.types.js +3 -0
- package/dist/clock.d.ts +25 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +11 -0
- package/dist/common/canonical-json.d.ts +30 -0
- package/dist/common/canonical-json.d.ts.map +1 -0
- package/dist/common/canonical-json.js +89 -0
- package/dist/common/hooks.d.ts +3 -0
- package/dist/common/hooks.d.ts.map +1 -1
- package/dist/content/content.repository.d.ts +2 -2
- package/dist/content/content.repository.d.ts.map +1 -1
- package/dist/content/deletion-ledger.d.ts +39 -0
- package/dist/content/deletion-ledger.d.ts.map +1 -0
- package/dist/content/deletion-ledger.js +3 -0
- package/dist/content/richtext/richtext-render.d.ts.map +1 -1
- package/dist/content/richtext/richtext-render.js +14 -1
- package/dist/content/seed.repository.d.ts +22 -0
- package/dist/content/seed.repository.d.ts.map +1 -1
- package/dist/content-scan.repository.d.ts +9 -0
- package/dist/content-scan.repository.d.ts.map +1 -0
- package/dist/content-scan.repository.js +3 -0
- package/dist/content.repository.d.ts +247 -0
- package/dist/content.repository.d.ts.map +1 -0
- package/dist/content.repository.js +59 -0
- package/dist/dashboard-layout/seed-layout.d.ts.map +1 -1
- package/dist/dashboard-layout/seed-layout.js +13 -6
- package/dist/dashboard-layout.d.ts +154 -0
- package/dist/dashboard-layout.d.ts.map +1 -0
- package/dist/dashboard-layout.js +186 -0
- package/dist/dashboard-layout.repository.d.ts +18 -0
- package/dist/dashboard-layout.repository.d.ts.map +1 -0
- package/dist/dashboard-layout.repository.js +3 -0
- package/dist/dashboard-permissions.d.ts +3 -0
- package/dist/dashboard-permissions.d.ts.map +1 -0
- package/dist/dashboard-permissions.js +11 -0
- package/dist/dashboard-scopes.d.ts +12 -0
- package/dist/dashboard-scopes.d.ts.map +1 -0
- package/dist/dashboard-scopes.js +30 -0
- package/dist/define-seed.d.ts +3 -0
- package/dist/define-seed.d.ts.map +1 -0
- package/dist/define-seed.js +5 -0
- package/dist/demo-data.repository.d.ts +5 -0
- package/dist/demo-data.repository.d.ts.map +1 -0
- package/dist/demo-data.repository.js +3 -0
- package/dist/email.provider.d.ts +32 -0
- package/dist/email.provider.d.ts.map +1 -0
- package/dist/email.provider.js +4 -0
- package/dist/engine/ddl/ddl.d.ts +216 -0
- package/dist/engine/ddl/ddl.d.ts.map +1 -0
- package/dist/engine/ddl/ddl.js +574 -0
- package/dist/engine/ddl/schema-mutator.d.ts +26 -0
- package/dist/engine/ddl/schema-mutator.d.ts.map +1 -0
- package/dist/engine/ddl/schema-mutator.js +3 -0
- package/dist/engine/ddl/seed-ddl-destructive.d.ts +40 -0
- package/dist/engine/ddl/seed-ddl-destructive.d.ts.map +1 -0
- package/dist/engine/ddl/seed-ddl-destructive.js +149 -0
- package/dist/engine/ddl/seed-ddl.d.ts +43 -0
- package/dist/engine/ddl/seed-ddl.d.ts.map +1 -0
- package/dist/engine/ddl/seed-ddl.js +115 -0
- package/dist/engine/ddl.d.ts +15 -0
- package/dist/engine/ddl.d.ts.map +1 -1
- package/dist/engine/ddl.js +30 -1
- package/dist/engine/introspection/introspection.d.ts +101 -0
- package/dist/engine/introspection/introspection.d.ts.map +1 -0
- package/dist/engine/introspection/introspection.js +127 -0
- package/dist/engine/introspection/schema-fingerprint.d.ts +73 -0
- package/dist/engine/introspection/schema-fingerprint.d.ts.map +1 -0
- package/dist/engine/introspection/schema-fingerprint.js +97 -0
- package/dist/engine/introspection.d.ts +101 -0
- package/dist/engine/introspection.d.ts.map +1 -0
- package/dist/engine/introspection.js +127 -0
- package/dist/engine/localization/localization.d.ts +127 -0
- package/dist/engine/localization/localization.d.ts.map +1 -0
- package/dist/engine/localization/localization.js +212 -0
- package/dist/engine/localization.d.ts +127 -0
- package/dist/engine/localization.d.ts.map +1 -0
- package/dist/engine/localization.js +212 -0
- package/dist/engine/policies.d.ts.map +1 -1
- package/dist/engine/policies.js +5 -0
- package/dist/engine/privacy/policies.d.ts +58 -0
- package/dist/engine/privacy/policies.d.ts.map +1 -0
- package/dist/engine/privacy/policies.js +185 -0
- package/dist/engine/privacy/privacy.service.d.ts +65 -0
- package/dist/engine/privacy/privacy.service.d.ts.map +1 -0
- package/dist/engine/privacy/privacy.service.js +114 -0
- package/dist/engine/privacy.service.d.ts.map +1 -1
- package/dist/engine/query/query.d.ts +23 -0
- package/dist/engine/query/query.d.ts.map +1 -0
- package/dist/engine/query/query.js +301 -0
- package/dist/engine/query/relations.d.ts +28 -0
- package/dist/engine/query/relations.d.ts.map +1 -0
- package/dist/engine/query/relations.js +25 -0
- package/dist/engine/query/serialize.d.ts +22 -0
- package/dist/engine/query/serialize.d.ts.map +1 -0
- package/dist/engine/query/serialize.js +175 -0
- package/dist/engine/schema-fingerprint.d.ts +73 -0
- package/dist/engine/schema-fingerprint.d.ts.map +1 -0
- package/dist/engine/schema-fingerprint.js +97 -0
- package/dist/engine/seed-registry.d.ts.map +1 -1
- package/dist/engine/seed-registry.js +23 -4
- package/dist/engine/seed-types-generator.d.ts.map +1 -1
- package/dist/engine/seed-types-generator.js +8 -5
- package/dist/engine/seed-validation.d.ts +7 -0
- package/dist/engine/seed-validation.d.ts.map +1 -1
- package/dist/engine/seed-validation.js +59 -1
- package/dist/engine/seeds/define-seed.d.ts +3 -0
- package/dist/engine/seeds/define-seed.d.ts.map +1 -0
- package/dist/engine/seeds/define-seed.js +5 -0
- package/dist/engine/seeds/seed-registry.d.ts +71 -0
- package/dist/engine/seeds/seed-registry.d.ts.map +1 -0
- package/dist/engine/seeds/seed-registry.js +167 -0
- package/dist/engine/seeds/seed-types-generator.d.ts +25 -0
- package/dist/engine/seeds/seed-types-generator.d.ts.map +1 -0
- package/dist/engine/seeds/seed-types-generator.js +117 -0
- package/dist/engine/seeds/seed-validation.d.ts +30 -0
- package/dist/engine/seeds/seed-validation.d.ts.map +1 -0
- package/dist/engine/seeds/seed-validation.js +364 -0
- package/dist/engine/seeds/seeds.d.ts +20 -0
- package/dist/engine/seeds/seeds.d.ts.map +1 -0
- package/dist/engine/seeds/seeds.js +21 -0
- package/dist/engine/types.d.ts +7 -0
- package/dist/engine/types.d.ts.map +1 -1
- package/dist/engine/validation/emptiness.d.ts +12 -0
- package/dist/engine/validation/emptiness.d.ts.map +1 -0
- package/dist/engine/validation/emptiness.js +33 -0
- package/dist/engine/validation/index.d.ts +1 -1
- package/dist/engine/validation/index.d.ts.map +1 -1
- package/dist/engine/validation/richtext-sanitizer.d.ts.map +1 -1
- package/dist/engine/validation/richtext-sanitizer.js +2 -1
- package/dist/engine.d.ts +144 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +741 -0
- package/dist/file-types.d.ts +13 -0
- package/dist/file-types.d.ts.map +1 -0
- package/dist/file-types.js +85 -0
- package/dist/hooks.d.ts +29 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +3 -0
- package/dist/id-generator.d.ts +26 -0
- package/dist/id-generator.d.ts.map +1 -0
- package/dist/id-generator.js +12 -0
- package/dist/idempotency.repository.d.ts +13 -0
- package/dist/idempotency.repository.d.ts.map +1 -0
- package/dist/idempotency.repository.js +3 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -1
- package/dist/kanban-position.repository.d.ts +19 -0
- package/dist/kanban-position.repository.d.ts.map +1 -0
- package/dist/kanban-position.repository.js +3 -0
- package/dist/kanban.d.ts +75 -0
- package/dist/kanban.d.ts.map +1 -0
- package/dist/kanban.js +82 -0
- package/dist/layout-permissions.d.ts +3 -0
- package/dist/layout-permissions.d.ts.map +1 -0
- package/dist/layout-permissions.js +11 -0
- package/dist/media/file-types.d.ts +103 -9
- package/dist/media/file-types.d.ts.map +1 -1
- package/dist/media/file-types.js +336 -52
- package/dist/media/image-transformer.d.ts +34 -0
- package/dist/media/image-transformer.d.ts.map +1 -0
- package/dist/media/image-transformer.js +3 -0
- package/dist/media/magic-bytes.d.ts +21 -0
- package/dist/media/magic-bytes.d.ts.map +1 -0
- package/dist/media/magic-bytes.js +66 -0
- package/dist/media/media-transform.d.ts +88 -0
- package/dist/media/media-transform.d.ts.map +1 -0
- package/dist/media/media-transform.js +193 -0
- package/dist/media.repository.d.ts +61 -0
- package/dist/media.repository.d.ts.map +1 -0
- package/dist/media.repository.js +3 -0
- package/dist/notifications/notification-service.d.ts +1 -1
- package/dist/oauth/authorization-code.repository.d.ts +33 -0
- package/dist/oauth/authorization-code.repository.d.ts.map +1 -0
- package/dist/oauth/authorization-code.repository.js +3 -0
- package/dist/oauth/client.repository.d.ts +17 -0
- package/dist/oauth/client.repository.d.ts.map +1 -0
- package/dist/oauth/client.repository.js +3 -0
- package/dist/oauth/consent.repository.d.ts +25 -0
- package/dist/oauth/consent.repository.d.ts.map +1 -0
- package/dist/oauth/consent.repository.js +3 -0
- package/dist/oauth/pkce.d.ts +21 -0
- package/dist/oauth/pkce.d.ts.map +1 -0
- package/dist/oauth/pkce.js +47 -0
- package/dist/oauth/role-guard.d.ts +32 -0
- package/dist/oauth/role-guard.d.ts.map +1 -0
- package/dist/oauth/role-guard.js +14 -0
- package/dist/oauth/scopes.d.ts +27 -0
- package/dist/oauth/scopes.d.ts.map +1 -0
- package/dist/oauth/scopes.js +41 -0
- package/dist/oauth/token.repository.d.ts +46 -0
- package/dist/oauth/token.repository.d.ts.map +1 -0
- package/dist/oauth/token.repository.js +3 -0
- package/dist/observability/activity-logger.d.ts +1 -1
- package/dist/observability/activity-logger.d.ts.map +1 -1
- package/dist/policies.d.ts +10 -0
- package/dist/policies.d.ts.map +1 -0
- package/dist/policies.js +33 -0
- package/dist/queue/queue.stub.d.ts.map +1 -1
- package/dist/queue/scheduler.stub.d.ts.map +1 -1
- package/dist/queue.interface.d.ts +39 -0
- package/dist/queue.interface.d.ts.map +1 -0
- package/dist/queue.interface.js +3 -0
- package/dist/queue.stub.d.ts +6 -0
- package/dist/queue.stub.d.ts.map +1 -0
- package/dist/queue.stub.js +8 -0
- package/dist/rate-limit/rate-limiter.d.ts +13 -5
- package/dist/rate-limit/rate-limiter.d.ts.map +1 -1
- package/dist/rate-limit/token-bucket-rate-limiter.d.ts +20 -0
- package/dist/rate-limit/token-bucket-rate-limiter.d.ts.map +1 -0
- package/dist/rate-limit/token-bucket-rate-limiter.js +63 -0
- package/dist/rbac/evaluate.d.ts +57 -0
- package/dist/rbac/evaluate.d.ts.map +1 -0
- package/dist/rbac/evaluate.js +113 -0
- package/dist/rbac/invitation.repository.d.ts +66 -0
- package/dist/rbac/invitation.repository.d.ts.map +1 -0
- package/dist/rbac/invitation.repository.js +3 -0
- package/dist/rbac/permissions.d.ts +39 -0
- package/dist/rbac/permissions.d.ts.map +1 -0
- package/dist/rbac/permissions.js +43 -0
- package/dist/rbac/types.d.ts +118 -0
- package/dist/rbac/types.d.ts.map +1 -0
- package/dist/rbac/types.js +3 -0
- package/dist/relations.d.ts +28 -0
- package/dist/relations.d.ts.map +1 -0
- package/dist/relations.js +25 -0
- package/dist/richtext-render.d.ts +11 -0
- package/dist/richtext-render.d.ts.map +1 -0
- package/dist/richtext-render.js +87 -0
- package/dist/richtext.d.ts +11 -0
- package/dist/richtext.d.ts.map +1 -0
- package/dist/richtext.js +13 -0
- package/dist/scheduler.interface.d.ts +4 -0
- package/dist/scheduler.interface.d.ts.map +1 -0
- package/dist/scheduler.interface.js +3 -0
- package/dist/scheduler.stub.d.ts +5 -0
- package/dist/scheduler.stub.d.ts.map +1 -0
- package/dist/scheduler.stub.js +7 -0
- package/dist/schema/canonical.d.ts +15 -0
- package/dist/schema/canonical.d.ts.map +1 -0
- package/dist/schema/canonical.js +37 -0
- package/dist/schema/define.d.ts +42 -0
- package/dist/schema/define.d.ts.map +1 -0
- package/dist/schema/define.js +56 -0
- package/dist/schema/emit.d.ts +14 -0
- package/dist/schema/emit.d.ts.map +1 -0
- package/dist/schema/emit.js +43 -0
- package/dist/schema/index.d.ts +12 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +13 -0
- package/dist/schema/manifest-seeds.d.ts +12 -0
- package/dist/schema/manifest-seeds.d.ts.map +1 -0
- package/dist/schema/manifest-seeds.js +48 -0
- package/dist/schema/manifest-validation.d.ts +13 -0
- package/dist/schema/manifest-validation.d.ts.map +1 -0
- package/dist/schema/manifest-validation.js +41 -0
- package/dist/schema/manifest.types.d.ts +54 -0
- package/dist/schema/manifest.types.d.ts.map +1 -0
- package/dist/schema/manifest.types.js +7 -0
- package/dist/schema-mutator.d.ts +26 -0
- package/dist/schema-mutator.d.ts.map +1 -0
- package/dist/schema-mutator.js +3 -0
- package/dist/search/vector-extractor.d.ts +11 -0
- package/dist/search/vector-extractor.d.ts.map +1 -0
- package/dist/search/vector-extractor.js +25 -0
- package/dist/search/vector.repository.d.ts +13 -0
- package/dist/search/vector.repository.d.ts.map +1 -0
- package/dist/search/vector.repository.js +3 -0
- package/dist/security/time-trap-token.repository.d.ts +9 -0
- package/dist/security/time-trap-token.repository.d.ts.map +1 -0
- package/dist/security/time-trap-token.repository.js +3 -0
- package/dist/security/time-trap.d.ts +7 -0
- package/dist/security/time-trap.d.ts.map +1 -0
- package/dist/security/time-trap.js +49 -0
- package/dist/seed-ddl-destructive.d.ts +40 -0
- package/dist/seed-ddl-destructive.d.ts.map +1 -0
- package/dist/seed-ddl-destructive.js +149 -0
- package/dist/seed-ddl.d.ts +40 -0
- package/dist/seed-ddl.d.ts.map +1 -0
- package/dist/seed-ddl.js +100 -0
- package/dist/seed-layout.d.ts +145 -0
- package/dist/seed-layout.d.ts.map +1 -0
- package/dist/seed-layout.js +192 -0
- package/dist/seed-layout.repository.d.ts +22 -0
- package/dist/seed-layout.repository.d.ts.map +1 -0
- package/dist/seed-layout.repository.js +3 -0
- package/dist/seed-registry.d.ts +71 -0
- package/dist/seed-registry.d.ts.map +1 -0
- package/dist/seed-registry.js +143 -0
- package/dist/seed-types-generator.d.ts +1 -1
- package/dist/seed-types-generator.d.ts.map +1 -1
- package/dist/seed-validation.d.ts +23 -0
- package/dist/seed-validation.d.ts.map +1 -0
- package/dist/seed-validation.js +224 -0
- package/dist/seed.repository.d.ts +37 -0
- package/dist/seed.repository.d.ts.map +1 -0
- package/dist/seed.repository.js +3 -0
- package/dist/seeds.d.ts +20 -0
- package/dist/seeds.d.ts.map +1 -0
- package/dist/seeds.js +21 -0
- package/dist/site-settings.repository.d.ts +16 -0
- package/dist/site-settings.repository.d.ts.map +1 -0
- package/dist/site-settings.repository.js +3 -0
- package/dist/slug-utils.d.ts +18 -0
- package/dist/slug-utils.d.ts.map +1 -0
- package/dist/slug-utils.js +32 -0
- package/dist/storage.d.ts +46 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +3 -0
- package/dist/transfer/csv.d.ts +34 -0
- package/dist/transfer/csv.d.ts.map +1 -0
- package/dist/transfer/csv.js +135 -0
- package/dist/transfer/flat-seed.d.ts +13 -0
- package/dist/transfer/flat-seed.d.ts.map +1 -0
- package/dist/transfer/flat-seed.js +45 -0
- package/dist/transfer/index.d.ts +7 -0
- package/dist/transfer/index.d.ts.map +1 -0
- package/dist/transfer/index.js +8 -0
- package/dist/transfer/ndjson.d.ts +25 -0
- package/dist/transfer/ndjson.d.ts.map +1 -0
- package/dist/transfer/ndjson.js +62 -0
- package/dist/transfer/row-mapping.d.ts +36 -0
- package/dist/transfer/row-mapping.d.ts.map +1 -0
- package/dist/transfer/row-mapping.js +90 -0
- package/dist/transfer/transfer.constants.d.ts +20 -0
- package/dist/transfer/transfer.constants.d.ts.map +1 -0
- package/dist/transfer/transfer.constants.js +21 -0
- package/dist/transfer/transfer.types.d.ts +46 -0
- package/dist/transfer/transfer.types.d.ts.map +1 -0
- package/dist/transfer/transfer.types.js +6 -0
- package/dist/types.d.ts +243 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/validation.d.ts +50 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +660 -0
- package/dist/webhook-crypto.d.ts +2 -0
- package/dist/webhook-crypto.d.ts.map +1 -1
- package/dist/webhook-crypto.js +2 -2
- package/dist/webhook-validation.d.ts +3 -0
- package/dist/webhook-validation.d.ts.map +1 -0
- package/dist/webhook-validation.js +6 -0
- package/dist/webhooks/webhook-crypto.d.ts +2 -0
- package/dist/webhooks/webhook-crypto.d.ts.map +1 -1
- package/dist/webhooks/webhook-crypto.js +2 -2
- package/package.json +23 -15
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { OAuthScope } from './scopes.js';
|
|
2
|
+
/** Outcome of a role arbitration. `deniedScopes` is empty when fully granted. */
|
|
3
|
+
export interface ScopeGrantDecision {
|
|
4
|
+
grantedScopes: OAuthScope[];
|
|
5
|
+
deniedScopes: OAuthScope[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Arbitrates which of the requested scopes a given user role may grant.
|
|
9
|
+
*
|
|
10
|
+
* This is the ONLY place role-based authorization may live in the OAuth flow.
|
|
11
|
+
* `/oauth/authorize` and `/oauth/token` must never branch on `role` themselves,
|
|
12
|
+
* so introducing a real role system later requires swapping the implementation
|
|
13
|
+
* bound in repositoryMiddleware and nothing else.
|
|
14
|
+
*/
|
|
15
|
+
export interface IRoleGuard {
|
|
16
|
+
/**
|
|
17
|
+
* @param role - The resource owner's role claim, or undefined when absent.
|
|
18
|
+
* @param requestedScopes - Scopes the client asked for, already validated.
|
|
19
|
+
*/
|
|
20
|
+
arbitrate(role: string | undefined, requestedScopes: readonly OAuthScope[]): Promise<ScopeGrantDecision>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Stub guard for the pre-roles world: grants every requested scope to every role.
|
|
24
|
+
*
|
|
25
|
+
* This permissiveness is EXPLICIT and directly tested, not an accidental default.
|
|
26
|
+
* When the roles feature lands, replace this binding with a real adapter; the
|
|
27
|
+
* behaviour change will then be visible as a failing test here, by design.
|
|
28
|
+
*/
|
|
29
|
+
export declare class AllowAllRoleGuard implements IRoleGuard {
|
|
30
|
+
arbitrate(_role: string | undefined, requestedScopes: readonly OAuthScope[]): Promise<ScopeGrantDecision>;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=role-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"role-guard.d.ts","sourceRoot":"","sources":["../../src/oauth/role-guard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,UAAU,EAAE,CAAA;IAC3B,YAAY,EAAE,UAAU,EAAE,CAAA;CAC3B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,eAAe,EAAE,SAAS,UAAU,EAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;CACzG;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,YAAW,UAAU;IAC5C,SAAS,CACb,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,eAAe,EAAE,SAAS,UAAU,EAAE,GACrC,OAAO,CAAC,kBAAkB,CAAC,CAE7B;CACF"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
/**
|
|
4
|
+
* Stub guard for the pre-roles world: grants every requested scope to every role.
|
|
5
|
+
*
|
|
6
|
+
* This permissiveness is EXPLICIT and directly tested, not an accidental default.
|
|
7
|
+
* When the roles feature lands, replace this binding with a real adapter; the
|
|
8
|
+
* behaviour change will then be visible as a failing test here, by design.
|
|
9
|
+
*/
|
|
10
|
+
export class AllowAllRoleGuard {
|
|
11
|
+
async arbitrate(_role, requestedScopes) {
|
|
12
|
+
return { grantedScopes: [...requestedScopes], deniedScopes: [] };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth scope vocabulary. Scopes map 1:1 onto the MCP tools exposed today; no
|
|
3
|
+
* speculative scopes are defined.
|
|
4
|
+
*
|
|
5
|
+
* - `schema:read` -> beech_list_seeds, beech_get_seed, beech_schema_export,
|
|
6
|
+
* beech_schema_validate, beech_schema_plan
|
|
7
|
+
* - `schema:write` -> beech_schema_apply
|
|
8
|
+
*
|
|
9
|
+
* `beech_schema_plan` is deliberately classified as READ: despite the imperative
|
|
10
|
+
* name it is a dry-run that computes a migration plan and mutates nothing.
|
|
11
|
+
*/
|
|
12
|
+
export declare const OAUTH_SCOPES: readonly ['schema:read', 'schema:write'];
|
|
13
|
+
export type OAuthScope = (typeof OAUTH_SCOPES)[number];
|
|
14
|
+
/** Narrows an arbitrary string to a known scope. */
|
|
15
|
+
export declare function isOAuthScope(value: string): value is OAuthScope;
|
|
16
|
+
/**
|
|
17
|
+
* Parses an RFC 6749 §3.3 space-delimited scope string.
|
|
18
|
+
* Returns null if the string is empty or contains any unknown scope — callers
|
|
19
|
+
* must reject the request with `invalid_scope` rather than silently dropping it.
|
|
20
|
+
* Duplicates are collapsed; order is not significant.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseScopeString(raw: string): OAuthScope[] | null;
|
|
23
|
+
/** Serializes scopes back to the space-delimited wire/storage format. */
|
|
24
|
+
export declare function formatScopes(scopes: readonly OAuthScope[]): string;
|
|
25
|
+
/** True when every scope in `requested` is present in `granted`. */
|
|
26
|
+
export declare function isScopeSubset(requested: readonly OAuthScope[], granted: readonly OAuthScope[]): boolean;
|
|
27
|
+
//# sourceMappingURL=scopes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scopes.d.ts","sourceRoot":"","sources":["../../src/oauth/scopes.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,YAAI,aAAa,EAAE,cAAc,CAAU,CAAA;AAEpE,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAA;AAEtD,oDAAoD;AACpD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,UAAU,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,GAAG,IAAI,CAMjE;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,CAElE;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAC3B,SAAS,EAAE,SAAS,UAAU,EAAE,EAChC,OAAO,EAAE,SAAS,UAAU,EAAE,GAC7B,OAAO,CAET"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
/**
|
|
4
|
+
* OAuth scope vocabulary. Scopes map 1:1 onto the MCP tools exposed today; no
|
|
5
|
+
* speculative scopes are defined.
|
|
6
|
+
*
|
|
7
|
+
* - `schema:read` -> beech_list_seeds, beech_get_seed, beech_schema_export,
|
|
8
|
+
* beech_schema_validate, beech_schema_plan
|
|
9
|
+
* - `schema:write` -> beech_schema_apply
|
|
10
|
+
*
|
|
11
|
+
* `beech_schema_plan` is deliberately classified as READ: despite the imperative
|
|
12
|
+
* name it is a dry-run that computes a migration plan and mutates nothing.
|
|
13
|
+
*/
|
|
14
|
+
export const OAUTH_SCOPES = ['schema:read', 'schema:write'];
|
|
15
|
+
/** Narrows an arbitrary string to a known scope. */
|
|
16
|
+
export function isOAuthScope(value) {
|
|
17
|
+
return OAUTH_SCOPES.includes(value);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parses an RFC 6749 §3.3 space-delimited scope string.
|
|
21
|
+
* Returns null if the string is empty or contains any unknown scope — callers
|
|
22
|
+
* must reject the request with `invalid_scope` rather than silently dropping it.
|
|
23
|
+
* Duplicates are collapsed; order is not significant.
|
|
24
|
+
*/
|
|
25
|
+
export function parseScopeString(raw) {
|
|
26
|
+
const parts = raw.trim().split(/\s+/).filter(Boolean);
|
|
27
|
+
if (parts.length === 0)
|
|
28
|
+
return null;
|
|
29
|
+
const unique = [...new Set(parts)];
|
|
30
|
+
if (!unique.every(isOAuthScope))
|
|
31
|
+
return null;
|
|
32
|
+
return unique;
|
|
33
|
+
}
|
|
34
|
+
/** Serializes scopes back to the space-delimited wire/storage format. */
|
|
35
|
+
export function formatScopes(scopes) {
|
|
36
|
+
return [...new Set(scopes)].join(' ');
|
|
37
|
+
}
|
|
38
|
+
/** True when every scope in `requested` is present in `granted`. */
|
|
39
|
+
export function isScopeSubset(requested, granted) {
|
|
40
|
+
return requested.every(scope => granted.includes(scope));
|
|
41
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { OAuthScope } from './scopes.js';
|
|
2
|
+
export type OAuthTokenType = 'access' | 'refresh';
|
|
3
|
+
export interface NewOAuthToken {
|
|
4
|
+
id: string;
|
|
5
|
+
/** SHA-256 hex hash of the token. The plaintext token is never persisted. */
|
|
6
|
+
tokenHash: string;
|
|
7
|
+
tokenType: OAuthTokenType;
|
|
8
|
+
clientId: string;
|
|
9
|
+
userId: string;
|
|
10
|
+
scope: OAuthScope[];
|
|
11
|
+
/** Hash of the authorization code this token descends from, for cascade revocation. */
|
|
12
|
+
authorizationCodeHash: string;
|
|
13
|
+
expiresAt: number;
|
|
14
|
+
}
|
|
15
|
+
export interface OAuthTokenRecord extends NewOAuthToken {
|
|
16
|
+
createdAt: number;
|
|
17
|
+
revokedAt: number | null;
|
|
18
|
+
}
|
|
19
|
+
export interface AuthorizedClientSummary {
|
|
20
|
+
clientId: string;
|
|
21
|
+
clientName: string;
|
|
22
|
+
scope: OAuthScope[];
|
|
23
|
+
/** Creation timestamp of the most recent live token for this client. */
|
|
24
|
+
lastIssuedAt: number;
|
|
25
|
+
}
|
|
26
|
+
export interface IOAuthTokenRepository {
|
|
27
|
+
save(record: NewOAuthToken): Promise<void>;
|
|
28
|
+
/** Finds an unexpired, unrevoked token by hash and type. */
|
|
29
|
+
findActiveByHash(tokenHash: string, tokenType: OAuthTokenType, nowTimestamp: number): Promise<OAuthTokenRecord | null>;
|
|
30
|
+
/** Revokes a single token. False when already revoked or absent. */
|
|
31
|
+
revokeByHash(tokenHash: string, nowTimestamp: number): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* Revokes EVERY token descending from one authorization code, both access and
|
|
34
|
+
* refresh. Called on authorization-code replay (OAuth 2.1 §4.1.3).
|
|
35
|
+
* Returns the number of tokens revoked.
|
|
36
|
+
*/
|
|
37
|
+
revokeByAuthorizationCode(authorizationCodeHash: string, nowTimestamp: number): Promise<number>;
|
|
38
|
+
/**
|
|
39
|
+
* Revokes every live token for one (client, user) pair — access AND refresh,
|
|
40
|
+
* never one without the other. Backs "revoke this app" in the dashboard.
|
|
41
|
+
*/
|
|
42
|
+
revokeAllForClientAndUser(clientId: string, userId: string, nowTimestamp: number): Promise<number>;
|
|
43
|
+
/** Lists the clients holding at least one live token for this user, newest first. */
|
|
44
|
+
listAuthorizedClientsForUser(userId: string, nowTimestamp: number): Promise<AuthorizedClientSummary[]>;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=token.repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.repository.d.ts","sourceRoot":"","sources":["../../src/oauth/token.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAA;AAEjD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,cAAc,CAAA;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,uFAAuF;IACvF,qBAAqB,EAAE,MAAM,CAAA;IAC7B,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE1C,4DAA4D;IAC5D,gBAAgB,CACd,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,cAAc,EACzB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IAEnC,oEAAoE;IACpE,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEvE;;;;OAIG;IACH,yBAAyB,CAAC,qBAAqB,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAE/F;;;OAGG;IACH,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAElG,qFAAqF;IACrF,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAAA;CACvG"}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @module @beechcms/core/observability/activity-logger
|
|
11
11
|
*/
|
|
12
|
-
export type ActivityAction = 'create' | 'update' | 'delete' | 'upload' | 'bulk_update';
|
|
12
|
+
export type ActivityAction = 'create' | 'update' | 'delete' | 'upload' | 'bulk_update' | 'security_alert';
|
|
13
13
|
export type EntityType = 'content' | 'media' | 'seed';
|
|
14
14
|
/**
|
|
15
15
|
* Identifies the human (or machine) actor that triggered the event.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activity-logger.d.ts","sourceRoot":"","sources":["../../src/observability/activity-logger.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"activity-logger.d.ts","sourceRoot":"","sources":["../../src/observability/activity-logger.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,aAAa,GAAG,gBAAgB,CAAA;AACzG,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAA;AAErD;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,cAAc,CAAA;IACtB,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,KAAK,EAAE,aAAa,CAAA;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B;;;;;;;;;OASG;IACH,GAAG,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CACnD"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Branch } from './types.js';
|
|
2
|
+
export declare function sha256hex(value: string): Promise<string>;
|
|
3
|
+
export declare function verifyHashField(stored: string, candidate: string): Promise<boolean>;
|
|
4
|
+
/**
|
|
5
|
+
* Risolve le policy di un branch applicando i valori di default.
|
|
6
|
+
* Tutta la logica di accesso ai campi deve passare per questa funzione,
|
|
7
|
+
* mai con inline `branch.policies?.x ?? default`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolvePolicies(branch: Branch): Required<NonNullable<Branch['policies']>>;
|
|
10
|
+
//# sourceMappingURL=policies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policies.d.ts","sourceRoot":"","sources":["../src/policies.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAExC,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK9D;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEzF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAgBzF"}
|
package/dist/policies.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
export async function sha256hex(value) {
|
|
4
|
+
const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(value));
|
|
5
|
+
return Array.from(new Uint8Array(buf))
|
|
6
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
7
|
+
.join('');
|
|
8
|
+
}
|
|
9
|
+
export async function verifyHashField(stored, candidate) {
|
|
10
|
+
return stored === (await sha256hex(candidate));
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Risolve le policy di un branch applicando i valori di default.
|
|
14
|
+
* Tutta la logica di accesso ai campi deve passare per questa funzione,
|
|
15
|
+
* mai con inline `branch.policies?.x ?? default`.
|
|
16
|
+
*/
|
|
17
|
+
export function resolvePolicies(branch) {
|
|
18
|
+
const privacy = branch.policies?.privacy ?? 'plain';
|
|
19
|
+
// Non-plain privacy implies hidden by default: the CMS hashes/encrypts on write,
|
|
20
|
+
// so returning the stored value would leak the digest to readers.
|
|
21
|
+
const defaultVisibility = privacy !== 'plain' ? 'hidden' : 'full';
|
|
22
|
+
// Repeaters live in a single JSON column — never filterable, sortable, or
|
|
23
|
+
// searchable/facetable in v1, regardless of what's set on the branch.
|
|
24
|
+
const isRepeater = branch.type === 'repeater';
|
|
25
|
+
return {
|
|
26
|
+
privacy,
|
|
27
|
+
visibility: branch.policies?.visibility ?? defaultVisibility,
|
|
28
|
+
search: isRepeater ? false : branch.policies?.search ?? true,
|
|
29
|
+
filter: isRepeater ? false : branch.policies?.filter ?? true,
|
|
30
|
+
sort: isRepeater ? false : branch.policies?.sort ?? true,
|
|
31
|
+
public: branch.policies?.public ?? true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue.stub.d.ts","sourceRoot":"","sources":["../../src/queue/queue.stub.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEzD,0EAA0E;AAC1E,qBAAa,gBAAiB,YAAW,aAAa;IAC9C,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"queue.stub.d.ts","sourceRoot":"","sources":["../../src/queue/queue.stub.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEzD,0EAA0E;AAC1E,qBAAa,gBAAiB,YAAW,aAAa;IAC9C,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAG7D;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.stub.d.ts","sourceRoot":"","sources":["../../src/queue/scheduler.stub.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,qBAAa,aAAc,YAAW,UAAU;IAC9C,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"scheduler.stub.d.ts","sourceRoot":"","sources":["../../src/queue/scheduler.stub.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,qBAAa,aAAc,YAAW,UAAU;IAC9C,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAE1C;CACF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ContentRepository } from './content.repository.js';
|
|
2
|
+
import type { BeechBucket } from './storage.js';
|
|
3
|
+
import type { IClock } from './clock.js';
|
|
4
|
+
import type { IIdGenerator } from './id-generator.js';
|
|
5
|
+
/**
|
|
6
|
+
* The envelope put on the wire / queue. `name` selects the handler from the
|
|
7
|
+
* JobRegistry; `payload` is the developer-supplied, JSON-serializable body.
|
|
8
|
+
*/
|
|
9
|
+
export interface QueueMessage<T = unknown> {
|
|
10
|
+
name: string;
|
|
11
|
+
payload: T;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Execution context handed to every job. Botanical Invariant: jobs receive the
|
|
15
|
+
* engine-mediated repository, NEVER a raw D1Database. `env` is a read-only,
|
|
16
|
+
* stringly-typed view of bindings/secrets for things like fetch targets — it
|
|
17
|
+
* deliberately does not expose `DB`.
|
|
18
|
+
*/
|
|
19
|
+
export interface JobContext {
|
|
20
|
+
repository: ContentRepository;
|
|
21
|
+
bucket: BeechBucket;
|
|
22
|
+
clock: IClock;
|
|
23
|
+
idGenerator: IIdGenerator;
|
|
24
|
+
env: Record<string, string | undefined>;
|
|
25
|
+
}
|
|
26
|
+
/** A single background worker. Receives the decoded payload + context. */
|
|
27
|
+
export type JobHandler<T = unknown> = (payload: T, context: JobContext) => Promise<void>;
|
|
28
|
+
/** Name -> handler map, supplied via BeechConfig.jobs. */
|
|
29
|
+
export type JobRegistry = Record<string, JobHandler<any>>;
|
|
30
|
+
/**
|
|
31
|
+
* Producer port. `enqueue` schedules deferred work and returns once the message
|
|
32
|
+
* is accepted by the transport. Like INotificationService, implementations MUST
|
|
33
|
+
* decide their own fire-and-forget vs inline semantics and MUST NOT let a
|
|
34
|
+
* transport failure crash the request that called enqueue.
|
|
35
|
+
*/
|
|
36
|
+
export interface IQueueService {
|
|
37
|
+
enqueue<T>(name: string, payload: T): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=queue.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.interface.d.ts","sourceRoot":"","sources":["../src/queue.interface.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,CAAC,CAAA;CACX;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,iBAAiB,CAAA;IAC7B,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,YAAY,CAAA;IACzB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CACxC;AAED,0EAA0E;AAC1E,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,CACpC,OAAO,EAAE,CAAC,EACV,OAAO,EAAE,UAAU,KAChB,OAAO,CAAC,IAAI,CAAC,CAAA;AAElB,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAA;AAEzD;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACpD"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IQueueService } from './queue.interface.js';
|
|
2
|
+
/** Safe no-op producer (e.g. unit tests that don't assert on enqueue). */
|
|
3
|
+
export declare class NoOpQueueService implements IQueueService {
|
|
4
|
+
enqueue<T>(_name: string, _payload: T): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=queue.stub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.stub.d.ts","sourceRoot":"","sources":["../src/queue.stub.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEzD,0EAA0E;AAC1E,qBAAa,gBAAiB,YAAW,aAAa;IAC9C,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAG5D"}
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
export interface RateLimitResult {
|
|
2
2
|
isAllowed: boolean;
|
|
3
3
|
/**
|
|
4
|
-
* The number of seconds after which the client may retry the request.
|
|
5
|
-
* Note: This field is optional and may not be supported by all implementations
|
|
6
|
-
* (e.g. Cloudflare Rate Limit binding does not return retry-after information).
|
|
4
|
+
* The number of whole seconds after which the client may retry the request.
|
|
7
5
|
*/
|
|
8
6
|
retryAfterSeconds?: number;
|
|
7
|
+
/**
|
|
8
|
+
* The maximum token capacity configured for this limiter.
|
|
9
|
+
*/
|
|
10
|
+
limit?: number;
|
|
11
|
+
/**
|
|
12
|
+
* The number of whole tokens remaining in the bucket.
|
|
13
|
+
*/
|
|
14
|
+
remaining?: number;
|
|
9
15
|
}
|
|
10
16
|
export interface IRateLimiter {
|
|
11
17
|
/**
|
|
12
18
|
* Checks whether the given key is within the rate limit.
|
|
13
|
-
* The key should combine the client IP address and an endpoint-specific prefix
|
|
14
|
-
* to prevent one endpoint's limit from being shared with another.
|
|
15
19
|
*/
|
|
16
20
|
checkLimit(key: string): Promise<RateLimitResult>;
|
|
21
|
+
/**
|
|
22
|
+
* Clears cached bucket state (for testing and isolation).
|
|
23
|
+
*/
|
|
24
|
+
reset?(): void;
|
|
17
25
|
}
|
|
18
26
|
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/rate-limit/rate-limiter.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,OAAO,CAAA;IAClB
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/rate-limit/rate-limiter.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,OAAO,CAAA;IAClB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;IACjD;;OAEG;IACH,KAAK,CAAC,IAAI,IAAI,CAAA;CACf"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { IRateLimiter, RateLimitResult } from './rate-limiter.js';
|
|
2
|
+
import type { IClock } from '../common/clock.js';
|
|
3
|
+
export interface TokenBucketOptions {
|
|
4
|
+
capacity?: number;
|
|
5
|
+
refillRatePerSecond?: number;
|
|
6
|
+
clock?: IClock;
|
|
7
|
+
maxIdleTimeSeconds?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class TokenBucketRateLimiter implements IRateLimiter {
|
|
10
|
+
private readonly buckets;
|
|
11
|
+
private readonly capacity;
|
|
12
|
+
private readonly refillRatePerSecond;
|
|
13
|
+
private readonly clock;
|
|
14
|
+
private readonly maxIdleTimeSeconds;
|
|
15
|
+
constructor(options?: TokenBucketOptions);
|
|
16
|
+
private pruneExpiredBuckets;
|
|
17
|
+
checkLimit(key: string): Promise<RateLimitResult>;
|
|
18
|
+
reset(): void;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=token-bucket-rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-bucket-rate-limiter.d.ts","sourceRoot":"","sources":["../../src/rate-limit/token-bucket-rate-limiter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACtE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAQhD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,qBAAa,sBAAuB,YAAW,YAAY;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAQ;IAC5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAQ;IAE3C,YAAY,OAAO,CAAC,EAAE,kBAAkB,EAMvC;IAED,OAAO,CAAC,mBAAmB;IASrB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAoCtD;IAED,KAAK,IAAI,IAAI,CAEZ;CACF"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
import { SystemClock } from '../common/clock.js';
|
|
4
|
+
export class TokenBucketRateLimiter {
|
|
5
|
+
buckets = new Map();
|
|
6
|
+
capacity;
|
|
7
|
+
refillRatePerSecond;
|
|
8
|
+
clock;
|
|
9
|
+
maxIdleTimeSeconds;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.capacity = options?.capacity ?? 17;
|
|
12
|
+
// Default: 1 token every 3.53 seconds (~0.283286 tokens/sec)
|
|
13
|
+
this.refillRatePerSecond = options?.refillRatePerSecond ?? (1 / 3.53);
|
|
14
|
+
this.clock = options?.clock ?? SystemClock;
|
|
15
|
+
this.maxIdleTimeSeconds = options?.maxIdleTimeSeconds ?? 3600; // 1 hour idle TTL
|
|
16
|
+
}
|
|
17
|
+
pruneExpiredBuckets(now) {
|
|
18
|
+
if (this.buckets.size < 500)
|
|
19
|
+
return;
|
|
20
|
+
for (const [key, state] of this.buckets.entries()) {
|
|
21
|
+
if (now - state.lastRefillTimestamp > this.maxIdleTimeSeconds) {
|
|
22
|
+
this.buckets.delete(key);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async checkLimit(key) {
|
|
27
|
+
const now = this.clock.now() / 1000; // fractional seconds for continuous refill
|
|
28
|
+
this.pruneExpiredBuckets(now);
|
|
29
|
+
let bucket = this.buckets.get(key);
|
|
30
|
+
if (!bucket) {
|
|
31
|
+
bucket = {
|
|
32
|
+
tokens: this.capacity,
|
|
33
|
+
lastRefillTimestamp: now,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const elapsed = Math.max(0, now - bucket.lastRefillTimestamp);
|
|
38
|
+
bucket.tokens = Math.min(this.capacity, bucket.tokens + elapsed * this.refillRatePerSecond);
|
|
39
|
+
bucket.lastRefillTimestamp = now;
|
|
40
|
+
}
|
|
41
|
+
if (bucket.tokens >= 1) {
|
|
42
|
+
bucket.tokens -= 1;
|
|
43
|
+
this.buckets.set(key, bucket);
|
|
44
|
+
return {
|
|
45
|
+
isAllowed: true,
|
|
46
|
+
limit: this.capacity,
|
|
47
|
+
remaining: Math.floor(bucket.tokens),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
this.buckets.set(key, bucket);
|
|
51
|
+
const tokensNeeded = 1 - bucket.tokens;
|
|
52
|
+
const retryAfterSeconds = Math.max(1, Math.ceil(tokensNeeded / this.refillRatePerSecond));
|
|
53
|
+
return {
|
|
54
|
+
isAllowed: false,
|
|
55
|
+
retryAfterSeconds,
|
|
56
|
+
limit: this.capacity,
|
|
57
|
+
remaining: Math.max(0, Math.floor(bucket.tokens)),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
reset() {
|
|
61
|
+
this.buckets.clear();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Permission, type Scope } from './permissions.js';
|
|
2
|
+
import type { EffectivePermissions, PermissionAssignment, RoleRecord } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Folds a user's active assignments into their effective authority.
|
|
5
|
+
*
|
|
6
|
+
* The model is strictly additive: every assignment can only widen the result, and
|
|
7
|
+
* assignments referencing an unknown role are ignored rather than treated as an error.
|
|
8
|
+
*
|
|
9
|
+
* @param assignments - Active assignments, already decay-filtered by the repository.
|
|
10
|
+
* @param roles - The roles those assignments reference; extra roles are harmless.
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildEffectivePermissions(assignments: readonly PermissionAssignment[], roles: readonly RoleRecord[]): EffectivePermissions;
|
|
13
|
+
/**
|
|
14
|
+
* Returns everything the caller may do within one scope: the union of their global
|
|
15
|
+
* permissions and the permissions granted on that specific seed.
|
|
16
|
+
*/
|
|
17
|
+
export declare function permissionsForScope(effective: EffectivePermissions, scope: Scope): ReadonlySet<Permission>;
|
|
18
|
+
/**
|
|
19
|
+
* The single authorization question: may this caller perform `permission` on `scope`?
|
|
20
|
+
*
|
|
21
|
+
* A global grant satisfies any scope. A scoped grant satisfies only its own seed and
|
|
22
|
+
* never {@link GLOBAL_SCOPE} — that asymmetry is what stops horizontal escalation.
|
|
23
|
+
*/
|
|
24
|
+
export declare function hasPermission(effective: EffectivePermissions, permission: Permission, scope: Scope): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* The anti-escalation rule: an actor may only hand out authority they already hold.
|
|
27
|
+
*
|
|
28
|
+
* A grant is legal only when the actor holds every permission being granted, at the
|
|
29
|
+
* scope it is being granted on. Consequently an actor scoped to seed X can never mint
|
|
30
|
+
* a global assignment, and can never include a permission absent from their own set —
|
|
31
|
+
* regardless of what the role itself contains.
|
|
32
|
+
*
|
|
33
|
+
* @param actor - The granting user's effective authority.
|
|
34
|
+
* @param targetScope - The scope the new assignment would apply to.
|
|
35
|
+
* @param targetPermissions - The permissions carried by the role being assigned.
|
|
36
|
+
*/
|
|
37
|
+
export declare function canGrant(actor: EffectivePermissions, targetScope: Scope, targetPermissions: readonly Permission[]): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Every permission the actor holds at ANY scope — global or seed-scoped — folded into
|
|
40
|
+
* one set.
|
|
41
|
+
*
|
|
42
|
+
* This is deliberately NOT an authorization primitive: it answers "could this actor
|
|
43
|
+
* ever grant X somewhere", not "may this actor do X here". Only {@link hasPermission}
|
|
44
|
+
* and {@link canGrant} answer the latter, and every route-level decision must keep
|
|
45
|
+
* using them.
|
|
46
|
+
*/
|
|
47
|
+
export declare function permissionsHeldAnywhere(effective: EffectivePermissions): ReadonlySet<Permission>;
|
|
48
|
+
/**
|
|
49
|
+
* Whether the actor holds `permission` on at least one scope.
|
|
50
|
+
*
|
|
51
|
+
* Backs the coarse route gate for administration endpoints whose scope is not in the
|
|
52
|
+
* URL: the gate keeps out callers with no administrative authority at all, and the
|
|
53
|
+
* slice then makes the exact per-scope decision with {@link hasPermission} /
|
|
54
|
+
* {@link canGrant}. Never use it as the final authorization check.
|
|
55
|
+
*/
|
|
56
|
+
export declare function hasPermissionAnywhere(effective: EffectivePermissions, permission: Permission): boolean;
|
|
57
|
+
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../../src/rbac/evaluate.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,UAAU,EAAE,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAC5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAExF;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,SAAS,oBAAoB,EAAE,EAC5C,KAAK,EAAE,SAAS,UAAU,EAAE,GAC3B,oBAAoB,CAuBtB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,oBAAoB,EAC/B,KAAK,EAAE,KAAK,GACX,WAAW,CAAC,UAAU,CAAC,CAKzB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,oBAAoB,EAC/B,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,KAAK,GACX,OAAO,CAIT;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,oBAAoB,EAC3B,WAAW,EAAE,KAAK,EAClB,iBAAiB,EAAE,SAAS,UAAU,EAAE,GACvC,OAAO,CAMT;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,oBAAoB,GAAG,WAAW,CAAC,UAAU,CAAC,CAMhG;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,oBAAoB,EAC/B,UAAU,EAAE,UAAU,GACrB,OAAO,CAMT"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
+
import { GLOBAL_SCOPE } from './permissions.js';
|
|
4
|
+
/**
|
|
5
|
+
* Folds a user's active assignments into their effective authority.
|
|
6
|
+
*
|
|
7
|
+
* The model is strictly additive: every assignment can only widen the result, and
|
|
8
|
+
* assignments referencing an unknown role are ignored rather than treated as an error.
|
|
9
|
+
*
|
|
10
|
+
* @param assignments - Active assignments, already decay-filtered by the repository.
|
|
11
|
+
* @param roles - The roles those assignments reference; extra roles are harmless.
|
|
12
|
+
*/
|
|
13
|
+
export function buildEffectivePermissions(assignments, roles) {
|
|
14
|
+
const rolesById = new Map(roles.map(role => [role.id, role]));
|
|
15
|
+
const global = new Set();
|
|
16
|
+
const byScope = new Map();
|
|
17
|
+
for (const assignment of assignments) {
|
|
18
|
+
const role = rolesById.get(assignment.roleId);
|
|
19
|
+
if (!role)
|
|
20
|
+
continue;
|
|
21
|
+
if (assignment.scope === GLOBAL_SCOPE) {
|
|
22
|
+
for (const permission of role.permissions)
|
|
23
|
+
global.add(permission);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
let bucket = byScope.get(assignment.scope);
|
|
27
|
+
if (!bucket) {
|
|
28
|
+
bucket = new Set();
|
|
29
|
+
byScope.set(assignment.scope, bucket);
|
|
30
|
+
}
|
|
31
|
+
for (const permission of role.permissions)
|
|
32
|
+
bucket.add(permission);
|
|
33
|
+
}
|
|
34
|
+
return { global, byScope };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns everything the caller may do within one scope: the union of their global
|
|
38
|
+
* permissions and the permissions granted on that specific seed.
|
|
39
|
+
*/
|
|
40
|
+
export function permissionsForScope(effective, scope) {
|
|
41
|
+
if (scope === GLOBAL_SCOPE)
|
|
42
|
+
return effective.global;
|
|
43
|
+
const scoped = effective.byScope.get(scope);
|
|
44
|
+
if (!scoped)
|
|
45
|
+
return effective.global;
|
|
46
|
+
return new Set([...effective.global, ...scoped]);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The single authorization question: may this caller perform `permission` on `scope`?
|
|
50
|
+
*
|
|
51
|
+
* A global grant satisfies any scope. A scoped grant satisfies only its own seed and
|
|
52
|
+
* never {@link GLOBAL_SCOPE} — that asymmetry is what stops horizontal escalation.
|
|
53
|
+
*/
|
|
54
|
+
export function hasPermission(effective, permission, scope) {
|
|
55
|
+
if (effective.global.has(permission))
|
|
56
|
+
return true;
|
|
57
|
+
if (scope === GLOBAL_SCOPE)
|
|
58
|
+
return false;
|
|
59
|
+
return effective.byScope.get(scope)?.has(permission) === true;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The anti-escalation rule: an actor may only hand out authority they already hold.
|
|
63
|
+
*
|
|
64
|
+
* A grant is legal only when the actor holds every permission being granted, at the
|
|
65
|
+
* scope it is being granted on. Consequently an actor scoped to seed X can never mint
|
|
66
|
+
* a global assignment, and can never include a permission absent from their own set —
|
|
67
|
+
* regardless of what the role itself contains.
|
|
68
|
+
*
|
|
69
|
+
* @param actor - The granting user's effective authority.
|
|
70
|
+
* @param targetScope - The scope the new assignment would apply to.
|
|
71
|
+
* @param targetPermissions - The permissions carried by the role being assigned.
|
|
72
|
+
*/
|
|
73
|
+
export function canGrant(actor, targetScope, targetPermissions) {
|
|
74
|
+
if (targetScope === GLOBAL_SCOPE) {
|
|
75
|
+
return targetPermissions.every(permission => actor.global.has(permission));
|
|
76
|
+
}
|
|
77
|
+
const held = permissionsForScope(actor, targetScope);
|
|
78
|
+
return targetPermissions.every(permission => held.has(permission));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Every permission the actor holds at ANY scope — global or seed-scoped — folded into
|
|
82
|
+
* one set.
|
|
83
|
+
*
|
|
84
|
+
* This is deliberately NOT an authorization primitive: it answers "could this actor
|
|
85
|
+
* ever grant X somewhere", not "may this actor do X here". Only {@link hasPermission}
|
|
86
|
+
* and {@link canGrant} answer the latter, and every route-level decision must keep
|
|
87
|
+
* using them.
|
|
88
|
+
*/
|
|
89
|
+
export function permissionsHeldAnywhere(effective) {
|
|
90
|
+
const held = new Set(effective.global);
|
|
91
|
+
for (const bucket of effective.byScope.values()) {
|
|
92
|
+
for (const permission of bucket)
|
|
93
|
+
held.add(permission);
|
|
94
|
+
}
|
|
95
|
+
return held;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Whether the actor holds `permission` on at least one scope.
|
|
99
|
+
*
|
|
100
|
+
* Backs the coarse route gate for administration endpoints whose scope is not in the
|
|
101
|
+
* URL: the gate keeps out callers with no administrative authority at all, and the
|
|
102
|
+
* slice then makes the exact per-scope decision with {@link hasPermission} /
|
|
103
|
+
* {@link canGrant}. Never use it as the final authorization check.
|
|
104
|
+
*/
|
|
105
|
+
export function hasPermissionAnywhere(effective, permission) {
|
|
106
|
+
if (effective.global.has(permission))
|
|
107
|
+
return true;
|
|
108
|
+
for (const bucket of effective.byScope.values()) {
|
|
109
|
+
if (bucket.has(permission))
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|