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.
- package/LICENSE +21 -0
- package/README.md +391 -0
- package/dist/constants.d.ts +65 -0
- package/dist/constants.js +72 -0
- package/dist/controllers/keycloak-admin.controller.d.ts +16 -0
- package/dist/controllers/keycloak-admin.controller.js +94 -0
- package/dist/decorators/access-token.decorator.d.ts +5 -0
- package/dist/decorators/access-token.decorator.js +13 -0
- package/dist/decorators/enforcer-options.decorator.d.ts +8 -0
- package/dist/decorators/enforcer-options.decorator.js +12 -0
- package/dist/decorators/keycloak-user.decorator.d.ts +5 -0
- package/dist/decorators/keycloak-user.decorator.js +13 -0
- package/dist/decorators/public.decorator.d.ts +6 -0
- package/dist/decorators/public.decorator.js +11 -0
- package/dist/decorators/resource.decorator.d.ts +6 -0
- package/dist/decorators/resource.decorator.js +11 -0
- package/dist/decorators/roles.decorator.d.ts +10 -0
- package/dist/decorators/roles.decorator.js +15 -0
- package/dist/decorators/scopes.decorator.d.ts +19 -0
- package/dist/decorators/scopes.decorator.js +27 -0
- package/dist/decorators/token-scopes.decorator.d.ts +19 -0
- package/dist/decorators/token-scopes.decorator.js +24 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.js +44 -0
- package/dist/guards/auth.guard.d.ts +25 -0
- package/dist/guards/auth.guard.js +176 -0
- package/dist/guards/resource.guard.d.ts +26 -0
- package/dist/guards/resource.guard.js +245 -0
- package/dist/guards/role.guard.d.ts +17 -0
- package/dist/guards/role.guard.js +113 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +18 -0
- package/dist/interface/enforcer-options.interface.d.ts +8 -0
- package/dist/interface/enforcer-options.interface.js +2 -0
- package/dist/interface/jwks.interface.d.ts +22 -0
- package/dist/interface/jwks.interface.js +2 -0
- package/dist/interface/jwt.interface.d.ts +46 -0
- package/dist/interface/jwt.interface.js +2 -0
- package/dist/interface/keycloak-auth-module-async-options.interface.d.ts +11 -0
- package/dist/interface/keycloak-auth-module-async-options.interface.js +2 -0
- package/dist/interface/keycloak-auth-options-factory.interface.d.ts +4 -0
- package/dist/interface/keycloak-auth-options-factory.interface.js +2 -0
- package/dist/interface/keycloak-auth-options.interface.d.ts +162 -0
- package/dist/interface/keycloak-auth-options.interface.js +3 -0
- package/dist/interface/keycloak-grant.interface.d.ts +33 -0
- package/dist/interface/keycloak-grant.interface.js +2 -0
- package/dist/interface/keycloak-request.interface.d.ts +8 -0
- package/dist/interface/keycloak-request.interface.js +2 -0
- package/dist/interface/oidc.interface.d.ts +14 -0
- package/dist/interface/oidc.interface.js +2 -0
- package/dist/interface/server.interface.d.ts +9 -0
- package/dist/interface/server.interface.js +2 -0
- package/dist/interface/tenant-config.interface.d.ts +13 -0
- package/dist/interface/tenant-config.interface.js +2 -0
- package/dist/internal.util.d.ts +13 -0
- package/dist/internal.util.js +54 -0
- package/dist/keycloak-auth.module.d.ts +54 -0
- package/dist/keycloak-auth.module.js +174 -0
- package/dist/keycloak-auth.providers.d.ts +7 -0
- package/dist/keycloak-auth.providers.js +122 -0
- package/dist/services/backchannel-logout.service.d.ts +30 -0
- package/dist/services/backchannel-logout.service.js +100 -0
- package/dist/services/jwks-cache.service.d.ts +25 -0
- package/dist/services/jwks-cache.service.js +130 -0
- package/dist/services/keycloak-admin.service.d.ts +37 -0
- package/dist/services/keycloak-admin.service.js +268 -0
- package/dist/services/keycloak-grant.service.d.ts +23 -0
- package/dist/services/keycloak-grant.service.js +88 -0
- package/dist/services/keycloak-http.service.d.ts +41 -0
- package/dist/services/keycloak-http.service.js +211 -0
- package/dist/services/keycloak-multitenant.service.d.ts +24 -0
- package/dist/services/keycloak-multitenant.service.js +161 -0
- package/dist/services/keycloak-url.service.d.ts +12 -0
- package/dist/services/keycloak-url.service.js +43 -0
- package/dist/services/oidc-discovery.service.d.ts +24 -0
- package/dist/services/oidc-discovery.service.js +86 -0
- package/dist/services/token-validation.service.d.ts +29 -0
- package/dist/services/token-validation.service.js +215 -0
- package/dist/token/keycloak-grant.d.ts +24 -0
- package/dist/token/keycloak-grant.js +37 -0
- package/dist/token/keycloak-token.d.ts +57 -0
- package/dist/token/keycloak-token.js +105 -0
- package/dist/types/conditional-scope.type.d.ts +2 -0
- package/dist/types/conditional-scope.type.js +2 -0
- package/dist/util.d.ts +3 -0
- package/dist/util.js +17 -0
- package/package.json +130 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
import { JwtContent, JwtHeader } from '../interface/jwt.interface';
|
|
4
|
+
export { JwtContent, JwtHeader };
|
|
5
|
+
export type { JwtPermission } from '../interface/jwt.interface';
|
|
6
|
+
/**
|
|
7
|
+
* Native KeycloakToken class that replaces keycloak-connect's Token.
|
|
8
|
+
* Parses a JWT and provides role/permission checking methods.
|
|
9
|
+
*/
|
|
10
|
+
export declare class KeycloakToken {
|
|
11
|
+
private readonly _token;
|
|
12
|
+
private readonly clientId?;
|
|
13
|
+
readonly header: JwtHeader;
|
|
14
|
+
readonly content: JwtContent;
|
|
15
|
+
readonly signature: Buffer;
|
|
16
|
+
readonly signed: string;
|
|
17
|
+
constructor(_token: string, clientId?: string);
|
|
18
|
+
/**
|
|
19
|
+
* The raw JWT string. Matches keycloak-connect's `token.token` property.
|
|
20
|
+
*/
|
|
21
|
+
get token(): string;
|
|
22
|
+
/**
|
|
23
|
+
* Check if the token has a specific role.
|
|
24
|
+
* Matches keycloak-connect's Token.hasRole() logic:
|
|
25
|
+
* - "realm:roleName" checks realm_access.roles
|
|
26
|
+
* - "clientId:roleName" checks resource_access[clientId].roles
|
|
27
|
+
* - "roleName" checks resource_access[this.clientId].roles
|
|
28
|
+
*/
|
|
29
|
+
hasRole(name: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Check if the token has a realm-level role.
|
|
32
|
+
*/
|
|
33
|
+
hasRealmRole(roleName: string): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Check if the token has an application/client-level role.
|
|
36
|
+
*/
|
|
37
|
+
hasApplicationRole(clientId: string, roleName: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Check if the token has a specific permission (for UMA/resource server).
|
|
40
|
+
* Matches keycloak-connect's Token.hasPermission() semantics:
|
|
41
|
+
* - If matching resource found with no scope requested, returns true.
|
|
42
|
+
* - If matching resource found with scope requested and scopes array
|
|
43
|
+
* exists with entries but does NOT include the scope, returns false.
|
|
44
|
+
* - If matching resource found with scope requested and scopes array
|
|
45
|
+
* is empty or absent, returns true (permission is granted without scope restriction).
|
|
46
|
+
*/
|
|
47
|
+
hasPermission(resource: string, scope?: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Check if the token is expired.
|
|
50
|
+
* Matches keycloak-connect's behavior: exp=0 is considered expired.
|
|
51
|
+
*/
|
|
52
|
+
isExpired(): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Get the raw JWT string.
|
|
55
|
+
*/
|
|
56
|
+
toString(): string;
|
|
57
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KeycloakToken = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Native KeycloakToken class that replaces keycloak-connect's Token.
|
|
6
|
+
* Parses a JWT and provides role/permission checking methods.
|
|
7
|
+
*/
|
|
8
|
+
class KeycloakToken {
|
|
9
|
+
constructor(_token, clientId) {
|
|
10
|
+
this._token = _token;
|
|
11
|
+
this.clientId = clientId;
|
|
12
|
+
this.header = {};
|
|
13
|
+
this.content = { exp: 0 };
|
|
14
|
+
this.signature = Buffer.alloc(0);
|
|
15
|
+
this.signed = '';
|
|
16
|
+
if (_token) {
|
|
17
|
+
try {
|
|
18
|
+
const parts = _token.split('.');
|
|
19
|
+
this.header = JSON.parse(Buffer.from(parts[0], 'base64').toString('utf8'));
|
|
20
|
+
this.content = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf8'));
|
|
21
|
+
this.signature = Buffer.from(parts[2], 'base64');
|
|
22
|
+
this.signed = `${parts[0]}.${parts[1]}`;
|
|
23
|
+
}
|
|
24
|
+
catch (_a) {
|
|
25
|
+
// defaults already set above
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The raw JWT string. Matches keycloak-connect's `token.token` property.
|
|
31
|
+
*/
|
|
32
|
+
get token() {
|
|
33
|
+
return this._token;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Check if the token has a specific role.
|
|
37
|
+
* Matches keycloak-connect's Token.hasRole() logic:
|
|
38
|
+
* - "realm:roleName" checks realm_access.roles
|
|
39
|
+
* - "clientId:roleName" checks resource_access[clientId].roles
|
|
40
|
+
* - "roleName" checks resource_access[this.clientId].roles
|
|
41
|
+
*/
|
|
42
|
+
hasRole(name) {
|
|
43
|
+
if (!this.clientId) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const parts = name.split(':');
|
|
47
|
+
if (parts.length === 1) {
|
|
48
|
+
// No prefix — check the default client's resource_access
|
|
49
|
+
return this.hasApplicationRole(this.clientId, parts[0]);
|
|
50
|
+
}
|
|
51
|
+
if (parts[0] === 'realm') {
|
|
52
|
+
return this.hasRealmRole(parts[1]);
|
|
53
|
+
}
|
|
54
|
+
return this.hasApplicationRole(parts[0], parts[1]);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if the token has a realm-level role.
|
|
58
|
+
*/
|
|
59
|
+
hasRealmRole(roleName) {
|
|
60
|
+
var _a, _b, _c;
|
|
61
|
+
return (_c = (_b = (_a = this.content.realm_access) === null || _a === void 0 ? void 0 : _a.roles) === null || _b === void 0 ? void 0 : _b.includes(roleName)) !== null && _c !== void 0 ? _c : false;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Check if the token has an application/client-level role.
|
|
65
|
+
*/
|
|
66
|
+
hasApplicationRole(clientId, roleName) {
|
|
67
|
+
var _a, _b, _c, _d;
|
|
68
|
+
return ((_d = (_c = (_b = (_a = this.content.resource_access) === null || _a === void 0 ? void 0 : _a[clientId]) === null || _b === void 0 ? void 0 : _b.roles) === null || _c === void 0 ? void 0 : _c.includes(roleName)) !== null && _d !== void 0 ? _d : false);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if the token has a specific permission (for UMA/resource server).
|
|
72
|
+
* Matches keycloak-connect's Token.hasPermission() semantics:
|
|
73
|
+
* - If matching resource found with no scope requested, returns true.
|
|
74
|
+
* - If matching resource found with scope requested and scopes array
|
|
75
|
+
* exists with entries but does NOT include the scope, returns false.
|
|
76
|
+
* - If matching resource found with scope requested and scopes array
|
|
77
|
+
* is empty or absent, returns true (permission is granted without scope restriction).
|
|
78
|
+
*/
|
|
79
|
+
hasPermission(resource, scope) {
|
|
80
|
+
var _a, _b, _c;
|
|
81
|
+
const match = (_b = (_a = this.content.authorization) === null || _a === void 0 ? void 0 : _a.permissions) === null || _b === void 0 ? void 0 : _b.find((p) => p.rsid === resource || p.rsname === resource);
|
|
82
|
+
if (!match) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
// Scope requested and the permission has an explicit scope list that doesn't include it
|
|
86
|
+
if (scope && ((_c = match.scopes) === null || _c === void 0 ? void 0 : _c.length) > 0 && !match.scopes.includes(scope)) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if the token is expired.
|
|
93
|
+
* Matches keycloak-connect's behavior: exp=0 is considered expired.
|
|
94
|
+
*/
|
|
95
|
+
isExpired() {
|
|
96
|
+
return this.content.exp * 1000 < Date.now();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get the raw JWT string.
|
|
100
|
+
*/
|
|
101
|
+
toString() {
|
|
102
|
+
return this._token;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.KeycloakToken = KeycloakToken;
|
package/dist/util.d.ts
ADDED
package/dist/util.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseToken = void 0;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
const parseToken = (token) => {
|
|
6
|
+
const parts = token.split('.');
|
|
7
|
+
if (parts.length < 2) {
|
|
8
|
+
throw new errors_1.KeycloakTokenError('Malformed JWT: expected at least 2 segments');
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(Buffer.from(parts[1], 'base64').toString());
|
|
12
|
+
}
|
|
13
|
+
catch (_a) {
|
|
14
|
+
throw new errors_1.KeycloakTokenError('Malformed JWT: payload is not valid JSON');
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
exports.parseToken = parseToken;
|
package/package.json
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nestjs-keycloak-auth",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Keycloak authentication and authorization module for NestJS",
|
|
5
|
+
"author": "B. Joshua Adedigba",
|
|
6
|
+
"contributors": [
|
|
7
|
+
{
|
|
8
|
+
"name": "B. Joshua Adedigba",
|
|
9
|
+
"email": "tbannaarr@gmail.com",
|
|
10
|
+
"url": "https://github.com/bannaarr01"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"start:dev": "tsc -w",
|
|
16
|
+
"clean": "rimraf dist",
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"test": "jest",
|
|
19
|
+
"test:cov": "jest --coverage --runInBand",
|
|
20
|
+
"release": "release-it",
|
|
21
|
+
"release:alpha": "release-it --preReleaseId=alpha --npm.tag=next --github.preRelease",
|
|
22
|
+
"format": "prettier src --write",
|
|
23
|
+
"lint": "eslint src",
|
|
24
|
+
"lint:fix": "eslint src --fix",
|
|
25
|
+
"prepare": "husky"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"nestjs",
|
|
29
|
+
"nest",
|
|
30
|
+
"keycloak",
|
|
31
|
+
"nest-keycloak",
|
|
32
|
+
"keycloak-nest",
|
|
33
|
+
"typescript",
|
|
34
|
+
"authentication",
|
|
35
|
+
"authorization",
|
|
36
|
+
"oauth2",
|
|
37
|
+
"openid-connect",
|
|
38
|
+
"oidc",
|
|
39
|
+
"rbac",
|
|
40
|
+
"guard",
|
|
41
|
+
"uma",
|
|
42
|
+
"multi-tenant",
|
|
43
|
+
"jwt",
|
|
44
|
+
"sso",
|
|
45
|
+
"access-control"
|
|
46
|
+
],
|
|
47
|
+
"homepage": "https://github.com/bannaarr01/nestjs-keycloak-auth#readme",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/bannaarr01/nestjs-keycloak-auth"
|
|
51
|
+
},
|
|
52
|
+
"bugs": "https://github.com/bannaarr01/nestjs-keycloak-auth/issues",
|
|
53
|
+
"files": [
|
|
54
|
+
"dist"
|
|
55
|
+
],
|
|
56
|
+
"main": "./dist/index.js",
|
|
57
|
+
"types": "./dist/index.d.ts",
|
|
58
|
+
"typesVersions": {
|
|
59
|
+
"*": {
|
|
60
|
+
"dist/index.d.ts": [
|
|
61
|
+
"dist/index.d.ts"
|
|
62
|
+
],
|
|
63
|
+
"*": [
|
|
64
|
+
"dist/*"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"exports": {
|
|
69
|
+
".": "./dist/index.js",
|
|
70
|
+
"./*": "./dist/*"
|
|
71
|
+
},
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"@nestjs/axios": ">=3.0.0",
|
|
74
|
+
"@nestjs/common": ">=10.0.0 <12.0.0",
|
|
75
|
+
"@nestjs/core": ">=10.0.0 <12.0.0",
|
|
76
|
+
"axios": ">=1.0.0"
|
|
77
|
+
},
|
|
78
|
+
"devDependencies": {
|
|
79
|
+
"@nestjs/axios": "^4.0.0",
|
|
80
|
+
"@nestjs/common": "^11.0.6",
|
|
81
|
+
"@nestjs/core": "^11.0.6",
|
|
82
|
+
"@nestjs/graphql": "^13.0.2",
|
|
83
|
+
"@release-it/conventional-changelog": "^10.0.6",
|
|
84
|
+
"@types/express": "^4.17.21",
|
|
85
|
+
"@types/jest": "^30.0.0",
|
|
86
|
+
"@types/node": "^18.19.23",
|
|
87
|
+
"axios": "^1.7.0",
|
|
88
|
+
"class-transformer": "0.5.1",
|
|
89
|
+
"class-validator": "0.14.1",
|
|
90
|
+
"cpr": "3.0.1",
|
|
91
|
+
"eslint": "^9.39.4",
|
|
92
|
+
"eslint-config-prettier": "9.1.0",
|
|
93
|
+
"eslint-plugin-prettier": "5.1.3",
|
|
94
|
+
"eslint-plugin-unused-imports": "^4.4.1",
|
|
95
|
+
"graphql": "^16.10.0",
|
|
96
|
+
"husky": "^9.1.7",
|
|
97
|
+
"jest": "^30.3.0",
|
|
98
|
+
"lint-staged": "^15.5.2",
|
|
99
|
+
"prettier": "3.2.5",
|
|
100
|
+
"reflect-metadata": "0.2.1",
|
|
101
|
+
"release-it": "^19.2.4",
|
|
102
|
+
"rimraf": "3.0.2",
|
|
103
|
+
"rxjs": "7.8.1",
|
|
104
|
+
"ts-jest": "^29.4.6",
|
|
105
|
+
"ts-node": "10.8.2",
|
|
106
|
+
"typescript": "5.4.2",
|
|
107
|
+
"typescript-eslint": "^8.57.1"
|
|
108
|
+
},
|
|
109
|
+
"peerDependenciesMeta": {
|
|
110
|
+
"@nestjs/axios": {
|
|
111
|
+
"optional": false
|
|
112
|
+
},
|
|
113
|
+
"axios": {
|
|
114
|
+
"optional": false
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"volta": {
|
|
118
|
+
"node": "21.7.1"
|
|
119
|
+
},
|
|
120
|
+
"lint-staged": {
|
|
121
|
+
"**/*.ts": [
|
|
122
|
+
"eslint"
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
"overrides": {
|
|
126
|
+
"release-it": {
|
|
127
|
+
"undici": "^7.0.0"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|