@adaptic/backend-legacy 0.0.996 → 0.0.997

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.
@@ -0,0 +1,104 @@
1
+ /**
2
+ * CORTEX-P0-001 — resolver-level authorization AuthChecker (SHADOW-FIRST).
3
+ *
4
+ * A TypeGraphQL {@link AuthChecker} that evaluates whether an operation WOULD be
5
+ * allowed under resolver-level authorization, closing the residual vector called
6
+ * out in the tenancy-scoping middleware docs: a governed model reached
7
+ * transitively through a non-governed root, and — more broadly — any
8
+ * `@Authorized()`-decorated field executed without a verified principal.
9
+ *
10
+ * CRITICAL SAFETY CONTRACT — this change is additive and default-OFF. When
11
+ * enforcement is OFF (the default), the checker NEVER denies: it returns `true`
12
+ * for every operation, emitting a structured warn log and incrementing a
13
+ * Prometheus counter for any operation that enforcement WOULD deny. Live
14
+ * behaviour is byte-identical to before this change until
15
+ * `CORTEX_AUTHCHECKER_ENFORCE` is flipped on. Additionally, TypeGraphQL only
16
+ * invokes an AuthChecker for fields decorated with `@Authorized()`; the
17
+ * auto-generated typegraphql-prisma resolvers carry no such decorator, so
18
+ * registering this checker via `buildSchema({ authChecker })` is inert at
19
+ * runtime until decorators are added in the separate enforcement phase.
20
+ *
21
+ * Decision model (mirrors the token-verifier principal kinds):
22
+ *
23
+ * - `server` — trusted server-to-server caller. ALWAYS allowed; the
24
+ * engine + utils call the CRUD resolvers with a server principal and must
25
+ * retain full access, bypassing any role gate.
26
+ * - `admin` / `user` — verified end-user. Allowed when the field requires no
27
+ * specific role, or when the principal carries one of the required roles;
28
+ * otherwise a would-deny with reason `insufficient_role`.
29
+ * - `null` (no verified principal) — a genuinely unauthenticated caller on a
30
+ * non-public (i.e. `@Authorized()`) operation. A would-deny with reason
31
+ * `unauthenticated`.
32
+ *
33
+ * @see src/auth/tenancy-scope.ts for the row-level (data-scope) counterpart.
34
+ * @see src/auth/token-verifier.ts for `BackendPrincipal`.
35
+ */
36
+ import { Counter } from 'prom-client';
37
+ import type { AuthChecker } from 'type-graphql';
38
+ import type { BackendPrincipal } from './token-verifier';
39
+ /**
40
+ * Environment variable gating enforcement. When set to `true`/`1`, the checker
41
+ * fails closed (returns `false`) for any would-deny operation. Unset or any
42
+ * other value keeps the checker in SHADOW mode (observe + count + allow).
43
+ */
44
+ export declare const CORTEX_AUTHCHECKER_ENFORCE_ENV = "CORTEX_AUTHCHECKER_ENFORCE";
45
+ /**
46
+ * Whether resolver-level authorization enforcement is ON. Read fresh on each
47
+ * call (a cheap env read) so the shadow→enforce graduation can be flipped
48
+ * operationally without a process restart — the same pattern the tenancy-scoping
49
+ * middleware uses. Defaults to OFF (shadow) for any unset/unrecognised value.
50
+ *
51
+ * @param env - Environment source (defaults to `process.env`); injectable for tests.
52
+ * @returns `true` only when the flag is explicitly `true`/`1`.
53
+ */
54
+ export declare function isAuthCheckerEnforced(env?: Record<string, string | undefined>): boolean;
55
+ /**
56
+ * Finite set of reasons enforcement WOULD deny an operation. Used as the sole
57
+ * label on {@link cortexAuthCheckerWouldDenyTotal}.
58
+ */
59
+ export type WouldDenyReason = 'unauthenticated' | 'insufficient_role';
60
+ /**
61
+ * Counts operations that resolver-level authorization WOULD deny. In shadow mode
62
+ * this is the primary signal proving whether flipping `CORTEX_AUTHCHECKER_ENFORCE`
63
+ * to `enforce` would reject any legitimate platform or engine traffic. Labelled
64
+ * by the discriminated {@link WouldDenyReason}.
65
+ */
66
+ export declare const cortexAuthCheckerWouldDenyTotal: Counter<"reason">;
67
+ /** The outcome of evaluating whether an operation would be allowed. */
68
+ interface AuthEvaluation {
69
+ readonly wouldAllow: boolean;
70
+ /** Populated only when `wouldAllow` is `false`. */
71
+ readonly reason?: WouldDenyReason;
72
+ }
73
+ /**
74
+ * Pure evaluation of whether a principal would satisfy the required roles for an
75
+ * operation. Performs no I/O and no logging.
76
+ *
77
+ * @param principal - The verified principal, or `null` for an unauthenticated caller.
78
+ * @param requiredRoles - Roles declared on the field's `@Authorized(...)` decorator
79
+ * (empty when the field only requires authentication).
80
+ * @returns The {@link AuthEvaluation}.
81
+ */
82
+ export declare function evaluateWouldAllow(principal: BackendPrincipal | null, requiredRoles: readonly string[]): AuthEvaluation;
83
+ /**
84
+ * GraphQL context shape this checker reads. `principal` is the verified
85
+ * `BackendPrincipal` attached to the context in `server.ts` (`null` for an
86
+ * unauthenticated caller).
87
+ */
88
+ export interface CortexAuthContext {
89
+ principal?: BackendPrincipal | null;
90
+ }
91
+ /**
92
+ * The CORTEX-P0-001 AuthChecker. Register via
93
+ * `buildSchema({ authChecker: cortexAuthChecker })`.
94
+ *
95
+ * SHADOW (default): a would-deny operation increments
96
+ * {@link cortexAuthCheckerWouldDenyTotal}, emits a warn log, and RETURNS TRUE —
97
+ * no behaviour change. ENFORCE (`CORTEX_AUTHCHECKER_ENFORCE=true`): a would-deny
98
+ * operation is logged, the counter is incremented, and the checker RETURNS FALSE
99
+ * (fail closed). Operations that would be allowed always return `true` with no
100
+ * metric or log emission.
101
+ */
102
+ export declare const cortexAuthChecker: AuthChecker<CortexAuthContext>;
103
+ export {};
104
+ //# sourceMappingURL=cortex-auth-checker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cortex-auth-checker.d.ts","sourceRoot":"","sources":["../../../src/auth/cortex-auth-checker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,cAAc,CAAC;AAC9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAQzD;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,+BAA+B,CAAC;AAK3E;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,OAAO,CAGT;AAMD;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,iBAAiB,GAAG,mBAAmB,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B,mBAK1C,CAAC;AAEH,uEAAuE;AACvE,UAAU,cAAc;IACtB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,mDAAmD;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC;CACnC;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,GAAG,IAAI,EAClC,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,cAAc,CAyBhB;AAMD;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACrC;AAcD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,iBAAiB,CA8B5D,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cortex-auth-checker.js","sourceRoot":"","sources":["../../../src/auth/cortex-auth-checker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * CORTEX-P0-001 — resolver-level authorization AuthChecker (SHADOW-FIRST).
3
+ *
4
+ * A TypeGraphQL {@link AuthChecker} that evaluates whether an operation WOULD be
5
+ * allowed under resolver-level authorization, closing the residual vector called
6
+ * out in the tenancy-scoping middleware docs: a governed model reached
7
+ * transitively through a non-governed root, and — more broadly — any
8
+ * `@Authorized()`-decorated field executed without a verified principal.
9
+ *
10
+ * CRITICAL SAFETY CONTRACT — this change is additive and default-OFF. When
11
+ * enforcement is OFF (the default), the checker NEVER denies: it returns `true`
12
+ * for every operation, emitting a structured warn log and incrementing a
13
+ * Prometheus counter for any operation that enforcement WOULD deny. Live
14
+ * behaviour is byte-identical to before this change until
15
+ * `CORTEX_AUTHCHECKER_ENFORCE` is flipped on. Additionally, TypeGraphQL only
16
+ * invokes an AuthChecker for fields decorated with `@Authorized()`; the
17
+ * auto-generated typegraphql-prisma resolvers carry no such decorator, so
18
+ * registering this checker via `buildSchema({ authChecker })` is inert at
19
+ * runtime until decorators are added in the separate enforcement phase.
20
+ *
21
+ * Decision model (mirrors the token-verifier principal kinds):
22
+ *
23
+ * - `server` — trusted server-to-server caller. ALWAYS allowed; the
24
+ * engine + utils call the CRUD resolvers with a server principal and must
25
+ * retain full access, bypassing any role gate.
26
+ * - `admin` / `user` — verified end-user. Allowed when the field requires no
27
+ * specific role, or when the principal carries one of the required roles;
28
+ * otherwise a would-deny with reason `insufficient_role`.
29
+ * - `null` (no verified principal) — a genuinely unauthenticated caller on a
30
+ * non-public (i.e. `@Authorized()`) operation. A would-deny with reason
31
+ * `unauthenticated`.
32
+ *
33
+ * @see src/auth/tenancy-scope.ts for the row-level (data-scope) counterpart.
34
+ * @see src/auth/token-verifier.ts for `BackendPrincipal`.
35
+ */
36
+ import { Counter } from 'prom-client';
37
+ import { metricsRegistry } from '../config/metrics.mjs';
38
+ import { logger } from '../utils/logger.mjs';
39
+ // -----------------------------------------------------------------------------
40
+ // Enforcement flag
41
+ // -----------------------------------------------------------------------------
42
+ /**
43
+ * Environment variable gating enforcement. When set to `true`/`1`, the checker
44
+ * fails closed (returns `false`) for any would-deny operation. Unset or any
45
+ * other value keeps the checker in SHADOW mode (observe + count + allow).
46
+ */
47
+ export const CORTEX_AUTHCHECKER_ENFORCE_ENV = 'CORTEX_AUTHCHECKER_ENFORCE';
48
+ /** The `admin` role string used to satisfy admin-gated `@Authorized()` fields. */
49
+ const ADMIN_ROLE = 'admin';
50
+ /**
51
+ * Whether resolver-level authorization enforcement is ON. Read fresh on each
52
+ * call (a cheap env read) so the shadow→enforce graduation can be flipped
53
+ * operationally without a process restart — the same pattern the tenancy-scoping
54
+ * middleware uses. Defaults to OFF (shadow) for any unset/unrecognised value.
55
+ *
56
+ * @param env - Environment source (defaults to `process.env`); injectable for tests.
57
+ * @returns `true` only when the flag is explicitly `true`/`1`.
58
+ */
59
+ export function isAuthCheckerEnforced(env = process.env) {
60
+ const raw = (env[CORTEX_AUTHCHECKER_ENFORCE_ENV] ?? '').trim().toLowerCase();
61
+ return raw === 'true' || raw === '1';
62
+ }
63
+ /**
64
+ * Counts operations that resolver-level authorization WOULD deny. In shadow mode
65
+ * this is the primary signal proving whether flipping `CORTEX_AUTHCHECKER_ENFORCE`
66
+ * to `enforce` would reject any legitimate platform or engine traffic. Labelled
67
+ * by the discriminated {@link WouldDenyReason}.
68
+ */
69
+ export const cortexAuthCheckerWouldDenyTotal = new Counter({
70
+ name: 'cortex_authchecker_would_deny_total',
71
+ help: 'Operations resolver-level authorization would deny, by reason (counted in shadow mode without denying)',
72
+ labelNames: ['reason'],
73
+ registers: [metricsRegistry],
74
+ });
75
+ /**
76
+ * Pure evaluation of whether a principal would satisfy the required roles for an
77
+ * operation. Performs no I/O and no logging.
78
+ *
79
+ * @param principal - The verified principal, or `null` for an unauthenticated caller.
80
+ * @param requiredRoles - Roles declared on the field's `@Authorized(...)` decorator
81
+ * (empty when the field only requires authentication).
82
+ * @returns The {@link AuthEvaluation}.
83
+ */
84
+ export function evaluateWouldAllow(principal, requiredRoles) {
85
+ if (!principal) {
86
+ return { wouldAllow: false, reason: 'unauthenticated' };
87
+ }
88
+ // Server-to-server callers are unconditionally allowed and bypass role gates.
89
+ if (principal.kind === 'server') {
90
+ return { wouldAllow: true };
91
+ }
92
+ // Authenticated user/admin with no specific role requirement -> allowed.
93
+ if (requiredRoles.length === 0) {
94
+ return { wouldAllow: true };
95
+ }
96
+ const principalRoles = principal.kind === 'admin'
97
+ ? [...principal.roles, ADMIN_ROLE]
98
+ : principal.roles;
99
+ const roleSet = new Set(principalRoles);
100
+ const hasRequiredRole = requiredRoles.some((role) => roleSet.has(role));
101
+ return hasRequiredRole
102
+ ? { wouldAllow: true }
103
+ : { wouldAllow: false, reason: 'insufficient_role' };
104
+ }
105
+ /** Describe an operation for structured logs, without leaking arguments. */
106
+ function describeOperation(resolverData) {
107
+ const { info } = resolverData;
108
+ return {
109
+ operationType: info.operation.operation,
110
+ operationName: info.operation.name?.value ?? info.fieldName,
111
+ field: info.fieldName,
112
+ };
113
+ }
114
+ /**
115
+ * The CORTEX-P0-001 AuthChecker. Register via
116
+ * `buildSchema({ authChecker: cortexAuthChecker })`.
117
+ *
118
+ * SHADOW (default): a would-deny operation increments
119
+ * {@link cortexAuthCheckerWouldDenyTotal}, emits a warn log, and RETURNS TRUE —
120
+ * no behaviour change. ENFORCE (`CORTEX_AUTHCHECKER_ENFORCE=true`): a would-deny
121
+ * operation is logged, the counter is incremented, and the checker RETURNS FALSE
122
+ * (fail closed). Operations that would be allowed always return `true` with no
123
+ * metric or log emission.
124
+ */
125
+ export const cortexAuthChecker = (resolverData, roles) => {
126
+ const principal = resolverData.context.principal ?? null;
127
+ const evaluation = evaluateWouldAllow(principal, roles);
128
+ if (evaluation.wouldAllow) {
129
+ return true;
130
+ }
131
+ const reason = evaluation.reason ?? 'unauthenticated';
132
+ const enforced = isAuthCheckerEnforced();
133
+ const operation = describeOperation(resolverData);
134
+ cortexAuthCheckerWouldDenyTotal.inc({ reason });
135
+ logger.warn(enforced
136
+ ? '[cortex-authchecker] denying operation (enforce)'
137
+ : '[cortex-authchecker] shadow would-deny (allowing)', {
138
+ ...operation,
139
+ reason,
140
+ enforced,
141
+ principalKind: principal?.kind ?? 'none',
142
+ requiredRoles: [...roles],
143
+ });
144
+ return !enforced;
145
+ };
146
+ //# sourceMappingURL=cortex-auth-checker.js.map
@@ -1,4 +1,27 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
+ import { Counter } from 'prom-client';
3
+ /**
4
+ * Environment variable gating 429 enforcement. When set to `true`/`1`, the
5
+ * limiters block over-limit requests with HTTP 429. Unset or any other value
6
+ * keeps them in SHADOW mode (observe + count + always proceed).
7
+ */
8
+ export declare const CORTEX_RATE_LIMIT_ENFORCE_ENV = "CORTEX_RATE_LIMIT_ENFORCE";
9
+ /**
10
+ * Whether rate-limit 429 enforcement is ON. Read fresh on each request (a cheap
11
+ * env read) so the shadow→enforce graduation can be flipped operationally
12
+ * without a restart. Defaults to OFF (shadow) for any unset/unrecognised value.
13
+ *
14
+ * @param env - Environment source (defaults to `process.env`); injectable for tests.
15
+ * @returns `true` only when the flag is explicitly `true`/`1`.
16
+ */
17
+ export declare function isRateLimitEnforced(env?: Record<string, string | undefined>): boolean;
18
+ /**
19
+ * Counts requests that rate limiting WOULD block (HTTP 429). In shadow mode this
20
+ * is the signal proving whether flipping `CORTEX_RATE_LIMIT_ENFORCE` to enforce
21
+ * would reject legitimate traffic. Labelled by the `limiter` name
22
+ * (`graphql`/`auth`) and the request `tier` (`auth`/`anon`).
23
+ */
24
+ export declare const cortexRateLimitWouldBlockTotal: Counter<"limiter" | "tier">;
2
25
  /**
3
26
  * Rate limiter for GraphQL endpoint.
4
27
  *
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../../src/middleware/rate-limiter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAyG1D;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,QA9ChB,OAAO,OAAO,QAAQ,QAAQ,YAAY,KAAG,IAuD1D,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,eAAe,QA/Db,OAAO,OAAO,QAAQ,QAAQ,YAAY,KAAG,IAsE1D,CAAC"}
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../../src/middleware/rate-limiter.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAItC;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,8BAA8B,CAAC;AAEzE;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,OAAO,CAGT;AAED;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,6BAKzC,CAAC;AAkIH;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,QArEhB,OAAO,OAAO,QAAQ,QAAQ,YAAY,KAAG,IA+E1D,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,eAAe,QAvFb,OAAO,OAAO,QAAQ,QAAQ,YAAY,KAAG,IA+F1D,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../../src/middleware/rate-limiter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../../src/middleware/rate-limiter.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,iEAAiE;AACjE,+EAA+E;AAC/E,2EAA2E;AAC3E,2EAA2E;AAC3E,2EAA2E;AAG3E,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC;;;;GAIG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,2BAA2B,CAAC;AAEzE;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAA0C,OAAO,CAAC,GAAG;IAErD,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,6BAA6B,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5E,OAAO,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC;AACvC,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,IAAI,OAAO,CAAC;IACxD,IAAI,EAAE,qCAAqC;IAC3C,IAAI,EAAE,yGAAyG;IAC/G,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,CAAU;IACxC,SAAS,EAAE,CAAC,eAAe,CAAC;CAC7B,CAAC,CAAC;AAsBH;;;;;GAKG;AACH,SAAS,eAAe,CAAC,GAAY;IACnC,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC;IACnD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACtC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAClC,2EAA2E;IAC3E,0EAA0E;IAC1E,wEAAwE;IACxE,mEAAmE;IACnE,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAS,iBAAiB,CAAC,MAAuB;IAChD,MAAM,KAAK,GAAmB,EAAE,CAAC;IAEjC,wCAAwC;IACxC,WAAW,CAAC,GAAG,EAAE;QACf,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YACjC,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC;gBAC/B,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC;YACpB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,KAAK,CAAC,CAAC;IAEV,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;QAC/D,MAAM,UAAU,GAAG,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,UAAU,CAAC,aAAa,IAAI,SAAS,CAAC;QACvE,MAAM,aAAa,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;QAC7C,MAAM,YAAY,GAAG,aAAa;YAChC,CAAC,CAAC,MAAM,CAAC,gBAAgB;YACzB,CAAC,CAAC,MAAM,CAAC,kBAAkB,CAAC;QAC9B,MAAM,QAAQ,GAAG,GAAG,UAAU,IAAI,IAAI,EAAE,CAAC;QACzC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC;YACxD,KAAK,CAAC,QAAQ,CAAC,GAAG;gBAChB,KAAK,EAAE,CAAC;gBACR,SAAS,EAAE,GAAG,GAAG,MAAM,CAAC,QAAQ;aACjC,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;QAC7B,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;QAChC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC5D,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;QACjE,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,GAAG,YAAY,CAAC;QAE/C,6EAA6E;QAC7E,0EAA0E;QAC1E,gEAAgE;QAChE,IAAI,CAAC,mBAAmB,EAAE,EAAE,CAAC;YAC3B,IAAI,SAAS,EAAE,CAAC;gBACd,8BAA8B,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;gBACnE,MAAM,CAAC,IAAI,CAAC,mDAAmD,EAAE;oBAC/D,OAAO,EAAE,MAAM,CAAC,IAAI;oBACpB,UAAU;oBACV,IAAI;oBACJ,KAAK,EAAE,OAAO,CAAC,KAAK;oBACpB,KAAK,EAAE,YAAY;oBACnB,YAAY;iBACb,CAAC,CAAC;YACL,CAAC;YACD,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QAED,oEAAoE;QACpE,qCAAqC;QACrC,IAAI,MAAM,CAAC,eAAe,KAAK,KAAK,EAAE,CAAC;YACrC,GAAG,CAAC,SAAS,CAAC,mBAAmB,EAAE,YAAY,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC5D,GAAG,CAAC,SAAS,CAAC,uBAAuB,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC7D,GAAG,CAAC,SAAS,CAAC,mBAAmB,EAAE,YAAY,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC9D,CAAC;QAED,IAAI,SAAS,EAAE,CAAC;YACd,oEAAoE;YACpE,GAAG,CAAC,SAAS,CAAC,aAAa,EAAE,YAAY,CAAC,QAAQ,EAAE,CAAC,CAAC;YACtD,8BAA8B,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YACnE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACrC,OAAO;QACT,CAAC;QAED,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;IAClD,IAAI,EAAE,SAAS;IACf,QAAQ,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,aAAa;IACvC,gBAAgB,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,MAAM,EAAE,EAAE,CAAC;IACpE,kBAAkB,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,KAAK,EAAE,EAAE,CAAC;IAC5E,eAAe,EAAE,IAAI;IACrB,aAAa,EAAE,KAAK;IACpB,OAAO,EAAE;QACP,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,4CAA4C,EAAE,CAAC;KACpE;CACF,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;IAC/C,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,aAAa;IACvC,gBAAgB,EAAE,EAAE;IACpB,kBAAkB,EAAE,EAAE;IACtB,eAAe,EAAE,IAAI;IACrB,aAAa,EAAE,KAAK;IACpB,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,mCAAmC,EAAE,CAAC,EAAE;CACxE,CAAC,CAAC"}
@@ -1,4 +1,42 @@
1
- // Integration: add to server.ts - app.use('/graphql', graphqlRateLimiter) and app.use('/auth', authRateLimiter)
1
+ // CORTEX-P0-001: mounted in server.ts as
2
+ // app.use('/graphql', graphqlRateLimiter) // GraphQL surface
3
+ // app.use('/api', authRateLimiter) // authenticated Express surface
4
+ // SHADOW-FIRST: 429 enforcement is gated behind CORTEX_RATE_LIMIT_ENFORCE.
5
+ // While OFF (default), the limiters observe + count requests that WOULD be
6
+ // blocked but never touch the response — live behaviour is byte-identical.
7
+ import { Counter } from 'prom-client';
8
+ import { metricsRegistry } from '../config/metrics.mjs';
9
+ import { logger } from '../utils/logger.mjs';
10
+ /**
11
+ * Environment variable gating 429 enforcement. When set to `true`/`1`, the
12
+ * limiters block over-limit requests with HTTP 429. Unset or any other value
13
+ * keeps them in SHADOW mode (observe + count + always proceed).
14
+ */
15
+ export const CORTEX_RATE_LIMIT_ENFORCE_ENV = 'CORTEX_RATE_LIMIT_ENFORCE';
16
+ /**
17
+ * Whether rate-limit 429 enforcement is ON. Read fresh on each request (a cheap
18
+ * env read) so the shadow→enforce graduation can be flipped operationally
19
+ * without a restart. Defaults to OFF (shadow) for any unset/unrecognised value.
20
+ *
21
+ * @param env - Environment source (defaults to `process.env`); injectable for tests.
22
+ * @returns `true` only when the flag is explicitly `true`/`1`.
23
+ */
24
+ export function isRateLimitEnforced(env = process.env) {
25
+ const raw = (env[CORTEX_RATE_LIMIT_ENFORCE_ENV] ?? '').trim().toLowerCase();
26
+ return raw === 'true' || raw === '1';
27
+ }
28
+ /**
29
+ * Counts requests that rate limiting WOULD block (HTTP 429). In shadow mode this
30
+ * is the signal proving whether flipping `CORTEX_RATE_LIMIT_ENFORCE` to enforce
31
+ * would reject legitimate traffic. Labelled by the `limiter` name
32
+ * (`graphql`/`auth`) and the request `tier` (`auth`/`anon`).
33
+ */
34
+ export const cortexRateLimitWouldBlockTotal = new Counter({
35
+ name: 'cortex_rate_limit_would_block_total',
36
+ help: 'Requests rate limiting would block (429), by limiter and tier (counted in shadow mode without blocking)',
37
+ labelNames: ['limiter', 'tier'],
38
+ registers: [metricsRegistry],
39
+ });
2
40
  /**
3
41
  * Checks whether a request carries a valid-looking authentication token.
4
42
  * Does not verify the token -- only checks for its presence in the
@@ -44,10 +82,11 @@ function createRateLimiter(config) {
44
82
  return (req, res, next) => {
45
83
  const identifier = req.ip || req.connection.remoteAddress || 'unknown';
46
84
  const authenticated = isAuthenticated(req);
85
+ const tier = authenticated ? 'auth' : 'anon';
47
86
  const effectiveMax = authenticated
48
87
  ? config.maxAuthenticated
49
88
  : config.maxUnauthenticated;
50
- const storeKey = `${identifier}:${authenticated ? 'auth' : 'anon'}`;
89
+ const storeKey = `${identifier}:${tier}`;
51
90
  const now = Date.now();
52
91
  if (!store[storeKey] || store[storeKey].resetTime < now) {
53
92
  store[storeKey] = {
@@ -61,15 +100,36 @@ function createRateLimiter(config) {
61
100
  const current = store[storeKey];
62
101
  const remaining = Math.max(0, effectiveMax - current.count);
63
102
  const resetSeconds = Math.ceil((current.resetTime - now) / 1000);
64
- // Add rate limit headers
103
+ const overLimit = current.count > effectiveMax;
104
+ // SHADOW mode (default): observe + count over-limit requests but NEVER touch
105
+ // the response — no rate-limit headers, no 429. This keeps live behaviour
106
+ // byte-identical until CORTEX_RATE_LIMIT_ENFORCE is flipped on.
107
+ if (!isRateLimitEnforced()) {
108
+ if (overLimit) {
109
+ cortexRateLimitWouldBlockTotal.inc({ limiter: config.name, tier });
110
+ logger.warn('[cortex-rate-limit] shadow would-block (allowing)', {
111
+ limiter: config.name,
112
+ identifier,
113
+ tier,
114
+ count: current.count,
115
+ limit: effectiveMax,
116
+ resetSeconds,
117
+ });
118
+ }
119
+ next();
120
+ return;
121
+ }
122
+ // ENFORCE mode: emit the informational rate-limit headers and block
123
+ // over-limit requests with HTTP 429.
65
124
  if (config.standardHeaders !== false) {
66
125
  res.setHeader('X-RateLimit-Limit', effectiveMax.toString());
67
126
  res.setHeader('X-RateLimit-Remaining', remaining.toString());
68
127
  res.setHeader('X-RateLimit-Reset', resetSeconds.toString());
69
128
  }
70
- if (current.count > effectiveMax) {
129
+ if (overLimit) {
71
130
  // Include Retry-After header on 429 responses (RFC 6585 / RFC 7231)
72
131
  res.setHeader('Retry-After', resetSeconds.toString());
132
+ cortexRateLimitWouldBlockTotal.inc({ limiter: config.name, tier });
73
133
  res.status(429).json(config.message);
74
134
  return;
75
135
  }
@@ -83,6 +143,7 @@ function createRateLimiter(config) {
83
143
  * Unauthenticated requests: 200 requests per 15 minutes (configurable via RATE_LIMIT_MAX_UNAUTH)
84
144
  */
85
145
  export const graphqlRateLimiter = createRateLimiter({
146
+ name: 'graphql',
86
147
  windowMs: 15 * 60 * 1000, // 15 minutes
87
148
  maxAuthenticated: parseInt(process.env.RATE_LIMIT_MAX || '1000', 10),
88
149
  maxUnauthenticated: parseInt(process.env.RATE_LIMIT_MAX_UNAUTH || '200', 10),
@@ -99,6 +160,7 @@ export const graphqlRateLimiter = createRateLimiter({
99
160
  * Unauthenticated requests: 20 requests per 15 minutes
100
161
  */
101
162
  export const authRateLimiter = createRateLimiter({
163
+ name: 'auth',
102
164
  windowMs: 15 * 60 * 1000, // 15 minutes
103
165
  maxAuthenticated: 50,
104
166
  maxUnauthenticated: 20,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaptic/backend-legacy",
3
- "version": "0.0.996",
3
+ "version": "0.0.997",
4
4
  "description": "Backend executable CRUD functions with dynamic variables construction, and type definitions for the Adaptic AI platform.",
5
5
  "type": "module",
6
6
  "types": "index.d.ts",
package/server.cjs CHANGED
@@ -59,8 +59,10 @@ const body_parser_1 = __importDefault(require("body-parser"));
59
59
  const ws_1 = require("ws");
60
60
  const ws_2 = require("graphql-ws/lib/use/ws");
61
61
  const auth_1 = require("./middleware/auth.cjs");
62
+ const rate_limiter_1 = require("./middleware/rate-limiter.cjs");
62
63
  const audit_logger_1 = require("./middleware/audit-logger.cjs");
63
64
  const tenancy_scoping_1 = require("./middleware/tenancy-scoping.cjs");
65
+ const cortex_auth_checker_1 = require("./auth/cortex-auth-checker.cjs");
64
66
  const http_status_mapper_1 = require("./plugins/http-status-mapper.cjs");
65
67
  const graphql_validation_plugin_1 = require("./middleware/graphql-validation-plugin.cjs");
66
68
  const query_complexity_1 = require("./middleware/query-complexity.cjs");
@@ -118,12 +120,24 @@ const startServer = async () => {
118
120
  // and unauthenticated callers are bypassed in every mode. Gated by
119
121
  // `TENANCY_SCOPING_MODE` (default `shadow`).
120
122
  globalMiddlewares: [(0, tenancy_scoping_1.createTenancyScopingMiddleware)()],
123
+ // Resolver-level authorization (CORTEX-P0-001). SHADOW-FIRST: only invoked
124
+ // for `@Authorized()`-decorated fields and, while `CORTEX_AUTHCHECKER_ENFORCE`
125
+ // is OFF (default), it observes + counts would-deny operations but always
126
+ // allows — byte-identical live behaviour until enforcement is flipped on.
127
+ authChecker: cortex_auth_checker_1.cortexAuthChecker,
121
128
  });
122
129
  const app = (0, express_1.default)();
123
130
  const httpServer = (0, http_1.createServer)(app);
124
131
  // HTTP request metrics — must be mounted early to capture every request.
125
132
  // The middleware is a no-op when metrics are disabled.
126
133
  app.use(metrics_1.metricsMiddleware);
134
+ // Rate limiting (CORTEX-P0-001). SHADOW-FIRST: mounted on the GraphQL surface
135
+ // and the authenticated Express `/api` surface. While `CORTEX_RATE_LIMIT_ENFORCE`
136
+ // is OFF (default), they observe + count requests that WOULD be blocked but
137
+ // never touch the response, so live behaviour is byte-identical. Mounted
138
+ // before `/api` auth so limiting precedes the (more expensive) auth work.
139
+ app.use('/graphql', rate_limiter_1.graphqlRateLimiter);
140
+ app.use('/api', rate_limiter_1.authRateLimiter);
127
141
  app.use('/api', (req, res, next) => (0, auth_1.authMiddleware)(req, res, next));
128
142
  // APQ cache: in-memory LRU, gated by `APQ_ENABLED` (default on). The
129
143
  // Apollo Server option only accepts a value when a cache is provided;