@decaf-ts/integrations 0.2.0 → 0.2.2
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/integrations.cjs +1 -1
- package/dist/integrations.cjs.map +1 -1
- package/dist/integrations.js +1 -1
- package/dist/integrations.js.map +1 -1
- package/lib/cjs/index.cjs +3 -3
- package/lib/cjs/nest/authService.cjs +53 -2
- package/lib/cjs/nest/authService.cjs.map +1 -1
- package/lib/cjs/nest/index.cjs +2 -0
- package/lib/cjs/nest/index.cjs.map +1 -1
- package/lib/cjs/nest/keycloakAuthHandler.cjs +49 -23
- package/lib/cjs/nest/keycloakAuthHandler.cjs.map +1 -1
- package/lib/cjs/nest/keycloakModule.cjs +5 -3
- package/lib/cjs/nest/keycloakModule.cjs.map +1 -1
- package/lib/cjs/nest/logging.cjs +41 -0
- package/lib/cjs/nest/logging.cjs.map +1 -0
- package/lib/cjs/nest/utils.cjs +24 -0
- package/lib/cjs/nest/utils.cjs.map +1 -1
- package/lib/cjs/utils.cjs +1 -1
- package/lib/esm/index.js +3 -3
- package/lib/esm/nest/authService.js +54 -3
- package/lib/esm/nest/authService.js.map +1 -1
- package/lib/esm/nest/index.js +2 -0
- package/lib/esm/nest/index.js.map +1 -1
- package/lib/esm/nest/keycloakAuthHandler.js +49 -23
- package/lib/esm/nest/keycloakAuthHandler.js.map +1 -1
- package/lib/esm/nest/keycloakModule.js +5 -3
- package/lib/esm/nest/keycloakModule.js.map +1 -1
- package/lib/esm/nest/logging.js +38 -0
- package/lib/esm/nest/logging.js.map +1 -0
- package/lib/esm/nest/utils.js +23 -0
- package/lib/esm/nest/utils.js.map +1 -1
- package/lib/esm/utils.js +1 -1
- package/lib/types/index.d.cts +2 -2
- package/lib/types/index.d.mts +2 -2
- package/lib/types/nest/authService.d.cts +55 -2
- package/lib/types/nest/authService.d.mts +55 -2
- package/lib/types/nest/index.d.cts +2 -0
- package/lib/types/nest/index.d.mts +2 -0
- package/lib/types/nest/keycloakAuthHandler.d.cts +51 -5
- package/lib/types/nest/keycloakAuthHandler.d.mts +51 -5
- package/lib/types/nest/keycloakModule.d.cts +3 -3
- package/lib/types/nest/keycloakModule.d.mts +3 -3
- package/lib/types/nest/logging.d.cts +1 -0
- package/lib/types/nest/logging.d.mts +1 -0
- package/lib/types/nest/types.d.cts +23 -18
- package/lib/types/nest/types.d.mts +23 -18
- package/lib/types/nest/utils.d.cts +9 -0
- package/lib/types/nest/utils.d.mts +9 -0
- package/package.json +32 -3
package/dist/integrations.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e,o;e=this,o=(e,o,
|
|
1
|
+
var e,o;e=this,o=(e,o,i,n)=>{"use strict";e.COMMIT="4990f39",e.FULL_VERSION="0.2.1-4990f39",e.PACKAGE_NAME="@decaf-ts/integrations",e.VERSION="0.2.1",e.execWithLogging=async(e,t,r,d=i.LogLevel.info)=>(r.debug("Executing: "+e),new Promise((i,s)=>{const c=o.exec(e,t),a=[],l=[];c.stdout?.on("data",e=>{a.push(e),r[d](e.trim())}),c.stderr?.on("data",e=>{l.push(e),r.debug(e.trim())}),c.on("close",e=>{0===e?(r.silly("Command completed successfully"),i({stdout:a.join("\n"),stderr:l.join("\n")})):(r.critical(`Command failed with code ${e}:\n ${l.join("\n")}`),s(new n.InternalError(`Command failed with code ${e}: ${l.join("\n")}`)))}),c.on("error",e=>{r.critical("Command execution error",e),s(e)})}))},"object"==typeof exports&&"undefined"!=typeof module?o(exports,require("child_process"),require("@decaf-ts/logging"),require("@decaf-ts/db-decorators")):"function"==typeof define&&define.amd?define(["exports","child_process","@decaf-ts/logging","@decaf-ts/db-decorators"],o):o((e="undefined"!=typeof globalThis?globalThis:e||self).integrations={},e.childProcess,e.decafTsLogging,e.decafTsDbDecorators);
|
|
2
2
|
//# sourceMappingURL=integrations.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integrations.cjs","sources":["../src/index.ts","../src/utils.ts"],"sourcesContent":[null,null],"names":["async","command","options","log","level","LogLevel","info","debug","Promise","resolve","reject","child","exec","stdout","stderr","on","data","push","trim","
|
|
1
|
+
{"version":3,"file":"integrations.cjs","sources":["../src/index.ts","../src/utils.ts"],"sourcesContent":[null,null],"names":["async","command","options","log","level","LogLevel","info","debug","Promise","resolve","reject","child","exec","stdout","stderr","on","data","push","trim","code","silly","join","critical","InternalError","err"],"mappings":"mDASsB,4BAEM,kCAEQ,wBANL,gCCExBA,MACLC,EACAC,EACAC,EACAC,EAAkBC,EAAAA,SAASC,QAE3BH,EAAII,MAAM,cAAcN,GAEjB,IAAIO,QAAQ,CAACC,EAASC,KAC3B,MAAMC,EAAQC,EAAAA,KAAKX,EAASC,GAEtBW,EAAmB,GACnBC,EAAmB,GAEzBH,EAAME,QAAQE,GAAG,OAASC,IACxBH,EAAOI,KAAKD,GACZb,EAAIC,GAAOY,EAAKE,UAGlBP,EAAMG,QAAQC,GAAG,OAASC,IACxBF,EAAOG,KAAKD,GACZb,EAAII,MAAMS,EAAKE,UAGjBP,EAAMI,GAAG,QAAUI,IACJ,IAATA,GACFhB,EAAIiB,MAAM,kCACVX,EAAQ,CAAEI,OAAQA,EAAOQ,KAAK,MAAOP,OAAQA,EAAOO,KAAK,UAEzDlB,EAAImB,SACF,4BAA4BH,QAAWL,EAAOO,KAAK,SAErDX,EACE,IAAIa,EAAAA,cACF,4BAA4BJ,MAASL,EAAOO,KAAK,aAMzDV,EAAMI,GAAG,QAAUS,IACjBrB,EAAImB,SAAS,0BAA2BE,GACxCd,EAAOc"}
|
package/dist/integrations.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{exec as o}from"child_process";import{LogLevel as
|
|
1
|
+
import{exec as o}from"child_process";import{LogLevel as n}from"@decaf-ts/logging";import{InternalError as t}from"@decaf-ts/db-decorators";async function r(r,e,i,d=n.info){return i.debug("Executing: "+r),new Promise((n,c)=>{const s=o(r,e),m=[],a=[];s.stdout?.on("data",o=>{m.push(o),i[d](o.trim())}),s.stderr?.on("data",o=>{a.push(o),i.debug(o.trim())}),s.on("close",o=>{0===o?(i.silly("Command completed successfully"),n({stdout:m.join("\n"),stderr:a.join("\n")})):(i.critical(`Command failed with code ${o}:\n ${a.join("\n")}`),c(new t(`Command failed with code ${o}: ${a.join("\n")}`)))}),s.on("error",o=>{i.critical("Command execution error",o),c(o)})})}const e="##VERSION##",i="##COMMIT##",d="##FULL_VERSION##",c="##PACKAGE##";export{i as COMMIT,d as FULL_VERSION,c as PACKAGE_NAME,e as VERSION,r as execWithLogging};
|
|
2
2
|
//# sourceMappingURL=integrations.js.map
|
package/dist/integrations.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integrations.js","sources":["../src/utils.ts","../src/index.ts"],"sourcesContent":[null,null],"names":["async","execWithLogging","command","options","log","level","LogLevel","info","debug","Promise","resolve","reject","child","exec","stdout","stderr","on","data","push","trim","
|
|
1
|
+
{"version":3,"file":"integrations.js","sources":["../src/utils.ts","../src/index.ts"],"sourcesContent":[null,null],"names":["async","execWithLogging","command","options","log","level","LogLevel","info","debug","Promise","resolve","reject","child","exec","stdout","stderr","on","data","push","trim","code","silly","join","critical","InternalError","err","VERSION","COMMIT","FULL_VERSION","PACKAGE_NAME"],"mappings":"0IASOA,eAAeC,EACpBC,EACAC,EACAC,EACAC,EAAkBC,EAASC,MAI3B,OAFAH,EAAII,MAAM,cAAcN,GAEjB,IAAIO,QAAQ,CAACC,EAASC,KAC3B,MAAMC,EAAQC,EAAKX,EAASC,GAEtBW,EAAmB,GACnBC,EAAmB,GAEzBH,EAAME,QAAQE,GAAG,OAASC,IACxBH,EAAOI,KAAKD,GACZb,EAAIC,GAAOY,EAAKE,UAGlBP,EAAMG,QAAQC,GAAG,OAASC,IACxBF,EAAOG,KAAKD,GACZb,EAAII,MAAMS,EAAKE,UAGjBP,EAAMI,GAAG,QAAUI,IACJ,IAATA,GACFhB,EAAIiB,MAAM,kCACVX,EAAQ,CAAEI,OAAQA,EAAOQ,KAAK,MAAOP,OAAQA,EAAOO,KAAK,UAEzDlB,EAAImB,SACF,4BAA4BH,QAAWL,EAAOO,KAAK,SAErDX,EACE,IAAIa,EACF,4BAA4BJ,MAASL,EAAOO,KAAK,aAMzDV,EAAMI,GAAG,QAAUS,IACjBrB,EAAImB,SAAS,0BAA2BE,GACxCd,EAAOc,MAGb,CC/CO,MAAMC,EAAkB,cAElBC,EAAS,aAETC,EAAe,mBAEfC,EAAuB"}
|
package/lib/cjs/index.cjs
CHANGED
|
@@ -21,9 +21,9 @@ exports.PACKAGE_NAME = exports.FULL_VERSION = exports.COMMIT = exports.VERSION =
|
|
|
21
21
|
* @description Exposes the package utilities and build-time version metadata for `@decaf-ts/integrations`.
|
|
22
22
|
*/
|
|
23
23
|
__exportStar(require("./utils.cjs"), exports);
|
|
24
|
-
exports.VERSION = "0.1
|
|
25
|
-
exports.COMMIT = "
|
|
26
|
-
exports.FULL_VERSION = "0.1
|
|
24
|
+
exports.VERSION = "0.2.1";
|
|
25
|
+
exports.COMMIT = "4990f39";
|
|
26
|
+
exports.FULL_VERSION = "0.2.1-4990f39";
|
|
27
27
|
exports.PACKAGE_NAME = "@decaf-ts/integrations";
|
|
28
28
|
//# sourceMappingURL=index.js.map
|
|
29
29
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -5,15 +5,28 @@ exports.AuthService = void 0;
|
|
|
5
5
|
* @module integrations/nest/authService
|
|
6
6
|
* @summary Nest-compatible authentication service helpers.
|
|
7
7
|
* @description Request-context-aware auth helpers for Nest applications using Decaf integration patterns.
|
|
8
|
+
*
|
|
9
|
+
* Supports two modes:
|
|
10
|
+
* - **Verify mode** (default when `verifyUrl` is set): validates the JWT signature
|
|
11
|
+
* against the Keycloak JWKS endpoint using `jose.jwtVerify`.
|
|
12
|
+
* - **Decode-only mode** (when `verifyToken` is false / `verifyUrl` is empty):
|
|
13
|
+
* decodes the JWT without signature verification (dev/local mode).
|
|
8
14
|
*/
|
|
9
15
|
const core_1 = require("@decaf-ts/core");
|
|
16
|
+
const jose_1 = require("jose");
|
|
10
17
|
const utils_js_1 = require("./utils.cjs");
|
|
11
18
|
class AuthService {
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.verifyToken = !!options.verifyToken && !!options.verifyUrl;
|
|
21
|
+
this.verifyUrl = options.verifyUrl ?? "";
|
|
22
|
+
this.clockToleranceSeconds = options.clockToleranceSeconds ?? 5;
|
|
23
|
+
this.excludedClients = options.excludedClients ?? ["account"];
|
|
24
|
+
}
|
|
12
25
|
getTokenPayload(jwt) {
|
|
13
26
|
return (0, utils_js_1.getTokenPayload)(jwt);
|
|
14
27
|
}
|
|
15
28
|
getRoles(jwt) {
|
|
16
|
-
return
|
|
29
|
+
return this.extractKeycloakRoles(this.getTokenPayload(jwt));
|
|
17
30
|
}
|
|
18
31
|
getUser(jwt) {
|
|
19
32
|
return (0, utils_js_1.getUser)(jwt);
|
|
@@ -21,16 +34,54 @@ class AuthService {
|
|
|
21
34
|
extractKeycloakRoles(payload) {
|
|
22
35
|
return (0, utils_js_1.extractKeycloakRoles)(payload);
|
|
23
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Extracts client roles from the `resource_access` claim, excluding
|
|
39
|
+
* the configured `excludedClients` (default: `["account"]`).
|
|
40
|
+
*/
|
|
41
|
+
extractClientRoles(payload) {
|
|
42
|
+
return (0, utils_js_1.getClientRoles)(payload, this.excludedClients);
|
|
43
|
+
}
|
|
24
44
|
getRealmFromIssuer(jwt) {
|
|
25
45
|
return (0, utils_js_1.getRealmFromIssuer)(jwt);
|
|
26
46
|
}
|
|
27
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Validates a JWT:
|
|
49
|
+
* - In **verify mode**: verifies the signature against the Keycloak JWKS
|
|
50
|
+
* endpoint using `jose.jwtVerify`, then returns the verified payload.
|
|
51
|
+
* - In **decode-only mode**: decodes the JWT without verification and
|
|
52
|
+
* returns the payload (or throws if unparseable).
|
|
53
|
+
*
|
|
54
|
+
* @throws {AuthorizationError} when the token is invalid, expired, or
|
|
55
|
+
* signature verification fails.
|
|
56
|
+
*/
|
|
57
|
+
async assertValidToken(jwt) {
|
|
58
|
+
if (this.verifyToken) {
|
|
59
|
+
return this.verifyAgainstJwks(jwt);
|
|
60
|
+
}
|
|
28
61
|
const payload = this.getTokenPayload(jwt);
|
|
29
62
|
if (!payload) {
|
|
30
63
|
throw new core_1.AuthorizationError("Invalid token");
|
|
31
64
|
}
|
|
32
65
|
return payload;
|
|
33
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Verifies the JWT signature against the Keycloak JWKS endpoint.
|
|
69
|
+
* Lazily creates the JWKS keystore on first call.
|
|
70
|
+
*/
|
|
71
|
+
async verifyAgainstJwks(jwt) {
|
|
72
|
+
if (!this.jwks) {
|
|
73
|
+
this.jwks = (0, jose_1.createRemoteJWKSet)(new URL(this.verifyUrl));
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const { payload } = await (0, jose_1.jwtVerify)(jwt, this.jwks, {
|
|
77
|
+
clockTolerance: `${this.clockToleranceSeconds}s`,
|
|
78
|
+
});
|
|
79
|
+
return payload;
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
throw new core_1.AuthorizationError("Invalid token: " + (e?.message ?? e));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
34
85
|
}
|
|
35
86
|
exports.AuthService = AuthService;
|
|
36
87
|
//# sourceMappingURL=authService.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nest/authService.cjs","sourceRoot":"","sources":["nest/authService.js"],"names":[],"mappings":";;;AAAA
|
|
1
|
+
{"version":3,"file":"nest/authService.cjs","sourceRoot":"","sources":["nest/authService.js"],"names":[],"mappings":";;;AAAA;;;;;;;;;;GAUG;AACH,yCAAoD;AACpD,+BAAqD;AACrD,yCAAiH;AACjH,MAAa,WAAW;IACpB,YAAY,OAAO,GAAG,EAAE;QACpB,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;QAChE,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;QACzC,IAAI,CAAC,qBAAqB,GAAG,OAAO,CAAC,qBAAqB,IAAI,CAAC,CAAC;QAChE,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,IAAI,CAAC,SAAS,CAAC,CAAC;IAClE,CAAC;IACD,eAAe,CAAC,GAAG;QACf,OAAO,IAAA,0BAAe,EAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IACD,QAAQ,CAAC,GAAG;QACR,OAAO,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,CAAC,GAAG;QACP,OAAO,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IACD,oBAAoB,CAAC,OAAO;QACxB,OAAO,IAAA,+BAAoB,EAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IACD;;;OAGG;IACH,kBAAkB,CAAC,OAAO;QACtB,OAAO,IAAA,yBAAc,EAAC,OAAO,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;IACzD,CAAC;IACD,kBAAkB,CAAC,GAAG;QAClB,OAAO,IAAA,6BAAkB,EAAC,GAAG,CAAC,CAAC;IACnC,CAAC;IACD;;;;;;;;;OASG;IACH,KAAK,CAAC,gBAAgB,CAAC,GAAG;QACtB,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,OAAO,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACvC,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,MAAM,IAAI,yBAAkB,CAAC,eAAe,CAAC,CAAC;QAClD,CAAC;QACD,OAAO,OAAO,CAAC;IACnB,CAAC;IACD;;;OAGG;IACH,KAAK,CAAC,iBAAiB,CAAC,GAAG;QACvB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACb,IAAI,CAAC,IAAI,GAAG,IAAA,yBAAkB,EAAC,IAAI,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;QAC5D,CAAC;QACD,IAAI,CAAC;YACD,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,IAAA,gBAAS,EAAC,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE;gBAChD,cAAc,EAAE,GAAG,IAAI,CAAC,qBAAqB,GAAG;aACnD,CAAC,CAAC;YACH,OAAO,OAAO,CAAC;QACnB,CAAC;QACD,OAAO,CAAC,EAAE,CAAC;YACP,MAAM,IAAI,yBAAkB,CAAC,iBAAiB,GAAG,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;QACxE,CAAC;IACL,CAAC;CACJ;AAnED,kCAmEC;AACD,uCAAuC"}
|
package/lib/cjs/nest/index.cjs
CHANGED
|
@@ -19,9 +19,11 @@ __exportStar(require("./utils.cjs"), exports);
|
|
|
19
19
|
__exportStar(require("./authService.cjs"), exports);
|
|
20
20
|
__exportStar(require("./keycloakAuthHandler.cjs"), exports);
|
|
21
21
|
__exportStar(require("./keycloakModule.cjs"), exports);
|
|
22
|
+
require("./logging.cjs");
|
|
22
23
|
/**
|
|
23
24
|
* @summary Nest-style auth integrations.
|
|
24
25
|
* @description Framework-agnostic auth helpers compatible with Decaf request context handling.
|
|
26
|
+
* Imports the `user`/`organization` log parameter registrations as a side effect.
|
|
25
27
|
* @namespace nest
|
|
26
28
|
* @memberOf module:integrations
|
|
27
29
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nest/index.cjs","sourceRoot":"","sources":["nest/index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,6CAA2B;AAC3B,6CAA2B;AAC3B,mDAAiC;AACjC,2DAAyC;AACzC,sDAAoC;AACpC
|
|
1
|
+
{"version":3,"file":"nest/index.cjs","sourceRoot":"","sources":["nest/index.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,6CAA2B;AAC3B,6CAA2B;AAC3B,mDAAiC;AACjC,2DAAyC;AACzC,sDAAoC;AACpC,wBAAsB;AACtB;;;;;;GAMG;AACH,iCAAiC"}
|
|
@@ -1,38 +1,57 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.KeycloakAuthHandler = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* @module integrations/nest/keycloakAuthHandler
|
|
6
|
-
* @summary Keycloak request auth handler.
|
|
7
|
-
* @description Translates Keycloak JWT payloads into Decaf-friendly auth context values.
|
|
8
|
-
*/
|
|
3
|
+
exports.getClientRoles = exports.KeycloakAuthHandler = void 0;
|
|
9
4
|
const core_1 = require("@decaf-ts/core");
|
|
5
|
+
const server_1 = require("@decaf-ts/for-http/server");
|
|
10
6
|
const authService_js_1 = require("./authService.cjs");
|
|
11
7
|
const utils_js_1 = require("./utils.cjs");
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
Object.defineProperty(exports, "getClientRoles", { enumerable: true, get: function () { return utils_js_1.getClientRoles; } });
|
|
9
|
+
class KeycloakAuthHandler extends server_1.AuthHandler {
|
|
10
|
+
/**
|
|
11
|
+
* @param authService An {@link AuthService} configured with JWKS verification
|
|
12
|
+
* options. When omitted a default decode-only service is
|
|
13
|
+
* used.
|
|
14
|
+
* @param authServiceOptions Convenience: if `authService` is omitted, these
|
|
15
|
+
* options are used to construct one.
|
|
16
|
+
*/
|
|
17
|
+
constructor(authService, authServiceOptions) {
|
|
18
|
+
super();
|
|
19
|
+
this.authService =
|
|
20
|
+
authService ?? new authService_js_1.AuthService(authServiceOptions ?? {});
|
|
15
21
|
}
|
|
16
|
-
|
|
17
|
-
const request =
|
|
18
|
-
if (request
|
|
19
|
-
return;
|
|
20
|
-
|
|
21
|
-
return;
|
|
22
|
+
extractFromAuth(ctx) {
|
|
23
|
+
const request = ctx.switchToHttp().getRequest();
|
|
24
|
+
if (isPublicRoute(request)) {
|
|
25
|
+
return { roles: [], token: "", isPublic: true };
|
|
26
|
+
}
|
|
22
27
|
const token = getToken(request);
|
|
23
28
|
if (!token)
|
|
24
29
|
throw new core_1.AuthorizationError("Token not found");
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
// Decode only — validation happens in `validate`
|
|
31
|
+
const payload = (0, utils_js_1.getTokenPayload)(token);
|
|
32
|
+
const roles = (0, utils_js_1.extractKeycloakRoles)(payload);
|
|
33
|
+
const organization = resolveOrganization(payload, token);
|
|
34
|
+
const user = payload?.email ?? payload?.preferred_username;
|
|
35
|
+
return { user, organization, roles, token, isPublic: false };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Validates the JWT against the Keycloak instance (signature, expiry, etc.)
|
|
39
|
+
* via {@link AuthService.assertValidToken}, then delegates to the base class
|
|
40
|
+
* for route-level and model-level role checks. Skips entirely for public routes.
|
|
41
|
+
*/
|
|
42
|
+
async validate(data, routeRoles, model, ...args) {
|
|
43
|
+
if (data.isPublic)
|
|
44
|
+
return;
|
|
45
|
+
await this.authService.assertValidToken(data.token);
|
|
46
|
+
await super.validate(data, routeRoles, model, ...args);
|
|
33
47
|
}
|
|
34
48
|
}
|
|
35
49
|
exports.KeycloakAuthHandler = KeycloakAuthHandler;
|
|
50
|
+
function isPublicRoute(req) {
|
|
51
|
+
if (req.path?.startsWith("/public"))
|
|
52
|
+
return true;
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
36
55
|
function getToken(req) {
|
|
37
56
|
const token = (req.headers?.["x-auth-request-access-token"] ??
|
|
38
57
|
req.headers?.["authorization"]);
|
|
@@ -40,5 +59,12 @@ function getToken(req) {
|
|
|
40
59
|
return undefined;
|
|
41
60
|
return token.startsWith("Bearer ") ? token.slice("Bearer ".length) : token;
|
|
42
61
|
}
|
|
62
|
+
function resolveOrganization(payload, token) {
|
|
63
|
+
if (payload?.aud)
|
|
64
|
+
return payload.aud;
|
|
65
|
+
if (payload?.azp)
|
|
66
|
+
return payload.azp;
|
|
67
|
+
return (0, utils_js_1.getRealmFromIssuer)(token);
|
|
68
|
+
}
|
|
43
69
|
//# sourceMappingURL=keycloakAuthHandler.js.map
|
|
44
70
|
//# sourceMappingURL=keycloakAuthHandler.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nest/keycloakAuthHandler.cjs","sourceRoot":"","sources":["nest/keycloakAuthHandler.js"],"names":[],"mappings":";;;AAAA
|
|
1
|
+
{"version":3,"file":"nest/keycloakAuthHandler.cjs","sourceRoot":"","sources":["nest/keycloakAuthHandler.js"],"names":[],"mappings":";;;AAAA,yCAAoD;AACpD,sDAAwD;AACxD,qDAA+C;AAC/C,yCAAwG;AA4D/F,+FA5D2D,yBAAc,OA4D3D;AA3DvB,MAAa,mBAAoB,SAAQ,oBAAW;IAChD;;;;;;OAMG;IACH,YAAY,WAAW,EAAE,kBAAkB;QACvC,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,WAAW;YACZ,WAAW,IAAI,IAAI,4BAAW,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAC;IACjE,CAAC;IACD,eAAe,CAAC,GAAG;QACf,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QAChD,IAAI,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;YACzB,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QACpD,CAAC;QACD,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;QAChC,IAAI,CAAC,KAAK;YACN,MAAM,IAAI,yBAAkB,CAAC,iBAAiB,CAAC,CAAC;QACpD,iDAAiD;QACjD,MAAM,OAAO,GAAG,IAAA,0BAAe,EAAC,KAAK,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG,IAAA,+BAAoB,EAAC,OAAO,CAAC,CAAC;QAC5C,MAAM,YAAY,GAAG,mBAAmB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACzD,MAAM,IAAI,GAAG,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,kBAAkB,CAAC;QAC3D,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IACjE,CAAC;IACD;;;;OAIG;IACH,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,IAAI;QAC3C,IAAI,IAAI,CAAC,QAAQ;YACb,OAAO;QACX,MAAM,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpD,MAAM,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC;IAC3D,CAAC;CACJ;AAvCD,kDAuCC;AACD,SAAS,aAAa,CAAC,GAAG;IACtB,IAAI,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC;QAC/B,OAAO,IAAI,CAAC;IAChB,OAAO,KAAK,CAAC;AACjB,CAAC;AACD,SAAS,QAAQ,CAAC,GAAG;IACjB,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,6BAA6B,CAAC;QACvD,GAAG,CAAC,OAAO,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IACpC,IAAI,CAAC,KAAK;QACN,OAAO,SAAS,CAAC;IACrB,OAAO,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AAC/E,CAAC;AACD,SAAS,mBAAmB,CAAC,OAAO,EAAE,KAAK;IACvC,IAAI,OAAO,EAAE,GAAG;QACZ,OAAO,OAAO,CAAC,GAAG,CAAC;IACvB,IAAI,OAAO,EAAE,GAAG;QACZ,OAAO,OAAO,CAAC,GAAG,CAAC;IACvB,OAAO,IAAA,6BAAkB,EAAC,KAAK,CAAC,CAAC;AACrC,CAAC;AAED,+CAA+C"}
|
|
@@ -9,12 +9,14 @@ exports.AuthModule = exports.KeycloakModule = void 0;
|
|
|
9
9
|
const authService_js_1 = require("./authService.cjs");
|
|
10
10
|
const keycloakAuthHandler_js_1 = require("./keycloakAuthHandler.cjs");
|
|
11
11
|
class KeycloakModule {
|
|
12
|
-
constructor(authService
|
|
12
|
+
constructor(authService, authHandler) {
|
|
13
13
|
this.authService = authService;
|
|
14
14
|
this.authHandler = authHandler;
|
|
15
15
|
}
|
|
16
|
-
static create() {
|
|
17
|
-
|
|
16
|
+
static create(options) {
|
|
17
|
+
const authService = new authService_js_1.AuthService(options);
|
|
18
|
+
const authHandler = new keycloakAuthHandler_js_1.KeycloakAuthHandler(authService);
|
|
19
|
+
return new KeycloakModule(authService, authHandler);
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
22
|
exports.KeycloakModule = KeycloakModule;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nest/keycloakModule.cjs","sourceRoot":"","sources":["nest/keycloakModule.js"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,qDAA+C;AAC/C,qEAA+D;AAC/D,MAAa,cAAc;IACvB,YAAY,WAAW,
|
|
1
|
+
{"version":3,"file":"nest/keycloakModule.cjs","sourceRoot":"","sources":["nest/keycloakModule.js"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,qDAA+C;AAC/C,qEAA+D;AAC/D,MAAa,cAAc;IACvB,YAAY,WAAW,EAAE,WAAW;QAChC,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACnC,CAAC;IACD,MAAM,CAAC,MAAM,CAAC,OAAO;QACjB,MAAM,WAAW,GAAG,IAAI,4BAAW,CAAC,OAAO,CAAC,CAAC;QAC7C,MAAM,WAAW,GAAG,IAAI,4CAAmB,CAAC,WAAW,CAAC,CAAC;QACzD,OAAO,IAAI,cAAc,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IACxD,CAAC;CACJ;AAVD,wCAUC;AACD,MAAa,UAAW,SAAQ,cAAc;CAC7C;AADD,gCACC;AACD,0CAA0C"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* @module integrations/nest/logging
|
|
5
|
+
* @summary Keycloak auth log parameters.
|
|
6
|
+
* @description Registers `user` and `organization` as log parameters sourced from the
|
|
7
|
+
* logging config (populated by the auth interceptor's child logger via `Logging.for({ user, organization })`).
|
|
8
|
+
*
|
|
9
|
+
* These parameters are registered here — in `integrations/nest` — because this is the
|
|
10
|
+
* layer that extracts user/organization identity from Keycloak JWTs. The `ip` parameter,
|
|
11
|
+
* by contrast, is registered in `@decaf-ts/for-http/server` where the HTTP request IP is
|
|
12
|
+
* available.
|
|
13
|
+
*/
|
|
14
|
+
const logging_1 = require("@decaf-ts/logging");
|
|
15
|
+
logging_1.logParameterRegistry
|
|
16
|
+
.register({
|
|
17
|
+
key: "user",
|
|
18
|
+
shouldInclude(payload) {
|
|
19
|
+
return Boolean(payload.config.user);
|
|
20
|
+
},
|
|
21
|
+
render(payload) {
|
|
22
|
+
return String(payload.config.user);
|
|
23
|
+
},
|
|
24
|
+
style(rendered, payload) {
|
|
25
|
+
return payload.applyTheme(rendered, "app");
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
.register({
|
|
29
|
+
key: "organization",
|
|
30
|
+
shouldInclude(payload) {
|
|
31
|
+
return Boolean(payload.config.organization);
|
|
32
|
+
},
|
|
33
|
+
render(payload) {
|
|
34
|
+
return String(payload.config.organization);
|
|
35
|
+
},
|
|
36
|
+
style(rendered, payload) {
|
|
37
|
+
return payload.applyTheme(rendered, "app");
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=logging.js.map
|
|
41
|
+
//# sourceMappingURL=logging.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nest/logging.cjs","sourceRoot":"","sources":["nest/logging.js"],"names":[],"mappings":";;AAAA;;;;;;;;;;GAUG;AACH,+CAAyD;AACzD,8BAAoB;KACf,QAAQ,CAAC;IACV,GAAG,EAAE,MAAM;IACX,aAAa,CAAC,OAAO;QACjB,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC;IACD,MAAM,CAAC,OAAO;QACV,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;IACD,KAAK,CAAC,QAAQ,EAAE,OAAO;QACnB,OAAO,OAAO,CAAC,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC/C,CAAC;CACJ,CAAC;KACG,QAAQ,CAAC;IACV,GAAG,EAAE,cAAc;IACnB,aAAa,CAAC,OAAO;QACjB,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAChD,CAAC;IACD,MAAM,CAAC,OAAO;QACV,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAC/C,CAAC;IACD,KAAK,CAAC,QAAQ,EAAE,OAAO;QACnB,OAAO,OAAO,CAAC,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC/C,CAAC;CACJ,CAAC,CAAC;AACH,mCAAmC"}
|
package/lib/cjs/nest/utils.cjs
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.DECAF_ADAPTER_OPTIONS = void 0;
|
|
4
4
|
exports.getTokenPayload = getTokenPayload;
|
|
5
5
|
exports.extractKeycloakRoles = extractKeycloakRoles;
|
|
6
|
+
exports.getClientRoles = getClientRoles;
|
|
6
7
|
exports.getUser = getUser;
|
|
7
8
|
exports.getRealmFromIssuer = getRealmFromIssuer;
|
|
8
9
|
const core_1 = require("@decaf-ts/core");
|
|
@@ -30,6 +31,29 @@ function extractKeycloakRoles(payload) {
|
|
|
30
31
|
}
|
|
31
32
|
return [...roles];
|
|
32
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Extracts roles from the `resource_access` claim only, optionally excluding
|
|
36
|
+
* specified client IDs (e.g. `"account"`). Unlike {@link extractKeycloakRoles}
|
|
37
|
+
* this does NOT include `realm_access.roles`.
|
|
38
|
+
*
|
|
39
|
+
* @param payload The decoded JWT payload.
|
|
40
|
+
* @param excluded Client IDs to skip. @default ["account"]
|
|
41
|
+
*/
|
|
42
|
+
function getClientRoles(payload, excluded = ["account"]) {
|
|
43
|
+
const resourceAccess = payload?.resource_access;
|
|
44
|
+
if (!resourceAccess || typeof resourceAccess !== "object")
|
|
45
|
+
return [];
|
|
46
|
+
const roles = new Set();
|
|
47
|
+
for (const [clientId, data] of Object.entries(resourceAccess)) {
|
|
48
|
+
if (excluded.includes(clientId))
|
|
49
|
+
continue;
|
|
50
|
+
if (Array.isArray(data?.roles)) {
|
|
51
|
+
for (const role of data.roles)
|
|
52
|
+
roles.add(role);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return [...roles];
|
|
56
|
+
}
|
|
33
57
|
function getUser(jwt) {
|
|
34
58
|
const payload = getTokenPayload(jwt);
|
|
35
59
|
if (!payload)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nest/utils.cjs","sourceRoot":"","sources":["nest/utils.js"],"names":[],"mappings":";;;AAEA,0CAOC;AACD,oDAcC;AACD,0BAaC;AACD,gDAmBC;
|
|
1
|
+
{"version":3,"file":"nest/utils.cjs","sourceRoot":"","sources":["nest/utils.js"],"names":[],"mappings":"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"}
|
package/lib/cjs/utils.cjs
CHANGED
|
@@ -21,7 +21,7 @@ async function execWithLogging(command, options, log, level = logging_1.LogLevel
|
|
|
21
21
|
});
|
|
22
22
|
child.stderr?.on("data", (data) => {
|
|
23
23
|
stderr.push(data);
|
|
24
|
-
log.
|
|
24
|
+
log.debug(data.trim());
|
|
25
25
|
});
|
|
26
26
|
child.on("close", (code) => {
|
|
27
27
|
if (code === 0) {
|
package/lib/esm/index.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* @description Exposes the package utilities and build-time version metadata for `@decaf-ts/integrations`.
|
|
5
5
|
*/
|
|
6
6
|
export * from "./utils.js";
|
|
7
|
-
export const VERSION = "0.1
|
|
8
|
-
export const COMMIT = "
|
|
9
|
-
export const FULL_VERSION = "0.1
|
|
7
|
+
export const VERSION = "0.2.1";
|
|
8
|
+
export const COMMIT = "4990f39";
|
|
9
|
+
export const FULL_VERSION = "0.2.1-4990f39";
|
|
10
10
|
export const PACKAGE_NAME = "@decaf-ts/integrations";
|
|
11
11
|
//# sourceMappingURL=index.js.map
|
|
@@ -2,15 +2,28 @@
|
|
|
2
2
|
* @module integrations/nest/authService
|
|
3
3
|
* @summary Nest-compatible authentication service helpers.
|
|
4
4
|
* @description Request-context-aware auth helpers for Nest applications using Decaf integration patterns.
|
|
5
|
+
*
|
|
6
|
+
* Supports two modes:
|
|
7
|
+
* - **Verify mode** (default when `verifyUrl` is set): validates the JWT signature
|
|
8
|
+
* against the Keycloak JWKS endpoint using `jose.jwtVerify`.
|
|
9
|
+
* - **Decode-only mode** (when `verifyToken` is false / `verifyUrl` is empty):
|
|
10
|
+
* decodes the JWT without signature verification (dev/local mode).
|
|
5
11
|
*/
|
|
6
12
|
import { AuthorizationError } from "@decaf-ts/core";
|
|
7
|
-
import {
|
|
13
|
+
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
14
|
+
import { extractKeycloakRoles, getRealmFromIssuer, getTokenPayload, getUser, getClientRoles, } from "./utils.js";
|
|
8
15
|
export class AuthService {
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
this.verifyToken = !!options.verifyToken && !!options.verifyUrl;
|
|
18
|
+
this.verifyUrl = options.verifyUrl ?? "";
|
|
19
|
+
this.clockToleranceSeconds = options.clockToleranceSeconds ?? 5;
|
|
20
|
+
this.excludedClients = options.excludedClients ?? ["account"];
|
|
21
|
+
}
|
|
9
22
|
getTokenPayload(jwt) {
|
|
10
23
|
return getTokenPayload(jwt);
|
|
11
24
|
}
|
|
12
25
|
getRoles(jwt) {
|
|
13
|
-
return extractKeycloakRoles(this.getTokenPayload(jwt));
|
|
26
|
+
return this.extractKeycloakRoles(this.getTokenPayload(jwt));
|
|
14
27
|
}
|
|
15
28
|
getUser(jwt) {
|
|
16
29
|
return getUser(jwt);
|
|
@@ -18,15 +31,53 @@ export class AuthService {
|
|
|
18
31
|
extractKeycloakRoles(payload) {
|
|
19
32
|
return extractKeycloakRoles(payload);
|
|
20
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Extracts client roles from the `resource_access` claim, excluding
|
|
36
|
+
* the configured `excludedClients` (default: `["account"]`).
|
|
37
|
+
*/
|
|
38
|
+
extractClientRoles(payload) {
|
|
39
|
+
return getClientRoles(payload, this.excludedClients);
|
|
40
|
+
}
|
|
21
41
|
getRealmFromIssuer(jwt) {
|
|
22
42
|
return getRealmFromIssuer(jwt);
|
|
23
43
|
}
|
|
24
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Validates a JWT:
|
|
46
|
+
* - In **verify mode**: verifies the signature against the Keycloak JWKS
|
|
47
|
+
* endpoint using `jose.jwtVerify`, then returns the verified payload.
|
|
48
|
+
* - In **decode-only mode**: decodes the JWT without verification and
|
|
49
|
+
* returns the payload (or throws if unparseable).
|
|
50
|
+
*
|
|
51
|
+
* @throws {AuthorizationError} when the token is invalid, expired, or
|
|
52
|
+
* signature verification fails.
|
|
53
|
+
*/
|
|
54
|
+
async assertValidToken(jwt) {
|
|
55
|
+
if (this.verifyToken) {
|
|
56
|
+
return this.verifyAgainstJwks(jwt);
|
|
57
|
+
}
|
|
25
58
|
const payload = this.getTokenPayload(jwt);
|
|
26
59
|
if (!payload) {
|
|
27
60
|
throw new AuthorizationError("Invalid token");
|
|
28
61
|
}
|
|
29
62
|
return payload;
|
|
30
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Verifies the JWT signature against the Keycloak JWKS endpoint.
|
|
66
|
+
* Lazily creates the JWKS keystore on first call.
|
|
67
|
+
*/
|
|
68
|
+
async verifyAgainstJwks(jwt) {
|
|
69
|
+
if (!this.jwks) {
|
|
70
|
+
this.jwks = createRemoteJWKSet(new URL(this.verifyUrl));
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const { payload } = await jwtVerify(jwt, this.jwks, {
|
|
74
|
+
clockTolerance: `${this.clockToleranceSeconds}s`,
|
|
75
|
+
});
|
|
76
|
+
return payload;
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
throw new AuthorizationError("Invalid token: " + (e?.message ?? e));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
31
82
|
}
|
|
32
83
|
//# sourceMappingURL=authService.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authService.js","sourceRoot":"","sources":["../../../src/nest/authService.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"authService.js","sourceRoot":"","sources":["../../../src/nest/authService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACrD,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,OAAO,EACP,cAAc,GACf,mBAAgB;AA+BjB,MAAM,OAAO,WAAW;IAOtB,YAAY,UAA8B,EAAE;QAC1C,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;QAChE,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;QACzC,IAAI,CAAC,qBAAqB,GAAG,OAAO,CAAC,qBAAqB,IAAI,CAAC,CAAC;QAChE,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,IAAI,CAAC,SAAS,CAAC,CAAC;IAChE,CAAC;IAED,eAAe,CAAC,GAAW;QACzB,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC;IAC9B,CAAC;IAED,QAAQ,CAAC,GAAW;QAClB,OAAO,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO,CAAC,GAAW;QACjB,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;IACtB,CAAC;IAED,oBAAoB,CAAC,OAA0C;QAC7D,OAAO,oBAAoB,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IAED;;;OAGG;IACH,kBAAkB,CAChB,OAA0C;QAE1C,OAAO,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;IACvD,CAAC;IAED,kBAAkB,CAAC,GAAW;QAC5B,OAAO,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,gBAAgB,CACpB,GAAW;QAEX,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACrC,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,kBAAkB,CAAC,eAAe,CAAC,CAAC;QAChD,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,iBAAiB,CAC7B,GAAW;QAEX,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;QAC1D,CAAC;QACD,IAAI,CAAC;YACH,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE;gBAClD,cAAc,EAAE,GAAG,IAAI,CAAC,qBAAqB,GAAG;aACjD,CAAC,CAAC;YACH,OAAO,OAAgD,CAAC;QAC1D,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,MAAM,IAAI,kBAAkB,CAAC,iBAAiB,GAAG,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;CACF"}
|
package/lib/esm/nest/index.js
CHANGED
|
@@ -3,9 +3,11 @@ export * from "./utils.js";
|
|
|
3
3
|
export * from "./authService.js";
|
|
4
4
|
export * from "./keycloakAuthHandler.js";
|
|
5
5
|
export * from "./keycloakModule.js";
|
|
6
|
+
import "./logging.js";
|
|
6
7
|
/**
|
|
7
8
|
* @summary Nest-style auth integrations.
|
|
8
9
|
* @description Framework-agnostic auth helpers compatible with Decaf request context handling.
|
|
10
|
+
* Imports the `user`/`organization` log parameter registrations as a side effect.
|
|
9
11
|
* @namespace nest
|
|
10
12
|
* @memberOf module:integrations
|
|
11
13
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/nest/index.ts"],"names":[],"mappings":"AAAA,2BAAwB;AACxB,2BAAwB;AACxB,iCAA8B;AAC9B,yCAAsC;AACtC,oCAAiC;AAEjC
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/nest/index.ts"],"names":[],"mappings":"AAAA,2BAAwB;AACxB,2BAAwB;AACxB,iCAA8B;AAC9B,yCAAsC;AACtC,oCAAiC;AAEjC,sBAAmB;AAEnB;;;;;;GAMG"}
|
|
@@ -1,34 +1,52 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module integrations/nest/keycloakAuthHandler
|
|
3
|
-
* @summary Keycloak request auth handler.
|
|
4
|
-
* @description Translates Keycloak JWT payloads into Decaf-friendly auth context values.
|
|
5
|
-
*/
|
|
6
1
|
import { AuthorizationError } from "@decaf-ts/core";
|
|
2
|
+
import { AuthHandler } from "@decaf-ts/for-http/server";
|
|
7
3
|
import { AuthService } from "./authService.js";
|
|
8
|
-
import {
|
|
9
|
-
export class KeycloakAuthHandler {
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
import { getRealmFromIssuer, getTokenPayload, extractKeycloakRoles, getClientRoles, } from "./utils.js";
|
|
5
|
+
export class KeycloakAuthHandler extends AuthHandler {
|
|
6
|
+
/**
|
|
7
|
+
* @param authService An {@link AuthService} configured with JWKS verification
|
|
8
|
+
* options. When omitted a default decode-only service is
|
|
9
|
+
* used.
|
|
10
|
+
* @param authServiceOptions Convenience: if `authService` is omitted, these
|
|
11
|
+
* options are used to construct one.
|
|
12
|
+
*/
|
|
13
|
+
constructor(authService, authServiceOptions) {
|
|
14
|
+
super();
|
|
15
|
+
this.authService =
|
|
16
|
+
authService ?? new AuthService(authServiceOptions ?? {});
|
|
12
17
|
}
|
|
13
|
-
|
|
14
|
-
const request =
|
|
15
|
-
if (request
|
|
16
|
-
return;
|
|
17
|
-
|
|
18
|
-
return;
|
|
18
|
+
extractFromAuth(ctx) {
|
|
19
|
+
const request = ctx.switchToHttp().getRequest();
|
|
20
|
+
if (isPublicRoute(request)) {
|
|
21
|
+
return { roles: [], token: "", isPublic: true };
|
|
22
|
+
}
|
|
19
23
|
const token = getToken(request);
|
|
20
24
|
if (!token)
|
|
21
25
|
throw new AuthorizationError("Token not found");
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
// Decode only — validation happens in `validate`
|
|
27
|
+
const payload = getTokenPayload(token);
|
|
28
|
+
const roles = extractKeycloakRoles(payload);
|
|
29
|
+
const organization = resolveOrganization(payload, token);
|
|
30
|
+
const user = payload?.email ?? payload?.preferred_username;
|
|
31
|
+
return { user, organization, roles, token, isPublic: false };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validates the JWT against the Keycloak instance (signature, expiry, etc.)
|
|
35
|
+
* via {@link AuthService.assertValidToken}, then delegates to the base class
|
|
36
|
+
* for route-level and model-level role checks. Skips entirely for public routes.
|
|
37
|
+
*/
|
|
38
|
+
async validate(data, routeRoles, model, ...args) {
|
|
39
|
+
if (data.isPublic)
|
|
40
|
+
return;
|
|
41
|
+
await this.authService.assertValidToken(data.token);
|
|
42
|
+
await super.validate(data, routeRoles, model, ...args);
|
|
30
43
|
}
|
|
31
44
|
}
|
|
45
|
+
function isPublicRoute(req) {
|
|
46
|
+
if (req.path?.startsWith("/public"))
|
|
47
|
+
return true;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
32
50
|
function getToken(req) {
|
|
33
51
|
const token = (req.headers?.["x-auth-request-access-token"] ??
|
|
34
52
|
req.headers?.["authorization"]);
|
|
@@ -36,4 +54,12 @@ function getToken(req) {
|
|
|
36
54
|
return undefined;
|
|
37
55
|
return token.startsWith("Bearer ") ? token.slice("Bearer ".length) : token;
|
|
38
56
|
}
|
|
57
|
+
function resolveOrganization(payload, token) {
|
|
58
|
+
if (payload?.aud)
|
|
59
|
+
return payload.aud;
|
|
60
|
+
if (payload?.azp)
|
|
61
|
+
return payload.azp;
|
|
62
|
+
return getRealmFromIssuer(token);
|
|
63
|
+
}
|
|
64
|
+
export { getClientRoles };
|
|
39
65
|
//# sourceMappingURL=keycloakAuthHandler.js.map
|