@beechcms/core 0.8.0 → 0.9.0
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/auth/account-email.d.ts +3 -0
- package/dist/auth/account-email.d.ts.map +1 -0
- package/dist/auth/account-email.js +9 -0
- package/dist/auth/password-reset-token.repository.d.ts +13 -2
- package/dist/auth/password-reset-token.repository.d.ts.map +1 -1
- package/dist/auth/user.repository.d.ts +69 -4
- package/dist/auth/user.repository.d.ts.map +1 -1
- 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 +31 -0
- package/dist/common/canonical-json.d.ts.map +1 -0
- package/dist/common/canonical-json.js +91 -0
- package/dist/content/content-scan.repository.d.ts +1 -1
- package/dist/content/content.repository.d.ts +154 -3
- package/dist/content/content.repository.d.ts.map +1 -1
- package/dist/content/content.repository.js +62 -0
- 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/seed-media-purge.repository.d.ts +34 -0
- package/dist/content/seed-media-purge.repository.d.ts.map +1 -0
- package/dist/content/seed-media-purge.repository.js +3 -0
- package/dist/content/seed.repository.d.ts +14 -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/content-view.d.ts +757 -0
- package/dist/dashboard-layout/content-view.d.ts.map +1 -0
- package/dist/dashboard-layout/content-view.js +285 -0
- package/dist/dashboard-layout/content-view.repository.d.ts +39 -0
- package/dist/dashboard-layout/content-view.repository.d.ts.map +1 -0
- package/dist/dashboard-layout/content-view.repository.js +3 -0
- package/dist/dashboard-layout/dashboard-layout.d.ts +5 -0
- package/dist/dashboard-layout/dashboard-layout.d.ts.map +1 -1
- package/dist/dashboard-layout/dashboard-layout.js +17 -9
- package/dist/dashboard-layout/kanban/kanban.d.ts +32 -18
- package/dist/dashboard-layout/kanban/kanban.d.ts.map +1 -1
- package/dist/dashboard-layout/kanban/kanban.js +45 -21
- package/dist/dashboard-layout/seed-layout.d.ts +22 -31
- package/dist/dashboard-layout/seed-layout.d.ts.map +1 -1
- package/dist/dashboard-layout/seed-layout.js +93 -18
- package/dist/dashboard-layout/seed-layout.repository.d.ts +1 -5
- package/dist/dashboard-layout/seed-layout.repository.d.ts.map +1 -1
- 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 +247 -0
- package/dist/engine/ddl/ddl.d.ts.map +1 -0
- package/dist/engine/ddl/ddl.js +653 -0
- package/dist/engine/ddl/schema-mutator.d.ts +29 -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/seed-purge-guards.d.ts +7 -0
- package/dist/engine/ddl/seed-purge-guards.d.ts.map +1 -0
- package/dist/engine/ddl/seed-purge-guards.js +19 -0
- package/dist/engine/engine.d.ts +4 -3
- package/dist/engine/engine.d.ts.map +1 -1
- package/dist/engine/engine.js +4 -3
- 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/relation-audit.d.ts +46 -0
- package/dist/engine/introspection/relation-audit.d.ts.map +1 -0
- package/dist/engine/introspection/relation-audit.js +103 -0
- package/dist/engine/introspection/schema-fingerprint.d.ts +74 -0
- package/dist/engine/introspection/schema-fingerprint.d.ts.map +1 -0
- package/dist/engine/introspection/schema-fingerprint.js +102 -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/privacy/policies.d.ts +58 -0
- package/dist/engine/privacy/policies.d.ts.map +1 -0
- package/dist/engine/privacy/policies.js +191 -0
- package/dist/engine/privacy/privacy.service.d.ts +78 -0
- package/dist/engine/privacy/privacy.service.d.ts.map +1 -0
- package/dist/engine/privacy/privacy.service.js +138 -0
- package/dist/engine/privacy.service.d.ts.map +1 -1
- package/dist/engine/query/active-clause.d.ts +19 -0
- package/dist/engine/query/active-clause.d.ts.map +1 -0
- package/dist/engine/query/active-clause.js +27 -0
- package/dist/engine/query/query.d.ts +36 -0
- package/dist/engine/query/query.d.ts.map +1 -0
- package/dist/engine/query/query.js +393 -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/seeds/candidate-ids.d.ts +14 -0
- package/dist/engine/seeds/candidate-ids.d.ts.map +1 -0
- package/dist/engine/seeds/candidate-ids.js +40 -0
- 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-definition-diff.d.ts +38 -0
- package/dist/engine/seeds/seed-definition-diff.d.ts.map +1 -0
- package/dist/engine/seeds/seed-definition-diff.js +51 -0
- package/dist/engine/seeds/seed-registry.d.ts +88 -0
- package/dist/engine/seeds/seed-registry.d.ts.map +1 -0
- package/dist/engine/seeds/seed-registry.js +194 -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 +116 -0
- package/dist/engine/seeds/seed-validation.d.ts +35 -0
- package/dist/engine/seeds/seed-validation.d.ts.map +1 -0
- package/dist/engine/seeds/seed-validation.js +384 -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/seeds/tag-migration.d.ts +73 -0
- package/dist/engine/seeds/tag-migration.d.ts.map +1 -0
- package/dist/engine/seeds/tag-migration.js +212 -0
- package/dist/engine/seeds/tag-options.d.ts +15 -0
- package/dist/engine/seeds/tag-options.d.ts.map +1 -0
- package/dist/engine/seeds/tag-options.js +111 -0
- package/dist/engine/seeds/tag-value.d.ts +31 -0
- package/dist/engine/seeds/tag-value.d.ts.map +1 -0
- package/dist/engine/seeds/tag-value.js +51 -0
- package/dist/engine/types.d.ts +59 -7
- package/dist/engine/types.d.ts.map +1 -1
- package/dist/engine/validation/cache.d.ts.map +1 -1
- package/dist/engine/validation/cache.js +20 -4
- 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 +11 -0
- package/dist/engine/validation/index.d.ts.map +1 -1
- package/dist/engine/validation/index.js +29 -33
- package/dist/engine/validation/primitives.d.ts +12 -0
- package/dist/engine/validation/primitives.d.ts.map +1 -1
- package/dist/engine/validation/primitives.js +22 -0
- package/dist/engine/validation/richtext-sanitizer.d.ts +9 -1
- package/dist/engine/validation/richtext-sanitizer.d.ts.map +1 -1
- package/dist/engine/validation/richtext-sanitizer.js +36 -7
- package/dist/engine/validation/schema-builders.d.ts +16 -0
- package/dist/engine/validation/schema-builders.d.ts.map +1 -1
- package/dist/engine/validation/schema-builders.js +125 -18
- 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 +34 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -11
- 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 +12 -0
- package/dist/media/file-types.d.ts.map +1 -1
- package/dist/media/file-types.js +24 -4
- 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.map +1 -1
- package/dist/media/magic-bytes.js +19 -9
- package/dist/media/media-transform.d.ts +92 -0
- package/dist/media/media-transform.d.ts.map +1 -0
- package/dist/media/media-transform.js +197 -0
- package/dist/media/media.repository.d.ts +5 -0
- package/dist/media/media.repository.d.ts.map +1 -1
- 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.repository.d.ts +11 -2
- package/dist/notifications/notification.repository.d.ts.map +1 -1
- package/dist/oauth/authorization-code.repository.d.ts +10 -5
- package/dist/oauth/authorization-code.repository.d.ts.map +1 -1
- package/dist/oauth/role-guard.d.ts +30 -11
- package/dist/oauth/role-guard.d.ts.map +1 -1
- package/dist/oauth/role-guard.js +27 -5
- package/dist/oauth/token.repository.d.ts +2 -0
- package/dist/oauth/token.repository.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.interface.d.ts +9 -1
- package/dist/queue/queue.interface.d.ts.map +1 -1
- package/dist/queue/queue.stub.d.ts +1 -1
- package/dist/queue/queue.stub.d.ts.map +1 -1
- package/dist/queue/queue.stub.js +2 -3
- 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/token-bucket-rate-limiter.d.ts +7 -0
- package/dist/rate-limit/token-bucket-rate-limiter.d.ts.map +1 -1
- package/dist/rate-limit/token-bucket-rate-limiter.js +66 -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 +84 -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 +130 -0
- package/dist/rbac/types.d.ts.map +1 -0
- package/dist/rbac/types.js +4 -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/search.repository.d.ts +5 -4
- package/dist/search/search.repository.d.ts.map +1 -1
- package/dist/search/vector-extractor.d.ts.map +1 -1
- package/dist/search/vector-extractor.js +23 -2
- package/dist/search/vector.repository.d.ts +2 -1
- package/dist/search/vector.repository.d.ts.map +1 -1
- package/dist/security/time-trap-token.repository.d.ts +5 -3
- package/dist/security/time-trap-token.repository.d.ts.map +1 -1
- 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/settings/site-settings.repository.d.ts +8 -0
- package/dist/settings/site-settings.repository.d.ts.map +1 -1
- 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 +50 -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 +97 -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-validation.d.ts +3 -0
- package/dist/webhook-validation.d.ts.map +1 -0
- package/dist/webhook-validation.js +6 -0
- package/package.json +11 -6
- package/dist/.tsbuildinfo +0 -1
|
@@ -16,6 +16,14 @@ export interface NotificationRecord {
|
|
|
16
16
|
isRead: boolean;
|
|
17
17
|
createdAt: number;
|
|
18
18
|
}
|
|
19
|
+
/** Input of {@link INotificationRepository.create}. */
|
|
20
|
+
export type CreateNotificationRecord = Omit<NotificationRecord, 'id' | 'createdAt' | 'isRead'> & {
|
|
21
|
+
/**
|
|
22
|
+
* Stable identifier of the upstream event (e.g. a QStash message id). Creating twice with the
|
|
23
|
+
* same key stores one notification, so at-least-once delivery cannot inflate the inbox.
|
|
24
|
+
*/
|
|
25
|
+
dedupeKey?: string;
|
|
26
|
+
};
|
|
19
27
|
export interface NotificationStats {
|
|
20
28
|
totalCount: number;
|
|
21
29
|
latestCreatedAt: number;
|
|
@@ -40,9 +48,10 @@ export interface INotificationRepository {
|
|
|
40
48
|
/**
|
|
41
49
|
* Insert a new notification and return its generated id so callers (e.g.
|
|
42
50
|
* the public form submission flow) can correlate the notification with the
|
|
43
|
-
* triggering request.
|
|
51
|
+
* triggering request. With `dedupeKey`, a repeated key stores nothing and returns the id of the
|
|
52
|
+
* notification the first call created.
|
|
44
53
|
*/
|
|
45
|
-
create(record:
|
|
54
|
+
create(record: CreateNotificationRecord): Promise<string>;
|
|
46
55
|
markRead(notificationId: string): Promise<void>;
|
|
47
56
|
markUnread(notificationId: string): Promise<void>;
|
|
48
57
|
markAllRead(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification.repository.d.ts","sourceRoot":"","sources":["../../src/notifications/notification.repository.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;AAEvE,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,gBAAgB,CAAA;IACtB,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAA;IAElD;;;;;;OAMG;IACH,KAAK,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAEnC
|
|
1
|
+
{"version":3,"file":"notification.repository.d.ts","sourceRoot":"","sources":["../../src/notifications/notification.repository.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;AAEvE,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,gBAAgB,CAAA;IACtB,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,uDAAuD;AACvD,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,kBAAkB,EAAE,IAAI,GAAG,WAAW,GAAG,QAAQ,CAAC,GAAG;IAC/F;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAA;IAElD;;;;;;OAMG;IACH,KAAK,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAEnC;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAEzD,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjD,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9C"}
|
|
@@ -19,15 +19,20 @@ export interface IOAuthAuthorizationCodeRepository {
|
|
|
19
19
|
/** Persists a new single-use authorization code. Hash only, never plaintext. */
|
|
20
20
|
save(record: NewAuthorizationCode): Promise<void>;
|
|
21
21
|
/**
|
|
22
|
-
* Returns the code regardless of its consumed state
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* Returns the code regardless of its consumed OR expired state — replay must be
|
|
23
|
+
* detectable even after the 60s TTL, since tokens derived from the code outlive it
|
|
24
|
+
* by 30 days. Callers MUST inspect `consumedAt` (replay signal, requires cascade
|
|
25
|
+
* revocation via IOAuthTokenRepository.revokeByAuthorizationCode) and `expiresAt`
|
|
26
|
+
* separately (an unconsumed-but-expired code is simply invalid, nothing to revoke).
|
|
25
27
|
*/
|
|
26
|
-
findByHash(codeHash: string
|
|
28
|
+
findByHash(codeHash: string): Promise<AuthorizationCodeRecord | null>;
|
|
27
29
|
/**
|
|
28
30
|
* Atomically marks the code consumed. Returns true only for the first caller;
|
|
29
|
-
* every subsequent call returns false, which is the replay signal.
|
|
31
|
+
* every subsequent call returns false, which is the replay signal. Expiry is
|
|
32
|
+
* enforced here — redemption, unlike replay detection, must respect the TTL.
|
|
30
33
|
*/
|
|
31
34
|
consumeByHash(codeHash: string, nowTimestamp: number): Promise<boolean>;
|
|
35
|
+
/** Invalidates every unconsumed code issued to a (client, user) pair, e.g. on consent revoke. */
|
|
36
|
+
invalidateByClientAndUser(clientId: string, userId: string): Promise<number>;
|
|
32
37
|
}
|
|
33
38
|
//# sourceMappingURL=authorization-code.repository.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorization-code.repository.d.ts","sourceRoot":"","sources":["../../src/oauth/authorization-code.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,wEAAwE;IACxE,mBAAmB,EAAE,MAAM,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,uBAAwB,SAAQ,oBAAoB;IACnE,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,iCAAiC;IAChD,gFAAgF;IAChF,IAAI,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEjD
|
|
1
|
+
{"version":3,"file":"authorization-code.repository.d.ts","sourceRoot":"","sources":["../../src/oauth/authorization-code.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,wEAAwE;IACxE,mBAAmB,EAAE,MAAM,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,uBAAwB,SAAQ,oBAAoB;IACnE,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,iCAAiC;IAChD,gFAAgF;IAChF,IAAI,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEjD;;;;;;OAMG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAAA;IAErE;;;;OAIG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEvE,iGAAiG;IACjG,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CAC7E"}
|
|
@@ -1,32 +1,51 @@
|
|
|
1
1
|
import type { OAuthScope } from './scopes.js';
|
|
2
|
+
import type { EffectivePermissions } from '../rbac/types.js';
|
|
2
3
|
/** Outcome of a role arbitration. `deniedScopes` is empty when fully granted. */
|
|
3
4
|
export interface ScopeGrantDecision {
|
|
4
5
|
grantedScopes: OAuthScope[];
|
|
5
6
|
deniedScopes: OAuthScope[];
|
|
6
7
|
}
|
|
7
8
|
/**
|
|
8
|
-
* Arbitrates which of the requested scopes a given
|
|
9
|
+
* Arbitrates which of the requested scopes a given caller may grant.
|
|
9
10
|
*
|
|
10
11
|
* This is the ONLY place role-based authorization may live in the OAuth flow.
|
|
11
|
-
* `/oauth/authorize` and `/oauth/token` must never branch on
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* `/oauth/authorize` and `/oauth/token` must never branch on authority themselves.
|
|
13
|
+
*
|
|
14
|
+
* The parameter is the caller's RESOLVED authority, not a role string: the role
|
|
15
|
+
* string is the pre-RBAC vocabulary and is on its way out.
|
|
14
16
|
*/
|
|
15
17
|
export interface IRoleGuard {
|
|
16
18
|
/**
|
|
17
|
-
* @param
|
|
19
|
+
* @param effective - The resource owner's effective permissions, already folded.
|
|
18
20
|
* @param requestedScopes - Scopes the client asked for, already validated.
|
|
19
21
|
*/
|
|
20
|
-
arbitrate(
|
|
22
|
+
arbitrate(effective: EffectivePermissions, requestedScopes: readonly OAuthScope[]): Promise<ScopeGrantDecision>;
|
|
21
23
|
}
|
|
22
24
|
/**
|
|
23
|
-
* Stub guard
|
|
25
|
+
* Stub guard: grants every requested scope to every caller.
|
|
24
26
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
27
|
+
* Retained as the explicit, directly-tested permissive baseline and as the test
|
|
28
|
+
* double for suites that are not exercising arbitration. It is NO LONGER the
|
|
29
|
+
* production binding — `repositoryMiddleware` binds {@link PermissionRoleGuard}.
|
|
28
30
|
*/
|
|
29
31
|
export declare class AllowAllRoleGuard implements IRoleGuard {
|
|
30
|
-
arbitrate(
|
|
32
|
+
arbitrate(_effective: EffectivePermissions, requestedScopes: readonly OAuthScope[]): Promise<ScopeGrantDecision>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Production guard: only a platform-wide administrator may delegate authority to an
|
|
36
|
+
* OAuth/MCP client.
|
|
37
|
+
*
|
|
38
|
+
* Every OAuth scope in this system (`schema:read`, `schema:write`) drives seed-schema
|
|
39
|
+
* tooling, which brief §2 keeps OUT of the dashboard permission axis entirely — there is
|
|
40
|
+
* deliberately no `manage_seeds` permission to map onto. So the question this guard
|
|
41
|
+
* answers is not "which seed?" but "may this account hand platform authority to a
|
|
42
|
+
* client at all?", and the marker for that is holding `manage_users` at
|
|
43
|
+
* {@link GLOBAL_SCOPE} — the same SuperAdmin marker the last-global-admin guard uses.
|
|
44
|
+
*
|
|
45
|
+
* A seed-scoped collaborator therefore cannot mint an MCP token, no matter which
|
|
46
|
+
* scopes the client requests. Denial is all-or-nothing: there is no partial grant.
|
|
47
|
+
*/
|
|
48
|
+
export declare class PermissionRoleGuard implements IRoleGuard {
|
|
49
|
+
arbitrate(effective: EffectivePermissions, requestedScopes: readonly OAuthScope[]): Promise<ScopeGrantDecision>;
|
|
31
50
|
}
|
|
32
51
|
//# sourceMappingURL=role-guard.d.ts.map
|
|
@@ -1 +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
|
|
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,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAE5D,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,UAAU,EAAE,CAAA;IAC3B,YAAY,EAAE,UAAU,EAAE,CAAA;CAC3B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,SAAS,CACP,SAAS,EAAE,oBAAoB,EAC/B,eAAe,EAAE,SAAS,UAAU,EAAE,GACrC,OAAO,CAAC,kBAAkB,CAAC,CAAA;CAC/B;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,YAAW,UAAU;IAC5C,SAAS,CACb,UAAU,EAAE,oBAAoB,EAChC,eAAe,EAAE,SAAS,UAAU,EAAE,GACrC,OAAO,CAAC,kBAAkB,CAAC;CAG/B;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,mBAAoB,YAAW,UAAU;IAC9C,SAAS,CACb,SAAS,EAAE,oBAAoB,EAC/B,eAAe,EAAE,SAAS,UAAU,EAAE,GACrC,OAAO,CAAC,kBAAkB,CAAC;CAM/B"}
|
package/dist/oauth/role-guard.js
CHANGED
|
@@ -1,14 +1,36 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
3
|
/**
|
|
4
|
-
* Stub guard
|
|
4
|
+
* Stub guard: grants every requested scope to every caller.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Retained as the explicit, directly-tested permissive baseline and as the test
|
|
7
|
+
* double for suites that are not exercising arbitration. It is NO LONGER the
|
|
8
|
+
* production binding — `repositoryMiddleware` binds {@link PermissionRoleGuard}.
|
|
9
9
|
*/
|
|
10
10
|
export class AllowAllRoleGuard {
|
|
11
|
-
async arbitrate(
|
|
11
|
+
async arbitrate(_effective, requestedScopes) {
|
|
12
|
+
return { grantedScopes: [...requestedScopes], deniedScopes: [] };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Production guard: only a platform-wide administrator may delegate authority to an
|
|
17
|
+
* OAuth/MCP client.
|
|
18
|
+
*
|
|
19
|
+
* Every OAuth scope in this system (`schema:read`, `schema:write`) drives seed-schema
|
|
20
|
+
* tooling, which brief §2 keeps OUT of the dashboard permission axis entirely — there is
|
|
21
|
+
* deliberately no `manage_seeds` permission to map onto. So the question this guard
|
|
22
|
+
* answers is not "which seed?" but "may this account hand platform authority to a
|
|
23
|
+
* client at all?", and the marker for that is holding `manage_users` at
|
|
24
|
+
* {@link GLOBAL_SCOPE} — the same SuperAdmin marker the last-global-admin guard uses.
|
|
25
|
+
*
|
|
26
|
+
* A seed-scoped collaborator therefore cannot mint an MCP token, no matter which
|
|
27
|
+
* scopes the client requests. Denial is all-or-nothing: there is no partial grant.
|
|
28
|
+
*/
|
|
29
|
+
export class PermissionRoleGuard {
|
|
30
|
+
async arbitrate(effective, requestedScopes) {
|
|
31
|
+
if (!effective.global.has('manage_users')) {
|
|
32
|
+
return { grantedScopes: [], deniedScopes: [...requestedScopes] };
|
|
33
|
+
}
|
|
12
34
|
return { grantedScopes: [...requestedScopes], deniedScopes: [] };
|
|
13
35
|
}
|
|
14
36
|
}
|
|
@@ -27,6 +27,8 @@ export interface IOAuthTokenRepository {
|
|
|
27
27
|
save(record: NewOAuthToken): Promise<void>;
|
|
28
28
|
/** Finds an unexpired, unrevoked token by hash and type. */
|
|
29
29
|
findActiveByHash(tokenHash: string, tokenType: OAuthTokenType, nowTimestamp: number): Promise<OAuthTokenRecord | null>;
|
|
30
|
+
/** Finds a token by hash and type in any state (expired or revoked included). */
|
|
31
|
+
findByHash(tokenHash: string, tokenType: OAuthTokenType): Promise<OAuthTokenRecord | null>;
|
|
30
32
|
/** Revokes a single token. False when already revoked or absent. */
|
|
31
33
|
revokeByHash(tokenHash: string, nowTimestamp: number): Promise<boolean>;
|
|
32
34
|
/**
|
|
@@ -1 +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"}
|
|
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,iFAAiF;IACjF,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IAE1F,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"}
|
|
@@ -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
|
+
}
|
|
@@ -12,7 +12,8 @@ export interface QueueMessage<T = unknown> {
|
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* Execution context handed to every job. Botanical Invariant: jobs receive the
|
|
15
|
-
* engine-mediated repository, NEVER a raw D1Database
|
|
15
|
+
* engine-mediated repository, NEVER a raw D1Database, and the engine-mediated
|
|
16
|
+
* queue port, NEVER a raw transport binding. `env` is a read-only,
|
|
16
17
|
* stringly-typed view of bindings/secrets for things like fetch targets — it
|
|
17
18
|
* deliberately does not expose `DB`.
|
|
18
19
|
*/
|
|
@@ -21,6 +22,13 @@ export interface JobContext {
|
|
|
21
22
|
bucket: BeechBucket;
|
|
22
23
|
clock: IClock;
|
|
23
24
|
idGenerator: IIdGenerator;
|
|
25
|
+
/**
|
|
26
|
+
* Producer port, so a job can schedule its own successor. Required for cursor-style
|
|
27
|
+
* jobs that process a bounded slice per invocation, persist their offset and re-enqueue
|
|
28
|
+
* (bulk import): without it a handler would have to reach into `env` for the raw QUEUE
|
|
29
|
+
* binding, which is exactly the transport coupling `repository` exists to prevent for D1.
|
|
30
|
+
*/
|
|
31
|
+
queue: IQueueService;
|
|
24
32
|
env: Record<string, string | undefined>;
|
|
25
33
|
}
|
|
26
34
|
/** A single background worker. Receives the decoded payload + context. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue.interface.d.ts","sourceRoot":"","sources":["../../src/queue/queue.interface.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAA;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,CAAC,CAAA;CACX;AAED
|
|
1
|
+
{"version":3,"file":"queue.interface.d.ts","sourceRoot":"","sources":["../../src/queue/queue.interface.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAA;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,CAAC,CAAA;CACX;AAED;;;;;;GAMG;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;;;;;OAKG;IACH,KAAK,EAAE,aAAa,CAAA;IACpB,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;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACvD"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IQueueService } from './queue.interface.js';
|
|
2
|
-
/**
|
|
2
|
+
/** Drops messages without a transport. */
|
|
3
3
|
export declare class NoOpQueueService implements IQueueService {
|
|
4
4
|
enqueue<T>(_name: string, _payload: T): Promise<boolean>;
|
|
5
5
|
}
|
|
@@ -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,
|
|
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,0CAA0C;AAC1C,qBAAa,gBAAiB,YAAW,aAAa;IAC9C,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;CAG/D"}
|
package/dist/queue/queue.stub.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
// Copyright (c) 2024–2026 Flavio De Musso
|
|
3
|
-
/**
|
|
3
|
+
/** Drops messages without a transport. */
|
|
4
4
|
export class NoOpQueueService {
|
|
5
5
|
async enqueue(_name, _payload) {
|
|
6
|
-
|
|
7
|
-
return true;
|
|
6
|
+
return false;
|
|
8
7
|
}
|
|
9
8
|
}
|
|
@@ -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"}
|
|
@@ -5,6 +5,8 @@ export interface TokenBucketOptions {
|
|
|
5
5
|
refillRatePerSecond?: number;
|
|
6
6
|
clock?: IClock;
|
|
7
7
|
maxIdleTimeSeconds?: number;
|
|
8
|
+
maxBuckets?: number;
|
|
9
|
+
pruneIntervalSeconds?: number;
|
|
8
10
|
}
|
|
9
11
|
export declare class TokenBucketRateLimiter implements IRateLimiter {
|
|
10
12
|
private readonly buckets;
|
|
@@ -12,8 +14,13 @@ export declare class TokenBucketRateLimiter implements IRateLimiter {
|
|
|
12
14
|
private readonly refillRatePerSecond;
|
|
13
15
|
private readonly clock;
|
|
14
16
|
private readonly maxIdleTimeSeconds;
|
|
17
|
+
private readonly maxBuckets;
|
|
18
|
+
private readonly pruneIntervalSeconds;
|
|
19
|
+
private lastPruneTimestamp;
|
|
15
20
|
constructor(options?: TokenBucketOptions);
|
|
21
|
+
private static readonly MAX_EVICTION_SCAN;
|
|
16
22
|
private pruneExpiredBuckets;
|
|
23
|
+
private evictRefilledBucket;
|
|
17
24
|
checkLimit(key: string): Promise<RateLimitResult>;
|
|
18
25
|
reset(): void;
|
|
19
26
|
}
|
|
@@ -1 +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;
|
|
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;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAC9B;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;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAQ;IAC7C,OAAO,CAAC,kBAAkB,CAAI;gBAElB,OAAO,CAAC,EAAE,kBAAkB;IAcxC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAK;IAE9C,OAAO,CAAC,mBAAmB;IA4B3B,OAAO,CAAC,mBAAmB;IAsBrB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAqDvD,KAAK,IAAI,IAAI;CAId"}
|
|
@@ -7,27 +7,90 @@ export class TokenBucketRateLimiter {
|
|
|
7
7
|
refillRatePerSecond;
|
|
8
8
|
clock;
|
|
9
9
|
maxIdleTimeSeconds;
|
|
10
|
+
maxBuckets;
|
|
11
|
+
pruneIntervalSeconds;
|
|
12
|
+
lastPruneTimestamp = 0;
|
|
10
13
|
constructor(options) {
|
|
11
14
|
this.capacity = options?.capacity ?? 17;
|
|
12
15
|
// Default: 1 token every 3.53 seconds (~0.283286 tokens/sec)
|
|
13
16
|
this.refillRatePerSecond = options?.refillRatePerSecond ?? (1 / 3.53);
|
|
14
17
|
this.clock = options?.clock ?? SystemClock;
|
|
15
18
|
this.maxIdleTimeSeconds = options?.maxIdleTimeSeconds ?? 3600; // 1 hour idle TTL
|
|
19
|
+
this.maxBuckets = options?.maxBuckets ?? 5000; // Prevent unbounded memory growth
|
|
20
|
+
this.pruneIntervalSeconds = options?.pruneIntervalSeconds ?? 60; // Throttle O(n) scans
|
|
16
21
|
}
|
|
22
|
+
// Bound the eviction scan so a flood of unique keys (each forcing an eviction attempt)
|
|
23
|
+
// cannot turn checkLimit into an O(maxBuckets) operation per request. Only the oldest
|
|
24
|
+
// slice of the map is examined; if nothing there qualifies, admission of the new key is
|
|
25
|
+
// denied instead of evicting something it shouldn't (see below).
|
|
26
|
+
static MAX_EVICTION_SCAN = 32;
|
|
17
27
|
pruneExpiredBuckets(now) {
|
|
18
28
|
if (this.buckets.size < 500)
|
|
19
29
|
return;
|
|
30
|
+
if (now - this.lastPruneTimestamp < this.pruneIntervalSeconds)
|
|
31
|
+
return;
|
|
32
|
+
this.lastPruneTimestamp = now;
|
|
20
33
|
for (const [key, state] of this.buckets.entries()) {
|
|
21
34
|
if (now - state.lastRefillTimestamp > this.maxIdleTimeSeconds) {
|
|
22
35
|
this.buckets.delete(key);
|
|
23
36
|
}
|
|
37
|
+
else {
|
|
38
|
+
// Since Map preserves insertion order and checkLimit re-inserts on every access,
|
|
39
|
+
// entries are monotonically ordered by lastRefillTimestamp. The first non-expired
|
|
40
|
+
// entry guarantees all subsequent entries are also active, allowing an immediate O(1) exit.
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
24
43
|
}
|
|
25
44
|
}
|
|
45
|
+
// Evicts the oldest bucket ONLY if it has refilled to full capacity, in which case it is
|
|
46
|
+
// behaviourally equivalent to an absent bucket and dropping it loses no state. A bucket
|
|
47
|
+
// that hasn't refilled — including one sitting at 0 tokens and actively blocking its key —
|
|
48
|
+
// is never evicted: doing so would let an attacker flood distinct keys to silently reset a
|
|
49
|
+
// victim's exhausted bucket and bypass its limit (#458).
|
|
50
|
+
//
|
|
51
|
+
// When nothing in the scanned slice is eligible, the caller must not admit a new bucket
|
|
52
|
+
// anyway (#634) — that would make the map grow without bound under a distinct-key flood.
|
|
53
|
+
// Instead, report how long until the earliest-refilling scanned bucket would become a
|
|
54
|
+
// legitimate eviction candidate, so the caller can deny admission with a meaningful
|
|
55
|
+
// retryAfterSeconds rather than guessing.
|
|
56
|
+
evictRefilledBucket(now) {
|
|
57
|
+
let scanned = 0;
|
|
58
|
+
let minSecondsUntilRefilled = this.maxIdleTimeSeconds;
|
|
59
|
+
for (const [key, state] of this.buckets.entries()) {
|
|
60
|
+
if (scanned >= TokenBucketRateLimiter.MAX_EVICTION_SCAN)
|
|
61
|
+
break;
|
|
62
|
+
scanned++;
|
|
63
|
+
const elapsed = Math.max(0, now - state.lastRefillTimestamp);
|
|
64
|
+
const refilled = Math.min(this.capacity, state.tokens + elapsed * this.refillRatePerSecond);
|
|
65
|
+
if (refilled >= this.capacity) {
|
|
66
|
+
this.buckets.delete(key);
|
|
67
|
+
return { evicted: true, retryAfterSeconds: 0 };
|
|
68
|
+
}
|
|
69
|
+
if (this.refillRatePerSecond > 0) {
|
|
70
|
+
const secondsUntilRefilled = (this.capacity - refilled) / this.refillRatePerSecond;
|
|
71
|
+
minSecondsUntilRefilled = Math.min(minSecondsUntilRefilled, secondsUntilRefilled);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { evicted: false, retryAfterSeconds: Math.max(1, Math.ceil(minSecondsUntilRefilled)) };
|
|
75
|
+
}
|
|
26
76
|
async checkLimit(key) {
|
|
27
77
|
const now = this.clock.now() / 1000; // fractional seconds for continuous refill
|
|
28
78
|
this.pruneExpiredBuckets(now);
|
|
29
79
|
let bucket = this.buckets.get(key);
|
|
30
80
|
if (!bucket) {
|
|
81
|
+
if (this.buckets.size >= this.maxBuckets) {
|
|
82
|
+
const { evicted, retryAfterSeconds } = this.evictRefilledBucket(now);
|
|
83
|
+
if (!evicted) {
|
|
84
|
+
// No safe eviction candidate: admitting a bucket for this never-seen key would
|
|
85
|
+
// grow the map past maxBuckets. Deny it instead of allocating state (#634).
|
|
86
|
+
return {
|
|
87
|
+
isAllowed: false,
|
|
88
|
+
retryAfterSeconds,
|
|
89
|
+
limit: this.capacity,
|
|
90
|
+
remaining: 0,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
31
94
|
bucket = {
|
|
32
95
|
tokens: this.capacity,
|
|
33
96
|
lastRefillTimestamp: now,
|
|
@@ -37,6 +100,8 @@ export class TokenBucketRateLimiter {
|
|
|
37
100
|
const elapsed = Math.max(0, now - bucket.lastRefillTimestamp);
|
|
38
101
|
bucket.tokens = Math.min(this.capacity, bucket.tokens + elapsed * this.refillRatePerSecond);
|
|
39
102
|
bucket.lastRefillTimestamp = now;
|
|
103
|
+
// Re-insert to maintain LRU access order
|
|
104
|
+
this.buckets.delete(key);
|
|
40
105
|
}
|
|
41
106
|
if (bucket.tokens >= 1) {
|
|
42
107
|
bucket.tokens -= 1;
|
|
@@ -59,5 +124,6 @@ export class TokenBucketRateLimiter {
|
|
|
59
124
|
}
|
|
60
125
|
reset() {
|
|
61
126
|
this.buckets.clear();
|
|
127
|
+
this.lastPruneTimestamp = 0;
|
|
62
128
|
}
|
|
63
129
|
}
|
|
@@ -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"}
|