@decaf-ts/integrations 0.2.1 → 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/keycloakAuthHandler.cjs +13 -6
- 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/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/keycloakAuthHandler.js +13 -6
- 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/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/keycloakAuthHandler.d.cts +14 -5
- package/lib/types/nest/keycloakAuthHandler.d.mts +14 -5
- package/lib/types/nest/keycloakModule.d.cts +3 -3
- package/lib/types/nest/keycloakModule.d.mts +3 -3
- package/lib/types/nest/utils.d.cts +9 -0
- package/lib/types/nest/utils.d.mts +9 -0
- package/package.json +11 -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.2.
|
|
25
|
-
exports.COMMIT = "
|
|
26
|
-
exports.FULL_VERSION = "0.2.
|
|
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"}
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.KeycloakAuthHandler = void 0;
|
|
3
|
+
exports.getClientRoles = exports.KeycloakAuthHandler = void 0;
|
|
4
4
|
const core_1 = require("@decaf-ts/core");
|
|
5
5
|
const server_1 = require("@decaf-ts/for-http/server");
|
|
6
6
|
const authService_js_1 = require("./authService.cjs");
|
|
7
7
|
const utils_js_1 = require("./utils.cjs");
|
|
8
|
+
Object.defineProperty(exports, "getClientRoles", { enumerable: true, get: function () { return utils_js_1.getClientRoles; } });
|
|
8
9
|
class KeycloakAuthHandler extends server_1.AuthHandler {
|
|
9
|
-
|
|
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) {
|
|
10
18
|
super();
|
|
11
|
-
this.authService =
|
|
19
|
+
this.authService =
|
|
20
|
+
authService ?? new authService_js_1.AuthService(authServiceOptions ?? {});
|
|
12
21
|
}
|
|
13
22
|
extractFromAuth(ctx) {
|
|
14
23
|
const request = ctx.switchToHttp().getRequest();
|
|
@@ -33,7 +42,7 @@ class KeycloakAuthHandler extends server_1.AuthHandler {
|
|
|
33
42
|
async validate(data, routeRoles, model, ...args) {
|
|
34
43
|
if (data.isPublic)
|
|
35
44
|
return;
|
|
36
|
-
this.authService.assertValidToken(data.token);
|
|
45
|
+
await this.authService.assertValidToken(data.token);
|
|
37
46
|
await super.validate(data, routeRoles, model, ...args);
|
|
38
47
|
}
|
|
39
48
|
}
|
|
@@ -41,8 +50,6 @@ exports.KeycloakAuthHandler = KeycloakAuthHandler;
|
|
|
41
50
|
function isPublicRoute(req) {
|
|
42
51
|
if (req.path?.startsWith("/public"))
|
|
43
52
|
return true;
|
|
44
|
-
if (req.path === "/account" && req.method === "POST")
|
|
45
|
-
return true;
|
|
46
53
|
return false;
|
|
47
54
|
}
|
|
48
55
|
function getToken(req) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nest/keycloakAuthHandler.cjs","sourceRoot":"","sources":["nest/keycloakAuthHandler.js"],"names":[],"mappings":";;;AAAA,yCAAoD;AACpD,sDAAwD;AACxD,qDAA+C;AAC/C,
|
|
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"}
|
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.2.
|
|
8
|
-
export const COMMIT = "
|
|
9
|
-
export const FULL_VERSION = "0.2.
|
|
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"}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { AuthorizationError } from "@decaf-ts/core";
|
|
2
2
|
import { AuthHandler } from "@decaf-ts/for-http/server";
|
|
3
3
|
import { AuthService } from "./authService.js";
|
|
4
|
-
import { getRealmFromIssuer, getTokenPayload, extractKeycloakRoles, } from "./utils.js";
|
|
4
|
+
import { getRealmFromIssuer, getTokenPayload, extractKeycloakRoles, getClientRoles, } from "./utils.js";
|
|
5
5
|
export class KeycloakAuthHandler extends AuthHandler {
|
|
6
|
-
|
|
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) {
|
|
7
14
|
super();
|
|
8
|
-
this.authService =
|
|
15
|
+
this.authService =
|
|
16
|
+
authService ?? new AuthService(authServiceOptions ?? {});
|
|
9
17
|
}
|
|
10
18
|
extractFromAuth(ctx) {
|
|
11
19
|
const request = ctx.switchToHttp().getRequest();
|
|
@@ -30,15 +38,13 @@ export class KeycloakAuthHandler extends AuthHandler {
|
|
|
30
38
|
async validate(data, routeRoles, model, ...args) {
|
|
31
39
|
if (data.isPublic)
|
|
32
40
|
return;
|
|
33
|
-
this.authService.assertValidToken(data.token);
|
|
41
|
+
await this.authService.assertValidToken(data.token);
|
|
34
42
|
await super.validate(data, routeRoles, model, ...args);
|
|
35
43
|
}
|
|
36
44
|
}
|
|
37
45
|
function isPublicRoute(req) {
|
|
38
46
|
if (req.path?.startsWith("/public"))
|
|
39
47
|
return true;
|
|
40
|
-
if (req.path === "/account" && req.method === "POST")
|
|
41
|
-
return true;
|
|
42
48
|
return false;
|
|
43
49
|
}
|
|
44
50
|
function getToken(req) {
|
|
@@ -55,4 +61,5 @@ function resolveOrganization(payload, token) {
|
|
|
55
61
|
return payload.azp;
|
|
56
62
|
return getRealmFromIssuer(token);
|
|
57
63
|
}
|
|
64
|
+
export { getClientRoles };
|
|
58
65
|
//# sourceMappingURL=keycloakAuthHandler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keycloakAuthHandler.js","sourceRoot":"","sources":["../../../src/nest/keycloakAuthHandler.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,kBAAkB,EAA2B,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAY,MAAM,2BAA2B,CAAC;AAElE,OAAO,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"keycloakAuthHandler.js","sourceRoot":"","sources":["../../../src/nest/keycloakAuthHandler.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,kBAAkB,EAA2B,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAY,MAAM,2BAA2B,CAAC;AAElE,OAAO,EAAE,WAAW,EAA2B,yBAAsB;AAErE,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,cAAc,GACf,mBAAgB;AAejB,MAAM,OAAO,mBAAoB,SAAQ,WAIxC;IACC;;;;;;OAMG;IACH,YACE,WAAyB,EACzB,kBAAuC;QAEvC,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,WAAW;YACd,WAAW,IAAI,IAAI,WAAW,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;IAIS,eAAe,CAAC,GAA6B;QACrD,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,UAAU,EAAmB,CAAC;QAEjE,IAAI,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAClD,CAAC;QAED,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;QAChC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,kBAAkB,CAAC,iBAAiB,CAAC,CAAC;QAE5D,iDAAiD;QACjD,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG,oBAAoB,CAAC,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;QAE3D,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC/D,CAAC;IAED;;;;OAIG;IACgB,KAAK,CAAC,QAAQ,CAC/B,IAAsB,EACtB,UAAgC,EAChC,KAA2B,EAC3B,GAAG,IAA6B;QAEhC,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,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;IACzD,CAAC;CACF;AAED,SAAS,aAAa,CAAC,GAAoB;IACzC,IAAI,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IACjD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,QAAQ,CAAC,GAAoB;IACpC,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,6BAA6B,CAAC;QACzD,GAAG,CAAC,OAAO,EAAE,CAAC,eAAe,CAAC,CAAuB,CAAC;IACxD,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,OAAO,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AAC7E,CAAC;AAED,SAAS,mBAAmB,CAC1B,OAA8C,EAC9C,KAAa;IAEb,IAAI,OAAO,EAAE,GAAG;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC;IACrC,IAAI,OAAO,EAAE,GAAG;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC;IACrC,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|
import { AuthService } from "./authService.js";
|
|
7
7
|
import { KeycloakAuthHandler } from "./keycloakAuthHandler.js";
|
|
8
8
|
export class KeycloakModule {
|
|
9
|
-
constructor(authService
|
|
9
|
+
constructor(authService, authHandler) {
|
|
10
10
|
this.authService = authService;
|
|
11
11
|
this.authHandler = authHandler;
|
|
12
12
|
}
|
|
13
|
-
static create() {
|
|
14
|
-
|
|
13
|
+
static create(options) {
|
|
14
|
+
const authService = new AuthService(options);
|
|
15
|
+
const authHandler = new KeycloakAuthHandler(authService);
|
|
16
|
+
return new KeycloakModule(authService, authHandler);
|
|
15
17
|
}
|
|
16
18
|
}
|
|
17
19
|
export class AuthModule extends KeycloakModule {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keycloakModule.js","sourceRoot":"","sources":["../../../src/nest/keycloakModule.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"keycloakModule.js","sourceRoot":"","sources":["../../../src/nest/keycloakModule.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAA2B,yBAAsB;AACrE,OAAO,EAAE,mBAAmB,EAAE,iCAA8B;AAE5D,MAAM,OAAO,cAAc;IACzB,YACkB,WAAwB,EACxB,WAAgC;QADhC,gBAAW,GAAX,WAAW,CAAa;QACxB,gBAAW,GAAX,WAAW,CAAqB;IAC/C,CAAC;IAEJ,MAAM,CAAC,MAAM,CAAC,OAA4B;QACxC,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;QAC7C,MAAM,WAAW,GAAG,IAAI,mBAAmB,CAAC,WAAW,CAAC,CAAC;QACzD,OAAO,IAAI,cAAc,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IACtD,CAAC;CACF;AAED,MAAM,OAAO,UAAW,SAAQ,cAAc;CAAG"}
|
package/lib/esm/nest/utils.js
CHANGED
|
@@ -23,6 +23,29 @@ export function extractKeycloakRoles(payload) {
|
|
|
23
23
|
}
|
|
24
24
|
return [...roles];
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Extracts roles from the `resource_access` claim only, optionally excluding
|
|
28
|
+
* specified client IDs (e.g. `"account"`). Unlike {@link extractKeycloakRoles}
|
|
29
|
+
* this does NOT include `realm_access.roles`.
|
|
30
|
+
*
|
|
31
|
+
* @param payload The decoded JWT payload.
|
|
32
|
+
* @param excluded Client IDs to skip. @default ["account"]
|
|
33
|
+
*/
|
|
34
|
+
export function getClientRoles(payload, excluded = ["account"]) {
|
|
35
|
+
const resourceAccess = payload?.resource_access;
|
|
36
|
+
if (!resourceAccess || typeof resourceAccess !== "object")
|
|
37
|
+
return [];
|
|
38
|
+
const roles = new Set();
|
|
39
|
+
for (const [clientId, data] of Object.entries(resourceAccess)) {
|
|
40
|
+
if (excluded.includes(clientId))
|
|
41
|
+
continue;
|
|
42
|
+
if (Array.isArray(data?.roles)) {
|
|
43
|
+
for (const role of data.roles)
|
|
44
|
+
roles.add(role);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return [...roles];
|
|
48
|
+
}
|
|
26
49
|
export function getUser(jwt) {
|
|
27
50
|
const payload = getTokenPayload(jwt);
|
|
28
51
|
if (!payload)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/nest/utils.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/nest/utils.ts"],"names":[],"mappings":"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"}
|
package/lib/esm/utils.js
CHANGED
|
@@ -18,7 +18,7 @@ export async function execWithLogging(command, options, log, level = LogLevel.in
|
|
|
18
18
|
});
|
|
19
19
|
child.stderr?.on("data", (data) => {
|
|
20
20
|
stderr.push(data);
|
|
21
|
-
log.
|
|
21
|
+
log.debug(data.trim());
|
|
22
22
|
});
|
|
23
23
|
child.on("close", (code) => {
|
|
24
24
|
if (code === 0) {
|
package/lib/types/index.d.cts
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export * from "./utils.d.cts";
|
|
7
7
|
export declare const VERSION: string;
|
|
8
|
-
export declare const COMMIT = "
|
|
9
|
-
export declare const FULL_VERSION = "0.2.
|
|
8
|
+
export declare const COMMIT = "4990f39";
|
|
9
|
+
export declare const FULL_VERSION = "0.2.1-4990f39";
|
|
10
10
|
export declare const PACKAGE_NAME: string;
|
package/lib/types/index.d.mts
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export * from "./utils.d.mts";
|
|
7
7
|
export declare const VERSION: string;
|
|
8
|
-
export declare const COMMIT = "
|
|
9
|
-
export declare const FULL_VERSION = "0.2.
|
|
8
|
+
export declare const COMMIT = "4990f39";
|
|
9
|
+
export declare const FULL_VERSION = "0.2.1-4990f39";
|
|
10
10
|
export declare const PACKAGE_NAME: string;
|
|
@@ -1,9 +1,62 @@
|
|
|
1
1
|
import type { KeycloakAccessTokenPayload, KeycloakUser } from "./types.d.cts";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for the {@link AuthService}.
|
|
4
|
+
*/
|
|
5
|
+
export interface AuthServiceOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Whether to verify the JWT signature against the Keycloak JWKS endpoint.
|
|
8
|
+
* When `false` (or `verifyUrl` is empty), tokens are decoded without
|
|
9
|
+
* signature verification (dev/local mode only).
|
|
10
|
+
* @default false
|
|
11
|
+
*/
|
|
12
|
+
verifyToken?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The Keycloak JWKS URL (e.g. `https://keycloak/realms/myrealm/protocol/openid-connect/certs`).
|
|
15
|
+
* Required when `verifyToken` is `true`.
|
|
16
|
+
*/
|
|
17
|
+
verifyUrl?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Clock tolerance for JWT expiry verification (in seconds).
|
|
20
|
+
* @default 5
|
|
21
|
+
*/
|
|
22
|
+
clockToleranceSeconds?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Keycloak client IDs to exclude when extracting roles from `resource_access`.
|
|
25
|
+
* @default ["account"]
|
|
26
|
+
*/
|
|
27
|
+
excludedClients?: string[];
|
|
28
|
+
}
|
|
2
29
|
export declare class AuthService {
|
|
30
|
+
private readonly verifyToken;
|
|
31
|
+
private readonly verifyUrl;
|
|
32
|
+
private readonly clockToleranceSeconds;
|
|
33
|
+
private readonly excludedClients;
|
|
34
|
+
private jwks;
|
|
35
|
+
constructor(options?: AuthServiceOptions);
|
|
3
36
|
getTokenPayload(jwt: string): KeycloakAccessTokenPayload | null;
|
|
4
37
|
getRoles(jwt: string): string[];
|
|
5
38
|
getUser(jwt: string): KeycloakUser | undefined;
|
|
6
|
-
extractKeycloakRoles(payload:
|
|
39
|
+
extractKeycloakRoles(payload: KeycloakAccessTokenPayload | null): string[];
|
|
40
|
+
/**
|
|
41
|
+
* Extracts client roles from the `resource_access` claim, excluding
|
|
42
|
+
* the configured `excludedClients` (default: `["account"]`).
|
|
43
|
+
*/
|
|
44
|
+
extractClientRoles(payload: KeycloakAccessTokenPayload | null): string[];
|
|
7
45
|
getRealmFromIssuer(jwt: string): string;
|
|
8
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Validates a JWT:
|
|
48
|
+
* - In **verify mode**: verifies the signature against the Keycloak JWKS
|
|
49
|
+
* endpoint using `jose.jwtVerify`, then returns the verified payload.
|
|
50
|
+
* - In **decode-only mode**: decodes the JWT without verification and
|
|
51
|
+
* returns the payload (or throws if unparseable).
|
|
52
|
+
*
|
|
53
|
+
* @throws {AuthorizationError} when the token is invalid, expired, or
|
|
54
|
+
* signature verification fails.
|
|
55
|
+
*/
|
|
56
|
+
assertValidToken(jwt: string): Promise<KeycloakAccessTokenPayload>;
|
|
57
|
+
/**
|
|
58
|
+
* Verifies the JWT signature against the Keycloak JWKS endpoint.
|
|
59
|
+
* Lazily creates the JWKS keystore on first call.
|
|
60
|
+
*/
|
|
61
|
+
private verifyAgainstJwks;
|
|
9
62
|
}
|
|
@@ -1,9 +1,62 @@
|
|
|
1
1
|
import type { KeycloakAccessTokenPayload, KeycloakUser } from "./types.d.mts";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for the {@link AuthService}.
|
|
4
|
+
*/
|
|
5
|
+
export interface AuthServiceOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Whether to verify the JWT signature against the Keycloak JWKS endpoint.
|
|
8
|
+
* When `false` (or `verifyUrl` is empty), tokens are decoded without
|
|
9
|
+
* signature verification (dev/local mode only).
|
|
10
|
+
* @default false
|
|
11
|
+
*/
|
|
12
|
+
verifyToken?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The Keycloak JWKS URL (e.g. `https://keycloak/realms/myrealm/protocol/openid-connect/certs`).
|
|
15
|
+
* Required when `verifyToken` is `true`.
|
|
16
|
+
*/
|
|
17
|
+
verifyUrl?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Clock tolerance for JWT expiry verification (in seconds).
|
|
20
|
+
* @default 5
|
|
21
|
+
*/
|
|
22
|
+
clockToleranceSeconds?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Keycloak client IDs to exclude when extracting roles from `resource_access`.
|
|
25
|
+
* @default ["account"]
|
|
26
|
+
*/
|
|
27
|
+
excludedClients?: string[];
|
|
28
|
+
}
|
|
2
29
|
export declare class AuthService {
|
|
30
|
+
private readonly verifyToken;
|
|
31
|
+
private readonly verifyUrl;
|
|
32
|
+
private readonly clockToleranceSeconds;
|
|
33
|
+
private readonly excludedClients;
|
|
34
|
+
private jwks;
|
|
35
|
+
constructor(options?: AuthServiceOptions);
|
|
3
36
|
getTokenPayload(jwt: string): KeycloakAccessTokenPayload | null;
|
|
4
37
|
getRoles(jwt: string): string[];
|
|
5
38
|
getUser(jwt: string): KeycloakUser | undefined;
|
|
6
|
-
extractKeycloakRoles(payload:
|
|
39
|
+
extractKeycloakRoles(payload: KeycloakAccessTokenPayload | null): string[];
|
|
40
|
+
/**
|
|
41
|
+
* Extracts client roles from the `resource_access` claim, excluding
|
|
42
|
+
* the configured `excludedClients` (default: `["account"]`).
|
|
43
|
+
*/
|
|
44
|
+
extractClientRoles(payload: KeycloakAccessTokenPayload | null): string[];
|
|
7
45
|
getRealmFromIssuer(jwt: string): string;
|
|
8
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Validates a JWT:
|
|
48
|
+
* - In **verify mode**: verifies the signature against the Keycloak JWKS
|
|
49
|
+
* endpoint using `jose.jwtVerify`, then returns the verified payload.
|
|
50
|
+
* - In **decode-only mode**: decodes the JWT without verification and
|
|
51
|
+
* returns the payload (or throws if unparseable).
|
|
52
|
+
*
|
|
53
|
+
* @throws {AuthorizationError} when the token is invalid, expired, or
|
|
54
|
+
* signature verification fails.
|
|
55
|
+
*/
|
|
56
|
+
assertValidToken(jwt: string): Promise<KeycloakAccessTokenPayload>;
|
|
57
|
+
/**
|
|
58
|
+
* Verifies the JWT signature against the Keycloak JWKS endpoint.
|
|
59
|
+
* Lazily creates the JWKS keystore on first call.
|
|
60
|
+
*/
|
|
61
|
+
private verifyAgainstJwks;
|
|
9
62
|
}
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* Extends the framework-agnostic {@link AuthHandler} from `@decaf-ts/for-http/server`.
|
|
6
6
|
*
|
|
7
7
|
* Only overrides the two extension points:
|
|
8
|
-
* - {@link extractFromAuth} — decodes the JWT and returns auth data (no validation).
|
|
8
|
+
* - {@link KeycloakAuthHandler.extractFromAuth} — decodes the JWT and returns auth data (no validation).
|
|
9
9
|
* Returns empty data for public routes without requiring a token.
|
|
10
|
-
* - {@link validate} — validates the JWT via {@link AuthService.assertValidToken},
|
|
10
|
+
* - {@link KeycloakAuthHandler.validate} — validates the JWT via {@link AuthService.assertValidToken},
|
|
11
11
|
* then delegates to the base class for route-level and model-level role checks.
|
|
12
12
|
* Skips entirely for public routes.
|
|
13
13
|
*
|
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
import type { Constructor } from "@decaf-ts/decoration";
|
|
18
18
|
import { Context, ContextualArgs } from "@decaf-ts/core";
|
|
19
19
|
import { AuthHandler, AuthData } from "@decaf-ts/for-http/server";
|
|
20
|
-
import { AuthService } from "./authService.d.cts";
|
|
20
|
+
import { AuthService, type AuthServiceOptions } from "./authService.d.cts";
|
|
21
21
|
import type { AuthExecutionContextLike } from "./types.d.cts";
|
|
22
|
+
import { getClientRoles } from "./utils.d.cts";
|
|
22
23
|
/**
|
|
23
24
|
* Auth data returned by {@link KeycloakAuthHandler.extractFromAuth}.
|
|
24
25
|
*
|
|
@@ -32,8 +33,15 @@ export interface KeycloakAuthData extends AuthData {
|
|
|
32
33
|
isPublic: boolean;
|
|
33
34
|
}
|
|
34
35
|
export declare class KeycloakAuthHandler extends AuthHandler<AuthExecutionContextLike, Context, KeycloakAuthData> {
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
/**
|
|
37
|
+
* @param authService An {@link AuthService} configured with JWKS verification
|
|
38
|
+
* options. When omitted a default decode-only service is
|
|
39
|
+
* used.
|
|
40
|
+
* @param authServiceOptions Convenience: if `authService` is omitted, these
|
|
41
|
+
* options are used to construct one.
|
|
42
|
+
*/
|
|
43
|
+
constructor(authService?: AuthService, authServiceOptions?: AuthServiceOptions);
|
|
44
|
+
protected readonly authService: AuthService;
|
|
37
45
|
protected extractFromAuth(ctx: AuthExecutionContextLike): KeycloakAuthData;
|
|
38
46
|
/**
|
|
39
47
|
* Validates the JWT against the Keycloak instance (signature, expiry, etc.)
|
|
@@ -42,3 +50,4 @@ export declare class KeycloakAuthHandler extends AuthHandler<AuthExecutionContex
|
|
|
42
50
|
*/
|
|
43
51
|
protected validate(data: KeycloakAuthData, routeRoles: string[] | undefined, model: string | Constructor, ...args: ContextualArgs<Context>): Promise<void>;
|
|
44
52
|
}
|
|
53
|
+
export { getClientRoles };
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* Extends the framework-agnostic {@link AuthHandler} from `@decaf-ts/for-http/server`.
|
|
6
6
|
*
|
|
7
7
|
* Only overrides the two extension points:
|
|
8
|
-
* - {@link extractFromAuth} — decodes the JWT and returns auth data (no validation).
|
|
8
|
+
* - {@link KeycloakAuthHandler.extractFromAuth} — decodes the JWT and returns auth data (no validation).
|
|
9
9
|
* Returns empty data for public routes without requiring a token.
|
|
10
|
-
* - {@link validate} — validates the JWT via {@link AuthService.assertValidToken},
|
|
10
|
+
* - {@link KeycloakAuthHandler.validate} — validates the JWT via {@link AuthService.assertValidToken},
|
|
11
11
|
* then delegates to the base class for route-level and model-level role checks.
|
|
12
12
|
* Skips entirely for public routes.
|
|
13
13
|
*
|
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
import type { Constructor } from "@decaf-ts/decoration";
|
|
18
18
|
import { Context, ContextualArgs } from "@decaf-ts/core";
|
|
19
19
|
import { AuthHandler, AuthData } from "@decaf-ts/for-http/server";
|
|
20
|
-
import { AuthService } from "./authService.d.mts";
|
|
20
|
+
import { AuthService, type AuthServiceOptions } from "./authService.d.mts";
|
|
21
21
|
import type { AuthExecutionContextLike } from "./types.d.mts";
|
|
22
|
+
import { getClientRoles } from "./utils.d.mts";
|
|
22
23
|
/**
|
|
23
24
|
* Auth data returned by {@link KeycloakAuthHandler.extractFromAuth}.
|
|
24
25
|
*
|
|
@@ -32,8 +33,15 @@ export interface KeycloakAuthData extends AuthData {
|
|
|
32
33
|
isPublic: boolean;
|
|
33
34
|
}
|
|
34
35
|
export declare class KeycloakAuthHandler extends AuthHandler<AuthExecutionContextLike, Context, KeycloakAuthData> {
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
/**
|
|
37
|
+
* @param authService An {@link AuthService} configured with JWKS verification
|
|
38
|
+
* options. When omitted a default decode-only service is
|
|
39
|
+
* used.
|
|
40
|
+
* @param authServiceOptions Convenience: if `authService` is omitted, these
|
|
41
|
+
* options are used to construct one.
|
|
42
|
+
*/
|
|
43
|
+
constructor(authService?: AuthService, authServiceOptions?: AuthServiceOptions);
|
|
44
|
+
protected readonly authService: AuthService;
|
|
37
45
|
protected extractFromAuth(ctx: AuthExecutionContextLike): KeycloakAuthData;
|
|
38
46
|
/**
|
|
39
47
|
* Validates the JWT against the Keycloak instance (signature, expiry, etc.)
|
|
@@ -42,3 +50,4 @@ export declare class KeycloakAuthHandler extends AuthHandler<AuthExecutionContex
|
|
|
42
50
|
*/
|
|
43
51
|
protected validate(data: KeycloakAuthData, routeRoles: string[] | undefined, model: string | Constructor, ...args: ContextualArgs<Context>): Promise<void>;
|
|
44
52
|
}
|
|
53
|
+
export { getClientRoles };
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* @summary Nest module wiring for Keycloak auth.
|
|
4
4
|
* @description Exposes the Nest module setup for the integration auth service and Keycloak handler.
|
|
5
5
|
*/
|
|
6
|
-
import { AuthService } from "./authService.d.cts";
|
|
6
|
+
import { AuthService, type AuthServiceOptions } from "./authService.d.cts";
|
|
7
7
|
import { KeycloakAuthHandler } from "./keycloakAuthHandler.d.cts";
|
|
8
8
|
export declare class KeycloakModule {
|
|
9
9
|
readonly authService: AuthService;
|
|
10
10
|
readonly authHandler: KeycloakAuthHandler;
|
|
11
|
-
constructor(authService
|
|
12
|
-
static create(): KeycloakModule;
|
|
11
|
+
constructor(authService: AuthService, authHandler: KeycloakAuthHandler);
|
|
12
|
+
static create(options?: AuthServiceOptions): KeycloakModule;
|
|
13
13
|
}
|
|
14
14
|
export declare class AuthModule extends KeycloakModule {
|
|
15
15
|
}
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* @summary Nest module wiring for Keycloak auth.
|
|
4
4
|
* @description Exposes the Nest module setup for the integration auth service and Keycloak handler.
|
|
5
5
|
*/
|
|
6
|
-
import { AuthService } from "./authService.d.mts";
|
|
6
|
+
import { AuthService, type AuthServiceOptions } from "./authService.d.mts";
|
|
7
7
|
import { KeycloakAuthHandler } from "./keycloakAuthHandler.d.mts";
|
|
8
8
|
export declare class KeycloakModule {
|
|
9
9
|
readonly authService: AuthService;
|
|
10
10
|
readonly authHandler: KeycloakAuthHandler;
|
|
11
|
-
constructor(authService
|
|
12
|
-
static create(): KeycloakModule;
|
|
11
|
+
constructor(authService: AuthService, authHandler: KeycloakAuthHandler);
|
|
12
|
+
static create(options?: AuthServiceOptions): KeycloakModule;
|
|
13
13
|
}
|
|
14
14
|
export declare class AuthModule extends KeycloakModule {
|
|
15
15
|
}
|
|
@@ -7,5 +7,14 @@ import type { KeycloakAccessTokenPayload, KeycloakUser } from "./types.d.cts";
|
|
|
7
7
|
export declare const DECAF_ADAPTER_OPTIONS: unique symbol;
|
|
8
8
|
export declare function getTokenPayload(jwt: string): KeycloakAccessTokenPayload | null;
|
|
9
9
|
export declare function extractKeycloakRoles(payload: KeycloakAccessTokenPayload | null): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Extracts roles from the `resource_access` claim only, optionally excluding
|
|
12
|
+
* specified client IDs (e.g. `"account"`). Unlike {@link extractKeycloakRoles}
|
|
13
|
+
* this does NOT include `realm_access.roles`.
|
|
14
|
+
*
|
|
15
|
+
* @param payload The decoded JWT payload.
|
|
16
|
+
* @param excluded Client IDs to skip. @default ["account"]
|
|
17
|
+
*/
|
|
18
|
+
export declare function getClientRoles(payload: KeycloakAccessTokenPayload | null, excluded?: string[]): string[];
|
|
10
19
|
export declare function getUser(jwt: string): KeycloakUser | undefined;
|
|
11
20
|
export declare function getRealmFromIssuer(jwt: string): string;
|
|
@@ -7,5 +7,14 @@ import type { KeycloakAccessTokenPayload, KeycloakUser } from "./types.d.mts";
|
|
|
7
7
|
export declare const DECAF_ADAPTER_OPTIONS: unique symbol;
|
|
8
8
|
export declare function getTokenPayload(jwt: string): KeycloakAccessTokenPayload | null;
|
|
9
9
|
export declare function extractKeycloakRoles(payload: KeycloakAccessTokenPayload | null): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Extracts roles from the `resource_access` claim only, optionally excluding
|
|
12
|
+
* specified client IDs (e.g. `"account"`). Unlike {@link extractKeycloakRoles}
|
|
13
|
+
* this does NOT include `realm_access.roles`.
|
|
14
|
+
*
|
|
15
|
+
* @param payload The decoded JWT payload.
|
|
16
|
+
* @param excluded Client IDs to skip. @default ["account"]
|
|
17
|
+
*/
|
|
18
|
+
export declare function getClientRoles(payload: KeycloakAccessTokenPayload | null, excluded?: string[]): string[];
|
|
10
19
|
export declare function getUser(jwt: string): KeycloakUser | undefined;
|
|
11
20
|
export declare function getRealmFromIssuer(jwt: string): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decaf-ts/integrations",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.mts",
|
|
@@ -255,7 +255,7 @@
|
|
|
255
255
|
},
|
|
256
256
|
"devDependencies": {
|
|
257
257
|
"@decaf-ts/for-http": "latest",
|
|
258
|
-
"@decaf-ts/for-nest": "^0.11.2-refactor",
|
|
258
|
+
"@decaf-ts/for-nest": "^0.11.2-refactor.2",
|
|
259
259
|
"@decaf-ts/utils": "latest",
|
|
260
260
|
"@nestjs/common": "^11.0.0",
|
|
261
261
|
"@nestjs/core": "^11.0.0",
|
|
@@ -269,11 +269,19 @@
|
|
|
269
269
|
"supertest": "^7.0.0"
|
|
270
270
|
},
|
|
271
271
|
"peerDependencies": {
|
|
272
|
-
"@decaf-ts/for-http": "
|
|
272
|
+
"@decaf-ts/for-http": "latest",
|
|
273
|
+
"@decaf-ts/for-nest": "0.11.2.refactor.2",
|
|
274
|
+
"jose": ">=5.0.0"
|
|
273
275
|
},
|
|
274
276
|
"peerDependenciesMeta": {
|
|
275
277
|
"@decaf-ts/for-http": {
|
|
276
278
|
"optional": true
|
|
279
|
+
},
|
|
280
|
+
"@decaf-ts/for-nest": {
|
|
281
|
+
"optional": true
|
|
282
|
+
},
|
|
283
|
+
"jose": {
|
|
284
|
+
"optional": true
|
|
277
285
|
}
|
|
278
286
|
},
|
|
279
287
|
"overrides": {
|