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,8 @@
1
+ import { KeycloakEnforcerOptions } from '../interface/enforcer-options.interface';
2
+ export declare const META_ENFORCER_OPTIONS = "enforcer-options";
3
+ /**
4
+ * Keycloak enforcer options
5
+ * @param opts - enforcer options
6
+ * @since 1.3.0
7
+ */
8
+ export declare const EnforcerOptions: (opts: KeycloakEnforcerOptions) => import("@nestjs/common").CustomDecorator<string>;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EnforcerOptions = exports.META_ENFORCER_OPTIONS = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ exports.META_ENFORCER_OPTIONS = 'enforcer-options';
6
+ /**
7
+ * Keycloak enforcer options
8
+ * @param opts - enforcer options
9
+ * @since 1.3.0
10
+ */
11
+ const EnforcerOptions = (opts) => (0, common_1.SetMetadata)(exports.META_ENFORCER_OPTIONS, opts);
12
+ exports.EnforcerOptions = EnforcerOptions;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Retrieves the current Keycloak logged-in user.
3
+ * @since 1.5.0
4
+ */
5
+ export declare const KeycloakUser: (...dataOrPipes: unknown[]) => ParameterDecorator;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeycloakUser = void 0;
4
+ const internal_util_1 = require("../internal.util");
5
+ const common_1 = require("@nestjs/common");
6
+ /**
7
+ * Retrieves the current Keycloak logged-in user.
8
+ * @since 1.5.0
9
+ */
10
+ exports.KeycloakUser = (0, common_1.createParamDecorator)((data, ctx) => {
11
+ const [req] = (0, internal_util_1.extractRequest)(ctx);
12
+ return req.user;
13
+ });
@@ -0,0 +1,6 @@
1
+ export declare const META_PUBLIC = "public";
2
+ /**
3
+ * Allows unauthorized traffic to enter the route.
4
+ * @since 1.2.0
5
+ */
6
+ export declare const Public: () => import("@nestjs/common").CustomDecorator<string>;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Public = exports.META_PUBLIC = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ exports.META_PUBLIC = 'public';
6
+ /**
7
+ * Allows unauthorized traffic to enter the route.
8
+ * @since 1.2.0
9
+ */
10
+ const Public = () => (0, common_1.SetMetadata)(exports.META_PUBLIC, true);
11
+ 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,10 @@
1
+ import { RoleMatch } from '../constants';
2
+ export declare const META_ROLES = "roles";
3
+ export declare const META_ROLE_MATCHING_MODE = "role-matching-mode";
4
+ /**
5
+ * Keycloak user roles.
6
+ * @param roles - the roles to match
7
+ * @since 2.0.0
8
+ */
9
+ export declare const Roles: (...roles: string[]) => import("@nestjs/common").CustomDecorator<string>;
10
+ export declare const RoleMatchingMode: (mode: RoleMatch) => import("@nestjs/common").CustomDecorator<string>;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RoleMatchingMode = exports.Roles = exports.META_ROLE_MATCHING_MODE = exports.META_ROLES = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ exports.META_ROLES = 'roles';
6
+ exports.META_ROLE_MATCHING_MODE = 'role-matching-mode';
7
+ /**
8
+ * Keycloak user roles.
9
+ * @param roles - the roles to match
10
+ * @since 2.0.0
11
+ */
12
+ const Roles = (...roles) => (0, common_1.SetMetadata)(exports.META_ROLES, roles);
13
+ exports.Roles = Roles;
14
+ const RoleMatchingMode = (mode) => (0, common_1.SetMetadata)(exports.META_ROLE_MATCHING_MODE, mode);
15
+ exports.RoleMatchingMode = RoleMatchingMode;
@@ -0,0 +1,19 @@
1
+ import { ConditionalScopeFn } from '../types/conditional-scope.type';
2
+ export { ConditionalScopeFn };
3
+ export declare const META_SCOPES = "scopes";
4
+ export declare const META_CONDITIONAL_SCOPES = "conditional-scopes";
5
+ /**
6
+ * Keycloak authorization scopes.
7
+ * @param scopes - scopes that are associated with the resource
8
+ */
9
+ export declare const Scopes: (...scopes: string[]) => import("@nestjs/common").CustomDecorator<string>;
10
+ /**
11
+ * Keycloak authorization conditional scopes.
12
+ * @param resolver
13
+ */
14
+ export declare const ConditionalScopes: (resolver: ConditionalScopeFn) => import("@nestjs/common").CustomDecorator<string>;
15
+ /**
16
+ * Retrieves the resolved scopes.
17
+ * @since 1.5.0
18
+ */
19
+ export declare const ResolvedScopes: (...dataOrPipes: unknown[]) => ParameterDecorator;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ResolvedScopes = exports.ConditionalScopes = exports.Scopes = exports.META_CONDITIONAL_SCOPES = exports.META_SCOPES = void 0;
4
+ const internal_util_1 = require("../internal.util");
5
+ const common_1 = require("@nestjs/common");
6
+ exports.META_SCOPES = 'scopes';
7
+ exports.META_CONDITIONAL_SCOPES = 'conditional-scopes';
8
+ /**
9
+ * Keycloak authorization scopes.
10
+ * @param scopes - scopes that are associated with the resource
11
+ */
12
+ const Scopes = (...scopes) => (0, common_1.SetMetadata)(exports.META_SCOPES, scopes);
13
+ exports.Scopes = Scopes;
14
+ /**
15
+ * Keycloak authorization conditional scopes.
16
+ * @param resolver
17
+ */
18
+ const ConditionalScopes = (resolver) => (0, common_1.SetMetadata)(exports.META_CONDITIONAL_SCOPES, resolver);
19
+ exports.ConditionalScopes = ConditionalScopes;
20
+ /**
21
+ * Retrieves the resolved scopes.
22
+ * @since 1.5.0
23
+ */
24
+ exports.ResolvedScopes = (0, common_1.createParamDecorator)((data, ctx) => {
25
+ const [req] = (0, internal_util_1.extractRequest)(ctx);
26
+ return req.scopes;
27
+ });
@@ -0,0 +1,19 @@
1
+ export declare const META_TOKEN_SCOPES = "token-scopes";
2
+ /**
3
+ * Decorator to require specific OAuth2 scopes from the JWT `scope` claim.
4
+ *
5
+ * Unlike `@Scopes()` which checks UMA resource permissions, this decorator
6
+ * validates the space-delimited `scope` string in the access token itself
7
+ * (e.g. `"openid profile email"`).
8
+ *
9
+ * Enforced by `AuthGuard` — returns 403 Forbidden when required scopes
10
+ * are missing (the user is authenticated but lacks the granted scope).
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * @Get('profile')
15
+ * @TokenScopes('openid', 'profile')
16
+ * getProfile() { ... }
17
+ * ```
18
+ */
19
+ export declare const TokenScopes: (...scopes: string[]) => import("@nestjs/common").CustomDecorator<string>;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TokenScopes = exports.META_TOKEN_SCOPES = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ exports.META_TOKEN_SCOPES = 'token-scopes';
6
+ /**
7
+ * Decorator to require specific OAuth2 scopes from the JWT `scope` claim.
8
+ *
9
+ * Unlike `@Scopes()` which checks UMA resource permissions, this decorator
10
+ * validates the space-delimited `scope` string in the access token itself
11
+ * (e.g. `"openid profile email"`).
12
+ *
13
+ * Enforced by `AuthGuard` — returns 403 Forbidden when required scopes
14
+ * are missing (the user is authenticated but lacks the granted scope).
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * @Get('profile')
19
+ * @TokenScopes('openid', 'profile')
20
+ * getProfile() { ... }
21
+ * ```
22
+ */
23
+ const TokenScopes = (...scopes) => (0, common_1.SetMetadata)(exports.META_TOKEN_SCOPES, scopes);
24
+ exports.TokenScopes = TokenScopes;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Base error class for all nestjs-keycloak-auth errors.
3
+ * Consumers can catch `instanceof KeycloakAuthError` to handle any library error.
4
+ */
5
+ export declare class KeycloakAuthError extends Error {
6
+ readonly code: string;
7
+ constructor(message: string, code?: string);
8
+ }
9
+ /** Configuration invariants, missing values, file not found. */
10
+ export declare class KeycloakConfigError extends KeycloakAuthError {
11
+ constructor(message: string);
12
+ }
13
+ /** Token parsing, grant validation, JWKS key not found. */
14
+ export declare class KeycloakTokenError extends KeycloakAuthError {
15
+ constructor(message: string);
16
+ }
17
+ /** Permission check failures. */
18
+ export declare class KeycloakPermissionError extends KeycloakAuthError {
19
+ constructor(message: string);
20
+ }
21
+ /** Admin callback signature verification, invalid admin tokens. */
22
+ export declare class KeycloakAdminError extends KeycloakAuthError {
23
+ constructor(message: string);
24
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeycloakAdminError = exports.KeycloakPermissionError = exports.KeycloakTokenError = exports.KeycloakConfigError = exports.KeycloakAuthError = void 0;
4
+ /**
5
+ * Base error class for all nestjs-keycloak-auth errors.
6
+ * Consumers can catch `instanceof KeycloakAuthError` to handle any library error.
7
+ */
8
+ class KeycloakAuthError extends Error {
9
+ constructor(message, code = 'KEYCLOAK_AUTH_ERROR') {
10
+ super(message);
11
+ this.name = this.constructor.name;
12
+ this.code = code;
13
+ Object.setPrototypeOf(this, new.target.prototype);
14
+ }
15
+ }
16
+ exports.KeycloakAuthError = KeycloakAuthError;
17
+ /** Configuration invariants, missing values, file not found. */
18
+ class KeycloakConfigError extends KeycloakAuthError {
19
+ constructor(message) {
20
+ super(message, 'KEYCLOAK_CONFIG_ERROR');
21
+ }
22
+ }
23
+ exports.KeycloakConfigError = KeycloakConfigError;
24
+ /** Token parsing, grant validation, JWKS key not found. */
25
+ class KeycloakTokenError extends KeycloakAuthError {
26
+ constructor(message) {
27
+ super(message, 'KEYCLOAK_TOKEN_ERROR');
28
+ }
29
+ }
30
+ exports.KeycloakTokenError = KeycloakTokenError;
31
+ /** Permission check failures. */
32
+ class KeycloakPermissionError extends KeycloakAuthError {
33
+ constructor(message) {
34
+ super(message, 'KEYCLOAK_PERMISSION_ERROR');
35
+ }
36
+ }
37
+ exports.KeycloakPermissionError = KeycloakPermissionError;
38
+ /** Admin callback signature verification, invalid admin tokens. */
39
+ class KeycloakAdminError extends KeycloakAuthError {
40
+ constructor(message) {
41
+ super(message, 'KEYCLOAK_ADMIN_ERROR');
42
+ }
43
+ }
44
+ exports.KeycloakAdminError = KeycloakAdminError;
@@ -0,0 +1,25 @@
1
+ import { KeycloakGrantService } from '../services/keycloak-grant.service';
2
+ import { ResolvedTenantConfig } from '../interface/tenant-config.interface';
3
+ import { TokenValidationService } from '../services/token-validation.service';
4
+ import { BackchannelLogoutService } from '../services/backchannel-logout.service';
5
+ import { KeycloakAuthConfig } from '../interface/keycloak-auth-options.interface';
6
+ import { KeycloakMultiTenantService } from '../services/keycloak-multitenant.service';
7
+ import { CanActivate, ExecutionContext } from '@nestjs/common';
8
+ /**
9
+ * An authentication guard. Will return a 401 unauthorized when it is unable to
10
+ * verify the JWT token or Bearer header is missing.
11
+ */
12
+ export declare class AuthGuard implements CanActivate {
13
+ private singleTenant;
14
+ private keycloakOpts;
15
+ private multiTenant;
16
+ private tokenValidation;
17
+ private keycloakGrant;
18
+ private backchannelLogout;
19
+ private readonly logger;
20
+ private readonly reflector;
21
+ constructor(singleTenant: ResolvedTenantConfig, keycloakOpts: KeycloakAuthConfig, multiTenant: KeycloakMultiTenantService, tokenValidation: TokenValidationService, keycloakGrant: KeycloakGrantService, backchannelLogout: BackchannelLogoutService);
22
+ canActivate(context: ExecutionContext): Promise<boolean>;
23
+ private validateToken;
24
+ private extractJwt;
25
+ }
@@ -0,0 +1,176 @@
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 AuthGuard_1;
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.AuthGuard = void 0;
26
+ const util_1 = require("../util");
27
+ const core_1 = require("@nestjs/core");
28
+ const public_decorator_1 = require("../decorators/public.decorator");
29
+ const internal_util_1 = require("../internal.util");
30
+ const token_scopes_decorator_1 = require("../decorators/token-scopes.decorator");
31
+ const keycloak_grant_service_1 = require("../services/keycloak-grant.service");
32
+ const token_validation_service_1 = require("../services/token-validation.service");
33
+ const backchannel_logout_service_1 = require("../services/backchannel-logout.service");
34
+ const keycloak_multitenant_service_1 = require("../services/keycloak-multitenant.service");
35
+ const common_1 = require("@nestjs/common");
36
+ const constants_1 = require("../constants");
37
+ /**
38
+ * An authentication guard. Will return a 401 unauthorized when it is unable to
39
+ * verify the JWT token or Bearer header is missing.
40
+ */
41
+ let AuthGuard = AuthGuard_1 = class AuthGuard {
42
+ constructor(singleTenant, keycloakOpts, multiTenant, tokenValidation, keycloakGrant, backchannelLogout) {
43
+ this.singleTenant = singleTenant;
44
+ this.keycloakOpts = keycloakOpts;
45
+ this.multiTenant = multiTenant;
46
+ this.tokenValidation = tokenValidation;
47
+ this.keycloakGrant = keycloakGrant;
48
+ this.backchannelLogout = backchannelLogout;
49
+ this.logger = new common_1.Logger(AuthGuard_1.name);
50
+ this.reflector = new core_1.Reflector();
51
+ }
52
+ canActivate(context) {
53
+ return __awaiter(this, void 0, void 0, function* () {
54
+ var _a, _b, _c, _d;
55
+ const isPublic = this.reflector.getAllAndOverride(public_decorator_1.META_PUBLIC, [
56
+ context.getClass(),
57
+ context.getHandler(),
58
+ ]);
59
+ // Extract request/response
60
+ const [request] = (0, internal_util_1.extractRequest)(context);
61
+ // if is not an HTTP request ignore this guard
62
+ if (!request) {
63
+ return true;
64
+ }
65
+ const jwt = this.extractJwt(request.headers);
66
+ const isJwtEmpty = jwt === null || jwt === undefined;
67
+ // Not a public route, require jwt
68
+ if (!isPublic && isJwtEmpty) {
69
+ this.logger.verbose('Empty jwt, unauthorized');
70
+ throw new common_1.UnauthorizedException();
71
+ }
72
+ // Public route, no jwt sent
73
+ if (isPublic && isJwtEmpty) {
74
+ return true;
75
+ }
76
+ this.logger.verbose(`Validating jwt [${jwt.substring(0, 20)}...]`);
77
+ const tenantConfig = yield (0, internal_util_1.useTenantConfig)(request, jwt, this.singleTenant, this.multiTenant, this.keycloakOpts);
78
+ const isValidToken = yield this.validateToken(tenantConfig, jwt);
79
+ if (isValidToken) {
80
+ // Attach user info object
81
+ request.user = (0, util_1.parseToken)(jwt);
82
+ // Attach raw access token JWT extracted from bearer
83
+ request.accessToken = jwt;
84
+ // Check back-channel logout revocation
85
+ const sid = (_a = request.user) === null || _a === void 0 ? void 0 : _a.sid;
86
+ const sub = (_b = request.user) === null || _b === void 0 ? void 0 : _b.sub;
87
+ const iat = (_c = request.user) === null || _c === void 0 ? void 0 : _c.iat;
88
+ if (this.backchannelLogout.isRevoked(sid, sub, iat)) {
89
+ this.logger.verbose('Session or user has been revoked via back-channel logout');
90
+ if (isPublic) {
91
+ request.user = undefined;
92
+ request.accessToken = undefined;
93
+ return true;
94
+ }
95
+ throw new common_1.UnauthorizedException();
96
+ }
97
+ // Check @TokenScopes() decorator requirements
98
+ const requiredScopes = this.reflector.getAllAndOverride(token_scopes_decorator_1.META_TOKEN_SCOPES, [context.getClass(), context.getHandler()]);
99
+ if (requiredScopes && requiredScopes.length > 0) {
100
+ const scopeClaim = (_d = request.user) === null || _d === void 0 ? void 0 : _d.scope;
101
+ const grantedScopes = new Set(typeof scopeClaim === 'string' ? scopeClaim.split(' ') : []);
102
+ const missing = requiredScopes.filter((s) => !grantedScopes.has(s));
103
+ if (missing.length > 0) {
104
+ this.logger.verbose(`Token missing required scopes: ${missing.join(', ')}`);
105
+ throw new common_1.ForbiddenException(`Missing required token scopes: ${missing.join(', ')}`);
106
+ }
107
+ }
108
+ this.logger.verbose('User authenticated', { user: request.user });
109
+ return true;
110
+ }
111
+ // Valid token should return, this time we warn
112
+ if (isPublic) {
113
+ this.logger.warn('A jwt token was retrieved but failed validation.');
114
+ request.user = undefined;
115
+ request.accessToken = undefined;
116
+ return true;
117
+ }
118
+ throw new common_1.UnauthorizedException();
119
+ });
120
+ }
121
+ validateToken(tenantConfig, jwt) {
122
+ return __awaiter(this, void 0, void 0, function* () {
123
+ const tokenValidationMethod = this.keycloakOpts.tokenValidation || constants_1.TokenValidation.ONLINE;
124
+ this.logger.verbose(`Using token validation method: ${tokenValidationMethod.toUpperCase()}`);
125
+ try {
126
+ switch (tokenValidationMethod) {
127
+ case constants_1.TokenValidation.ONLINE:
128
+ try {
129
+ yield this.keycloakGrant.createGrant({ access_token: jwt }, tenantConfig.realmUrl, tenantConfig.clientId);
130
+ }
131
+ catch (ex) {
132
+ this.logger.warn(`Cannot validate access token: ${ex}`);
133
+ return false;
134
+ }
135
+ return yield this.tokenValidation.validateOnline(jwt, tenantConfig.realmUrl, tenantConfig.clientId, tenantConfig.secret);
136
+ case constants_1.TokenValidation.OFFLINE:
137
+ return yield this.tokenValidation.validateOffline(jwt, tenantConfig.realmUrl, tenantConfig.clientId);
138
+ case constants_1.TokenValidation.NONE:
139
+ return true;
140
+ default:
141
+ this.logger.warn(`Unknown validation method: ${tokenValidationMethod}`);
142
+ return false;
143
+ }
144
+ }
145
+ catch (ex) {
146
+ this.logger.warn(`Cannot validate access token: ${ex}`);
147
+ }
148
+ return false;
149
+ });
150
+ }
151
+ extractJwt(headers) {
152
+ const authorization = headers === null || headers === void 0 ? void 0 : headers.authorization;
153
+ if (!authorization || typeof authorization !== 'string') {
154
+ this.logger.verbose('No authorization header');
155
+ return null;
156
+ }
157
+ const auth = authorization.split(' ');
158
+ // We only allow bearer
159
+ if (auth[0].toLowerCase() !== 'bearer') {
160
+ this.logger.verbose('No bearer header');
161
+ return null;
162
+ }
163
+ return auth[1];
164
+ }
165
+ };
166
+ exports.AuthGuard = AuthGuard;
167
+ exports.AuthGuard = AuthGuard = AuthGuard_1 = __decorate([
168
+ (0, common_1.Injectable)(),
169
+ __param(0, (0, common_1.Inject)(constants_1.KEYCLOAK_INSTANCE)),
170
+ __param(1, (0, common_1.Inject)(constants_1.KEYCLOAK_AUTH_OPTIONS)),
171
+ __param(2, (0, common_1.Inject)(constants_1.KEYCLOAK_MULTITENANT_SERVICE)),
172
+ __metadata("design:paramtypes", [Object, Object, keycloak_multitenant_service_1.KeycloakMultiTenantService,
173
+ token_validation_service_1.TokenValidationService,
174
+ keycloak_grant_service_1.KeycloakGrantService,
175
+ backchannel_logout_service_1.BackchannelLogoutService])
176
+ ], AuthGuard);
@@ -0,0 +1,26 @@
1
+ import { KeycloakHttpService } from '../services/keycloak-http.service';
2
+ import { ResolvedTenantConfig } from '../interface/tenant-config.interface';
3
+ import { KeycloakAuthConfig } from '../interface/keycloak-auth-options.interface';
4
+ import { KeycloakMultiTenantService } from '../services/keycloak-multitenant.service';
5
+ import { CanActivate, ExecutionContext } from '@nestjs/common';
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 multiTenant;
15
+ private keycloakHttp;
16
+ private readonly logger;
17
+ private readonly reflector;
18
+ constructor(singleTenant: ResolvedTenantConfig, keycloakOpts: KeycloakAuthConfig, multiTenant: KeycloakMultiTenantService, keycloakHttp: KeycloakHttpService);
19
+ canActivate(context: ExecutionContext): Promise<boolean>;
20
+ /**
21
+ * Validate server-returned permissions against expected resource:scope pairs.
22
+ * Matches keycloak-connect enforcer.js handlePermissions logic.
23
+ */
24
+ private validatePermissionsLocally;
25
+ private resolveClaims;
26
+ }