nestjs-keycloak-auth 1.0.0

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.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +391 -0
  3. package/dist/constants.d.ts +65 -0
  4. package/dist/constants.js +72 -0
  5. package/dist/controllers/keycloak-admin.controller.d.ts +16 -0
  6. package/dist/controllers/keycloak-admin.controller.js +94 -0
  7. package/dist/decorators/access-token.decorator.d.ts +5 -0
  8. package/dist/decorators/access-token.decorator.js +13 -0
  9. package/dist/decorators/enforcer-options.decorator.d.ts +8 -0
  10. package/dist/decorators/enforcer-options.decorator.js +12 -0
  11. package/dist/decorators/keycloak-user.decorator.d.ts +5 -0
  12. package/dist/decorators/keycloak-user.decorator.js +13 -0
  13. package/dist/decorators/public.decorator.d.ts +6 -0
  14. package/dist/decorators/public.decorator.js +11 -0
  15. package/dist/decorators/resource.decorator.d.ts +6 -0
  16. package/dist/decorators/resource.decorator.js +11 -0
  17. package/dist/decorators/roles.decorator.d.ts +10 -0
  18. package/dist/decorators/roles.decorator.js +15 -0
  19. package/dist/decorators/scopes.decorator.d.ts +19 -0
  20. package/dist/decorators/scopes.decorator.js +27 -0
  21. package/dist/decorators/token-scopes.decorator.d.ts +19 -0
  22. package/dist/decorators/token-scopes.decorator.js +24 -0
  23. package/dist/errors.d.ts +24 -0
  24. package/dist/errors.js +44 -0
  25. package/dist/guards/auth.guard.d.ts +25 -0
  26. package/dist/guards/auth.guard.js +176 -0
  27. package/dist/guards/resource.guard.d.ts +26 -0
  28. package/dist/guards/resource.guard.js +245 -0
  29. package/dist/guards/role.guard.d.ts +17 -0
  30. package/dist/guards/role.guard.js +113 -0
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.js +18 -0
  33. package/dist/interface/enforcer-options.interface.d.ts +8 -0
  34. package/dist/interface/enforcer-options.interface.js +2 -0
  35. package/dist/interface/jwks.interface.d.ts +22 -0
  36. package/dist/interface/jwks.interface.js +2 -0
  37. package/dist/interface/jwt.interface.d.ts +46 -0
  38. package/dist/interface/jwt.interface.js +2 -0
  39. package/dist/interface/keycloak-auth-module-async-options.interface.d.ts +11 -0
  40. package/dist/interface/keycloak-auth-module-async-options.interface.js +2 -0
  41. package/dist/interface/keycloak-auth-options-factory.interface.d.ts +4 -0
  42. package/dist/interface/keycloak-auth-options-factory.interface.js +2 -0
  43. package/dist/interface/keycloak-auth-options.interface.d.ts +162 -0
  44. package/dist/interface/keycloak-auth-options.interface.js +3 -0
  45. package/dist/interface/keycloak-grant.interface.d.ts +33 -0
  46. package/dist/interface/keycloak-grant.interface.js +2 -0
  47. package/dist/interface/keycloak-request.interface.d.ts +8 -0
  48. package/dist/interface/keycloak-request.interface.js +2 -0
  49. package/dist/interface/oidc.interface.d.ts +14 -0
  50. package/dist/interface/oidc.interface.js +2 -0
  51. package/dist/interface/server.interface.d.ts +9 -0
  52. package/dist/interface/server.interface.js +2 -0
  53. package/dist/interface/tenant-config.interface.d.ts +13 -0
  54. package/dist/interface/tenant-config.interface.js +2 -0
  55. package/dist/internal.util.d.ts +13 -0
  56. package/dist/internal.util.js +54 -0
  57. package/dist/keycloak-auth.module.d.ts +54 -0
  58. package/dist/keycloak-auth.module.js +174 -0
  59. package/dist/keycloak-auth.providers.d.ts +7 -0
  60. package/dist/keycloak-auth.providers.js +122 -0
  61. package/dist/services/backchannel-logout.service.d.ts +30 -0
  62. package/dist/services/backchannel-logout.service.js +100 -0
  63. package/dist/services/jwks-cache.service.d.ts +25 -0
  64. package/dist/services/jwks-cache.service.js +130 -0
  65. package/dist/services/keycloak-admin.service.d.ts +37 -0
  66. package/dist/services/keycloak-admin.service.js +268 -0
  67. package/dist/services/keycloak-grant.service.d.ts +23 -0
  68. package/dist/services/keycloak-grant.service.js +88 -0
  69. package/dist/services/keycloak-http.service.d.ts +41 -0
  70. package/dist/services/keycloak-http.service.js +211 -0
  71. package/dist/services/keycloak-multitenant.service.d.ts +24 -0
  72. package/dist/services/keycloak-multitenant.service.js +161 -0
  73. package/dist/services/keycloak-url.service.d.ts +12 -0
  74. package/dist/services/keycloak-url.service.js +43 -0
  75. package/dist/services/oidc-discovery.service.d.ts +24 -0
  76. package/dist/services/oidc-discovery.service.js +86 -0
  77. package/dist/services/token-validation.service.d.ts +29 -0
  78. package/dist/services/token-validation.service.js +215 -0
  79. package/dist/token/keycloak-grant.d.ts +24 -0
  80. package/dist/token/keycloak-grant.js +37 -0
  81. package/dist/token/keycloak-token.d.ts +57 -0
  82. package/dist/token/keycloak-token.js +105 -0
  83. package/dist/types/conditional-scope.type.d.ts +2 -0
  84. package/dist/types/conditional-scope.type.js +2 -0
  85. package/dist/util.d.ts +3 -0
  86. package/dist/util.js +17 -0
  87. package/package.json +130 -0
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ 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;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
15
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
16
+ return new (P || (P = Promise))(function (resolve, reject) {
17
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
18
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
19
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
20
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
21
+ });
22
+ };
23
+ var ResourceGuard_1;
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.ResourceGuard = void 0;
26
+ const core_1 = require("@nestjs/core");
27
+ const keycloak_token_1 = require("../token/keycloak-token");
28
+ const public_decorator_1 = require("../decorators/public.decorator");
29
+ const resource_decorator_1 = require("../decorators/resource.decorator");
30
+ const internal_util_1 = require("../internal.util");
31
+ const keycloak_http_service_1 = require("../services/keycloak-http.service");
32
+ const enforcer_options_decorator_1 = require("../decorators/enforcer-options.decorator");
33
+ const keycloak_multitenant_service_1 = require("../services/keycloak-multitenant.service");
34
+ const common_1 = require("@nestjs/common");
35
+ const scopes_decorator_1 = require("../decorators/scopes.decorator");
36
+ const constants_1 = require("../constants");
37
+ /**
38
+ * This adds a resource guard, which is policy enforcement by default is permissive.
39
+ * Only controllers annotated with `@Resource` and methods with `@Scopes`
40
+ * are handled by this guard.
41
+ */
42
+ let ResourceGuard = ResourceGuard_1 = class ResourceGuard {
43
+ constructor(singleTenant, keycloakOpts, multiTenant, keycloakHttp) {
44
+ this.singleTenant = singleTenant;
45
+ this.keycloakOpts = keycloakOpts;
46
+ this.multiTenant = multiTenant;
47
+ this.keycloakHttp = keycloakHttp;
48
+ this.logger = new common_1.Logger(ResourceGuard_1.name);
49
+ this.reflector = new core_1.Reflector();
50
+ }
51
+ canActivate(context) {
52
+ return __awaiter(this, void 0, void 0, function* () {
53
+ var _a, _b, _c, _d, _e;
54
+ const resource = this.reflector.get(resource_decorator_1.META_RESOURCE, context.getClass());
55
+ const explicitScopes = (_a = this.reflector.get(scopes_decorator_1.META_SCOPES, context.getHandler())) !== null && _a !== void 0 ? _a : [];
56
+ const conditionalScopes = this.reflector.get(scopes_decorator_1.META_CONDITIONAL_SCOPES, context.getHandler());
57
+ const isPublic = this.reflector.getAllAndOverride(public_decorator_1.META_PUBLIC, [
58
+ context.getClass(),
59
+ context.getHandler(),
60
+ ]);
61
+ const enforcerOptions = this.reflector.getAllAndOverride(enforcer_options_decorator_1.META_ENFORCER_OPTIONS, [context.getClass(), context.getHandler()]);
62
+ // Default to permissive
63
+ const policyEnforcementMode = this.keycloakOpts.policyEnforcement || constants_1.PolicyEnforcementMode.PERMISSIVE;
64
+ const shouldAllow = policyEnforcementMode === constants_1.PolicyEnforcementMode.PERMISSIVE;
65
+ // Extract request/response
66
+ const [request] = (0, internal_util_1.extractRequest)(context);
67
+ // if is not an HTTP request ignore this guard
68
+ if (!request) {
69
+ return true;
70
+ }
71
+ if (!request.user && isPublic) {
72
+ this.logger.verbose('Route has no user, and is public, allowed');
73
+ return true;
74
+ }
75
+ const tenantConfig = yield (0, internal_util_1.useTenantConfig)(request, request.accessToken, this.singleTenant, this.multiTenant, this.keycloakOpts);
76
+ // No resource given, check policy enforcement mode
77
+ if (!resource) {
78
+ if (shouldAllow) {
79
+ this.logger.verbose('Controller has no @Resource defined, request allowed due to policy enforcement');
80
+ }
81
+ else {
82
+ this.logger.verbose('Controller has no @Resource defined, request denied due to policy enforcement');
83
+ }
84
+ return shouldAllow;
85
+ }
86
+ // Build the required scopes
87
+ let token;
88
+ if (conditionalScopes) {
89
+ token = new keycloak_token_1.KeycloakToken(request.accessToken, tenantConfig.clientId);
90
+ }
91
+ const conditionalScopesResult = conditionalScopes
92
+ ? conditionalScopes(request, token)
93
+ : [];
94
+ const scopes = [...explicitScopes, ...conditionalScopesResult];
95
+ // Attach resolved scopes
96
+ request.scopes = scopes;
97
+ // No scopes given, check policy enforcement mode
98
+ if (!scopes || scopes.length === 0) {
99
+ if (shouldAllow) {
100
+ this.logger.verbose('Route has no @Scope/@ConditionalScopes defined, request allowed due to policy enforcement');
101
+ }
102
+ else {
103
+ this.logger.verbose('Route has no @Scope/@ConditionalScopes defined, request denied due to policy enforcement');
104
+ }
105
+ return shouldAllow;
106
+ }
107
+ this.logger.verbose(`Protecting resource [ ${resource} ] with scopes: [ ${scopes} ]`);
108
+ const user = (_c = (_b = request.user) === null || _b === void 0 ? void 0 : _b.preferred_username) !== null && _c !== void 0 ? _c : 'user';
109
+ // Build permissions as "resource#scope" pairs (Keycloak UMA format)
110
+ const permissions = scopes.map((scope) => `${resource}#${scope}`);
111
+ // Local permission check: if the access token already contains the
112
+ // required permissions, allow immediately without a server round-trip
113
+ // (matches keycloak-connect enforcer.js behavior)
114
+ if (request.accessToken) {
115
+ const accessToken = token !== null && token !== void 0 ? token : new keycloak_token_1.KeycloakToken(request.accessToken, tenantConfig.clientId);
116
+ const allPermissionsGranted = scopes.every((scope) => accessToken.hasPermission(resource, scope));
117
+ if (allPermissionsGranted) {
118
+ this.logger.verbose(`Resource [ ${resource} ] granted to [ ${user} ] (local token check)`);
119
+ return true;
120
+ }
121
+ }
122
+ // Server-side permission check
123
+ const claims = this.resolveClaims(request, enforcerOptions);
124
+ const responseMode = (_d = enforcerOptions === null || enforcerOptions === void 0 ? void 0 : enforcerOptions.response_mode) !== null && _d !== void 0 ? _d : 'permissions';
125
+ const audience = (_e = enforcerOptions === null || enforcerOptions === void 0 ? void 0 : enforcerOptions.resource_server_id) !== null && _e !== void 0 ? _e : tenantConfig.clientId;
126
+ const isPublicClient = tenantConfig.isPublic;
127
+ if (responseMode === 'permissions') {
128
+ // Permissions mode: get permission list from server, validate locally
129
+ try {
130
+ const serverPermissions = (yield this.keycloakHttp.checkPermission(tenantConfig.realmUrl, tenantConfig.clientId, tenantConfig.secret, request.accessToken, permissions, {
131
+ claims: claims || undefined,
132
+ response_mode: 'permissions',
133
+ audience,
134
+ isPublic: isPublicClient,
135
+ }));
136
+ const isAllowed = this.validatePermissionsLocally(serverPermissions, resource, scopes);
137
+ if (isAllowed) {
138
+ // Attach permissions to request (matches original enforcer.js)
139
+ request.permissions = serverPermissions;
140
+ this.logger.verbose(`Resource [ ${resource} ] granted to [ ${user} ]`);
141
+ }
142
+ else {
143
+ this.logger.verbose(`Resource [ ${resource} ] denied to [ ${user} ]`);
144
+ }
145
+ return isAllowed;
146
+ }
147
+ catch (_f) {
148
+ this.logger.verbose(`Resource [ ${resource} ] denied to [ ${user} ] (permissions check failed)`);
149
+ return false;
150
+ }
151
+ }
152
+ if (responseMode === 'token') {
153
+ // Token mode: get a new grant with permissions, validate locally
154
+ try {
155
+ const grant = (yield this.keycloakHttp.checkPermission(tenantConfig.realmUrl, tenantConfig.clientId, tenantConfig.secret, request.accessToken, permissions, {
156
+ claims: claims || undefined,
157
+ response_mode: 'token',
158
+ audience,
159
+ isPublic: isPublicClient,
160
+ }));
161
+ const grantToken = new keycloak_token_1.KeycloakToken(grant.access_token, tenantConfig.clientId);
162
+ const isAllowed = scopes.every((scope) => grantToken.hasPermission(resource, scope));
163
+ if (isAllowed) {
164
+ this.logger.verbose(`Resource [ ${resource} ] granted to [ ${user} ]`);
165
+ }
166
+ else {
167
+ this.logger.verbose(`Resource [ ${resource} ] denied to [ ${user} ]`);
168
+ }
169
+ return isAllowed;
170
+ }
171
+ catch (_g) {
172
+ this.logger.verbose(`Resource [ ${resource} ] denied to [ ${user} ] (token check failed)`);
173
+ return false;
174
+ }
175
+ }
176
+ // Default: decision mode
177
+ const isAllowed = (yield this.keycloakHttp.checkPermission(tenantConfig.realmUrl, tenantConfig.clientId, tenantConfig.secret, request.accessToken, permissions, {
178
+ claims: claims || undefined,
179
+ response_mode: 'decision',
180
+ audience,
181
+ isPublic: isPublicClient,
182
+ }));
183
+ // If statement for verbose logging only
184
+ if (!isAllowed) {
185
+ this.logger.verbose(`Resource [ ${resource} ] denied to [ ${user} ]`);
186
+ }
187
+ else {
188
+ this.logger.verbose(`Resource [ ${resource} ] granted to [ ${user} ]`);
189
+ }
190
+ return isAllowed;
191
+ });
192
+ }
193
+ /**
194
+ * Validate server-returned permissions against expected resource:scope pairs.
195
+ * Matches keycloak-connect enforcer.js handlePermissions logic.
196
+ */
197
+ validatePermissionsLocally(serverPermissions, resource, scopes) {
198
+ if (!serverPermissions || serverPermissions.length === 0) {
199
+ return false;
200
+ }
201
+ for (const scope of scopes) {
202
+ let found = false;
203
+ for (const permission of serverPermissions) {
204
+ if (permission.rsid === resource || permission.rsname === resource) {
205
+ if (scope) {
206
+ if (permission.scopes && permission.scopes.length > 0) {
207
+ if (!permission.scopes.includes(scope)) {
208
+ return false;
209
+ }
210
+ found = true;
211
+ break;
212
+ }
213
+ return false;
214
+ }
215
+ found = true;
216
+ break;
217
+ }
218
+ }
219
+ if (!found) {
220
+ return false;
221
+ }
222
+ }
223
+ return true;
224
+ }
225
+ resolveClaims(request, enforcerOptions) {
226
+ var _a;
227
+ if (!enforcerOptions) {
228
+ return undefined;
229
+ }
230
+ const claims = (_a = enforcerOptions.claims) === null || _a === void 0 ? void 0 : _a.call(enforcerOptions, request);
231
+ if (claims) {
232
+ this.logger.verbose(`Enforcing claims, keys: ${Object.keys(claims).join(', ')}`);
233
+ }
234
+ return claims;
235
+ }
236
+ };
237
+ exports.ResourceGuard = ResourceGuard;
238
+ exports.ResourceGuard = ResourceGuard = ResourceGuard_1 = __decorate([
239
+ (0, common_1.Injectable)(),
240
+ __param(0, (0, common_1.Inject)(constants_1.KEYCLOAK_INSTANCE)),
241
+ __param(1, (0, common_1.Inject)(constants_1.KEYCLOAK_AUTH_OPTIONS)),
242
+ __param(2, (0, common_1.Inject)(constants_1.KEYCLOAK_MULTITENANT_SERVICE)),
243
+ __metadata("design:paramtypes", [Object, Object, keycloak_multitenant_service_1.KeycloakMultiTenantService,
244
+ keycloak_http_service_1.KeycloakHttpService])
245
+ ], ResourceGuard);
@@ -0,0 +1,17 @@
1
+ import { ResolvedTenantConfig } from '../interface/tenant-config.interface';
2
+ import { KeycloakAuthConfig } from '../interface/keycloak-auth-options.interface';
3
+ import { KeycloakMultiTenantService } from '../services/keycloak-multitenant.service';
4
+ import { CanActivate, ExecutionContext } from '@nestjs/common';
5
+ /**
6
+ * A permissive type of role guard. Roles are set via `@Roles` decorator.
7
+ * @since 1.1.0
8
+ */
9
+ export declare class RoleGuard implements CanActivate {
10
+ private singleTenant;
11
+ private keycloakOpts;
12
+ private multiTenant;
13
+ private readonly logger;
14
+ private readonly reflector;
15
+ constructor(singleTenant: ResolvedTenantConfig, keycloakOpts: KeycloakAuthConfig, multiTenant: KeycloakMultiTenantService);
16
+ canActivate(context: ExecutionContext): Promise<boolean>;
17
+ }
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ 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;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
15
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
16
+ return new (P || (P = Promise))(function (resolve, reject) {
17
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
18
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
19
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
20
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
21
+ });
22
+ };
23
+ var RoleGuard_1;
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.RoleGuard = void 0;
26
+ const core_1 = require("@nestjs/core");
27
+ const errors_1 = require("../errors");
28
+ const keycloak_token_1 = require("../token/keycloak-token");
29
+ const internal_util_1 = require("../internal.util");
30
+ const roles_decorator_1 = require("../decorators/roles.decorator");
31
+ const keycloak_multitenant_service_1 = require("../services/keycloak-multitenant.service");
32
+ const common_1 = require("@nestjs/common");
33
+ const constants_1 = require("../constants");
34
+ /**
35
+ * A permissive type of role guard. Roles are set via `@Roles` decorator.
36
+ * @since 1.1.0
37
+ */
38
+ let RoleGuard = RoleGuard_1 = class RoleGuard {
39
+ constructor(singleTenant, keycloakOpts, multiTenant) {
40
+ this.singleTenant = singleTenant;
41
+ this.keycloakOpts = keycloakOpts;
42
+ this.multiTenant = multiTenant;
43
+ this.logger = new common_1.Logger(RoleGuard_1.name);
44
+ this.reflector = new core_1.Reflector();
45
+ }
46
+ canActivate(context) {
47
+ return __awaiter(this, void 0, void 0, function* () {
48
+ const roleMerge = this.keycloakOpts.roleMerge
49
+ ? this.keycloakOpts.roleMerge
50
+ : constants_1.RoleMerge.OVERRIDE;
51
+ const roles = [];
52
+ const matchingMode = this.reflector.getAllAndOverride(roles_decorator_1.META_ROLE_MATCHING_MODE, [context.getClass(), context.getHandler()]);
53
+ if (roleMerge == constants_1.RoleMerge.ALL) {
54
+ const mergedRoles = this.reflector.getAllAndMerge(roles_decorator_1.META_ROLES, [
55
+ context.getClass(),
56
+ context.getHandler(),
57
+ ]);
58
+ if (mergedRoles) {
59
+ roles.push(...mergedRoles);
60
+ }
61
+ }
62
+ else if (roleMerge == constants_1.RoleMerge.OVERRIDE) {
63
+ const resultRoles = this.reflector.getAllAndOverride(roles_decorator_1.META_ROLES, [context.getClass(), context.getHandler()]);
64
+ if (resultRoles) {
65
+ roles.push(...resultRoles);
66
+ }
67
+ }
68
+ else {
69
+ throw new errors_1.KeycloakConfigError(`Unknown role merge: ${roleMerge}`);
70
+ }
71
+ if (roles.length === 0) {
72
+ return true;
73
+ }
74
+ const roleMatchingMode = matchingMode !== null && matchingMode !== void 0 ? matchingMode : constants_1.RoleMatch.ANY;
75
+ this.logger.verbose(`Using matching mode: ${roleMatchingMode}`, { roles });
76
+ // Extract request
77
+ const [request] = (0, internal_util_1.extractRequest)(context);
78
+ // if is not an HTTP request ignore this guard
79
+ if (!request) {
80
+ return true;
81
+ }
82
+ const { accessToken } = request;
83
+ if (!accessToken) {
84
+ // No access token attached, auth guard should have attached the necessary token
85
+ this.logger.warn('No access token found in request, are you sure AuthGuard is first in the chain?');
86
+ return false;
87
+ }
88
+ // Resolve tenant config to get clientId
89
+ const tenantConfig = yield (0, internal_util_1.useTenantConfig)(request, accessToken, this.singleTenant, this.multiTenant, this.keycloakOpts);
90
+ // Use native KeycloakToken for role checking — pure in-memory, no HTTP
91
+ const token = new keycloak_token_1.KeycloakToken(accessToken, tenantConfig.clientId);
92
+ // For verbose logging, we store it instead of returning it immediately
93
+ const granted = roleMatchingMode === constants_1.RoleMatch.ANY
94
+ ? roles.some((r) => token.hasRole(r))
95
+ : roles.every((r) => token.hasRole(r));
96
+ if (granted) {
97
+ this.logger.verbose('Resource granted due to role(s)');
98
+ }
99
+ else {
100
+ this.logger.verbose('Resource denied due to mismatched role(s)');
101
+ }
102
+ return granted;
103
+ });
104
+ }
105
+ };
106
+ exports.RoleGuard = RoleGuard;
107
+ exports.RoleGuard = RoleGuard = RoleGuard_1 = __decorate([
108
+ (0, common_1.Injectable)(),
109
+ __param(0, (0, common_1.Inject)(constants_1.KEYCLOAK_INSTANCE)),
110
+ __param(1, (0, common_1.Inject)(constants_1.KEYCLOAK_AUTH_OPTIONS)),
111
+ __param(2, (0, common_1.Inject)(constants_1.KEYCLOAK_MULTITENANT_SERVICE)),
112
+ __metadata("design:paramtypes", [Object, Object, keycloak_multitenant_service_1.KeycloakMultiTenantService])
113
+ ], RoleGuard);
@@ -0,0 +1 @@
1
+ export * from './keycloak-auth.module';
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ // public-api
18
+ __exportStar(require("./keycloak-auth.module"), exports);
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Local replacement for keycloak-connect's EnforcerOptions.
3
+ */
4
+ export interface KeycloakEnforcerOptions {
5
+ response_mode?: 'decision' | 'permissions' | 'token';
6
+ resource_server_id?: string;
7
+ claims?: (request: unknown) => Record<string, unknown>;
8
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,22 @@
1
+ export interface JwksKey {
2
+ kid: string;
3
+ kty: string;
4
+ alg?: string;
5
+ use?: string;
6
+ n?: string;
7
+ e?: string;
8
+ x5c?: string[];
9
+ x5t?: string;
10
+ 'x5t#S256'?: string;
11
+ crv?: string;
12
+ x?: string;
13
+ y?: string;
14
+ [key: string]: unknown;
15
+ }
16
+ export interface JwksResponse {
17
+ keys: JwksKey[];
18
+ }
19
+ export interface CachedJwks {
20
+ keys: Map<string, JwksKey>;
21
+ fetchedAt: number;
22
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,46 @@
1
+ export interface JwtHeader {
2
+ alg?: string;
3
+ typ?: string;
4
+ kid?: string;
5
+ [key: string]: unknown;
6
+ }
7
+ export interface JwtPermission {
8
+ rsid?: string;
9
+ rsname?: string;
10
+ scopes?: string[];
11
+ }
12
+ export interface JwtContent {
13
+ exp: number;
14
+ iat?: number;
15
+ typ?: string;
16
+ iss?: string;
17
+ aud?: string | string[];
18
+ azp?: string;
19
+ sub?: string;
20
+ preferred_username?: string;
21
+ realm_access?: {
22
+ roles: string[];
23
+ };
24
+ resource_access?: Record<string, {
25
+ roles: string[];
26
+ }>;
27
+ authorization?: {
28
+ permissions: JwtPermission[];
29
+ };
30
+ scope?: string;
31
+ sid?: string;
32
+ events?: Record<string, unknown>;
33
+ action?: string;
34
+ adapterSessionIds?: string[];
35
+ notBefore?: number;
36
+ [key: string]: unknown;
37
+ }
38
+ export interface JwtPayload {
39
+ iss?: string;
40
+ sub?: string;
41
+ aud?: string | string[];
42
+ exp?: number;
43
+ iat?: number;
44
+ preferred_username?: string;
45
+ [key: string]: unknown;
46
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,11 @@
1
+ import { ModuleMetadata, Type } from '@nestjs/common/interfaces';
2
+ import { KeycloakAuthOptions } from './keycloak-auth-options.interface';
3
+ import { KeycloakAuthOptionsFactory } from './keycloak-auth-options-factory.interface';
4
+ import { InjectionToken } from '@nestjs/common/interfaces/modules/injection-token.interface';
5
+ import { OptionalFactoryDependency } from '@nestjs/common/interfaces/modules/optional-factory-dependency.interface';
6
+ export interface KeycloakAuthModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
7
+ inject?: Array<InjectionToken | OptionalFactoryDependency>;
8
+ useExisting?: Type<KeycloakAuthOptionsFactory>;
9
+ useClass?: Type<KeycloakAuthOptionsFactory>;
10
+ useFactory?: (...args: unknown[]) => Promise<KeycloakAuthOptions> | KeycloakAuthOptions;
11
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ import { KeycloakAuthOptions } from './keycloak-auth-options.interface';
2
+ export interface KeycloakAuthOptionsFactory {
3
+ createKeycloakAuthOptions(): Promise<KeycloakAuthOptions> | KeycloakAuthOptions;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });