@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.
- package/README.md +45 -0
- package/dist/src/async-class-provider.type-guard.d.ts +9 -0
- package/dist/src/async-class-provider.type-guard.js +6 -0
- package/dist/src/common/interfaces/authentication-request.interface.d.ts +11 -0
- package/dist/src/common/interfaces/authentication-request.interface.js +2 -0
- package/dist/src/common/interfaces/jwt-payload.interface.d.ts +12 -0
- package/dist/src/common/interfaces/jwt-payload.interface.js +2 -0
- package/dist/src/common/interfaces/token.interface.d.ts +2 -0
- package/dist/src/common/interfaces/token.interface.js +2 -0
- package/dist/src/common/utils/extract-request.util.d.ts +2 -0
- package/dist/src/common/utils/extract-request.util.js +27 -0
- package/dist/src/common/utils/extract-request.util.spec.d.ts +1 -0
- package/dist/src/common/utils/extract-request.util.spec.js +64 -0
- package/dist/src/common/utils/get-keycloak.util.d.ts +4 -0
- package/dist/src/common/utils/get-keycloak.util.js +25 -0
- package/dist/src/common/utils/get-keycloak.util.spec.d.ts +1 -0
- package/dist/src/common/utils/get-keycloak.util.spec.js +55 -0
- package/dist/src/common/utils/parse-token.util.d.ts +2 -0
- package/dist/src/common/utils/parse-token.util.js +7 -0
- package/dist/src/common/utils/parse-token.util.spec.d.ts +1 -0
- package/dist/src/common/utils/parse-token.util.spec.js +19 -0
- package/dist/src/constants/index.d.ts +5 -0
- package/dist/src/constants/index.js +21 -0
- package/dist/src/constants/keycloak-module.constants.d.ts +20 -0
- package/dist/src/constants/keycloak-module.constants.js +23 -0
- package/dist/src/constants/policy-enforcement-mode.enum.d.ts +13 -0
- package/dist/src/constants/policy-enforcement-mode.enum.js +17 -0
- package/dist/src/constants/role-matching-mode.enum.d.ts +13 -0
- package/dist/src/constants/role-matching-mode.enum.js +17 -0
- package/dist/src/constants/role-merge.enum.d.ts +10 -0
- package/dist/src/constants/role-merge.enum.js +14 -0
- package/dist/src/constants/token-validation.enum.d.ts +17 -0
- package/dist/src/constants/token-validation.enum.js +21 -0
- package/dist/src/decorators/authenticated-user.decorator.d.ts +4 -0
- package/dist/src/decorators/authenticated-user.decorator.js +12 -0
- package/dist/src/decorators/authenticated-user.decorator.spec.d.ts +1 -0
- package/dist/src/decorators/authenticated-user.decorator.spec.js +28 -0
- package/dist/src/decorators/enforcer-options.decorator.d.ts +8 -0
- package/dist/src/decorators/enforcer-options.decorator.js +12 -0
- package/dist/src/decorators/public.decorator.d.ts +14 -0
- package/dist/src/decorators/public.decorator.js +20 -0
- package/dist/src/decorators/resource.decorator.d.ts +6 -0
- package/dist/src/decorators/resource.decorator.js +11 -0
- package/dist/src/decorators/roles.decorator.d.ts +8 -0
- package/dist/src/decorators/roles.decorator.js +12 -0
- package/dist/src/decorators/scopes.decorator.d.ts +6 -0
- package/dist/src/decorators/scopes.decorator.js +11 -0
- package/dist/src/guards/auth.guard.d.ts +21 -0
- package/dist/src/guards/auth.guard.js +179 -0
- package/dist/src/guards/resource.guard.d.ts +19 -0
- package/dist/src/guards/resource.guard.js +151 -0
- package/dist/src/guards/role.guard.d.ts +18 -0
- package/dist/src/guards/role.guard.js +140 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +18 -0
- package/dist/src/interface/keycloak-connect-module-async-options.interface.d.ts +9 -0
- package/dist/src/interface/keycloak-connect-module-async-options.interface.js +2 -0
- package/dist/src/interface/keycloak-connect-options-factory.interface.d.ts +4 -0
- package/dist/src/interface/keycloak-connect-options-factory.interface.js +2 -0
- package/dist/src/interface/keycloak-connect-options.interface.d.ts +121 -0
- package/dist/src/interface/keycloak-connect-options.interface.js +2 -0
- package/dist/src/interface/role-decorator-options.interface.d.ts +11 -0
- package/dist/src/interface/role-decorator-options.interface.js +2 -0
- package/dist/src/keycloak-connect.module.d.ts +37 -0
- package/dist/src/keycloak-connect.module.js +142 -0
- package/dist/src/providers/keycloak.provider.d.ts +2 -0
- package/dist/src/providers/keycloak.provider.js +25 -0
- package/dist/src/providers/logger.provider.d.ts +2 -0
- package/dist/src/providers/logger.provider.js +20 -0
- package/dist/src/services/keycloak-multitenant.service.d.ts +23 -0
- package/dist/src/services/keycloak-multitenant.service.js +139 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- 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,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,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
|
+
}
|