@cifra-x/nest-keycloak 0.0.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.
Files changed (73) hide show
  1. package/README.md +45 -0
  2. package/dist/src/async-class-provider.type-guard.d.ts +9 -0
  3. package/dist/src/async-class-provider.type-guard.js +6 -0
  4. package/dist/src/common/interfaces/authentication-request.interface.d.ts +11 -0
  5. package/dist/src/common/interfaces/authentication-request.interface.js +2 -0
  6. package/dist/src/common/interfaces/jwt-payload.interface.d.ts +12 -0
  7. package/dist/src/common/interfaces/jwt-payload.interface.js +2 -0
  8. package/dist/src/common/interfaces/token.interface.d.ts +2 -0
  9. package/dist/src/common/interfaces/token.interface.js +2 -0
  10. package/dist/src/common/utils/extract-request.util.d.ts +2 -0
  11. package/dist/src/common/utils/extract-request.util.js +27 -0
  12. package/dist/src/common/utils/extract-request.util.spec.d.ts +1 -0
  13. package/dist/src/common/utils/extract-request.util.spec.js +64 -0
  14. package/dist/src/common/utils/get-keycloak.util.d.ts +4 -0
  15. package/dist/src/common/utils/get-keycloak.util.js +25 -0
  16. package/dist/src/common/utils/get-keycloak.util.spec.d.ts +1 -0
  17. package/dist/src/common/utils/get-keycloak.util.spec.js +55 -0
  18. package/dist/src/common/utils/parse-token.util.d.ts +2 -0
  19. package/dist/src/common/utils/parse-token.util.js +7 -0
  20. package/dist/src/common/utils/parse-token.util.spec.d.ts +1 -0
  21. package/dist/src/common/utils/parse-token.util.spec.js +19 -0
  22. package/dist/src/constants/index.d.ts +5 -0
  23. package/dist/src/constants/index.js +21 -0
  24. package/dist/src/constants/keycloak-module.constants.d.ts +20 -0
  25. package/dist/src/constants/keycloak-module.constants.js +23 -0
  26. package/dist/src/constants/policy-enforcement-mode.enum.d.ts +13 -0
  27. package/dist/src/constants/policy-enforcement-mode.enum.js +17 -0
  28. package/dist/src/constants/role-matching-mode.enum.d.ts +13 -0
  29. package/dist/src/constants/role-matching-mode.enum.js +17 -0
  30. package/dist/src/constants/role-merge.enum.d.ts +10 -0
  31. package/dist/src/constants/role-merge.enum.js +14 -0
  32. package/dist/src/constants/token-validation.enum.d.ts +17 -0
  33. package/dist/src/constants/token-validation.enum.js +21 -0
  34. package/dist/src/decorators/authenticated-user.decorator.d.ts +4 -0
  35. package/dist/src/decorators/authenticated-user.decorator.js +12 -0
  36. package/dist/src/decorators/authenticated-user.decorator.spec.d.ts +1 -0
  37. package/dist/src/decorators/authenticated-user.decorator.spec.js +28 -0
  38. package/dist/src/decorators/enforcer-options.decorator.d.ts +8 -0
  39. package/dist/src/decorators/enforcer-options.decorator.js +12 -0
  40. package/dist/src/decorators/public.decorator.d.ts +14 -0
  41. package/dist/src/decorators/public.decorator.js +20 -0
  42. package/dist/src/decorators/resource.decorator.d.ts +6 -0
  43. package/dist/src/decorators/resource.decorator.js +11 -0
  44. package/dist/src/decorators/roles.decorator.d.ts +8 -0
  45. package/dist/src/decorators/roles.decorator.js +12 -0
  46. package/dist/src/decorators/scopes.decorator.d.ts +6 -0
  47. package/dist/src/decorators/scopes.decorator.js +11 -0
  48. package/dist/src/guards/auth.guard.d.ts +21 -0
  49. package/dist/src/guards/auth.guard.js +179 -0
  50. package/dist/src/guards/resource.guard.d.ts +19 -0
  51. package/dist/src/guards/resource.guard.js +151 -0
  52. package/dist/src/guards/role.guard.d.ts +18 -0
  53. package/dist/src/guards/role.guard.js +140 -0
  54. package/dist/src/index.d.ts +1 -0
  55. package/dist/src/index.js +18 -0
  56. package/dist/src/interface/keycloak-connect-module-async-options.interface.d.ts +9 -0
  57. package/dist/src/interface/keycloak-connect-module-async-options.interface.js +2 -0
  58. package/dist/src/interface/keycloak-connect-options-factory.interface.d.ts +4 -0
  59. package/dist/src/interface/keycloak-connect-options-factory.interface.js +2 -0
  60. package/dist/src/interface/keycloak-connect-options.interface.d.ts +121 -0
  61. package/dist/src/interface/keycloak-connect-options.interface.js +2 -0
  62. package/dist/src/interface/role-decorator-options.interface.d.ts +11 -0
  63. package/dist/src/interface/role-decorator-options.interface.js +2 -0
  64. package/dist/src/keycloak-connect.module.d.ts +37 -0
  65. package/dist/src/keycloak-connect.module.js +142 -0
  66. package/dist/src/providers/keycloak.provider.d.ts +2 -0
  67. package/dist/src/providers/keycloak.provider.js +25 -0
  68. package/dist/src/providers/logger.provider.d.ts +2 -0
  69. package/dist/src/providers/logger.provider.js +20 -0
  70. package/dist/src/services/keycloak-multitenant.service.d.ts +23 -0
  71. package/dist/src/services/keycloak-multitenant.service.js +139 -0
  72. package/dist/tsconfig.tsbuildinfo +1 -0
  73. package/package.json +135 -0
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Public = exports.Unprotected = exports.META_SKIP_AUTH = exports.META_UNPROTECTED = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ exports.META_UNPROTECTED = 'unprotected';
6
+ exports.META_SKIP_AUTH = 'skip-auth';
7
+ /**
8
+ * Allow user to use unprotected routes.
9
+ * @since 1.2.0
10
+ * @param skipAuth attaches authorization header to user object when `false`, defaults to `true`
11
+ */
12
+ const Unprotected = (skipAuth = true) => (0, common_1.applyDecorators)((0, common_1.SetMetadata)(exports.META_UNPROTECTED, true), (0, common_1.SetMetadata)(exports.META_SKIP_AUTH, skipAuth));
13
+ exports.Unprotected = Unprotected;
14
+ /**
15
+ * Alias for `@Unprotected`.
16
+ * @since 1.2.0
17
+ * @param skipAuth attaches authorization header to user object when `false`, defaults to `true`
18
+ */
19
+ const Public = (skipAuth = true) => (0, common_1.applyDecorators)((0, common_1.SetMetadata)(exports.META_UNPROTECTED, true), (0, common_1.SetMetadata)(exports.META_SKIP_AUTH, skipAuth));
20
+ exports.Public = Public;
@@ -0,0 +1,6 @@
1
+ export declare const META_RESOURCE = "resource";
2
+ /**
3
+ * Keycloak Resource.
4
+ * @param resource - name of resource
5
+ */
6
+ export declare const Resource: (resource: string) => import("@nestjs/common").CustomDecorator<string>;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Resource = exports.META_RESOURCE = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ exports.META_RESOURCE = 'resource';
6
+ /**
7
+ * Keycloak Resource.
8
+ * @param resource - name of resource
9
+ */
10
+ const Resource = (resource) => (0, common_1.SetMetadata)(exports.META_RESOURCE, resource);
11
+ exports.Resource = Resource;
@@ -0,0 +1,8 @@
1
+ import { RoleDecoratorOptionsInterface } from '../interface/role-decorator-options.interface';
2
+ export declare const META_ROLES = "roles";
3
+ /**
4
+ * Keycloak user roles.
5
+ * @param roleMetaData - meta data for roles and matching mode
6
+ * @since 1.1.0
7
+ */
8
+ export declare const Roles: (roleMetaData: RoleDecoratorOptionsInterface) => import("@nestjs/common").CustomDecorator<string>;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Roles = exports.META_ROLES = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ exports.META_ROLES = 'roles';
6
+ /**
7
+ * Keycloak user roles.
8
+ * @param roleMetaData - meta data for roles and matching mode
9
+ * @since 1.1.0
10
+ */
11
+ const Roles = (roleMetaData) => (0, common_1.SetMetadata)(exports.META_ROLES, roleMetaData);
12
+ exports.Roles = Roles;
@@ -0,0 +1,6 @@
1
+ export declare const META_SCOPES = "scopes";
2
+ /**
3
+ * Keycloak Authorization Scopes.
4
+ * @param scopes - scopes that are associated with the resource
5
+ */
6
+ export declare const Scopes: (...scopes: string[]) => import("@nestjs/common").CustomDecorator<string>;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Scopes = exports.META_SCOPES = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ exports.META_SCOPES = 'scopes';
6
+ /**
7
+ * Keycloak Authorization Scopes.
8
+ * @param scopes - scopes that are associated with the resource
9
+ */
10
+ const Scopes = (...scopes) => (0, common_1.SetMetadata)(exports.META_SCOPES, scopes);
11
+ exports.Scopes = Scopes;
@@ -0,0 +1,21 @@
1
+ import { CanActivate, ExecutionContext, Logger } from '@nestjs/common';
2
+ import { Reflector } from '@nestjs/core';
3
+ import * as KeycloakConnect from 'keycloak-connect';
4
+ import { KeycloakMultiTenantService } from '../services/keycloak-multitenant.service';
5
+ import { KeycloakConnectOptions } from '../interface/keycloak-connect-options.interface';
6
+ /**
7
+ * An authentication guard. Will return a 401 unauthorized when it is unable to
8
+ * verify the JWT token or Bearer header is missing.
9
+ */
10
+ export declare class AuthGuard implements CanActivate {
11
+ private singleTenant;
12
+ private keycloakOpts;
13
+ private logger;
14
+ private multiTenant;
15
+ private readonly reflector;
16
+ constructor(singleTenant: KeycloakConnect.Keycloak, keycloakOpts: KeycloakConnectOptions, logger: Logger, multiTenant: KeycloakMultiTenantService, reflector: Reflector);
17
+ canActivate(context: ExecutionContext): Promise<boolean>;
18
+ private validateToken;
19
+ private extractJwtFromHeaders;
20
+ private extractJwtFromCookie;
21
+ }
@@ -0,0 +1,179 @@
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
+ 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;
22
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
23
+ };
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __metadata = (this && this.__metadata) || function (k, v) {
42
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
43
+ };
44
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
45
+ return function (target, key) { decorator(target, key, paramIndex); }
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.AuthGuard = void 0;
49
+ const common_1 = require("@nestjs/common");
50
+ const core_1 = require("@nestjs/core");
51
+ const KeycloakConnect = __importStar(require("keycloak-connect"));
52
+ const public_decorator_1 = require("../decorators/public.decorator");
53
+ const keycloak_multitenant_service_1 = require("../services/keycloak-multitenant.service");
54
+ const constants_1 = require("../constants");
55
+ const extract_request_util_1 = require("../common/utils/extract-request.util");
56
+ const get_keycloak_util_1 = require("../common/utils/get-keycloak.util");
57
+ const parse_token_util_1 = require("../common/utils/parse-token.util");
58
+ /**
59
+ * An authentication guard. Will return a 401 unauthorized when it is unable to
60
+ * verify the JWT token or Bearer header is missing.
61
+ */
62
+ let AuthGuard = class AuthGuard {
63
+ constructor(singleTenant, keycloakOpts, logger, multiTenant, reflector) {
64
+ this.singleTenant = singleTenant;
65
+ this.keycloakOpts = keycloakOpts;
66
+ this.logger = logger;
67
+ this.multiTenant = multiTenant;
68
+ this.reflector = reflector;
69
+ }
70
+ async canActivate(context) {
71
+ const isUnprotected = this.reflector.getAllAndOverride(public_decorator_1.META_UNPROTECTED, [context.getClass(), context.getHandler()]);
72
+ const skipAuth = this.reflector.getAllAndOverride(public_decorator_1.META_SKIP_AUTH, [
73
+ context.getClass(),
74
+ context.getHandler(),
75
+ ]);
76
+ // If unprotected is set skip Keycloak authentication
77
+ if (isUnprotected && skipAuth) {
78
+ return true;
79
+ }
80
+ // Extract request/response
81
+ const [request] = (0, extract_request_util_1.extractRequest)(context);
82
+ // if is not an HTTP request ignore this guard
83
+ if (!request) {
84
+ return true;
85
+ }
86
+ const jwt = this.extractJwtFromCookie(request.cookies) ??
87
+ this.extractJwtFromHeaders(request.headers);
88
+ // Empty jwt, but skipAuth = false, isUnprotected = true allow fallback
89
+ if (!jwt && !skipAuth && isUnprotected) {
90
+ this.logger.verbose('Empty JWT, skipAuth disabled, and a publicly marked route, allowed for fallback');
91
+ return true;
92
+ }
93
+ // Empty jwt given, immediate return
94
+ if (!jwt) {
95
+ this.logger.verbose('Empty JWT, unauthorized');
96
+ throw new common_1.UnauthorizedException();
97
+ }
98
+ this.logger.verbose(`User JWT: ${jwt}`);
99
+ const keycloak = await (0, get_keycloak_util_1.getKeycloakInstance)(request, jwt, this.singleTenant, this.multiTenant, this.keycloakOpts);
100
+ const isValidToken = await this.validateToken(keycloak, jwt);
101
+ if (isValidToken) {
102
+ // Attach user info object
103
+ request.user = (0, parse_token_util_1.parseToken)(jwt);
104
+ // Attach raw access token JWT extracted from bearer/cookie
105
+ request.accessTokenJWT = jwt;
106
+ this.logger.verbose(`Authenticated User: ${JSON.stringify(request.user)}`);
107
+ return true;
108
+ }
109
+ throw new common_1.UnauthorizedException();
110
+ }
111
+ async validateToken(keycloak, jwt) {
112
+ const tokenValidation = this.keycloakOpts.tokenValidation || constants_1.TokenValidation.ONLINE;
113
+ const grantManager = keycloak.grantManager;
114
+ let grant;
115
+ try {
116
+ const grantData = JSON.stringify({ access_token: jwt });
117
+ grant = await grantManager.createGrant(grantData);
118
+ }
119
+ catch (ex) {
120
+ this.logger.warn(`Cannot validate access token: ${ex}`);
121
+ // It will fail to create grants on invalid access token (i.e expired or wrong domain)
122
+ return false;
123
+ }
124
+ const token = grant.access_token;
125
+ if (!token) {
126
+ this.logger.warn(`No token`);
127
+ return false;
128
+ }
129
+ this.logger.verbose(`Using token validation method: ${tokenValidation.toUpperCase()}`);
130
+ try {
131
+ let result;
132
+ switch (tokenValidation) {
133
+ case constants_1.TokenValidation.ONLINE:
134
+ result = await grantManager.validateAccessToken(token);
135
+ return result === token;
136
+ case constants_1.TokenValidation.OFFLINE:
137
+ result = await grantManager.validateToken(token, 'Bearer');
138
+ return result === token;
139
+ case constants_1.TokenValidation.NONE:
140
+ return true;
141
+ default:
142
+ this.logger.warn(`Unknown validation method: ${tokenValidation}`);
143
+ return false;
144
+ }
145
+ }
146
+ catch (ex) {
147
+ this.logger.warn(`Cannot validate access token: ${ex}`);
148
+ }
149
+ return false;
150
+ }
151
+ extractJwtFromHeaders(headers) {
152
+ if (!headers?.authorization) {
153
+ this.logger.verbose(`No authorization header`);
154
+ return null;
155
+ }
156
+ const auth = headers.authorization.split(' ');
157
+ // We only allow bearer
158
+ if (auth[0].toLowerCase() !== 'bearer') {
159
+ this.logger.verbose(`No bearer header`);
160
+ return null;
161
+ }
162
+ return auth[1];
163
+ }
164
+ extractJwtFromCookie(cookies) {
165
+ const cookieKey = this.keycloakOpts.cookieKey || constants_1.KEYCLOAK_COOKIE_DEFAULT;
166
+ return cookies && cookies[cookieKey];
167
+ }
168
+ };
169
+ exports.AuthGuard = AuthGuard;
170
+ exports.AuthGuard = AuthGuard = __decorate([
171
+ (0, common_1.Injectable)(),
172
+ __param(0, (0, common_1.Inject)(constants_1.KEYCLOAK_INSTANCE)),
173
+ __param(1, (0, common_1.Inject)(constants_1.KEYCLOAK_CONNECT_OPTIONS)),
174
+ __param(2, (0, common_1.Inject)(constants_1.KEYCLOAK_LOGGER)),
175
+ __param(3, (0, common_1.Inject)(constants_1.KEYCLOAK_MULTITENANT_SERVICE)),
176
+ __metadata("design:paramtypes", [Object, Object, common_1.Logger,
177
+ keycloak_multitenant_service_1.KeycloakMultiTenantService,
178
+ core_1.Reflector])
179
+ ], AuthGuard);
@@ -0,0 +1,19 @@
1
+ import { CanActivate, ExecutionContext, Logger } from '@nestjs/common';
2
+ import { Reflector } from '@nestjs/core';
3
+ import * as KeycloakConnect from 'keycloak-connect';
4
+ import { KeycloakMultiTenantService } from '../services/keycloak-multitenant.service';
5
+ import { KeycloakConnectOptions } from '../interface/keycloak-connect-options.interface';
6
+ /**
7
+ * This adds a resource guard, which is policy enforcement by default is permissive.
8
+ * Only controllers annotated with `@Resource` and methods with `@Scopes`
9
+ * are handled by this guard.
10
+ */
11
+ export declare class ResourceGuard implements CanActivate {
12
+ private singleTenant;
13
+ private keycloakOpts;
14
+ private logger;
15
+ private multiTenant;
16
+ private readonly reflector;
17
+ constructor(singleTenant: KeycloakConnect.Keycloak, keycloakOpts: KeycloakConnectOptions, logger: Logger, multiTenant: KeycloakMultiTenantService, reflector: Reflector);
18
+ canActivate(context: ExecutionContext): Promise<boolean>;
19
+ }
@@ -0,0 +1,151 @@
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
+ 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;
22
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
23
+ };
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __metadata = (this && this.__metadata) || function (k, v) {
42
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
43
+ };
44
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
45
+ return function (target, key) { decorator(target, key, paramIndex); }
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.ResourceGuard = void 0;
49
+ const common_1 = require("@nestjs/common");
50
+ const core_1 = require("@nestjs/core");
51
+ const KeycloakConnect = __importStar(require("keycloak-connect"));
52
+ const enforcer_options_decorator_1 = require("../decorators/enforcer-options.decorator");
53
+ const public_decorator_1 = require("../decorators/public.decorator");
54
+ const resource_decorator_1 = require("../decorators/resource.decorator");
55
+ const scopes_decorator_1 = require("../decorators/scopes.decorator");
56
+ const keycloak_multitenant_service_1 = require("../services/keycloak-multitenant.service");
57
+ const constants_1 = require("../constants");
58
+ const extract_request_util_1 = require("../common/utils/extract-request.util");
59
+ const get_keycloak_util_1 = require("../common/utils/get-keycloak.util");
60
+ /**
61
+ * This adds a resource guard, which is policy enforcement by default is permissive.
62
+ * Only controllers annotated with `@Resource` and methods with `@Scopes`
63
+ * are handled by this guard.
64
+ */
65
+ let ResourceGuard = class ResourceGuard {
66
+ constructor(singleTenant, keycloakOpts, logger, multiTenant, reflector) {
67
+ this.singleTenant = singleTenant;
68
+ this.keycloakOpts = keycloakOpts;
69
+ this.logger = logger;
70
+ this.multiTenant = multiTenant;
71
+ this.reflector = reflector;
72
+ }
73
+ async canActivate(context) {
74
+ const resource = this.reflector.get(resource_decorator_1.META_RESOURCE, context.getClass());
75
+ const scopes = this.reflector.get(scopes_decorator_1.META_SCOPES, context.getHandler());
76
+ const isUnprotected = this.reflector.getAllAndOverride(public_decorator_1.META_UNPROTECTED, [context.getClass(), context.getHandler()]);
77
+ const enforcerOpts = this.reflector.getAllAndOverride(enforcer_options_decorator_1.META_ENFORCER_OPTIONS, [context.getClass(), context.getHandler()]);
78
+ // Default to permissive
79
+ const pem = this.keycloakOpts.policyEnforcement || constants_1.PolicyEnforcementMode.PERMISSIVE;
80
+ const shouldAllow = pem === constants_1.PolicyEnforcementMode.PERMISSIVE;
81
+ // No resource given, check policy enforcement mode
82
+ if (!resource) {
83
+ if (shouldAllow) {
84
+ this.logger.verbose(`Controller has no @Resource defined, request allowed due to policy enforcement`);
85
+ }
86
+ else {
87
+ this.logger.verbose(`Controller has no @Resource defined, request denied due to policy enforcement`);
88
+ }
89
+ return shouldAllow;
90
+ }
91
+ // No scopes given, check policy enforcement mode
92
+ if (!scopes) {
93
+ if (shouldAllow) {
94
+ this.logger.verbose(`Route has no @Scope defined, request allowed due to policy enforcement`);
95
+ }
96
+ else {
97
+ this.logger.verbose(`Route has no @Scope defined, request denied due to policy enforcement`);
98
+ }
99
+ return shouldAllow;
100
+ }
101
+ this.logger.verbose(`Protecting resource [ ${resource} ] with scopes: [ ${scopes} ]`);
102
+ // Build permissions
103
+ const permissions = scopes.map((scope) => `${resource}:${scope}`);
104
+ // Extract request/response
105
+ const [request, response] = (0, extract_request_util_1.extractRequest)(context);
106
+ const { accessTokenJWT } = request;
107
+ // if is not an HTTP request ignore this guard
108
+ if (!request) {
109
+ return true;
110
+ }
111
+ if (!request.user && isUnprotected) {
112
+ this.logger.verbose(`Route has no user, and is public, allowed`);
113
+ return true;
114
+ }
115
+ if (!accessTokenJWT) {
116
+ this.logger.warn('No access token found in request, are you sure ResourceGuard is first in the chain?');
117
+ return false;
118
+ }
119
+ const user = request.user?.preferred_username ?? 'user';
120
+ const enforcerFn = createEnforcerContext(request, response, enforcerOpts);
121
+ const keycloak = await (0, get_keycloak_util_1.getKeycloakInstance)(request, accessTokenJWT, this.singleTenant, this.multiTenant, this.keycloakOpts);
122
+ const isAllowed = await enforcerFn(keycloak, permissions);
123
+ // If statement for verbose logging only
124
+ if (!isAllowed) {
125
+ this.logger.verbose(`Resource [ ${resource} ] denied to [ ${user} ]`);
126
+ }
127
+ else {
128
+ this.logger.verbose(`Resource [ ${resource} ] granted to [ ${user} ]`);
129
+ }
130
+ return isAllowed;
131
+ }
132
+ };
133
+ exports.ResourceGuard = ResourceGuard;
134
+ exports.ResourceGuard = ResourceGuard = __decorate([
135
+ (0, common_1.Injectable)(),
136
+ __param(0, (0, common_1.Inject)(constants_1.KEYCLOAK_INSTANCE)),
137
+ __param(1, (0, common_1.Inject)(constants_1.KEYCLOAK_CONNECT_OPTIONS)),
138
+ __param(2, (0, common_1.Inject)(constants_1.KEYCLOAK_LOGGER)),
139
+ __param(3, (0, common_1.Inject)(constants_1.KEYCLOAK_MULTITENANT_SERVICE)),
140
+ __metadata("design:paramtypes", [Object, Object, common_1.Logger,
141
+ keycloak_multitenant_service_1.KeycloakMultiTenantService,
142
+ core_1.Reflector])
143
+ ], ResourceGuard);
144
+ const createEnforcerContext = (request, response, options) => (keycloak, permissions) => new Promise((resolve, _) => keycloak.enforcer(permissions, options)(request, response, (_) => {
145
+ if (request.resourceDenied) {
146
+ resolve(false);
147
+ }
148
+ else {
149
+ resolve(true);
150
+ }
151
+ }));
@@ -0,0 +1,18 @@
1
+ import { CanActivate, ExecutionContext, Logger } from '@nestjs/common';
2
+ import { Reflector } from '@nestjs/core';
3
+ import * as KeycloakConnect from 'keycloak-connect';
4
+ import { KeycloakMultiTenantService } from '../services/keycloak-multitenant.service';
5
+ import { KeycloakConnectOptions } from '../interface/keycloak-connect-options.interface';
6
+ /**
7
+ * A permissive type of role guard. Roles are set via `@Roles` decorator.
8
+ * @since 1.1.0
9
+ */
10
+ export declare class RoleGuard implements CanActivate {
11
+ private singleTenant;
12
+ private keycloakOpts;
13
+ private logger;
14
+ private multiTenant;
15
+ private readonly reflector;
16
+ constructor(singleTenant: KeycloakConnect.Keycloak, keycloakOpts: KeycloakConnectOptions, logger: Logger, multiTenant: KeycloakMultiTenantService, reflector: Reflector);
17
+ canActivate(context: ExecutionContext): Promise<boolean>;
18
+ }
@@ -0,0 +1,140 @@
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
+ 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;
22
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
23
+ };
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __metadata = (this && this.__metadata) || function (k, v) {
42
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
43
+ };
44
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
45
+ return function (target, key) { decorator(target, key, paramIndex); }
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.RoleGuard = void 0;
49
+ const common_1 = require("@nestjs/common");
50
+ const core_1 = require("@nestjs/core");
51
+ const KeycloakConnect = __importStar(require("keycloak-connect"));
52
+ const roles_decorator_1 = require("../decorators/roles.decorator");
53
+ const keycloak_multitenant_service_1 = require("../services/keycloak-multitenant.service");
54
+ const constants_1 = require("../constants");
55
+ const extract_request_util_1 = require("../common/utils/extract-request.util");
56
+ const get_keycloak_util_1 = require("../common/utils/get-keycloak.util");
57
+ /**
58
+ * A permissive type of role guard. Roles are set via `@Roles` decorator.
59
+ * @since 1.1.0
60
+ */
61
+ let RoleGuard = class RoleGuard {
62
+ constructor(singleTenant, keycloakOpts, logger, multiTenant, reflector) {
63
+ this.singleTenant = singleTenant;
64
+ this.keycloakOpts = keycloakOpts;
65
+ this.logger = logger;
66
+ this.multiTenant = multiTenant;
67
+ this.reflector = reflector;
68
+ }
69
+ async canActivate(context) {
70
+ const roleMerge = this.keycloakOpts.roleMerge
71
+ ? this.keycloakOpts.roleMerge
72
+ : constants_1.RoleMerge.OVERRIDE;
73
+ const rolesMetaDataArray = [];
74
+ if (roleMerge == constants_1.RoleMerge.ALL) {
75
+ const mergedRoleMetaData = this.reflector.getAllAndMerge(roles_decorator_1.META_ROLES, [context.getClass(), context.getHandler()]);
76
+ if (mergedRoleMetaData) {
77
+ rolesMetaDataArray.push(...mergedRoleMetaData);
78
+ }
79
+ }
80
+ else if (roleMerge == constants_1.RoleMerge.OVERRIDE) {
81
+ const roleMetaData = this.reflector.getAllAndOverride(roles_decorator_1.META_ROLES, [context.getClass(), context.getHandler()]);
82
+ if (roleMetaData) {
83
+ rolesMetaDataArray.push(roleMetaData);
84
+ }
85
+ }
86
+ else {
87
+ throw Error(`Unknown role merge: ${roleMerge}`);
88
+ }
89
+ const combinedRoles = rolesMetaDataArray.flatMap((x) => x.roles);
90
+ if (combinedRoles.length === 0) {
91
+ return true;
92
+ }
93
+ // Use matching mode of first item
94
+ const roleMetaData = rolesMetaDataArray[0];
95
+ const roleMatchingMode = roleMetaData.mode
96
+ ? roleMetaData.mode
97
+ : constants_1.RoleMatchingMode.ANY;
98
+ this.logger.verbose(`Using matching mode: ${roleMatchingMode}`);
99
+ this.logger.verbose(`Roles: ${JSON.stringify(combinedRoles)}`);
100
+ // Extract request
101
+ const [request] = (0, extract_request_util_1.extractRequest)(context);
102
+ const { accessTokenJWT } = request;
103
+ // if is not an HTTP request ignore this guard
104
+ if (!request) {
105
+ return true;
106
+ }
107
+ if (!accessTokenJWT) {
108
+ this.logger.warn('No access token found in request, are you sure AuthGuard is first in the chain?');
109
+ return false;
110
+ }
111
+ // Create grant
112
+ const keycloak = await (0, get_keycloak_util_1.getKeycloakInstance)(request, accessTokenJWT, this.singleTenant, this.multiTenant, this.keycloakOpts);
113
+ const grantData = JSON.stringify({ access_token: accessTokenJWT });
114
+ const grant = await keycloak.grantManager.createGrant(grantData);
115
+ // Grab access token from grant
116
+ const accessToken = grant.access_token;
117
+ // For verbose logging, we store it instead of returning it immediately
118
+ const granted = roleMatchingMode === constants_1.RoleMatchingMode.ANY
119
+ ? combinedRoles.some((r) => accessToken.hasRole(r))
120
+ : combinedRoles.every((r) => accessToken.hasRole(r));
121
+ if (granted) {
122
+ this.logger.verbose(`Resource granted due to role(s)`);
123
+ }
124
+ else {
125
+ this.logger.verbose(`Resource denied due to mismatched role(s)`);
126
+ }
127
+ return granted;
128
+ }
129
+ };
130
+ exports.RoleGuard = RoleGuard;
131
+ exports.RoleGuard = RoleGuard = __decorate([
132
+ (0, common_1.Injectable)(),
133
+ __param(0, (0, common_1.Inject)(constants_1.KEYCLOAK_INSTANCE)),
134
+ __param(1, (0, common_1.Inject)(constants_1.KEYCLOAK_CONNECT_OPTIONS)),
135
+ __param(2, (0, common_1.Inject)(constants_1.KEYCLOAK_LOGGER)),
136
+ __param(3, (0, common_1.Inject)(constants_1.KEYCLOAK_MULTITENANT_SERVICE)),
137
+ __metadata("design:paramtypes", [Object, Object, common_1.Logger,
138
+ keycloak_multitenant_service_1.KeycloakMultiTenantService,
139
+ core_1.Reflector])
140
+ ], RoleGuard);
@@ -0,0 +1 @@
1
+ export * from './keycloak-connect.module';
@@ -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-connect.module"), exports);
@@ -0,0 +1,9 @@
1
+ import { FactoryProvider, ModuleMetadata, Type } from '@nestjs/common/interfaces';
2
+ import { KeycloakConnectOptionsFactory } from './keycloak-connect-options-factory.interface';
3
+ import { KeycloakConnectOptions } from './keycloak-connect-options.interface';
4
+ export interface KeycloakConnectModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
5
+ inject?: FactoryProvider['inject'];
6
+ useExisting?: Type<KeycloakConnectOptionsFactory>;
7
+ useClass?: Type<KeycloakConnectOptionsFactory>;
8
+ useFactory?: (...args: unknown[]) => Promise<KeycloakConnectOptions> | KeycloakConnectOptions;
9
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ import { KeycloakConnectOptions } from './keycloak-connect-options.interface';
2
+ export interface KeycloakConnectOptionsFactory {
3
+ createKeycloakConnectOptions(): Promise<KeycloakConnectOptions> | KeycloakConnectOptions;
4
+ }