@astram/guard 0.1.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -0
  3. package/dist/authentication/argon2id-password-hasher.d.ts +61 -0
  4. package/dist/authentication/argon2id-password-hasher.d.ts.map +1 -0
  5. package/dist/authentication/argon2id-password-hasher.js +108 -0
  6. package/dist/boot/boot-sequence.d.ts +43 -0
  7. package/dist/boot/boot-sequence.d.ts.map +1 -0
  8. package/dist/boot/boot-sequence.js +90 -0
  9. package/dist/boot/boot.module.d.ts +8 -0
  10. package/dist/boot/boot.module.d.ts.map +1 -0
  11. package/dist/boot/boot.module.js +24 -0
  12. package/dist/config/app-config.d.ts +112 -0
  13. package/dist/config/app-config.d.ts.map +1 -0
  14. package/dist/config/app-config.js +94 -0
  15. package/dist/config/config.module.d.ts +12 -0
  16. package/dist/config/config.module.d.ts.map +1 -0
  17. package/dist/config/config.module.js +35 -0
  18. package/dist/enforcement/classification.decorator.d.ts +41 -0
  19. package/dist/enforcement/classification.decorator.d.ts.map +1 -0
  20. package/dist/enforcement/classification.decorator.js +46 -0
  21. package/dist/enforcement/core-seam.d.ts +40 -0
  22. package/dist/enforcement/core-seam.d.ts.map +1 -0
  23. package/dist/enforcement/core-seam.js +38 -0
  24. package/dist/enforcement/enforcement.module.d.ts +18 -0
  25. package/dist/enforcement/enforcement.module.d.ts.map +1 -0
  26. package/dist/enforcement/enforcement.module.js +40 -0
  27. package/dist/enforcement/global-enforcement.guard.d.ts +31 -0
  28. package/dist/enforcement/global-enforcement.guard.d.ts.map +1 -0
  29. package/dist/enforcement/global-enforcement.guard.js +76 -0
  30. package/dist/index.d.ts +26 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +88 -0
  33. package/dist/module/shield.module.d.ts +79 -0
  34. package/dist/module/shield.module.d.ts.map +1 -0
  35. package/dist/module/shield.module.js +108 -0
  36. package/dist/session/crypto-adapters.d.ts +60 -0
  37. package/dist/session/crypto-adapters.d.ts.map +1 -0
  38. package/dist/session/crypto-adapters.js +80 -0
  39. package/dist/session/index.d.ts +17 -0
  40. package/dist/session/index.d.ts.map +1 -0
  41. package/dist/session/index.js +32 -0
  42. package/dist/session/mariadb-session-record-store.d.ts +50 -0
  43. package/dist/session/mariadb-session-record-store.d.ts.map +1 -0
  44. package/dist/session/mariadb-session-record-store.js +162 -0
  45. package/dist/session/redis-hot-session-store.d.ts +30 -0
  46. package/dist/session/redis-hot-session-store.d.ts.map +1 -0
  47. package/dist/session/redis-hot-session-store.js +102 -0
  48. package/package.json +52 -0
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SCHEMA_SVC_POOL_HARD_CAP = exports.DEFAULT_POOL_BUDGET = void 0;
4
+ exports.loadPoolBudget = loadPoolBudget;
5
+ exports.loadAppConfig = loadAppConfig;
6
+ exports.evaluatePoolBudget = evaluatePoolBudget;
7
+ /** Documented MVP single-node defaults (see the class doc + my.cnf). */
8
+ exports.DEFAULT_POOL_BUDGET = {
9
+ workers: 8,
10
+ maxConnections: 250,
11
+ headroom: 40,
12
+ perAccountMax: {
13
+ shield_core: 12,
14
+ shield_app_crud: 8,
15
+ shield_schema_svc: 1,
16
+ },
17
+ };
18
+ /** Upper bound the schema-service pool must never exceed (ADR-0018 §2: 1–2). */
19
+ exports.SCHEMA_SVC_POOL_HARD_CAP = 2;
20
+ function parsePort(raw, fallback) {
21
+ if (raw === undefined || raw.trim() === '')
22
+ return fallback;
23
+ const n = Number.parseInt(raw, 10);
24
+ return Number.isInteger(n) && n > 0 && n < 65_536 ? n : fallback;
25
+ }
26
+ function parseEnvironment(raw) {
27
+ if (raw === 'production' || raw === 'test')
28
+ return raw;
29
+ return 'development';
30
+ }
31
+ /**
32
+ * Parse a positive integer from env with a fallback. Returns the fallback for
33
+ * missing/blank/non-integer/≤0 values — the boot assertion is the authority on
34
+ * whether the RESULTING budget is valid, so parsing stays lenient + total.
35
+ */
36
+ function parsePositiveInt(raw, fallback) {
37
+ if (raw === undefined || raw.trim() === '')
38
+ return fallback;
39
+ const n = Number.parseInt(raw, 10);
40
+ return Number.isInteger(n) && n > 0 ? n : fallback;
41
+ }
42
+ /**
43
+ * Build the {@link PoolBudgetConfig} from env with documented defaults.
44
+ * Field ↔ env name:
45
+ * workers ← SHIELD_POOL_WORKERS
46
+ * maxConnections ← SHIELD_DB_MAX_CONNECTIONS
47
+ * headroom ← SHIELD_POOL_HEADROOM
48
+ * perAccountMax.shield_core ← SHIELD_POOL_MAX_CORE
49
+ * perAccountMax.shield_app_crud ← SHIELD_POOL_MAX_APP_CRUD
50
+ * perAccountMax.shield_schema_svc ← SHIELD_POOL_MAX_SCHEMA_SVC
51
+ */
52
+ function loadPoolBudget(env = process.env) {
53
+ return {
54
+ workers: parsePositiveInt(env.SHIELD_POOL_WORKERS, exports.DEFAULT_POOL_BUDGET.workers),
55
+ maxConnections: parsePositiveInt(env.SHIELD_DB_MAX_CONNECTIONS, exports.DEFAULT_POOL_BUDGET.maxConnections),
56
+ headroom: parsePositiveInt(env.SHIELD_POOL_HEADROOM, exports.DEFAULT_POOL_BUDGET.headroom),
57
+ perAccountMax: {
58
+ shield_core: parsePositiveInt(env.SHIELD_POOL_MAX_CORE, exports.DEFAULT_POOL_BUDGET.perAccountMax.shield_core),
59
+ shield_app_crud: parsePositiveInt(env.SHIELD_POOL_MAX_APP_CRUD, exports.DEFAULT_POOL_BUDGET.perAccountMax.shield_app_crud),
60
+ shield_schema_svc: parsePositiveInt(env.SHIELD_POOL_MAX_SCHEMA_SVC, exports.DEFAULT_POOL_BUDGET.perAccountMax.shield_schema_svc),
61
+ },
62
+ };
63
+ }
64
+ /**
65
+ * Build the {@link AppConfig} from `process.env` (or an injected env bag, for
66
+ * testability). Pure + side-effect-free so it is trivially unit-testable.
67
+ */
68
+ function loadAppConfig(env = process.env) {
69
+ return {
70
+ port: parsePort(env.PORT, 3000),
71
+ environment: parseEnvironment(env.NODE_ENV),
72
+ pool: loadPoolBudget(env),
73
+ };
74
+ }
75
+ /**
76
+ * Evaluate a pool budget. Pure: no throwing, no I/O — the caller decides how to
77
+ * react (the boot assertion throws; tests inspect the fields).
78
+ */
79
+ function evaluatePoolBudget(cfg) {
80
+ const perWorkerConnections = cfg.perAccountMax.shield_core +
81
+ cfg.perAccountMax.shield_app_crud +
82
+ cfg.perAccountMax.shield_schema_svc;
83
+ const runtimeConnections = cfg.workers * perWorkerConnections;
84
+ const requiredConnections = runtimeConnections + cfg.headroom;
85
+ const slack = cfg.maxConnections - requiredConnections;
86
+ return {
87
+ perWorkerConnections,
88
+ runtimeConnections,
89
+ requiredConnections,
90
+ slack,
91
+ withinBudget: requiredConnections <= cfg.maxConnections,
92
+ schemaSvcWithinHardCap: cfg.perAccountMax.shield_schema_svc <= exports.SCHEMA_SVC_POOL_HARD_CAP,
93
+ };
94
+ }
@@ -0,0 +1,12 @@
1
+ /** DI token for the resolved {@link AppConfig}. */
2
+ export declare const APP_CONFIG: unique symbol;
3
+ /**
4
+ * Minimal config module (Scaffold).
5
+ *
6
+ * Provides the resolved {@link AppConfig} under {@link APP_CONFIG} so any
7
+ * provider can inject it. `@Global()` so it need not be re-imported everywhere.
8
+ * A richer, validated config system replaces this in the Infrastructure layer.
9
+ */
10
+ export declare class ConfigModule {
11
+ }
12
+ //# sourceMappingURL=config.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.module.d.ts","sourceRoot":"","sources":["../../src/config/config.module.ts"],"names":[],"mappings":"AAGA,mDAAmD;AACnD,eAAO,MAAM,UAAU,eAAuB,CAAC;AAE/C;;;;;;GAMG;AACH,qBAUa,YAAY;CAAG"}
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.ConfigModule = exports.APP_CONFIG = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const app_config_js_1 = require("./app-config.js");
12
+ /** DI token for the resolved {@link AppConfig}. */
13
+ exports.APP_CONFIG = Symbol('APP_CONFIG');
14
+ /**
15
+ * Minimal config module (Scaffold).
16
+ *
17
+ * Provides the resolved {@link AppConfig} under {@link APP_CONFIG} so any
18
+ * provider can inject it. `@Global()` so it need not be re-imported everywhere.
19
+ * A richer, validated config system replaces this in the Infrastructure layer.
20
+ */
21
+ let ConfigModule = class ConfigModule {
22
+ };
23
+ exports.ConfigModule = ConfigModule;
24
+ exports.ConfigModule = ConfigModule = __decorate([
25
+ (0, common_1.Global)(),
26
+ (0, common_1.Module)({
27
+ providers: [
28
+ {
29
+ provide: exports.APP_CONFIG,
30
+ useFactory: () => (0, app_config_js_1.loadAppConfig)(),
31
+ },
32
+ ],
33
+ exports: [exports.APP_CONFIG],
34
+ })
35
+ ], ConfigModule);
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Route-classification metadata keys (ADR-0002).
3
+ *
4
+ * Every HTTP handler in Shield must carry a classification. The global
5
+ * enforcement guard (see {@link GlobalEnforcementGuard}) reads these keys and,
6
+ * for any route WITHOUT one, denies the request (deny-by-default). This is the
7
+ * structurally-unavoidable, fail-closed posture ADR-0002 mandates — present from
8
+ * day one, even though the Scaffold stub does not yet consult a real decision
9
+ * authority.
10
+ */
11
+ export declare const CLASSIFICATION_METADATA: {
12
+ /** Marks a route as an explicit, code-reviewed public opt-out. */
13
+ readonly PUBLIC: "shield:classification:public";
14
+ /**
15
+ * Marks a route as classified against a permission/tier. In the Scaffold this
16
+ * is a placeholder value only — a LATER layer (ADR-0004 tiers, ADR-0012
17
+ * authoring) resolves it to a real decision. Its mere PRESENCE is what the
18
+ * stub guard checks: a classified route is "declared", an undeclared route is
19
+ * denied.
20
+ */
21
+ readonly CLASSIFIED: "shield:classification:classified";
22
+ };
23
+ /**
24
+ * `@Public()` — the explicit, deny-by-default opt-OUT (ADR-0002).
25
+ *
26
+ * A route marked `@Public()` is reachable without a classification. This is the
27
+ * ONLY sanctioned way to make a route publicly reachable; it is intended to be
28
+ * rare and code-reviewed. Everything not `@Public()` and not `@Classified()` is
29
+ * denied by the global guard.
30
+ */
31
+ export declare const Public: () => MethodDecorator & ClassDecorator;
32
+ /**
33
+ * `@Classified(...)` — declare a route's permission/tier classification.
34
+ *
35
+ * SCAFFOLD STUB: this only records that the route was classified; it does not
36
+ * yet gate on a real permission. A later authorization layer replaces the stub
37
+ * with an actual decision. Present here so business routes can be authored with
38
+ * the final API shape from the start.
39
+ */
40
+ export declare const Classified: (classification: string) => MethodDecorator & ClassDecorator;
41
+ //# sourceMappingURL=classification.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classification.decorator.d.ts","sourceRoot":"","sources":["../../src/enforcement/classification.decorator.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB;IAClC,kEAAkE;;IAElE;;;;;;OAMG;;CAEK,CAAC;AAEX;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,QAAO,eAAe,GAAG,cACO,CAAC;AAEpD;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,GAAI,gBAAgB,MAAM,KAAG,eAAe,GAAG,cACL,CAAC"}
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Classified = exports.Public = exports.CLASSIFICATION_METADATA = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ /**
6
+ * Route-classification metadata keys (ADR-0002).
7
+ *
8
+ * Every HTTP handler in Shield must carry a classification. The global
9
+ * enforcement guard (see {@link GlobalEnforcementGuard}) reads these keys and,
10
+ * for any route WITHOUT one, denies the request (deny-by-default). This is the
11
+ * structurally-unavoidable, fail-closed posture ADR-0002 mandates — present from
12
+ * day one, even though the Scaffold stub does not yet consult a real decision
13
+ * authority.
14
+ */
15
+ exports.CLASSIFICATION_METADATA = {
16
+ /** Marks a route as an explicit, code-reviewed public opt-out. */
17
+ PUBLIC: 'shield:classification:public',
18
+ /**
19
+ * Marks a route as classified against a permission/tier. In the Scaffold this
20
+ * is a placeholder value only — a LATER layer (ADR-0004 tiers, ADR-0012
21
+ * authoring) resolves it to a real decision. Its mere PRESENCE is what the
22
+ * stub guard checks: a classified route is "declared", an undeclared route is
23
+ * denied.
24
+ */
25
+ CLASSIFIED: 'shield:classification:classified',
26
+ };
27
+ /**
28
+ * `@Public()` — the explicit, deny-by-default opt-OUT (ADR-0002).
29
+ *
30
+ * A route marked `@Public()` is reachable without a classification. This is the
31
+ * ONLY sanctioned way to make a route publicly reachable; it is intended to be
32
+ * rare and code-reviewed. Everything not `@Public()` and not `@Classified()` is
33
+ * denied by the global guard.
34
+ */
35
+ const Public = () => (0, common_1.SetMetadata)(exports.CLASSIFICATION_METADATA.PUBLIC, true);
36
+ exports.Public = Public;
37
+ /**
38
+ * `@Classified(...)` — declare a route's permission/tier classification.
39
+ *
40
+ * SCAFFOLD STUB: this only records that the route was classified; it does not
41
+ * yet gate on a real permission. A later authorization layer replaces the stub
42
+ * with an actual decision. Present here so business routes can be authored with
43
+ * the final API shape from the start.
44
+ */
45
+ const Classified = (classification) => (0, common_1.SetMetadata)(exports.CLASSIFICATION_METADATA.CLASSIFIED, classification);
46
+ exports.Classified = Classified;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Integration seam to @astram/core (ADR-0003).
3
+ *
4
+ * The NestJS layer is a THIN integration that CALLS the framework-agnostic Core;
5
+ * it contains no security logic itself. This module is the single, clearly-named
6
+ * place where the Core's decision authority is wired into the global enforcement
7
+ * guard.
8
+ *
9
+ * It imports REAL Core types from the published `@astram/core` package barrel —
10
+ * proving the ADR-0003 integration seam compiles against the actual Core
11
+ * contract (`DecisionAuthority`, `Decision`, `Principal`). These are TYPE-ONLY
12
+ * imports, so this layer takes on no runtime dependency on Core internals yet.
13
+ *
14
+ * The Scaffold guard does NOT consult a real authority yet (ADR-0002: the stub
15
+ * below only proves deny-by-default). When the Authorization layer lands, wire
16
+ * the Core `DecisionAuthority` in here — the guard code above this seam is
17
+ * unchanged.
18
+ */
19
+ import type { DecisionAuthority as CoreDecisionAuthority, Decision as CoreDecision, Principal } from '@astram/core';
20
+ /** Re-export the Core contracts this layer wires against, for local consumers. */
21
+ export type { CoreDecisionAuthority, CoreDecision, Principal };
22
+ /**
23
+ * The fail-closed default effect (ADR-0002): if a decision cannot be rendered,
24
+ * the system denies. Mirrors the Core `Decision.effect` vocabulary.
25
+ */
26
+ export type Effect = 'allow' | 'deny';
27
+ /**
28
+ * Fail-closed placeholder authority for the Scaffold.
29
+ *
30
+ * SCAFFOLD STUB: returns a `deny_by_default` `Decision` for every classified
31
+ * route so that "classified but not yet backed by a real policy" cannot silently
32
+ * open a door. `@Public()` routes never reach this — they are the explicit,
33
+ * code-reviewed opt-out handled by the guard before any authority is consulted.
34
+ *
35
+ * Typed as the full Core `DecisionAuthority` so it stays a true drop-in if the
36
+ * interface grows — replacing it with the real Core authority later is a
37
+ * one-line swap in the guard's DI.
38
+ */
39
+ export declare const stubDecisionAuthority: CoreDecisionAuthority;
40
+ //# sourceMappingURL=core-seam.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-seam.d.ts","sourceRoot":"","sources":["../../src/enforcement/core-seam.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH,OAAO,KAAK,EACV,iBAAiB,IAAI,qBAAqB,EAC1C,QAAQ,IAAI,YAAY,EACxB,SAAS,EACV,MAAM,cAAc,CAAC;AAEtB,kFAAkF;AAClF,YAAY,EAAE,qBAAqB,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;AAE/D;;;GAGG;AACH,MAAM,MAAM,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEtC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,EAAE,qBAInC,CAAC"}
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ /**
3
+ * Integration seam to @astram/core (ADR-0003).
4
+ *
5
+ * The NestJS layer is a THIN integration that CALLS the framework-agnostic Core;
6
+ * it contains no security logic itself. This module is the single, clearly-named
7
+ * place where the Core's decision authority is wired into the global enforcement
8
+ * guard.
9
+ *
10
+ * It imports REAL Core types from the published `@astram/core` package barrel —
11
+ * proving the ADR-0003 integration seam compiles against the actual Core
12
+ * contract (`DecisionAuthority`, `Decision`, `Principal`). These are TYPE-ONLY
13
+ * imports, so this layer takes on no runtime dependency on Core internals yet.
14
+ *
15
+ * The Scaffold guard does NOT consult a real authority yet (ADR-0002: the stub
16
+ * below only proves deny-by-default). When the Authorization layer lands, wire
17
+ * the Core `DecisionAuthority` in here — the guard code above this seam is
18
+ * unchanged.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.stubDecisionAuthority = void 0;
22
+ /**
23
+ * Fail-closed placeholder authority for the Scaffold.
24
+ *
25
+ * SCAFFOLD STUB: returns a `deny_by_default` `Decision` for every classified
26
+ * route so that "classified but not yet backed by a real policy" cannot silently
27
+ * open a door. `@Public()` routes never reach this — they are the explicit,
28
+ * code-reviewed opt-out handled by the guard before any authority is consulted.
29
+ *
30
+ * Typed as the full Core `DecisionAuthority` so it stays a true drop-in if the
31
+ * interface grows — replacing it with the real Core authority later is a
32
+ * one-line swap in the guard's DI.
33
+ */
34
+ exports.stubDecisionAuthority = {
35
+ decide() {
36
+ return Promise.resolve({ effect: 'deny', reason: 'deny_by_default' });
37
+ },
38
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The SOLE definition of the global `APP_GUARD` binding (ADR-0002).
3
+ *
4
+ * Registering {@link GlobalEnforcementGuard} as an `APP_GUARD` makes it apply to
5
+ * EVERY route across the whole application — it is impossible to opt a route out
6
+ * of enforcement by omission. The only sanctioned opt-out is the explicit,
7
+ * code-reviewed `@Public()` decorator. This is the "one global mechanism through
8
+ * which all requests pass" ADR-0002 mandates.
9
+ *
10
+ * INTERNAL — this module is NOT public API and is NOT exported from the package
11
+ * barrel. It is composed internally by {@link ShieldModule.forRoot} (the ONE
12
+ * registration seam, ADR-0023 §4); consumers must NOT import it directly. Keeping
13
+ * this the single `APP_GUARD` definition (instead of re-inlining it in
14
+ * `ShieldModule`) guarantees exactly one wiring path for the guard.
15
+ */
16
+ export declare class EnforcementModule {
17
+ }
18
+ //# sourceMappingURL=enforcement.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enforcement.module.d.ts","sourceRoot":"","sources":["../../src/enforcement/enforcement.module.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AACH,qBAQa,iBAAiB;CAAG"}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.EnforcementModule = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const core_1 = require("@nestjs/core");
12
+ const global_enforcement_guard_js_1 = require("./global-enforcement.guard.js");
13
+ /**
14
+ * The SOLE definition of the global `APP_GUARD` binding (ADR-0002).
15
+ *
16
+ * Registering {@link GlobalEnforcementGuard} as an `APP_GUARD` makes it apply to
17
+ * EVERY route across the whole application — it is impossible to opt a route out
18
+ * of enforcement by omission. The only sanctioned opt-out is the explicit,
19
+ * code-reviewed `@Public()` decorator. This is the "one global mechanism through
20
+ * which all requests pass" ADR-0002 mandates.
21
+ *
22
+ * INTERNAL — this module is NOT public API and is NOT exported from the package
23
+ * barrel. It is composed internally by {@link ShieldModule.forRoot} (the ONE
24
+ * registration seam, ADR-0023 §4); consumers must NOT import it directly. Keeping
25
+ * this the single `APP_GUARD` definition (instead of re-inlining it in
26
+ * `ShieldModule`) guarantees exactly one wiring path for the guard.
27
+ */
28
+ let EnforcementModule = class EnforcementModule {
29
+ };
30
+ exports.EnforcementModule = EnforcementModule;
31
+ exports.EnforcementModule = EnforcementModule = __decorate([
32
+ (0, common_1.Module)({
33
+ providers: [
34
+ {
35
+ provide: core_1.APP_GUARD,
36
+ useClass: global_enforcement_guard_js_1.GlobalEnforcementGuard,
37
+ },
38
+ ],
39
+ })
40
+ ], EnforcementModule);
@@ -0,0 +1,31 @@
1
+ import { CanActivate, ExecutionContext } from '@nestjs/common';
2
+ import { Reflector } from '@nestjs/core';
3
+ /**
4
+ * Global, fail-closed enforcement guard (ADR-0002).
5
+ *
6
+ * This guard is the SINGLE, structurally-unavoidable point every request passes
7
+ * through. It is bound as an `APP_GUARD` through the ONE registration seam,
8
+ * `ShieldModule.forRoot()` (which composes the internal `EnforcementModule` that
9
+ * holds the sole binding) — NOT opt-in per controller — so no endpoint can escape
10
+ * it. Its posture is **deny-by-default**:
11
+ *
12
+ * - A route marked `@Public()` → ALLOWED (explicit, code-reviewed opt-out).
13
+ * - A route marked `@Classified()` → allowed to PROCEED in the Scaffold stub
14
+ * (a LATER layer will consult the real Core `DecisionAuthority` here; see
15
+ * {@link ../enforcement/core-seam.ts}). Its presence proves the route was
16
+ * declared.
17
+ * - A route with NO classification → DENIED (403). A forgotten declaration
18
+ * locks the door instead of opening it.
19
+ *
20
+ * SCAFFOLD SCOPE: this does not yet perform real authorization. It only proves
21
+ * the fail-closed, globally-unavoidable, deny-by-default seam exists from day
22
+ * one. When the Authorization layer lands, the `@Classified` branch calls the
23
+ * Core decision authority (wired via the core-seam) — the deny-by-default
24
+ * structure here does not change.
25
+ */
26
+ export declare class GlobalEnforcementGuard implements CanActivate {
27
+ private readonly reflector;
28
+ constructor(reflector: Reflector);
29
+ canActivate(context: ExecutionContext): boolean;
30
+ }
31
+ //# sourceMappingURL=global-enforcement.guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global-enforcement.guard.d.ts","sourceRoot":"","sources":["../../src/enforcement/global-enforcement.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAkC,MAAM,gBAAgB,CAAC;AAC/F,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBACa,sBAAuB,YAAW,WAAW;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,SAAS;IAEjD,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO;CAsChD"}
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.GlobalEnforcementGuard = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const core_1 = require("@nestjs/core");
15
+ const classification_decorator_js_1 = require("./classification.decorator.js");
16
+ /**
17
+ * Global, fail-closed enforcement guard (ADR-0002).
18
+ *
19
+ * This guard is the SINGLE, structurally-unavoidable point every request passes
20
+ * through. It is bound as an `APP_GUARD` through the ONE registration seam,
21
+ * `ShieldModule.forRoot()` (which composes the internal `EnforcementModule` that
22
+ * holds the sole binding) — NOT opt-in per controller — so no endpoint can escape
23
+ * it. Its posture is **deny-by-default**:
24
+ *
25
+ * - A route marked `@Public()` → ALLOWED (explicit, code-reviewed opt-out).
26
+ * - A route marked `@Classified()` → allowed to PROCEED in the Scaffold stub
27
+ * (a LATER layer will consult the real Core `DecisionAuthority` here; see
28
+ * {@link ../enforcement/core-seam.ts}). Its presence proves the route was
29
+ * declared.
30
+ * - A route with NO classification → DENIED (403). A forgotten declaration
31
+ * locks the door instead of opening it.
32
+ *
33
+ * SCAFFOLD SCOPE: this does not yet perform real authorization. It only proves
34
+ * the fail-closed, globally-unavoidable, deny-by-default seam exists from day
35
+ * one. When the Authorization layer lands, the `@Classified` branch calls the
36
+ * Core decision authority (wired via the core-seam) — the deny-by-default
37
+ * structure here does not change.
38
+ */
39
+ let GlobalEnforcementGuard = class GlobalEnforcementGuard {
40
+ reflector;
41
+ constructor(reflector) {
42
+ this.reflector = reflector;
43
+ }
44
+ canActivate(context) {
45
+ // Only HTTP requests are classified here. Non-HTTP contexts (if ever added)
46
+ // must be handled explicitly rather than silently allowed.
47
+ if (context.getType() !== 'http') {
48
+ throw new common_1.ForbiddenException('Shield enforcement: non-HTTP execution context is not classified (deny-by-default).');
49
+ }
50
+ const handler = context.getHandler();
51
+ const controller = context.getClass();
52
+ // 1. Explicit public opt-out wins. Method-level OR class-level @Public().
53
+ const isPublic = this.reflector.getAllAndOverride(classification_decorator_js_1.CLASSIFICATION_METADATA.PUBLIC, [
54
+ handler,
55
+ controller,
56
+ ]);
57
+ if (isPublic === true) {
58
+ return true;
59
+ }
60
+ // 2. Classified route → declared. Scaffold stub lets it proceed (a real
61
+ // decision authority is wired in a later layer via the core-seam).
62
+ const classification = this.reflector.getAllAndOverride(classification_decorator_js_1.CLASSIFICATION_METADATA.CLASSIFIED, [handler, controller]);
63
+ if (typeof classification === 'string' && classification.length > 0) {
64
+ return true;
65
+ }
66
+ // 3. No classification at all → DENY (ADR-0002 deny-by-default / fail-closed).
67
+ throw new common_1.ForbiddenException('Shield enforcement: this route carries no access classification. ' +
68
+ 'It is denied by default (ADR-0002). Mark it @Public() to opt out, or ' +
69
+ '@Classified(...) to declare its permission.');
70
+ }
71
+ };
72
+ exports.GlobalEnforcementGuard = GlobalEnforcementGuard;
73
+ exports.GlobalEnforcementGuard = GlobalEnforcementGuard = __decorate([
74
+ (0, common_1.Injectable)(),
75
+ __metadata("design:paramtypes", [core_1.Reflector])
76
+ ], GlobalEnforcementGuard);
@@ -0,0 +1,26 @@
1
+ /**
2
+ * `@astram/guard` — the thin NestJS integration for the Shield framework
3
+ * (ADR-0023 §1, ADR-0003).
4
+ *
5
+ * This is the shippable, consumer-INSTALLED framework glue: the global
6
+ * deny-by-default enforcement guard (ADR-0002), its `@Public()`/`@Classified()`
7
+ * classification decorators, the ADR-0018 boot-pool assertion, and the ONE
8
+ * registration seam — {@link ShieldModule}`.forRoot()` / `.forRootAsync()`.
9
+ *
10
+ * A consumer app depends on this package and `@astram/core`, imports
11
+ * `ShieldModule.forRoot({...})` once, and builds features in its own editable
12
+ * `src/`. It never edits the framework; upgrades flow through `npm update`.
13
+ *
14
+ * Depends on `@astram/core` (workspace); PEER-depends on `@nestjs/common`,
15
+ * `@nestjs/core`, `reflect-metadata`, `rxjs` so the host owns one copy of each.
16
+ */
17
+ export { ShieldModule, SHIELD_DECISION_AUTHORITY, type ShieldModuleOptions, type ShieldModuleAsyncOptions, } from './module/shield.module.js';
18
+ export { Public, Classified, CLASSIFICATION_METADATA, } from './enforcement/classification.decorator.js';
19
+ export { GlobalEnforcementGuard } from './enforcement/global-enforcement.guard.js';
20
+ export { stubDecisionAuthority, type Effect, type CoreDecisionAuthority, type CoreDecision, type Principal, } from './enforcement/core-seam.js';
21
+ export { Argon2idPasswordHasher, argon2idHasherProvider, SHIELD_PASSWORD_HASHER, type Argon2idParams, } from './authentication/argon2id-password-hasher.js';
22
+ export { MariaDbSessionRecordStore, SHIELD_SESSION_RECORD_STORE, RedisHotSessionStore, SHIELD_HOT_SESSION_STORE, CryptoTokenGenerator, Sha256RefreshTokenHasher, SystemClock, cryptoTokenGeneratorProvider, refreshHasherProvider, systemClockProvider, SHIELD_TOKEN_GENERATOR, SHIELD_REFRESH_HASHER, SHIELD_CLOCK, } from './session/index.js';
23
+ export { BootSequence, assertPoolBudget } from './boot/boot-sequence.js';
24
+ export { APP_CONFIG } from './config/config.module.js';
25
+ export { loadAppConfig, loadPoolBudget, evaluatePoolBudget, DEFAULT_POOL_BUDGET, SCHEMA_SVC_POOL_HARD_CAP, type AppConfig, type PoolBudgetConfig, type PoolBudgetEvaluation, type RuntimeAccountPools, } from './config/app-config.js';
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EACL,YAAY,EACZ,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,GAC9B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EACL,MAAM,EACN,UAAU,EACV,uBAAuB,GACxB,MAAM,2CAA2C,CAAC;AAUnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AAKnF,OAAO,EACL,qBAAqB,EACrB,KAAK,MAAM,EACX,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,SAAS,GACf,MAAM,4BAA4B,CAAC;AAMpC,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,sBAAsB,EACtB,KAAK,cAAc,GACpB,MAAM,8CAA8C,CAAC;AAOtD,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAC3B,oBAAoB,EACpB,wBAAwB,EACxB,oBAAoB,EACpB,wBAAwB,EACxB,WAAW,EACX,4BAA4B,EAC5B,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAOzE,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EACL,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,GACzB,MAAM,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ /**
3
+ * `@astram/guard` — the thin NestJS integration for the Shield framework
4
+ * (ADR-0023 §1, ADR-0003).
5
+ *
6
+ * This is the shippable, consumer-INSTALLED framework glue: the global
7
+ * deny-by-default enforcement guard (ADR-0002), its `@Public()`/`@Classified()`
8
+ * classification decorators, the ADR-0018 boot-pool assertion, and the ONE
9
+ * registration seam — {@link ShieldModule}`.forRoot()` / `.forRootAsync()`.
10
+ *
11
+ * A consumer app depends on this package and `@astram/core`, imports
12
+ * `ShieldModule.forRoot({...})` once, and builds features in its own editable
13
+ * `src/`. It never edits the framework; upgrades flow through `npm update`.
14
+ *
15
+ * Depends on `@astram/core` (workspace); PEER-depends on `@nestjs/common`,
16
+ * `@nestjs/core`, `reflect-metadata`, `rxjs` so the host owns one copy of each.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.SCHEMA_SVC_POOL_HARD_CAP = exports.DEFAULT_POOL_BUDGET = exports.evaluatePoolBudget = exports.loadPoolBudget = exports.loadAppConfig = exports.APP_CONFIG = exports.assertPoolBudget = exports.BootSequence = exports.SHIELD_CLOCK = exports.SHIELD_REFRESH_HASHER = exports.SHIELD_TOKEN_GENERATOR = exports.systemClockProvider = exports.refreshHasherProvider = exports.cryptoTokenGeneratorProvider = exports.SystemClock = exports.Sha256RefreshTokenHasher = exports.CryptoTokenGenerator = exports.SHIELD_HOT_SESSION_STORE = exports.RedisHotSessionStore = exports.SHIELD_SESSION_RECORD_STORE = exports.MariaDbSessionRecordStore = exports.SHIELD_PASSWORD_HASHER = exports.argon2idHasherProvider = exports.Argon2idPasswordHasher = exports.stubDecisionAuthority = exports.GlobalEnforcementGuard = exports.CLASSIFICATION_METADATA = exports.Classified = exports.Public = exports.SHIELD_DECISION_AUTHORITY = exports.ShieldModule = void 0;
20
+ // The single registration seam.
21
+ var shield_module_js_1 = require("./module/shield.module.js");
22
+ Object.defineProperty(exports, "ShieldModule", { enumerable: true, get: function () { return shield_module_js_1.ShieldModule; } });
23
+ Object.defineProperty(exports, "SHIELD_DECISION_AUTHORITY", { enumerable: true, get: function () { return shield_module_js_1.SHIELD_DECISION_AUTHORITY; } });
24
+ // Route classification (the consumer authors these on its controllers).
25
+ var classification_decorator_js_1 = require("./enforcement/classification.decorator.js");
26
+ Object.defineProperty(exports, "Public", { enumerable: true, get: function () { return classification_decorator_js_1.Public; } });
27
+ Object.defineProperty(exports, "Classified", { enumerable: true, get: function () { return classification_decorator_js_1.Classified; } });
28
+ Object.defineProperty(exports, "CLASSIFICATION_METADATA", { enumerable: true, get: function () { return classification_decorator_js_1.CLASSIFICATION_METADATA; } });
29
+ // The global enforcement guard (framework-owned; exported so the enforcement
30
+ // point is inspectable/testable, not so consumers re-wire it).
31
+ //
32
+ // NOTE: `EnforcementModule` / `BootModule` / `ConfigModule` are DELIBERATELY NOT
33
+ // exported. `ShieldModule.forRoot()` is the ONE registration seam and composes
34
+ // them internally (EnforcementModule holds the SOLE `APP_GUARD` binding). Exposing
35
+ // them would let a consumer import a module that independently re-binds `APP_GUARD`
36
+ // — a second, divergent wiring path that contradicts ADR-0023 §4 ("one seam").
37
+ var global_enforcement_guard_js_1 = require("./enforcement/global-enforcement.guard.js");
38
+ Object.defineProperty(exports, "GlobalEnforcementGuard", { enumerable: true, get: function () { return global_enforcement_guard_js_1.GlobalEnforcementGuard; } });
39
+ // The Core integration seam (ADR-0003): the decision-authority contract this
40
+ // layer wires against, plus the fail-closed stub used until a real authority is
41
+ // supplied through `ShieldModule.forRoot`.
42
+ var core_seam_js_1 = require("./enforcement/core-seam.js");
43
+ Object.defineProperty(exports, "stubDecisionAuthority", { enumerable: true, get: function () { return core_seam_js_1.stubDecisionAuthority; } });
44
+ // The Authentication crypto adapter (ADR-0003): the concrete Argon2id
45
+ // `PasswordHasher` implementation lives HERE (real `argon2` dependency) and is
46
+ // injected into `@astram/core`'s `createAuthenticator({ hasher })` — Core stays
47
+ // zero-dep. The DI token + provider factory wire it into a Nest container.
48
+ var argon2id_password_hasher_js_1 = require("./authentication/argon2id-password-hasher.js");
49
+ Object.defineProperty(exports, "Argon2idPasswordHasher", { enumerable: true, get: function () { return argon2id_password_hasher_js_1.Argon2idPasswordHasher; } });
50
+ Object.defineProperty(exports, "argon2idHasherProvider", { enumerable: true, get: function () { return argon2id_password_hasher_js_1.argon2idHasherProvider; } });
51
+ Object.defineProperty(exports, "SHIELD_PASSWORD_HASHER", { enumerable: true, get: function () { return argon2id_password_hasher_js_1.SHIELD_PASSWORD_HASHER; } });
52
+ // The Session storage/crypto adapters (ADR-0003 / ADR-0013): the concrete durable
53
+ // (MariaDB) + hot (Redis) stores, the node:crypto token generator, the keyed HMAC
54
+ // refresh hasher, and the system clock — all implementing `@astram/core`'s Session
55
+ // PORTs and injected into `createSessionAuthority(...)`. Core stays zero-dep; the
56
+ // real `mariadb` / `ioredis` / `node:crypto` live here.
57
+ var index_js_1 = require("./session/index.js");
58
+ Object.defineProperty(exports, "MariaDbSessionRecordStore", { enumerable: true, get: function () { return index_js_1.MariaDbSessionRecordStore; } });
59
+ Object.defineProperty(exports, "SHIELD_SESSION_RECORD_STORE", { enumerable: true, get: function () { return index_js_1.SHIELD_SESSION_RECORD_STORE; } });
60
+ Object.defineProperty(exports, "RedisHotSessionStore", { enumerable: true, get: function () { return index_js_1.RedisHotSessionStore; } });
61
+ Object.defineProperty(exports, "SHIELD_HOT_SESSION_STORE", { enumerable: true, get: function () { return index_js_1.SHIELD_HOT_SESSION_STORE; } });
62
+ Object.defineProperty(exports, "CryptoTokenGenerator", { enumerable: true, get: function () { return index_js_1.CryptoTokenGenerator; } });
63
+ Object.defineProperty(exports, "Sha256RefreshTokenHasher", { enumerable: true, get: function () { return index_js_1.Sha256RefreshTokenHasher; } });
64
+ Object.defineProperty(exports, "SystemClock", { enumerable: true, get: function () { return index_js_1.SystemClock; } });
65
+ Object.defineProperty(exports, "cryptoTokenGeneratorProvider", { enumerable: true, get: function () { return index_js_1.cryptoTokenGeneratorProvider; } });
66
+ Object.defineProperty(exports, "refreshHasherProvider", { enumerable: true, get: function () { return index_js_1.refreshHasherProvider; } });
67
+ Object.defineProperty(exports, "systemClockProvider", { enumerable: true, get: function () { return index_js_1.systemClockProvider; } });
68
+ Object.defineProperty(exports, "SHIELD_TOKEN_GENERATOR", { enumerable: true, get: function () { return index_js_1.SHIELD_TOKEN_GENERATOR; } });
69
+ Object.defineProperty(exports, "SHIELD_REFRESH_HASHER", { enumerable: true, get: function () { return index_js_1.SHIELD_REFRESH_HASHER; } });
70
+ Object.defineProperty(exports, "SHIELD_CLOCK", { enumerable: true, get: function () { return index_js_1.SHIELD_CLOCK; } });
71
+ // Boot infrastructure (ADR-0018 connection-pool budget assertion). `BootModule`
72
+ // itself is NOT exported — it is composed internally by `ShieldModule.forRoot()`.
73
+ var boot_sequence_js_1 = require("./boot/boot-sequence.js");
74
+ Object.defineProperty(exports, "BootSequence", { enumerable: true, get: function () { return boot_sequence_js_1.BootSequence; } });
75
+ Object.defineProperty(exports, "assertPoolBudget", { enumerable: true, get: function () { return boot_sequence_js_1.assertPoolBudget; } });
76
+ // Application config surface (port / environment / ADR-0018 pool budget). The
77
+ // consumer skeleton reads `loadAppConfig()` for its HTTP port; the boot assertion
78
+ // validates the pool budget. The `APP_CONFIG` DI token is exported; `ConfigModule`
79
+ // (its env-only provider) is NOT — `ShieldModule` provides `APP_CONFIG` itself so
80
+ // the one seam can honor caller-supplied config.
81
+ var config_module_js_1 = require("./config/config.module.js");
82
+ Object.defineProperty(exports, "APP_CONFIG", { enumerable: true, get: function () { return config_module_js_1.APP_CONFIG; } });
83
+ var app_config_js_1 = require("./config/app-config.js");
84
+ Object.defineProperty(exports, "loadAppConfig", { enumerable: true, get: function () { return app_config_js_1.loadAppConfig; } });
85
+ Object.defineProperty(exports, "loadPoolBudget", { enumerable: true, get: function () { return app_config_js_1.loadPoolBudget; } });
86
+ Object.defineProperty(exports, "evaluatePoolBudget", { enumerable: true, get: function () { return app_config_js_1.evaluatePoolBudget; } });
87
+ Object.defineProperty(exports, "DEFAULT_POOL_BUDGET", { enumerable: true, get: function () { return app_config_js_1.DEFAULT_POOL_BUDGET; } });
88
+ Object.defineProperty(exports, "SCHEMA_SVC_POOL_HARD_CAP", { enumerable: true, get: function () { return app_config_js_1.SCHEMA_SVC_POOL_HARD_CAP; } });