@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.
- package/esm/auth/cortex-auth-checker.d.ts +104 -0
- package/esm/auth/cortex-auth-checker.d.ts.map +1 -0
- package/esm/auth/cortex-auth-checker.js.map +1 -0
- package/esm/auth/cortex-auth-checker.mjs +146 -0
- package/esm/middleware/rate-limiter.d.ts +23 -0
- package/esm/middleware/rate-limiter.d.ts.map +1 -1
- package/esm/middleware/rate-limiter.js.map +1 -1
- package/esm/middleware/rate-limiter.mjs +66 -4
- package/package.json +1 -1
- package/server.cjs +14 -0
|
@@ -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":"
|
|
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":"AAAA,
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../../src/middleware/rate-limiter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,42 @@
|
|
|
1
|
-
//
|
|
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}:${
|
|
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
|
-
|
|
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 (
|
|
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.
|
|
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;
|