@adatechnology/auth-keycloak 0.0.3 → 0.0.6

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/index.js CHANGED
@@ -1,16 +1,743 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.KeycloakError = exports.RolesGuard = exports.Roles = exports.KEYCLOAK_HTTP_INTERCEPTOR = exports.KEYCLOAK_CLIENT = exports.KEYCLOAK_CONFIG = exports.KeycloakModule = void 0;
4
- var keycloak_module_1 = require("./keycloak.module");
5
- Object.defineProperty(exports, "KeycloakModule", { enumerable: true, get: function () { return keycloak_module_1.KeycloakModule; } });
6
- var keycloak_token_1 = require("./keycloak.token");
7
- Object.defineProperty(exports, "KEYCLOAK_CONFIG", { enumerable: true, get: function () { return keycloak_token_1.KEYCLOAK_CONFIG; } });
8
- Object.defineProperty(exports, "KEYCLOAK_CLIENT", { enumerable: true, get: function () { return keycloak_token_1.KEYCLOAK_CLIENT; } });
9
- Object.defineProperty(exports, "KEYCLOAK_HTTP_INTERCEPTOR", { enumerable: true, get: function () { return keycloak_token_1.KEYCLOAK_HTTP_INTERCEPTOR; } });
10
- var roles_decorator_1 = require("./roles.decorator");
11
- Object.defineProperty(exports, "Roles", { enumerable: true, get: function () { return roles_decorator_1.Roles; } });
12
- var roles_guard_1 = require("./roles.guard");
13
- Object.defineProperty(exports, "RolesGuard", { enumerable: true, get: function () { return roles_guard_1.RolesGuard; } });
14
- var keycloak_error_1 = require("./errors/keycloak-error");
15
- Object.defineProperty(exports, "KeycloakError", { enumerable: true, get: function () { return keycloak_error_1.KeycloakError; } });
16
- //# sourceMappingURL=index.js.map
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __commonJS = (cb, mod) => function __require() {
8
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
+ };
10
+ var __export = (target, all) => {
11
+ for (var name in all)
12
+ __defProp(target, name, { get: all[name], enumerable: true });
13
+ };
14
+ var __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from === "object" || typeof from === "function") {
16
+ for (let key of __getOwnPropNames(from))
17
+ if (!__hasOwnProp.call(to, key) && key !== except)
18
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
23
+ // If the importer is in node compatibility mode or this is not an ESM
24
+ // file that has been converted to a CommonJS file using a Babel-
25
+ // compatible transform (i.e. "__esModule" has not been set), then set
26
+ // "default" to the CommonJS "module.exports" for node compatibility.
27
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
28
+ mod
29
+ ));
30
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
31
+ var __decorateClass = (decorators, target, key, kind) => {
32
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
33
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
34
+ if (decorator = decorators[i])
35
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
36
+ if (kind && result) __defProp(target, key, result);
37
+ return result;
38
+ };
39
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
40
+
41
+ // ../shared/dist/types.js
42
+ var require_types = __commonJS({
43
+ "../shared/dist/types.js"(exports2) {
44
+ "use strict";
45
+ Object.defineProperty(exports2, "__esModule", { value: true });
46
+ }
47
+ });
48
+
49
+ // ../shared/dist/utils.js
50
+ var require_utils = __commonJS({
51
+ "../shared/dist/utils.js"(exports2) {
52
+ "use strict";
53
+ Object.defineProperty(exports2, "__esModule", { value: true });
54
+ exports2.noop = noop;
55
+ exports2.prefixWith = prefixWith;
56
+ function noop() {
57
+ return void 0;
58
+ }
59
+ function prefixWith(prefix, value) {
60
+ return `${prefix}-${value}`;
61
+ }
62
+ }
63
+ });
64
+
65
+ // ../shared/dist/errors/base-app-error.js
66
+ var require_base_app_error = __commonJS({
67
+ "../shared/dist/errors/base-app-error.js"(exports2) {
68
+ "use strict";
69
+ Object.defineProperty(exports2, "__esModule", { value: true });
70
+ exports2.BaseAppError = void 0;
71
+ var BaseAppError2 = class extends Error {
72
+ code;
73
+ status;
74
+ context;
75
+ constructor(params) {
76
+ var _a;
77
+ super(params.message);
78
+ this.name = new.target.name;
79
+ this.status = params.status;
80
+ this.code = params.code;
81
+ this.context = params.context;
82
+ const capturable = Error;
83
+ (_a = capturable.captureStackTrace) == null ? void 0 : _a.call(capturable, this, this.constructor);
84
+ }
85
+ };
86
+ exports2.BaseAppError = BaseAppError2;
87
+ }
88
+ });
89
+
90
+ // ../shared/dist/errors/errors.constants.js
91
+ var require_errors_constants = __commonJS({
92
+ "../shared/dist/errors/errors.constants.js"(exports2) {
93
+ "use strict";
94
+ Object.defineProperty(exports2, "__esModule", { value: true });
95
+ exports2.SHARED_INTERNAL_FRAME_RE = exports2.SHARED_ERROR_MESSAGES = exports2.SHARED_ERRORS = void 0;
96
+ exports2.SHARED_ERRORS = {
97
+ DEFAULT_STATUS: 502,
98
+ INTERNAL_STATUS: 500
99
+ };
100
+ exports2.SHARED_ERROR_MESSAGES = {
101
+ UPSTREAM_ERROR: "Upstream error",
102
+ NO_RESPONSE: "No response from upstream service",
103
+ UNEXPECTED_ERROR: "Unexpected error",
104
+ MAPPING_FAILURE: "Error mapping failure"
105
+ };
106
+ exports2.SHARED_INTERNAL_FRAME_RE = /node_modules|internal|\(internal|axios|packages\/http|@adatechnology\/http-client/;
107
+ }
108
+ });
109
+
110
+ // ../shared/dist/errors/error-mapper.service.js
111
+ var require_error_mapper_service = __commonJS({
112
+ "../shared/dist/errors/error-mapper.service.js"(exports2) {
113
+ "use strict";
114
+ var __decorate = exports2 && exports2.__decorate || function(decorators, target, key, desc) {
115
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
116
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
117
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
118
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
119
+ };
120
+ Object.defineProperty(exports2, "__esModule", { value: true });
121
+ exports2.ErrorMapperService = void 0;
122
+ var common_1 = require("@nestjs/common");
123
+ var base_app_error_1 = require_base_app_error();
124
+ var errors_constants_1 = require_errors_constants();
125
+ var ErrorMapperService = class ErrorMapperService {
126
+ /**
127
+ * Map an upstream/internal error to a BaseAppError with normalized fields.
128
+ * Keeps a small context to help tracing origin without leaking secrets.
129
+ */
130
+ mapUpstreamError(err) {
131
+ if (err instanceof base_app_error_1.BaseAppError)
132
+ return err;
133
+ try {
134
+ const obj = err ?? void 0;
135
+ const context = {};
136
+ if (obj && typeof obj.stack === "string") {
137
+ const frames = this.parseStack(obj.stack);
138
+ if (frames.length) {
139
+ context.stack = frames;
140
+ const origin = frames.find((f) => !this.isInternalFrame(f.file));
141
+ if (origin)
142
+ context.origin = origin;
143
+ }
144
+ }
145
+ if (obj && typeof obj.config === "object" && obj.config !== null) {
146
+ const cfg = obj.config;
147
+ context.url = typeof cfg.url === "string" ? cfg.url : typeof cfg.baseURL === "string" ? cfg.baseURL : void 0;
148
+ context.method = typeof cfg.method === "string" ? cfg.method : void 0;
149
+ }
150
+ if (obj && typeof obj.response === "object" && obj.response !== null) {
151
+ const resp = obj.response;
152
+ const status = typeof resp.status === "number" ? resp.status : errors_constants_1.SHARED_ERRORS.DEFAULT_STATUS;
153
+ const data = resp.data;
154
+ const message = data && typeof data.message === "string" ? data.message : typeof obj.message === "string" ? obj.message : errors_constants_1.SHARED_ERROR_MESSAGES.UPSTREAM_ERROR;
155
+ const code = typeof obj.code === "string" ? obj.code : void 0;
156
+ return new base_app_error_1.BaseAppError({
157
+ message,
158
+ status,
159
+ code,
160
+ context
161
+ });
162
+ }
163
+ if (obj && typeof obj.request === "object") {
164
+ const code = typeof obj.code === "string" ? obj.code : void 0;
165
+ return new base_app_error_1.BaseAppError({
166
+ message: errors_constants_1.SHARED_ERROR_MESSAGES.NO_RESPONSE,
167
+ status: errors_constants_1.SHARED_ERRORS.DEFAULT_STATUS,
168
+ code,
169
+ context
170
+ });
171
+ }
172
+ return new base_app_error_1.BaseAppError({
173
+ message: obj && typeof obj.message === "string" ? obj.message : errors_constants_1.SHARED_ERROR_MESSAGES.UNEXPECTED_ERROR,
174
+ status: errors_constants_1.SHARED_ERRORS.INTERNAL_STATUS,
175
+ code: typeof (obj == null ? void 0 : obj.code) === "string" ? obj.code : void 0,
176
+ context
177
+ });
178
+ } catch (e) {
179
+ return new base_app_error_1.BaseAppError({
180
+ message: errors_constants_1.SHARED_ERROR_MESSAGES.MAPPING_FAILURE,
181
+ status: errors_constants_1.SHARED_ERRORS.INTERNAL_STATUS,
182
+ context: { original: String(err) }
183
+ });
184
+ }
185
+ }
186
+ parseStack(stack) {
187
+ const lines = stack.split("\n").map((l) => l.trim()).filter(Boolean);
188
+ const frames = [];
189
+ const re = /^at\s+(?:(.*?)\s+\()?(.*?):(\d+):(\d+)\)?$/;
190
+ for (const line of lines) {
191
+ const m = re.exec(line);
192
+ if (m) {
193
+ const fn = m[1] || void 0;
194
+ const file = m[2];
195
+ const lineNum = parseInt(m[3], 10);
196
+ const colNum = parseInt(m[4], 10);
197
+ frames.push({ fn, file, line: lineNum, column: colNum });
198
+ }
199
+ }
200
+ return frames;
201
+ }
202
+ isInternalFrame(file) {
203
+ if (!file)
204
+ return false;
205
+ return errors_constants_1.SHARED_INTERNAL_FRAME_RE.test(file);
206
+ }
207
+ };
208
+ exports2.ErrorMapperService = ErrorMapperService;
209
+ exports2.ErrorMapperService = ErrorMapperService = __decorate([
210
+ (0, common_1.Injectable)()
211
+ ], ErrorMapperService);
212
+ }
213
+ });
214
+
215
+ // ../shared/dist/errors/index.js
216
+ var require_errors = __commonJS({
217
+ "../shared/dist/errors/index.js"(exports2) {
218
+ "use strict";
219
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
220
+ if (k2 === void 0) k2 = k;
221
+ var desc = Object.getOwnPropertyDescriptor(m, k);
222
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
223
+ desc = { enumerable: true, get: function() {
224
+ return m[k];
225
+ } };
226
+ }
227
+ Object.defineProperty(o, k2, desc);
228
+ }) : (function(o, m, k, k2) {
229
+ if (k2 === void 0) k2 = k;
230
+ o[k2] = m[k];
231
+ }));
232
+ var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
233
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
234
+ };
235
+ Object.defineProperty(exports2, "__esModule", { value: true });
236
+ __exportStar(require_base_app_error(), exports2);
237
+ __exportStar(require_error_mapper_service(), exports2);
238
+ }
239
+ });
240
+
241
+ // ../shared/dist/index.js
242
+ var require_dist = __commonJS({
243
+ "../shared/dist/index.js"(exports2) {
244
+ "use strict";
245
+ var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
246
+ if (k2 === void 0) k2 = k;
247
+ var desc = Object.getOwnPropertyDescriptor(m, k);
248
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
249
+ desc = { enumerable: true, get: function() {
250
+ return m[k];
251
+ } };
252
+ }
253
+ Object.defineProperty(o, k2, desc);
254
+ }) : (function(o, m, k, k2) {
255
+ if (k2 === void 0) k2 = k;
256
+ o[k2] = m[k];
257
+ }));
258
+ var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
259
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
260
+ };
261
+ Object.defineProperty(exports2, "__esModule", { value: true });
262
+ __exportStar(require_types(), exports2);
263
+ __exportStar(require_utils(), exports2);
264
+ __exportStar(require_errors(), exports2);
265
+ }
266
+ });
267
+
268
+ // src/index.ts
269
+ var index_exports = {};
270
+ __export(index_exports, {
271
+ KEYCLOAK_CLIENT: () => KEYCLOAK_CLIENT,
272
+ KEYCLOAK_CONFIG: () => KEYCLOAK_CONFIG,
273
+ KEYCLOAK_HTTP_INTERCEPTOR: () => KEYCLOAK_HTTP_INTERCEPTOR,
274
+ KeycloakError: () => KeycloakError,
275
+ KeycloakModule: () => KeycloakModule,
276
+ Roles: () => Roles,
277
+ RolesGuard: () => RolesGuard
278
+ });
279
+ module.exports = __toCommonJS(index_exports);
280
+
281
+ // src/keycloak.module.ts
282
+ var import_common5 = require("@nestjs/common");
283
+ var import_core2 = require("@nestjs/core");
284
+ var import_http_client2 = require("@adatechnology/http-client");
285
+ var import_logger2 = require("@adatechnology/logger");
286
+
287
+ // src/keycloak.client.ts
288
+ var import_common = require("@nestjs/common");
289
+ var import_http_client = require("@adatechnology/http-client");
290
+ var import_logger = require("@adatechnology/logger");
291
+
292
+ // src/errors/keycloak-error.ts
293
+ var KeycloakError = class _KeycloakError extends Error {
294
+ statusCode;
295
+ details;
296
+ keycloakError;
297
+ constructor(message, opts) {
298
+ super(message);
299
+ this.name = "KeycloakError";
300
+ this.statusCode = opts == null ? void 0 : opts.statusCode;
301
+ this.details = opts == null ? void 0 : opts.details;
302
+ this.keycloakError = opts == null ? void 0 : opts.keycloakError;
303
+ Object.setPrototypeOf(this, _KeycloakError.prototype);
304
+ }
305
+ };
306
+
307
+ // src/keycloak.client.ts
308
+ var LIB_NAME = "@adatechnology/auth-keycloak";
309
+ var LIB_VERSION = "0.0.2";
310
+ function extractErrorInfo(err) {
311
+ var _a, _b, _c, _d, _e;
312
+ const statusCode = (err == null ? void 0 : err.status) ?? ((_a = err == null ? void 0 : err.response) == null ? void 0 : _a.status);
313
+ const details = ((_b = err == null ? void 0 : err.response) == null ? void 0 : _b.data) ?? ((_c = err == null ? void 0 : err.context) == null ? void 0 : _c.data) ?? (err == null ? void 0 : err.context) ?? (err == null ? void 0 : err.details);
314
+ const errorCode = (err == null ? void 0 : err.code) ?? ((_e = (_d = err == null ? void 0 : err.response) == null ? void 0 : _d.data) == null ? void 0 : _e.error);
315
+ let keycloakError = void 0;
316
+ if (details && typeof details === "object" && details !== null) {
317
+ const raw = details.error;
318
+ if (typeof raw === "string") {
319
+ keycloakError = raw;
320
+ } else if (raw) {
321
+ try {
322
+ keycloakError = JSON.stringify(raw);
323
+ } catch {
324
+ keycloakError = String(raw);
325
+ }
326
+ }
327
+ }
328
+ return {
329
+ statusCode,
330
+ details: details ?? (err == null ? void 0 : err.message),
331
+ keycloakError: keycloakError ?? (errorCode ? `NETWORK_ERROR_${String(errorCode)}` : void 0)
332
+ };
333
+ }
334
+ var KeycloakClient = class {
335
+ constructor(config, httpProvider, logger) {
336
+ this.config = config;
337
+ this.httpProvider = httpProvider;
338
+ this.logger = logger;
339
+ }
340
+ tokenCache = null;
341
+ tokenPromise = null;
342
+ log(level, message, libMethod, meta) {
343
+ if (!this.logger) return;
344
+ const httpCtx = (0, import_http_client.getHttpRequestContext)();
345
+ const requestId = httpCtx == null ? void 0 : httpCtx.requestId;
346
+ const source = (httpCtx == null ? void 0 : httpCtx.className) && (httpCtx == null ? void 0 : httpCtx.methodName) ? `${httpCtx.className}.${httpCtx.methodName}` : void 0;
347
+ const payload = {
348
+ message,
349
+ context: "KeycloakClient",
350
+ lib: LIB_NAME,
351
+ libVersion: LIB_VERSION,
352
+ libMethod,
353
+ source,
354
+ requestId,
355
+ meta
356
+ };
357
+ if (level === "debug") this.logger.debug(payload);
358
+ else if (level === "info") this.logger.info(payload);
359
+ else if (level === "warn") this.logger.warn(payload);
360
+ else if (level === "error") this.logger.error(payload);
361
+ }
362
+ async getAccessToken() {
363
+ const method = "getAccessToken";
364
+ this.log("debug", `${method} - Start`, method);
365
+ const now = Date.now();
366
+ if (this.tokenCache && now < this.tokenCache.expiresAt) {
367
+ this.log("debug", `${method} - Returning cached token`, method);
368
+ return this.tokenCache.token;
369
+ }
370
+ if (this.tokenPromise) {
371
+ this.log("debug", `${method} - Waiting for existing token request`, method);
372
+ return this.tokenPromise;
373
+ }
374
+ this.tokenPromise = (async () => {
375
+ try {
376
+ const tokenResponse = await this.requestToken();
377
+ const expiresAt = this.config.tokenCacheTtl ? Date.now() + this.config.tokenCacheTtl : Date.now() + (tokenResponse.expires_in - 60) * 1e3;
378
+ this.tokenCache = { token: tokenResponse.access_token, expiresAt };
379
+ this.log("debug", `${method} - Token obtained and cached`, method);
380
+ return tokenResponse.access_token;
381
+ } finally {
382
+ this.tokenPromise = null;
383
+ }
384
+ })();
385
+ return this.tokenPromise;
386
+ }
387
+ async getTokenWithCredentials(params) {
388
+ const method = "getTokenWithCredentials";
389
+ const { username } = params;
390
+ this.log("info", `${method} - Start for user: ${username}`, method);
391
+ const { password } = params;
392
+ const tokenUrl = `${this.config.baseUrl}/realms/${this.config.realm}/protocol/openid-connect/token`;
393
+ const body = new URLSearchParams();
394
+ body.append("client_id", this.config.credentials.clientId);
395
+ body.append("grant_type", "password");
396
+ body.append("username", username);
397
+ body.append("password", password);
398
+ if (this.config.credentials.clientSecret) {
399
+ body.append("client_secret", this.config.credentials.clientSecret);
400
+ }
401
+ body.append("scope", KeycloakClient.scopesToString(this.config.scopes));
402
+ try {
403
+ const response = await this.httpProvider.post({
404
+ url: tokenUrl,
405
+ data: body,
406
+ config: {
407
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
408
+ logContext: { className: "KeycloakClient", methodName: method }
409
+ }
410
+ });
411
+ this.log("info", `${method} - Success for user: ${username}`, method);
412
+ return response.data;
413
+ } catch (err) {
414
+ const { statusCode, details, keycloakError } = extractErrorInfo(err);
415
+ this.log("error", `${method} - Failed for user: ${username}`, method, { statusCode, keycloakError });
416
+ throw new KeycloakError("Failed to obtain token with credentials", {
417
+ statusCode,
418
+ details,
419
+ keycloakError
420
+ });
421
+ }
422
+ }
423
+ async requestToken() {
424
+ const method = "requestToken";
425
+ this.log("debug", `${method} - Start`, method);
426
+ const tokenUrl = `${this.config.baseUrl}/realms/${this.config.realm}/protocol/openid-connect/token`;
427
+ const data = new URLSearchParams();
428
+ data.append("client_id", this.config.credentials.clientId);
429
+ data.append("grant_type", this.config.credentials.grantType);
430
+ if (this.config.credentials.clientSecret) {
431
+ data.append("client_secret", this.config.credentials.clientSecret);
432
+ }
433
+ if (this.config.credentials.grantType === "password") {
434
+ if (this.config.credentials.username && this.config.credentials.password) {
435
+ data.append("username", this.config.credentials.username);
436
+ data.append("password", this.config.credentials.password);
437
+ data.append("scope", KeycloakClient.scopesToString(this.config.scopes));
438
+ }
439
+ }
440
+ try {
441
+ const response = await this.httpProvider.post({
442
+ url: tokenUrl,
443
+ data,
444
+ config: {
445
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
446
+ logContext: { className: "KeycloakClient", methodName: method }
447
+ }
448
+ });
449
+ this.log("debug", `${method} - Success`, method);
450
+ return response.data;
451
+ } catch (err) {
452
+ const { statusCode, details, keycloakError } = extractErrorInfo(err);
453
+ this.log("error", `${method} - Failed`, method, { statusCode, keycloakError });
454
+ throw new KeycloakError("Failed to request token", {
455
+ statusCode,
456
+ details,
457
+ keycloakError
458
+ });
459
+ }
460
+ }
461
+ async refreshToken(refreshToken) {
462
+ const method = "refreshToken";
463
+ this.log("debug", `${method} - Start`, method);
464
+ const tokenUrl = `${this.config.baseUrl}/realms/${this.config.realm}/protocol/openid-connect/token`;
465
+ const data = new URLSearchParams();
466
+ data.append("client_id", this.config.credentials.clientId);
467
+ data.append("grant_type", "refresh_token");
468
+ data.append("refresh_token", refreshToken);
469
+ if (this.config.credentials.clientSecret) {
470
+ data.append("client_secret", this.config.credentials.clientSecret);
471
+ }
472
+ try {
473
+ const response = await this.httpProvider.post({
474
+ url: tokenUrl,
475
+ data,
476
+ config: {
477
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
478
+ logContext: { className: "KeycloakClient", methodName: method }
479
+ }
480
+ });
481
+ const expiresAt = this.config.tokenCacheTtl ? Date.now() + this.config.tokenCacheTtl : Date.now() + (response.data.expires_in - 60) * 1e3;
482
+ this.tokenCache = { token: response.data.access_token, expiresAt };
483
+ this.log("debug", `${method} - Success`, method);
484
+ return response.data;
485
+ } catch (err) {
486
+ const { statusCode, details, keycloakError } = extractErrorInfo(err);
487
+ this.log("error", `${method} - Failed`, method, { statusCode, keycloakError });
488
+ throw new KeycloakError("Failed to refresh token", {
489
+ statusCode,
490
+ details,
491
+ keycloakError
492
+ });
493
+ }
494
+ }
495
+ async validateToken(token) {
496
+ var _a;
497
+ const method = "validateToken";
498
+ this.log("debug", `${method} - Start`, method);
499
+ try {
500
+ const introspectUrl = `${this.config.baseUrl}/realms/${this.config.realm}/protocol/openid-connect/token/introspect`;
501
+ const data = new URLSearchParams();
502
+ data.append("token", token);
503
+ data.append("client_id", this.config.credentials.clientId);
504
+ if (this.config.credentials.clientSecret) {
505
+ data.append("client_secret", this.config.credentials.clientSecret);
506
+ }
507
+ const response = await this.httpProvider.post({
508
+ url: introspectUrl,
509
+ data,
510
+ config: {
511
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
512
+ logContext: { className: "KeycloakClient", methodName: method }
513
+ }
514
+ });
515
+ const active = ((_a = response.data) == null ? void 0 : _a.active) === true;
516
+ this.log("debug", `${method} - Success (Active: ${active})`, method);
517
+ return active;
518
+ } catch (error) {
519
+ const { statusCode, details, keycloakError } = extractErrorInfo(error);
520
+ this.log("error", `${method} - Failed`, method, { statusCode, keycloakError });
521
+ throw new KeycloakError("Token introspection failed", {
522
+ statusCode,
523
+ details,
524
+ keycloakError
525
+ });
526
+ }
527
+ }
528
+ async getUserInfo(token) {
529
+ const method = "getUserInfo";
530
+ this.log("debug", `${method} - Start`, method);
531
+ const userInfoUrl = `${this.config.baseUrl}/realms/${this.config.realm}/protocol/openid-connect/userinfo`;
532
+ try {
533
+ const response = await this.httpProvider.get({
534
+ url: userInfoUrl,
535
+ config: {
536
+ headers: { Authorization: `Bearer ${token}` },
537
+ logContext: { className: "KeycloakClient", methodName: method }
538
+ }
539
+ });
540
+ this.log("debug", `${method} - Success`, method);
541
+ return response.data;
542
+ } catch (err) {
543
+ const { statusCode, details, keycloakError } = extractErrorInfo(err);
544
+ this.log("error", `${method} - Failed`, method, { statusCode, keycloakError });
545
+ throw new KeycloakError("Failed to retrieve userinfo", {
546
+ statusCode,
547
+ details,
548
+ keycloakError
549
+ });
550
+ }
551
+ }
552
+ clearTokenCache() {
553
+ this.tokenCache = null;
554
+ }
555
+ static maskToken(token, visibleChars = 8) {
556
+ if (!token || typeof token !== "string") return "";
557
+ return token.length <= visibleChars ? token : `${token.slice(0, visibleChars)}...`;
558
+ }
559
+ static scopesToString(scopes) {
560
+ if (!scopes) return "openid profile email";
561
+ return Array.isArray(scopes) ? scopes.join(" ") : String(scopes);
562
+ }
563
+ };
564
+ KeycloakClient = __decorateClass([
565
+ (0, import_common.Injectable)(),
566
+ __decorateParam(1, (0, import_common.Inject)(import_http_client.HTTP_PROVIDER)),
567
+ __decorateParam(2, (0, import_common.Optional)()),
568
+ __decorateParam(2, (0, import_common.Inject)(import_logger.LOGGER_PROVIDER))
569
+ ], KeycloakClient);
570
+
571
+ // src/keycloak.http.interceptor.ts
572
+ var import_common2 = require("@nestjs/common");
573
+ var KeycloakHttpInterceptor = class {
574
+ constructor() {
575
+ }
576
+ intercept(context, next) {
577
+ const request = context.switchToHttp().getRequest();
578
+ if (typeof request.url === "string" && !request.url.includes("keycloak")) {
579
+ }
580
+ return next.handle();
581
+ }
582
+ };
583
+ KeycloakHttpInterceptor = __decorateClass([
584
+ (0, import_common2.Injectable)()
585
+ ], KeycloakHttpInterceptor);
586
+
587
+ // src/roles.guard.ts
588
+ var import_common4 = require("@nestjs/common");
589
+ var import_core = require("@nestjs/core");
590
+
591
+ // src/roles.decorator.ts
592
+ var import_common3 = require("@nestjs/common");
593
+ var ROLES_META_KEY = "roles";
594
+ function Roles(...args) {
595
+ let payload;
596
+ if (args.length === 1 && typeof args[0] === "object" && !Array.isArray(args[0])) {
597
+ payload = args[0];
598
+ } else {
599
+ const roles = [].concat(
600
+ ...args.map((a) => Array.isArray(a) ? a : String(a))
601
+ );
602
+ payload = { roles };
603
+ }
604
+ payload.mode = payload.mode ?? "any";
605
+ payload.type = payload.type ?? "both";
606
+ return (0, import_common3.SetMetadata)(ROLES_META_KEY, payload);
607
+ }
608
+
609
+ // src/keycloak.token.ts
610
+ var KEYCLOAK_CONFIG = "KEYCLOAK_CONFIG";
611
+ var KEYCLOAK_CLIENT = "KEYCLOAK_CLIENT";
612
+ var KEYCLOAK_HTTP_INTERCEPTOR = "KEYCLOAK_HTTP_INTERCEPTOR";
613
+
614
+ // src/roles.guard.ts
615
+ var import_shared = __toESM(require_dist());
616
+ var RolesGuard = class {
617
+ constructor(reflector, config) {
618
+ this.reflector = reflector;
619
+ this.config = config;
620
+ }
621
+ canActivate(context) {
622
+ var _a, _b, _c, _d, _e, _f, _g, _h;
623
+ const meta = this.reflector.get(ROLES_META_KEY, context.getHandler()) || this.reflector.get(ROLES_META_KEY, context.getClass());
624
+ if (!meta || !meta.roles || meta.roles.length === 0) return true;
625
+ const req = context.switchToHttp().getRequest();
626
+ const authHeader = ((_a = req.headers) == null ? void 0 : _a.authorization) || ((_b = req.headers) == null ? void 0 : _b.Authorization);
627
+ const token = authHeader ? String(authHeader).split(" ")[1] : (_c = req.query) == null ? void 0 : _c.token;
628
+ if (!token)
629
+ throw new import_shared.BaseAppError({
630
+ message: "Authorization token not provided",
631
+ status: 403,
632
+ code: "FORBIDDEN_MISSING_TOKEN",
633
+ context: {}
634
+ });
635
+ const payload = this.decodeJwtPayload(token);
636
+ const availableRoles = /* @__PURE__ */ new Set();
637
+ if (((_d = payload == null ? void 0 : payload.realm_access) == null ? void 0 : _d.roles) && Array.isArray(payload.realm_access.roles)) {
638
+ payload.realm_access.roles.forEach((r) => availableRoles.add(r));
639
+ }
640
+ const clientId = (_f = (_e = this.config) == null ? void 0 : _e.credentials) == null ? void 0 : _f.clientId;
641
+ if (clientId && ((_h = (_g = payload == null ? void 0 : payload.resource_access) == null ? void 0 : _g[clientId]) == null ? void 0 : _h.roles)) {
642
+ payload.resource_access[clientId].roles.forEach(
643
+ (r) => availableRoles.add(r)
644
+ );
645
+ }
646
+ if (meta.type === "both" && (payload == null ? void 0 : payload.resource_access)) {
647
+ Object.values(payload.resource_access).forEach((entry) => {
648
+ if ((entry == null ? void 0 : entry.roles) && Array.isArray(entry.roles)) {
649
+ entry.roles.forEach(
650
+ (r) => availableRoles.add(r)
651
+ );
652
+ }
653
+ });
654
+ }
655
+ const required = meta.roles || [];
656
+ const hasMatch = required.map((r) => availableRoles.has(r));
657
+ const result = meta.mode === "all" ? hasMatch.every(Boolean) : hasMatch.some(Boolean);
658
+ if (!result)
659
+ throw new import_shared.BaseAppError({
660
+ message: "Insufficient roles",
661
+ status: 403,
662
+ code: "FORBIDDEN_INSUFFICIENT_ROLES",
663
+ context: { required }
664
+ });
665
+ return true;
666
+ }
667
+ decodeJwtPayload(token) {
668
+ try {
669
+ const parts = token.split(".");
670
+ if (parts.length < 2) return {};
671
+ const payload = parts[1];
672
+ const BufferCtor = globalThis.Buffer;
673
+ if (!BufferCtor) return {};
674
+ const decoded = BufferCtor.from(payload, "base64").toString("utf8");
675
+ return JSON.parse(decoded);
676
+ } catch (e) {
677
+ return {};
678
+ }
679
+ }
680
+ };
681
+ RolesGuard = __decorateClass([
682
+ (0, import_common4.Injectable)(),
683
+ __decorateParam(0, (0, import_common4.Inject)(import_core.Reflector)),
684
+ __decorateParam(1, (0, import_common4.Optional)()),
685
+ __decorateParam(1, (0, import_common4.Inject)(KEYCLOAK_CONFIG))
686
+ ], RolesGuard);
687
+
688
+ // src/keycloak.module.ts
689
+ var KeycloakModule = class {
690
+ static forRoot(config, httpConfig) {
691
+ return {
692
+ module: KeycloakModule,
693
+ global: true,
694
+ imports: [
695
+ import_http_client2.HttpModule.forRoot(
696
+ httpConfig || { baseURL: config.baseUrl, timeout: 5e3 },
697
+ {
698
+ logging: {
699
+ enabled: true,
700
+ includeBody: true,
701
+ context: "KeycloakHttpClient",
702
+ environments: ["development", "test"]
703
+ }
704
+ }
705
+ )
706
+ ],
707
+ providers: [
708
+ { provide: import_core2.Reflector, useClass: import_core2.Reflector },
709
+ { provide: KEYCLOAK_CONFIG, useValue: config },
710
+ {
711
+ provide: KEYCLOAK_CLIENT,
712
+ useFactory: (cfg, httpProvider, logger) => new KeycloakClient(cfg, httpProvider, logger),
713
+ inject: [KEYCLOAK_CONFIG, import_http_client2.HTTP_PROVIDER, { token: import_logger2.LOGGER_PROVIDER, optional: true }]
714
+ },
715
+ {
716
+ provide: KEYCLOAK_HTTP_INTERCEPTOR,
717
+ useFactory: () => new KeycloakHttpInterceptor()
718
+ },
719
+ RolesGuard
720
+ ],
721
+ exports: [
722
+ import_core2.Reflector,
723
+ KEYCLOAK_CLIENT,
724
+ KEYCLOAK_HTTP_INTERCEPTOR,
725
+ KEYCLOAK_CONFIG,
726
+ RolesGuard
727
+ ]
728
+ };
729
+ }
730
+ };
731
+ KeycloakModule = __decorateClass([
732
+ (0, import_common5.Module)({})
733
+ ], KeycloakModule);
734
+ // Annotate the CommonJS export names for ESM import in node:
735
+ 0 && (module.exports = {
736
+ KEYCLOAK_CLIENT,
737
+ KEYCLOAK_CONFIG,
738
+ KEYCLOAK_HTTP_INTERCEPTOR,
739
+ KeycloakError,
740
+ KeycloakModule,
741
+ Roles,
742
+ RolesGuard
743
+ });