@generazioneai/authz 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/codegen/authz-check.d.ts +3 -0
- package/dist/codegen/authz-check.d.ts.map +1 -0
- package/dist/codegen/authz-check.js +76 -0
- package/dist/codegen/authz-check.js.map +1 -0
- package/dist/codegen/check-rules.d.ts +73 -0
- package/dist/codegen/check-rules.d.ts.map +1 -0
- package/dist/codegen/check-rules.js +387 -0
- package/dist/codegen/check-rules.js.map +1 -0
- package/dist/codegen/effective-actions.d.ts +13 -0
- package/dist/codegen/effective-actions.d.ts.map +1 -0
- package/dist/codegen/effective-actions.js +44 -0
- package/dist/codegen/effective-actions.js.map +1 -0
- package/dist/codegen/generate-types.d.ts +8 -0
- package/dist/codegen/generate-types.d.ts.map +1 -0
- package/dist/codegen/generate-types.js +121 -0
- package/dist/codegen/generate-types.js.map +1 -0
- package/dist/codegen/index.d.ts +3 -0
- package/dist/codegen/index.d.ts.map +1 -0
- package/dist/codegen/index.js +74 -0
- package/dist/codegen/index.js.map +1 -0
- package/dist/codegen/manifest-io.d.ts +19 -0
- package/dist/codegen/manifest-io.d.ts.map +1 -0
- package/dist/codegen/manifest-io.js +59 -0
- package/dist/codegen/manifest-io.js.map +1 -0
- package/dist/context/als.d.ts +14 -0
- package/dist/context/als.d.ts.map +1 -0
- package/dist/context/als.js +30 -0
- package/dist/context/als.js.map +1 -0
- package/dist/context/authz-context.d.ts +54 -0
- package/dist/context/authz-context.d.ts.map +1 -0
- package/dist/context/authz-context.js +24 -0
- package/dist/context/authz-context.js.map +1 -0
- package/dist/define-resource.d.ts +150 -0
- package/dist/define-resource.d.ts.map +1 -0
- package/dist/define-resource.js +26 -0
- package/dist/define-resource.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/nats/canonical-hash.d.ts +5 -0
- package/dist/nats/canonical-hash.d.ts.map +1 -0
- package/dist/nats/canonical-hash.js +24 -0
- package/dist/nats/canonical-hash.js.map +1 -0
- package/dist/nats/index.d.ts +7 -0
- package/dist/nats/index.d.ts.map +1 -0
- package/dist/nats/index.js +27 -0
- package/dist/nats/index.js.map +1 -0
- package/dist/nats/internal-token.d.ts +56 -0
- package/dist/nats/internal-token.d.ts.map +1 -0
- package/dist/nats/internal-token.js +93 -0
- package/dist/nats/internal-token.js.map +1 -0
- package/dist/nats/internal-token.signer.d.ts +21 -0
- package/dist/nats/internal-token.signer.d.ts.map +1 -0
- package/dist/nats/internal-token.signer.js +48 -0
- package/dist/nats/internal-token.signer.js.map +1 -0
- package/dist/nats/jwks-client.d.ts +10 -0
- package/dist/nats/jwks-client.d.ts.map +1 -0
- package/dist/nats/jwks-client.js +14 -0
- package/dist/nats/jwks-client.js.map +1 -0
- package/dist/nats/key-loader.d.ts +24 -0
- package/dist/nats/key-loader.d.ts.map +1 -0
- package/dist/nats/key-loader.js +65 -0
- package/dist/nats/key-loader.js.map +1 -0
- package/dist/nats/replay-cache.d.ts +14 -0
- package/dist/nats/replay-cache.d.ts.map +1 -0
- package/dist/nats/replay-cache.js +23 -0
- package/dist/nats/replay-cache.js.map +1 -0
- package/dist/nest/authz-context.interceptor.d.ts +6 -0
- package/dist/nest/authz-context.interceptor.d.ts.map +1 -0
- package/dist/nest/authz-context.interceptor.js +47 -0
- package/dist/nest/authz-context.interceptor.js.map +1 -0
- package/dist/nest/authz-context.middleware.d.ts +15 -0
- package/dist/nest/authz-context.middleware.d.ts.map +1 -0
- package/dist/nest/authz-context.middleware.js +40 -0
- package/dist/nest/authz-context.middleware.js.map +1 -0
- package/dist/nest/index.d.ts +6 -0
- package/dist/nest/index.d.ts.map +1 -0
- package/dist/nest/index.js +25 -0
- package/dist/nest/index.js.map +1 -0
- package/dist/nest/internal-auth.interceptor.d.ts +29 -0
- package/dist/nest/internal-auth.interceptor.d.ts.map +1 -0
- package/dist/nest/internal-auth.interceptor.js +140 -0
- package/dist/nest/internal-auth.interceptor.js.map +1 -0
- package/dist/nest/nats-scoped-client.proxy.d.ts +23 -0
- package/dist/nest/nats-scoped-client.proxy.d.ts.map +1 -0
- package/dist/nest/nats-scoped-client.proxy.js +50 -0
- package/dist/nest/nats-scoped-client.proxy.js.map +1 -0
- package/dist/nest/skip-internal-auth.decorator.d.ts +4 -0
- package/dist/nest/skip-internal-auth.decorator.d.ts.map +1 -0
- package/dist/nest/skip-internal-auth.decorator.js +13 -0
- package/dist/nest/skip-internal-auth.decorator.js.map +1 -0
- package/dist/resource-registry.d.ts +31 -0
- package/dist/resource-registry.d.ts.map +1 -0
- package/dist/resource-registry.js +64 -0
- package/dist/resource-registry.js.map +1 -0
- package/dist/resource-registry.module.d.ts +25 -0
- package/dist/resource-registry.module.d.ts.map +1 -0
- package/dist/resource-registry.module.js +67 -0
- package/dist/resource-registry.module.js.map +1 -0
- package/dist/scope-substitute.d.ts +20 -0
- package/dist/scope-substitute.d.ts.map +1 -0
- package/dist/scope-substitute.js +58 -0
- package/dist/scope-substitute.js.map +1 -0
- package/package.json +94 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Step 1 — Resource Manifest DSL
|
|
3
|
+
// Reference: plan riprendiamo-il-plan-di-delegated-petal.md sezione "DSL `defineResource()`"
|
|
4
|
+
//
|
|
5
|
+
// Ogni microservizio possessore (skillAuth, skillID, skillCertet) dichiara i suoi
|
|
6
|
+
// resource manifest in `*.resource.ts` co-locati con il modulo Prisma corrispondente.
|
|
7
|
+
// skillID importa il registry aggregato a build-time per buildare gli snapshot CASL.
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.defineResource = defineResource;
|
|
10
|
+
/**
|
|
11
|
+
* Identity function generica. Type inference vincola scope templates a essere
|
|
12
|
+
* strutturalmente compatibili con `Prisma.<Model>WhereInput`.
|
|
13
|
+
*
|
|
14
|
+
* Esempio:
|
|
15
|
+
* export const CourseResource = defineResource<Prisma.CourseWhereInput, 'Course'>({
|
|
16
|
+
* subject: 'Course',
|
|
17
|
+
* prismaModel: 'course',
|
|
18
|
+
* service: 'skillID',
|
|
19
|
+
* tenancy: { kind: 'single', field: 'juridicalId' },
|
|
20
|
+
* // ...
|
|
21
|
+
* });
|
|
22
|
+
*/
|
|
23
|
+
function defineResource(manifest) {
|
|
24
|
+
return manifest;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=define-resource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-resource.js","sourceRoot":"","sources":["../src/define-resource.ts"],"names":[],"mappings":";AAAA,iCAAiC;AACjC,6FAA6F;AAC7F,EAAE;AACF,kFAAkF;AAClF,sFAAsF;AACtF,qFAAqF;;AAgKrF,wCAMC;AAnBD;;;;;;;;;;;;GAYG;AACH,SAAgB,cAAc,CAI5B,QAAoC;IACpC,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './define-resource';
|
|
2
|
+
export * from './resource-registry';
|
|
3
|
+
export * from './resource-registry.module';
|
|
4
|
+
export * from './scope-substitute';
|
|
5
|
+
export * from './context/als';
|
|
6
|
+
export * from './context/authz-context';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,yBAAyB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./define-resource"), exports);
|
|
18
|
+
__exportStar(require("./resource-registry"), exports);
|
|
19
|
+
__exportStar(require("./resource-registry.module"), exports);
|
|
20
|
+
__exportStar(require("./scope-substitute"), exports);
|
|
21
|
+
__exportStar(require("./context/als"), exports);
|
|
22
|
+
__exportStar(require("./context/authz-context"), exports);
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,sDAAoC;AACpC,6DAA2C;AAC3C,qDAAmC;AACnC,gDAA8B;AAC9B,0DAAwC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Deterministic string form of a value (recursively key-sorted). */
|
|
2
|
+
export declare function canonicalize(value: unknown): string;
|
|
3
|
+
/** sha256 hex of the canonical form. 64 hex chars. */
|
|
4
|
+
export declare function bodyHash(value: unknown): string;
|
|
5
|
+
//# sourceMappingURL=canonical-hash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical-hash.d.ts","sourceRoot":"","sources":["../../src/nats/canonical-hash.ts"],"names":[],"mappings":"AAUA,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEnD;AAED,sDAAsD;AACtD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE/C"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Step 2 DEC-S2.8/19 — canonical body hashing for the internal NATS JWT `req` claim.
|
|
3
|
+
//
|
|
4
|
+
// The gateway hashes the outbound NATS payload (minus the injected token field) and
|
|
5
|
+
// puts the hash in the JWT; the downstream verifier re-hashes and compares. Plain
|
|
6
|
+
// JSON.stringify is insufficient (Node only guarantees insertion order), so we
|
|
7
|
+
// canonicalize with json-stable-stringify (deterministic, recursive key sort).
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.canonicalize = canonicalize;
|
|
13
|
+
exports.bodyHash = bodyHash;
|
|
14
|
+
const node_crypto_1 = require("node:crypto");
|
|
15
|
+
const json_stable_stringify_1 = __importDefault(require("json-stable-stringify"));
|
|
16
|
+
/** Deterministic string form of a value (recursively key-sorted). */
|
|
17
|
+
function canonicalize(value) {
|
|
18
|
+
return (0, json_stable_stringify_1.default)(value) ?? 'null';
|
|
19
|
+
}
|
|
20
|
+
/** sha256 hex of the canonical form. 64 hex chars. */
|
|
21
|
+
function bodyHash(value) {
|
|
22
|
+
return (0, node_crypto_1.createHash)('sha256').update(canonicalize(value)).digest('hex');
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=canonical-hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical-hash.js","sourceRoot":"","sources":["../../src/nats/canonical-hash.ts"],"names":[],"mappings":";AAAA,qFAAqF;AACrF,EAAE;AACF,oFAAoF;AACpF,kFAAkF;AAClF,+EAA+E;AAC/E,+EAA+E;;;;;AAM/E,oCAEC;AAGD,4BAEC;AAXD,6CAAyC;AACzC,kFAAoD;AAEpD,qEAAqE;AACrE,SAAgB,YAAY,CAAC,KAAc;IACzC,OAAO,IAAA,+BAAe,EAAC,KAAK,CAAC,IAAI,MAAM,CAAC;AAC1C,CAAC;AAED,sDAAsD;AACtD,SAAgB,QAAQ,CAAC,KAAc;IACrC,OAAO,IAAA,wBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACxE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/nats/index.ts"],"names":[],"mappings":"AAIA,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
// @generazioneai/authz/nats — Step 2 internal NATS JWT surface.
|
|
18
|
+
// Crypto core (no NestJS runtime dep). The NestJS proxy/interceptor that wrap these
|
|
19
|
+
// (NatsScopedClientProxy, InternalAuthInterceptor) are added once @nestjs/microservices
|
|
20
|
+
// is wired in the consuming services.
|
|
21
|
+
__exportStar(require("./canonical-hash"), exports);
|
|
22
|
+
__exportStar(require("./internal-token"), exports);
|
|
23
|
+
__exportStar(require("./internal-token.signer"), exports);
|
|
24
|
+
__exportStar(require("./key-loader"), exports);
|
|
25
|
+
__exportStar(require("./jwks-client"), exports);
|
|
26
|
+
__exportStar(require("./replay-cache"), exports);
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/nats/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,gEAAgE;AAChE,oFAAoF;AACpF,wFAAwF;AACxF,sCAAsC;AACtC,mDAAiC;AACjC,mDAAiC;AACjC,0DAAwC;AACxC,+CAA6B;AAC7B,gDAA8B;AAC9B,iDAA+B"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { SignJWT, jwtVerify } from 'jose';
|
|
2
|
+
/** Top-level payload field carrying the token (DEC-S2.18). */
|
|
3
|
+
export declare const INTERNAL_JWT_FIELD = "_internalJwt";
|
|
4
|
+
export declare const INTERNAL_ISSUER = "skillera-gateway";
|
|
5
|
+
export declare const INTERNAL_ALG = "EdDSA";
|
|
6
|
+
export declare const INTERNAL_JWT_TTL_SEC = 30;
|
|
7
|
+
export declare const INTERNAL_JWT_CLOCK_TOLERANCE = "5s";
|
|
8
|
+
/** Signing key material jose accepts for EdDSA (CryptoKey/KeyObject/Uint8Array). */
|
|
9
|
+
export type SigningKey = Parameters<SignJWT['sign']>[0];
|
|
10
|
+
/** Verification key material: a static key OR a remote JWKS getter function. */
|
|
11
|
+
export type VerificationKey = SigningKey | Parameters<typeof jwtVerify>[1];
|
|
12
|
+
export interface InternalTokenInput {
|
|
13
|
+
audience: string;
|
|
14
|
+
subject: string;
|
|
15
|
+
cmd: string;
|
|
16
|
+
reqHash: string;
|
|
17
|
+
juridicalIndividualId?: string;
|
|
18
|
+
tenantId?: string;
|
|
19
|
+
snapId?: string;
|
|
20
|
+
permHash?: string;
|
|
21
|
+
jti?: string;
|
|
22
|
+
nowSec?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface InternalClaims {
|
|
25
|
+
iss: string;
|
|
26
|
+
aud: string;
|
|
27
|
+
sub: string;
|
|
28
|
+
ji?: string;
|
|
29
|
+
tnt?: string;
|
|
30
|
+
snap?: string;
|
|
31
|
+
ph?: string;
|
|
32
|
+
cmd: string;
|
|
33
|
+
req: string;
|
|
34
|
+
jti: string;
|
|
35
|
+
iat: number;
|
|
36
|
+
exp: number;
|
|
37
|
+
}
|
|
38
|
+
/** DEC-S2.7: sign the canonical claim set. Header carries `kid` for rotation. */
|
|
39
|
+
export declare function signInternalToken(key: SigningKey, kid: string, input: InternalTokenInput): Promise<string>;
|
|
40
|
+
/** Reason labels for verify failures (DEC-S2.30 metrics + Step 7 audit). */
|
|
41
|
+
export type InternalAuthReason = 'missing' | 'sig' | 'exp' | 'aud' | 'jwks' | 'hash' | 'cmd' | 'replay' | 'backend';
|
|
42
|
+
export declare class InternalAuthError extends Error {
|
|
43
|
+
readonly reason: InternalAuthReason;
|
|
44
|
+
constructor(reason: InternalAuthReason, message?: string);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* DEC-S2.23 step 2: verify signature + iss + aud + exp via jose. Maps jose errors to
|
|
48
|
+
* an InternalAuthError with a precise reason label. Does NOT check body-hash/cmd/replay
|
|
49
|
+
* (the caller does, after re-extracting the payload) — see assert* helpers below.
|
|
50
|
+
*/
|
|
51
|
+
export declare function verifyInternalToken(jwt: string, key: VerificationKey, audience: string): Promise<InternalClaims>;
|
|
52
|
+
/** DEC-S2.23 step 3: re-hash the payload (token field already removed) and compare. */
|
|
53
|
+
export declare function assertBodyHash(claims: InternalClaims, payloadWithoutToken: unknown): void;
|
|
54
|
+
/** DEC-S2.23 step 4: the token's cmd must match the actual NATS pattern. */
|
|
55
|
+
export declare function assertCmd(claims: InternalClaims, cmd: string): void;
|
|
56
|
+
//# sourceMappingURL=internal-token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal-token.d.ts","sourceRoot":"","sources":["../../src/nats/internal-token.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAI1C,8DAA8D;AAC9D,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AACjD,eAAO,MAAM,eAAe,qBAAqB,CAAC;AAClD,eAAO,MAAM,YAAY,UAAU,CAAC;AACpC,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,oFAAoF;AACpF,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACxD,gFAAgF;AAChF,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,iFAAiF;AACjF,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,UAAU,EACf,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,MAAM,CAAC,CAoBjB;AAED,4EAA4E;AAC5E,MAAM,MAAM,kBAAkB,GAC1B,SAAS,GACT,KAAK,GACL,KAAK,GACL,KAAK,GACL,MAAM,GACN,MAAM,GACN,KAAK,GACL,QAAQ,GACR,SAAS,CAAC;AAEd,qBAAa,iBAAkB,SAAQ,KAAK;aACd,MAAM,EAAE,kBAAkB;gBAA1B,MAAM,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,MAAM;CAIzE;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,eAAe,EACpB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,CAAC,CAkBzB;AAED,uFAAuF;AACvF,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,mBAAmB,EAAE,OAAO,GAAG,IAAI,CAIzF;AAED,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAInE"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Step 2 DEC-S2.1/7/11/23 — internal NATS JWT sign + verify (EdDSA / Ed25519).
|
|
3
|
+
//
|
|
4
|
+
// Pure crypto core (no NestJS): the gateway signs a short-lived token per NATS call;
|
|
5
|
+
// the downstream verifier checks signature/issuer/audience/expiry via jose, then the
|
|
6
|
+
// caller asserts body-hash + cmd (this module's assert* helpers) and replay (Redis).
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.InternalAuthError = exports.INTERNAL_JWT_CLOCK_TOLERANCE = exports.INTERNAL_JWT_TTL_SEC = exports.INTERNAL_ALG = exports.INTERNAL_ISSUER = exports.INTERNAL_JWT_FIELD = void 0;
|
|
9
|
+
exports.signInternalToken = signInternalToken;
|
|
10
|
+
exports.verifyInternalToken = verifyInternalToken;
|
|
11
|
+
exports.assertBodyHash = assertBodyHash;
|
|
12
|
+
exports.assertCmd = assertCmd;
|
|
13
|
+
const jose_1 = require("jose");
|
|
14
|
+
const node_crypto_1 = require("node:crypto");
|
|
15
|
+
const canonical_hash_1 = require("./canonical-hash");
|
|
16
|
+
/** Top-level payload field carrying the token (DEC-S2.18). */
|
|
17
|
+
exports.INTERNAL_JWT_FIELD = '_internalJwt';
|
|
18
|
+
exports.INTERNAL_ISSUER = 'skillera-gateway';
|
|
19
|
+
exports.INTERNAL_ALG = 'EdDSA';
|
|
20
|
+
exports.INTERNAL_JWT_TTL_SEC = 30; // DEC-S2.11
|
|
21
|
+
exports.INTERNAL_JWT_CLOCK_TOLERANCE = '5s'; // DEC-S2.11
|
|
22
|
+
/** DEC-S2.7: sign the canonical claim set. Header carries `kid` for rotation. */
|
|
23
|
+
async function signInternalToken(key, kid, input) {
|
|
24
|
+
const now = input.nowSec ?? Math.floor(Date.now() / 1000);
|
|
25
|
+
const claims = {
|
|
26
|
+
cmd: input.cmd,
|
|
27
|
+
req: input.reqHash,
|
|
28
|
+
jti: input.jti ?? (0, node_crypto_1.randomUUID)(),
|
|
29
|
+
};
|
|
30
|
+
if (input.juridicalIndividualId !== undefined)
|
|
31
|
+
claims.ji = input.juridicalIndividualId;
|
|
32
|
+
if (input.tenantId !== undefined)
|
|
33
|
+
claims.tnt = input.tenantId;
|
|
34
|
+
if (input.snapId !== undefined)
|
|
35
|
+
claims.snap = input.snapId;
|
|
36
|
+
if (input.permHash !== undefined)
|
|
37
|
+
claims.ph = input.permHash;
|
|
38
|
+
return new jose_1.SignJWT(claims)
|
|
39
|
+
.setProtectedHeader({ alg: exports.INTERNAL_ALG, kid, typ: 'JWT' })
|
|
40
|
+
.setIssuer(exports.INTERNAL_ISSUER)
|
|
41
|
+
.setAudience(input.audience)
|
|
42
|
+
.setSubject(input.subject)
|
|
43
|
+
.setIssuedAt(now)
|
|
44
|
+
.setExpirationTime(now + exports.INTERNAL_JWT_TTL_SEC)
|
|
45
|
+
.sign(key);
|
|
46
|
+
}
|
|
47
|
+
class InternalAuthError extends Error {
|
|
48
|
+
constructor(reason, message) {
|
|
49
|
+
super(message ?? `internal-auth: ${reason}`);
|
|
50
|
+
this.reason = reason;
|
|
51
|
+
this.name = 'InternalAuthError';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.InternalAuthError = InternalAuthError;
|
|
55
|
+
/**
|
|
56
|
+
* DEC-S2.23 step 2: verify signature + iss + aud + exp via jose. Maps jose errors to
|
|
57
|
+
* an InternalAuthError with a precise reason label. Does NOT check body-hash/cmd/replay
|
|
58
|
+
* (the caller does, after re-extracting the payload) — see assert* helpers below.
|
|
59
|
+
*/
|
|
60
|
+
async function verifyInternalToken(jwt, key, audience) {
|
|
61
|
+
try {
|
|
62
|
+
const { payload } = await (0, jose_1.jwtVerify)(jwt, key, {
|
|
63
|
+
issuer: exports.INTERNAL_ISSUER,
|
|
64
|
+
audience,
|
|
65
|
+
algorithms: [exports.INTERNAL_ALG],
|
|
66
|
+
clockTolerance: exports.INTERNAL_JWT_CLOCK_TOLERANCE,
|
|
67
|
+
});
|
|
68
|
+
return payload;
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
const code = e.code ?? '';
|
|
72
|
+
if (code === 'ERR_JWT_EXPIRED')
|
|
73
|
+
throw new InternalAuthError('exp', e.message);
|
|
74
|
+
if (code === 'ERR_JWT_CLAIM_VALIDATION_FAILED')
|
|
75
|
+
throw new InternalAuthError('aud', e.message);
|
|
76
|
+
if (code === 'ERR_JWKS_NO_MATCHING_KEY' || code === 'ERR_JWKS_MULTIPLE_MATCHING_KEYS')
|
|
77
|
+
throw new InternalAuthError('jwks', e.message);
|
|
78
|
+
throw new InternalAuthError('sig', e.message);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** DEC-S2.23 step 3: re-hash the payload (token field already removed) and compare. */
|
|
82
|
+
function assertBodyHash(claims, payloadWithoutToken) {
|
|
83
|
+
if (claims.req !== (0, canonical_hash_1.bodyHash)(payloadWithoutToken)) {
|
|
84
|
+
throw new InternalAuthError('hash', 'request body hash mismatch (payload tampered)');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** DEC-S2.23 step 4: the token's cmd must match the actual NATS pattern. */
|
|
88
|
+
function assertCmd(claims, cmd) {
|
|
89
|
+
if (claims.cmd !== cmd) {
|
|
90
|
+
throw new InternalAuthError('cmd', `cmd mismatch: token='${claims.cmd}' actual='${cmd}'`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=internal-token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal-token.js","sourceRoot":"","sources":["../../src/nats/internal-token.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AuthzContext } from '../context/authz-context';
|
|
2
|
+
import { type SigningKey } from './internal-token';
|
|
3
|
+
export interface SignerConfig {
|
|
4
|
+
key: SigningKey;
|
|
5
|
+
kid: string;
|
|
6
|
+
/** Subject used when there is no user in context (system/cron calls). */
|
|
7
|
+
systemSubject?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class InternalTokenSigner {
|
|
10
|
+
private readonly cfg;
|
|
11
|
+
constructor(cfg: SignerConfig);
|
|
12
|
+
/**
|
|
13
|
+
* Returns a shallow clone of `payload` with `_internalJwt` attached. The `req`
|
|
14
|
+
* body-hash is computed over the payload BEFORE the token is added (DEC-S2.19),
|
|
15
|
+
* so the downstream verifier reproduces it after stripping the field.
|
|
16
|
+
*/
|
|
17
|
+
sign(cmd: string, audience: string, payload: Record<string, unknown>, ctx?: AuthzContext): Promise<Record<string, unknown>>;
|
|
18
|
+
}
|
|
19
|
+
/** `{cmd:'x'}` → 'x'; anything else → stable JSON (EDGE-S2.36). */
|
|
20
|
+
export declare function patternToCmd(pattern: unknown): string;
|
|
21
|
+
//# sourceMappingURL=internal-token.signer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal-token.signer.d.ts","sourceRoot":"","sources":["../../src/nats/internal-token.signer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EAAyC,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE1F,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,UAAU,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,mBAAmB;IAClB,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,YAAY;IAE9C;;;;OAIG;IACG,IAAI,CACR,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,GAAG,CAAC,EAAE,YAAY,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAmBpC;AAED,mEAAmE;AACnE,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAKrD"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Step 2 DEC-S2.16/19 — signer logic (transport-agnostic, testable).
|
|
3
|
+
// The NatsScopedClientProxy (src/nest) calls this to build the outbound payload:
|
|
4
|
+
// hash the payload WITHOUT the token field, sign, then attach `_internalJwt`.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.InternalTokenSigner = void 0;
|
|
7
|
+
exports.patternToCmd = patternToCmd;
|
|
8
|
+
const als_1 = require("../context/als");
|
|
9
|
+
const canonical_hash_1 = require("./canonical-hash");
|
|
10
|
+
const internal_token_1 = require("./internal-token");
|
|
11
|
+
class InternalTokenSigner {
|
|
12
|
+
constructor(cfg) {
|
|
13
|
+
this.cfg = cfg;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Returns a shallow clone of `payload` with `_internalJwt` attached. The `req`
|
|
17
|
+
* body-hash is computed over the payload BEFORE the token is added (DEC-S2.19),
|
|
18
|
+
* so the downstream verifier reproduces it after stripping the field.
|
|
19
|
+
*/
|
|
20
|
+
async sign(cmd, audience, payload, ctx) {
|
|
21
|
+
const c = ctx ?? als_1.authzAls.getStore();
|
|
22
|
+
const subject = c?.userId ?? this.cfg.systemSubject;
|
|
23
|
+
if (!subject) {
|
|
24
|
+
throw new Error(`[authz] cannot sign internal token for '${cmd}': no userId in context`);
|
|
25
|
+
}
|
|
26
|
+
const reqHash = (0, canonical_hash_1.bodyHash)(payload ?? {});
|
|
27
|
+
const jwt = await (0, internal_token_1.signInternalToken)(this.cfg.key, this.cfg.kid, {
|
|
28
|
+
audience,
|
|
29
|
+
subject,
|
|
30
|
+
cmd,
|
|
31
|
+
reqHash,
|
|
32
|
+
juridicalIndividualId: c?.juridicalIndividualId,
|
|
33
|
+
tenantId: c?.tenantId,
|
|
34
|
+
snapId: c?.snapId,
|
|
35
|
+
permHash: c?.permHash,
|
|
36
|
+
});
|
|
37
|
+
return { ...(payload ?? {}), [internal_token_1.INTERNAL_JWT_FIELD]: jwt };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.InternalTokenSigner = InternalTokenSigner;
|
|
41
|
+
/** `{cmd:'x'}` → 'x'; anything else → stable JSON (EDGE-S2.36). */
|
|
42
|
+
function patternToCmd(pattern) {
|
|
43
|
+
if (pattern && typeof pattern === 'object' && typeof pattern.cmd === 'string') {
|
|
44
|
+
return pattern.cmd;
|
|
45
|
+
}
|
|
46
|
+
return JSON.stringify(pattern);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=internal-token.signer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal-token.signer.js","sourceRoot":"","sources":["../../src/nats/internal-token.signer.ts"],"names":[],"mappings":";AAAA,qEAAqE;AACrE,iFAAiF;AACjF,8EAA8E;;;AAiD9E,oCAKC;AApDD,wCAA0C;AAE1C,qDAA4C;AAC5C,qDAA0F;AAS1F,MAAa,mBAAmB;IAC9B,YAA6B,GAAiB;QAAjB,QAAG,GAAH,GAAG,CAAc;IAAG,CAAC;IAElD;;;;OAIG;IACH,KAAK,CAAC,IAAI,CACR,GAAW,EACX,QAAgB,EAChB,OAAgC,EAChC,GAAkB;QAElB,MAAM,CAAC,GAAG,GAAG,IAAI,cAAQ,CAAC,QAAQ,EAAE,CAAC;QACrC,MAAM,OAAO,GAAG,CAAC,EAAE,MAAM,IAAI,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC;QACpD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,2CAA2C,GAAG,yBAAyB,CAAC,CAAC;QAC3F,CAAC;QACD,MAAM,OAAO,GAAG,IAAA,yBAAQ,EAAC,OAAO,IAAI,EAAE,CAAC,CAAC;QACxC,MAAM,GAAG,GAAG,MAAM,IAAA,kCAAiB,EAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE;YAC9D,QAAQ;YACR,OAAO;YACP,GAAG;YACH,OAAO;YACP,qBAAqB,EAAE,CAAC,EAAE,qBAAqB;YAC/C,QAAQ,EAAE,CAAC,EAAE,QAAQ;YACrB,MAAM,EAAE,CAAC,EAAE,MAAM;YACjB,QAAQ,EAAE,CAAC,EAAE,QAAQ;SACtB,CAAC,CAAC;QACH,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC,mCAAkB,CAAC,EAAE,GAAG,EAAE,CAAC;IAC3D,CAAC;CACF;AAhCD,kDAgCC;AAED,mEAAmE;AACnE,SAAgB,YAAY,CAAC,OAAgB;IAC3C,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAQ,OAA6B,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;QACrG,OAAQ,OAA2B,CAAC,GAAG,CAAC;IAC1C,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createRemoteJWKSet } from 'jose';
|
|
2
|
+
export interface JwksClientOptions {
|
|
3
|
+
/** e.g. http://backend-skillera-auth-svc:3000/internal/jwks */
|
|
4
|
+
url: string;
|
|
5
|
+
cacheMaxAgeMs?: number;
|
|
6
|
+
cooldownMs?: number;
|
|
7
|
+
}
|
|
8
|
+
export type JwksKeyGetter = ReturnType<typeof createRemoteJWKSet>;
|
|
9
|
+
export declare function createInternalJwks(opts: JwksClientOptions): JwksKeyGetter;
|
|
10
|
+
//# sourceMappingURL=jwks-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwks-client.d.ts","sourceRoot":"","sources":["../../src/nats/jwks-client.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,MAAM,CAAC;AAE1C,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAElE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,aAAa,CAKzE"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Step 2 DEC-S2.14 — remote JWKS verification key set (skillAuth /internal/jwks).
|
|
3
|
+
// Singleton per process: caches keys 1h, 10min cooldown between refetch attempts,
|
|
4
|
+
// keeps the last-known set in memory across fetch failures (DEC-S2.15 fail-soft).
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createInternalJwks = createInternalJwks;
|
|
7
|
+
const jose_1 = require("jose");
|
|
8
|
+
function createInternalJwks(opts) {
|
|
9
|
+
return (0, jose_1.createRemoteJWKSet)(new URL(opts.url), {
|
|
10
|
+
cacheMaxAge: opts.cacheMaxAgeMs ?? 3_600_000,
|
|
11
|
+
cooldownDuration: opts.cooldownMs ?? 600_000,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=jwks-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwks-client.js","sourceRoot":"","sources":["../../src/nats/jwks-client.ts"],"names":[],"mappings":";AAAA,kFAAkF;AAClF,kFAAkF;AAClF,kFAAkF;;AAalF,gDAKC;AAhBD,+BAA0C;AAW1C,SAAgB,kBAAkB,CAAC,IAAuB;IACxD,OAAO,IAAA,yBAAkB,EAAC,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;QAC3C,WAAW,EAAE,IAAI,CAAC,aAAa,IAAI,SAAS;QAC5C,gBAAgB,EAAE,IAAI,CAAC,UAAU,IAAI,OAAO;KAC7C,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { importJWK, type JWK } from 'jose';
|
|
2
|
+
type ImportedKey = Awaited<ReturnType<typeof importJWK>>;
|
|
3
|
+
export declare const DEFAULT_SIGNING_KEY_PATH = "/etc/skillera/authz/signing-key.jwk.json";
|
|
4
|
+
export declare const SIGNING_KEY_ENV = "AUTHZ_SIGNING_KEY_JWK";
|
|
5
|
+
export declare const SIGNING_KEY_PATH_ENV = "AUTHZ_SIGNING_KEY_PATH";
|
|
6
|
+
export interface LoadedSigningKey {
|
|
7
|
+
key: ImportedKey;
|
|
8
|
+
kid: string;
|
|
9
|
+
}
|
|
10
|
+
/** Load the private signing key. Throws (fail-closed) if absent/corrupt. */
|
|
11
|
+
export declare function loadSigningKey(opts?: {
|
|
12
|
+
path?: string;
|
|
13
|
+
envVar?: string;
|
|
14
|
+
}): Promise<LoadedSigningKey>;
|
|
15
|
+
/**
|
|
16
|
+
* Generate an Ed25519 keypair as JWKs (DEC-S2.2). Used by the rotation script
|
|
17
|
+
* (packages/authz/scripts/rotate.ts) and tests. `kid` convention: timestamped.
|
|
18
|
+
*/
|
|
19
|
+
export declare function generateSigningKeyPair(kid: string): Promise<{
|
|
20
|
+
privateJwk: JWK;
|
|
21
|
+
publicJwk: JWK;
|
|
22
|
+
}>;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=key-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-loader.d.ts","sourceRoot":"","sources":["../../src/nats/key-loader.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,SAAS,EAA8B,KAAK,GAAG,EAAE,MAAM,MAAM,CAAC;AAGvE,KAAK,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC;AAEzD,eAAO,MAAM,wBAAwB,6CAA6C,CAAC;AACnF,eAAO,MAAM,eAAe,0BAA0B,CAAC;AACvD,eAAO,MAAM,oBAAoB,2BAA2B,CAAC;AAE7D,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,WAAW,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AA8BD,4EAA4E;AAC5E,wBAAsB,cAAc,CAClC,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5C,OAAO,CAAC,gBAAgB,CAAC,CAI3B;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,UAAU,EAAE,GAAG,CAAC;IAAC,SAAS,EAAE,GAAG,CAAA;CAAE,CAAC,CAU9C"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Step 2 DEC-S2.3 / EDGE-S2.21 — load the gateway Ed25519 signing key (fail-closed).
|
|
3
|
+
//
|
|
4
|
+
// Primary: K8s Secret mounted as a JWK file (/etc/skillera/authz/signing-key.jwk.json).
|
|
5
|
+
// Fallback (dev): base64-encoded JWK in env AUTHZ_SIGNING_KEY_JWK.
|
|
6
|
+
// A missing/corrupt key throws — the gateway must refuse to boot (fail-closed).
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.SIGNING_KEY_PATH_ENV = exports.SIGNING_KEY_ENV = exports.DEFAULT_SIGNING_KEY_PATH = void 0;
|
|
9
|
+
exports.loadSigningKey = loadSigningKey;
|
|
10
|
+
exports.generateSigningKeyPair = generateSigningKeyPair;
|
|
11
|
+
const node_fs_1 = require("node:fs");
|
|
12
|
+
const jose_1 = require("jose");
|
|
13
|
+
const internal_token_1 = require("./internal-token");
|
|
14
|
+
exports.DEFAULT_SIGNING_KEY_PATH = '/etc/skillera/authz/signing-key.jwk.json';
|
|
15
|
+
exports.SIGNING_KEY_ENV = 'AUTHZ_SIGNING_KEY_JWK';
|
|
16
|
+
exports.SIGNING_KEY_PATH_ENV = 'AUTHZ_SIGNING_KEY_PATH';
|
|
17
|
+
function readKeyJwk(opts) {
|
|
18
|
+
const path = opts.path ?? process.env[exports.SIGNING_KEY_PATH_ENV] ?? exports.DEFAULT_SIGNING_KEY_PATH;
|
|
19
|
+
const envVar = opts.envVar ?? exports.SIGNING_KEY_ENV;
|
|
20
|
+
let raw;
|
|
21
|
+
if ((0, node_fs_1.existsSync)(path)) {
|
|
22
|
+
raw = (0, node_fs_1.readFileSync)(path, 'utf8');
|
|
23
|
+
}
|
|
24
|
+
else if (process.env[envVar]) {
|
|
25
|
+
raw = Buffer.from(process.env[envVar], 'base64').toString('utf8');
|
|
26
|
+
}
|
|
27
|
+
if (!raw) {
|
|
28
|
+
throw new Error(`[authz] signing key not found — neither file '${path}' nor env '${envVar}' present`);
|
|
29
|
+
}
|
|
30
|
+
let jwk;
|
|
31
|
+
try {
|
|
32
|
+
jwk = JSON.parse(raw);
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
throw new Error(`[authz] signing key JWK is not valid JSON: ${e.message}`);
|
|
36
|
+
}
|
|
37
|
+
if (!jwk.kid)
|
|
38
|
+
throw new Error('[authz] signing key JWK is missing the required "kid"');
|
|
39
|
+
if (jwk.crv && jwk.crv !== 'Ed25519') {
|
|
40
|
+
throw new Error(`[authz] signing key must be Ed25519 (got crv='${jwk.crv}')`);
|
|
41
|
+
}
|
|
42
|
+
return jwk;
|
|
43
|
+
}
|
|
44
|
+
/** Load the private signing key. Throws (fail-closed) if absent/corrupt. */
|
|
45
|
+
async function loadSigningKey(opts = {}) {
|
|
46
|
+
const jwk = readKeyJwk(opts);
|
|
47
|
+
const key = await (0, jose_1.importJWK)(jwk, internal_token_1.INTERNAL_ALG);
|
|
48
|
+
return { key, kid: jwk.kid };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Generate an Ed25519 keypair as JWKs (DEC-S2.2). Used by the rotation script
|
|
52
|
+
* (packages/authz/scripts/rotate.ts) and tests. `kid` convention: timestamped.
|
|
53
|
+
*/
|
|
54
|
+
async function generateSigningKeyPair(kid) {
|
|
55
|
+
const { privateKey, publicKey } = await (0, jose_1.generateKeyPair)(internal_token_1.INTERNAL_ALG, {
|
|
56
|
+
crv: 'Ed25519',
|
|
57
|
+
extractable: true,
|
|
58
|
+
});
|
|
59
|
+
const base = { kid, alg: internal_token_1.INTERNAL_ALG, use: 'sig' };
|
|
60
|
+
return {
|
|
61
|
+
privateJwk: { ...(await (0, jose_1.exportJWK)(privateKey)), ...base },
|
|
62
|
+
publicJwk: { ...(await (0, jose_1.exportJWK)(publicKey)), ...base },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=key-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-loader.js","sourceRoot":"","sources":["../../src/nats/key-loader.ts"],"names":[],"mappings":";AAAA,qFAAqF;AACrF,EAAE;AACF,wFAAwF;AACxF,mEAAmE;AACnE,gFAAgF;;;AA8ChF,wCAMC;AAMD,wDAYC;AApED,qCAAmD;AACnD,+BAAuE;AACvE,qDAAgD;AAInC,QAAA,wBAAwB,GAAG,0CAA0C,CAAC;AACtE,QAAA,eAAe,GAAG,uBAAuB,CAAC;AAC1C,QAAA,oBAAoB,GAAG,wBAAwB,CAAC;AAO7D,SAAS,UAAU,CAAC,IAAwC;IAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,4BAAoB,CAAC,IAAI,gCAAwB,CAAC;IACxF,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,uBAAe,CAAC;IAE9C,IAAI,GAAuB,CAAC;IAC5B,IAAI,IAAA,oBAAU,EAAC,IAAI,CAAC,EAAE,CAAC;QACrB,GAAG,GAAG,IAAA,sBAAY,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;SAAM,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAW,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,iDAAiD,IAAI,cAAc,MAAM,WAAW,CACrF,CAAC;IACJ,CAAC;IACD,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAQ,CAAC;IAC/B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,8CAA+C,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IACxF,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;IACvF,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,iDAAiD,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,4EAA4E;AACrE,KAAK,UAAU,cAAc,CAClC,OAA2C,EAAE;IAE7C,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,GAAG,GAAG,MAAM,IAAA,gBAAS,EAAC,GAAG,EAAE,6BAAY,CAAC,CAAC;IAC/C,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAa,EAAE,CAAC;AACzC,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,sBAAsB,CAC1C,GAAW;IAEX,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,MAAM,IAAA,sBAAe,EAAC,6BAAY,EAAE;QACpE,GAAG,EAAE,SAAS;QACd,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,6BAAY,EAAE,GAAG,EAAE,KAAc,EAAE,CAAC;IAC7D,OAAO;QACL,UAAU,EAAE,EAAE,GAAG,CAAC,MAAM,IAAA,gBAAS,EAAC,UAAU,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE;QACzD,SAAS,EAAE,EAAE,GAAG,CAAC,MAAM,IAAA,gBAAS,EAAC,SAAS,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE;KACxD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type Redis from 'ioredis';
|
|
2
|
+
export interface ReplayCache {
|
|
3
|
+
/** Returns true if the jti is first-seen (allowed), false if already seen (replay). */
|
|
4
|
+
firstSeen(jti: string, ttlSec?: number): Promise<boolean>;
|
|
5
|
+
}
|
|
6
|
+
export declare const DEFAULT_JTI_PREFIX = "authz:jti:";
|
|
7
|
+
export declare const DEFAULT_JTI_TTL_SEC = 60;
|
|
8
|
+
export declare class RedisReplayCache implements ReplayCache {
|
|
9
|
+
private readonly redis;
|
|
10
|
+
private readonly prefix;
|
|
11
|
+
constructor(redis: Redis, prefix?: string);
|
|
12
|
+
firstSeen(jti: string, ttlSec?: number): Promise<boolean>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=replay-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-cache.d.ts","sourceRoot":"","sources":["../../src/nats/replay-cache.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AAEjC,MAAM,WAAW,WAAW;IAC1B,uFAAuF;IACvF,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3D;AAED,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAC/C,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,qBAAa,gBAAiB,YAAW,WAAW;IAEhD,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,KAAK,EAAE,KAAK,EACZ,MAAM,GAAE,MAA2B;IAGhD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,MAA4B,GAAG,OAAO,CAAC,OAAO,CAAC;CAKrF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Step 2 DEC-S2.25 — JWT replay protection via Redis (SET NX EX).
|
|
3
|
+
//
|
|
4
|
+
// Key `authz:jti:<jti>` with TTL 60s (2× the 30s JWT TTL → covers clock skew + retry).
|
|
5
|
+
// First-seen → SET succeeds → allowed. Already present → replay → rejected.
|
|
6
|
+
// Redis unreachable → throw → the verifier fails CLOSED (INTERNAL_AUTH_BACKEND).
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.RedisReplayCache = exports.DEFAULT_JTI_TTL_SEC = exports.DEFAULT_JTI_PREFIX = void 0;
|
|
9
|
+
exports.DEFAULT_JTI_PREFIX = 'authz:jti:';
|
|
10
|
+
exports.DEFAULT_JTI_TTL_SEC = 60;
|
|
11
|
+
class RedisReplayCache {
|
|
12
|
+
constructor(redis, prefix = exports.DEFAULT_JTI_PREFIX) {
|
|
13
|
+
this.redis = redis;
|
|
14
|
+
this.prefix = prefix;
|
|
15
|
+
}
|
|
16
|
+
async firstSeen(jti, ttlSec = exports.DEFAULT_JTI_TTL_SEC) {
|
|
17
|
+
// SET key 1 EX <ttl> NX → 'OK' when newly set, null when the key already exists.
|
|
18
|
+
const res = await this.redis.set(this.prefix + jti, '1', 'EX', ttlSec, 'NX');
|
|
19
|
+
return res === 'OK';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.RedisReplayCache = RedisReplayCache;
|
|
23
|
+
//# sourceMappingURL=replay-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-cache.js","sourceRoot":"","sources":["../../src/nats/replay-cache.ts"],"names":[],"mappings":";AAAA,kEAAkE;AAClE,EAAE;AACF,uFAAuF;AACvF,4EAA4E;AAC5E,iFAAiF;;;AASpE,QAAA,kBAAkB,GAAG,YAAY,CAAC;AAClC,QAAA,mBAAmB,GAAG,EAAE,CAAC;AAEtC,MAAa,gBAAgB;IAC3B,YACmB,KAAY,EACZ,SAAiB,0BAAkB;QADnC,UAAK,GAAL,KAAK,CAAO;QACZ,WAAM,GAAN,MAAM,CAA6B;IACnD,CAAC;IAEJ,KAAK,CAAC,SAAS,CAAC,GAAW,EAAE,SAAiB,2BAAmB;QAC/D,iFAAiF;QACjF,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QAC7E,OAAO,GAAG,KAAK,IAAI,CAAC;IACtB,CAAC;CACF;AAXD,4CAWC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type CallHandler, type ExecutionContext, type NestInterceptor } from '@nestjs/common';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
3
|
+
export declare class AuthzContextInterceptor implements NestInterceptor {
|
|
4
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=authz-context.interceptor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz-context.interceptor.d.ts","sourceRoot":"","sources":["../../src/nest/authz-context.interceptor.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAKlC,qBACa,uBAAwB,YAAW,eAAe;IAC7D,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC;CAyB7E"}
|