@pawells/nestjs-auth 1.0.0-dev.4c8c698
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 +602 -0
- package/build/LICENSE +21 -0
- package/build/README.md +602 -0
- package/build/admin/client/client.d.ts +82 -0
- package/build/admin/client/client.d.ts.map +1 -0
- package/build/admin/client/client.js +157 -0
- package/build/admin/client/client.js.map +1 -0
- package/build/admin/client/errors/base-error.d.ts +58 -0
- package/build/admin/client/errors/base-error.d.ts.map +1 -0
- package/build/admin/client/errors/base-error.js +100 -0
- package/build/admin/client/errors/base-error.js.map +1 -0
- package/build/admin/client/errors/index.d.ts +2 -0
- package/build/admin/client/errors/index.d.ts.map +1 -0
- package/build/admin/client/errors/index.js +2 -0
- package/build/admin/client/errors/index.js.map +1 -0
- package/build/admin/client/index.d.ts +6 -0
- package/build/admin/client/index.d.ts.map +1 -0
- package/build/admin/client/index.js +11 -0
- package/build/admin/client/index.js.map +1 -0
- package/build/admin/client/services/authentication.service.d.ts +54 -0
- package/build/admin/client/services/authentication.service.d.ts.map +1 -0
- package/build/admin/client/services/authentication.service.js +99 -0
- package/build/admin/client/services/authentication.service.js.map +1 -0
- package/build/admin/client/services/base-service.d.ts +39 -0
- package/build/admin/client/services/base-service.d.ts.map +1 -0
- package/build/admin/client/services/base-service.js +107 -0
- package/build/admin/client/services/base-service.js.map +1 -0
- package/build/admin/client/services/client.service.d.ts +86 -0
- package/build/admin/client/services/client.service.d.ts.map +1 -0
- package/build/admin/client/services/client.service.js +193 -0
- package/build/admin/client/services/client.service.js.map +1 -0
- package/build/admin/client/services/event.service.d.ts +84 -0
- package/build/admin/client/services/event.service.d.ts.map +1 -0
- package/build/admin/client/services/event.service.js +155 -0
- package/build/admin/client/services/event.service.js.map +1 -0
- package/build/admin/client/services/federated-identity.service.d.ts +89 -0
- package/build/admin/client/services/federated-identity.service.d.ts.map +1 -0
- package/build/admin/client/services/federated-identity.service.js +120 -0
- package/build/admin/client/services/federated-identity.service.js.map +1 -0
- package/build/admin/client/services/group.service.d.ts +52 -0
- package/build/admin/client/services/group.service.d.ts.map +1 -0
- package/build/admin/client/services/group.service.js +105 -0
- package/build/admin/client/services/group.service.js.map +1 -0
- package/build/admin/client/services/identity-provider.service.d.ts +47 -0
- package/build/admin/client/services/identity-provider.service.d.ts.map +1 -0
- package/build/admin/client/services/identity-provider.service.js +86 -0
- package/build/admin/client/services/identity-provider.service.js.map +1 -0
- package/build/admin/client/services/index.d.ts +11 -0
- package/build/admin/client/services/index.d.ts.map +1 -0
- package/build/admin/client/services/index.js +11 -0
- package/build/admin/client/services/index.js.map +1 -0
- package/build/admin/client/services/realm.service.d.ts +41 -0
- package/build/admin/client/services/realm.service.d.ts.map +1 -0
- package/build/admin/client/services/realm.service.js +80 -0
- package/build/admin/client/services/realm.service.js.map +1 -0
- package/build/admin/client/services/role.service.d.ts +45 -0
- package/build/admin/client/services/role.service.d.ts.map +1 -0
- package/build/admin/client/services/role.service.js +92 -0
- package/build/admin/client/services/role.service.js.map +1 -0
- package/build/admin/client/services/user.service.d.ts +84 -0
- package/build/admin/client/services/user.service.d.ts.map +1 -0
- package/build/admin/client/services/user.service.js +216 -0
- package/build/admin/client/services/user.service.js.map +1 -0
- package/build/admin/client/types/config.types.d.ts +59 -0
- package/build/admin/client/types/config.types.d.ts.map +1 -0
- package/build/admin/client/types/config.types.js +13 -0
- package/build/admin/client/types/config.types.js.map +1 -0
- package/build/admin/client/types/event.types.d.ts +176 -0
- package/build/admin/client/types/event.types.d.ts.map +1 -0
- package/build/admin/client/types/event.types.js +2 -0
- package/build/admin/client/types/event.types.js.map +1 -0
- package/build/admin/client/types/index.d.ts +4 -0
- package/build/admin/client/types/index.d.ts.map +1 -0
- package/build/admin/client/types/index.js +4 -0
- package/build/admin/client/types/index.js.map +1 -0
- package/build/admin/client/types/keycloak.types.d.ts +169 -0
- package/build/admin/client/types/keycloak.types.d.ts.map +1 -0
- package/build/admin/client/types/keycloak.types.js +2 -0
- package/build/admin/client/types/keycloak.types.js.map +1 -0
- package/build/admin/client/utils/index.d.ts +2 -0
- package/build/admin/client/utils/index.d.ts.map +1 -0
- package/build/admin/client/utils/index.js +2 -0
- package/build/admin/client/utils/index.js.map +1 -0
- package/build/admin/client/utils/retry.d.ts +40 -0
- package/build/admin/client/utils/retry.d.ts.map +1 -0
- package/build/admin/client/utils/retry.js +72 -0
- package/build/admin/client/utils/retry.js.map +1 -0
- package/build/admin/config/keycloak.config.d.ts +33 -0
- package/build/admin/config/keycloak.config.d.ts.map +1 -0
- package/build/admin/config/keycloak.config.js +2 -0
- package/build/admin/config/keycloak.config.js.map +1 -0
- package/build/admin/config/keycloak.defaults.d.ts +11 -0
- package/build/admin/config/keycloak.defaults.d.ts.map +1 -0
- package/build/admin/config/keycloak.defaults.js +60 -0
- package/build/admin/config/keycloak.defaults.js.map +1 -0
- package/build/admin/health/keycloak.health.d.ts +13 -0
- package/build/admin/health/keycloak.health.d.ts.map +1 -0
- package/build/admin/health/keycloak.health.js +54 -0
- package/build/admin/health/keycloak.health.js.map +1 -0
- package/build/admin/index.d.ts +10 -0
- package/build/admin/index.d.ts.map +1 -0
- package/build/admin/index.js +9 -0
- package/build/admin/index.js.map +1 -0
- package/build/admin/keycloak-admin.interfaces.d.ts +45 -0
- package/build/admin/keycloak-admin.interfaces.d.ts.map +1 -0
- package/build/admin/keycloak-admin.interfaces.js +2 -0
- package/build/admin/keycloak-admin.interfaces.js.map +1 -0
- package/build/admin/keycloak-admin.module.d.ts +23 -0
- package/build/admin/keycloak-admin.module.d.ts.map +1 -0
- package/build/admin/keycloak-admin.module.js +101 -0
- package/build/admin/keycloak-admin.module.js.map +1 -0
- package/build/admin/keycloak.constants.d.ts +16 -0
- package/build/admin/keycloak.constants.d.ts.map +1 -0
- package/build/admin/keycloak.constants.js +16 -0
- package/build/admin/keycloak.constants.js.map +1 -0
- package/build/admin/permissions/index.d.ts +2 -0
- package/build/admin/permissions/index.d.ts.map +1 -0
- package/build/admin/permissions/index.js +2 -0
- package/build/admin/permissions/index.js.map +1 -0
- package/build/admin/permissions/keycloak-admin.permissions.d.ts +45 -0
- package/build/admin/permissions/keycloak-admin.permissions.d.ts.map +1 -0
- package/build/admin/permissions/keycloak-admin.permissions.js +68 -0
- package/build/admin/permissions/keycloak-admin.permissions.js.map +1 -0
- package/build/admin/services/keycloak-admin.service.d.ts +64 -0
- package/build/admin/services/keycloak-admin.service.d.ts.map +1 -0
- package/build/admin/services/keycloak-admin.service.js +152 -0
- package/build/admin/services/keycloak-admin.service.js.map +1 -0
- package/build/decorators/auth-decorators.d.ts +217 -0
- package/build/decorators/auth-decorators.d.ts.map +1 -0
- package/build/decorators/auth-decorators.js +251 -0
- package/build/decorators/auth-decorators.js.map +1 -0
- package/build/decorators/context-utils.d.ts +101 -0
- package/build/decorators/context-utils.d.ts.map +1 -0
- package/build/decorators/context-utils.js +178 -0
- package/build/decorators/context-utils.js.map +1 -0
- package/build/decorators/graphql-auth-decorators.d.ts +144 -0
- package/build/decorators/graphql-auth-decorators.d.ts.map +1 -0
- package/build/decorators/graphql-auth-decorators.js +152 -0
- package/build/decorators/graphql-auth-decorators.js.map +1 -0
- package/build/decorators/index.d.ts +5 -0
- package/build/decorators/index.d.ts.map +1 -0
- package/build/decorators/index.js +4 -0
- package/build/decorators/index.js.map +1 -0
- package/build/guards/index.d.ts +4 -0
- package/build/guards/index.d.ts.map +1 -0
- package/build/guards/index.js +4 -0
- package/build/guards/index.js.map +1 -0
- package/build/guards/jwt-auth.guard.d.ts +52 -0
- package/build/guards/jwt-auth.guard.d.ts.map +1 -0
- package/build/guards/jwt-auth.guard.js +97 -0
- package/build/guards/jwt-auth.guard.js.map +1 -0
- package/build/guards/permission.guard.d.ts +37 -0
- package/build/guards/permission.guard.d.ts.map +1 -0
- package/build/guards/permission.guard.js +73 -0
- package/build/guards/permission.guard.js.map +1 -0
- package/build/guards/role.guard.d.ts +33 -0
- package/build/guards/role.guard.d.ts.map +1 -0
- package/build/guards/role.guard.js +69 -0
- package/build/guards/role.guard.js.map +1 -0
- package/build/index.d.ts +92 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +98 -0
- package/build/index.js.map +1 -0
- package/build/keycloak/index.d.ts +7 -0
- package/build/keycloak/index.d.ts.map +1 -0
- package/build/keycloak/index.js +5 -0
- package/build/keycloak/index.js.map +1 -0
- package/build/keycloak/keycloak.constants.d.ts +2 -0
- package/build/keycloak/keycloak.constants.d.ts.map +1 -0
- package/build/keycloak/keycloak.constants.js +2 -0
- package/build/keycloak/keycloak.constants.js.map +1 -0
- package/build/keycloak/keycloak.interfaces.d.ts +12 -0
- package/build/keycloak/keycloak.interfaces.d.ts.map +1 -0
- package/build/keycloak/keycloak.interfaces.js +2 -0
- package/build/keycloak/keycloak.interfaces.js.map +1 -0
- package/build/keycloak/keycloak.module.d.ts +56 -0
- package/build/keycloak/keycloak.module.d.ts.map +1 -0
- package/build/keycloak/keycloak.module.js +104 -0
- package/build/keycloak/keycloak.module.js.map +1 -0
- package/build/keycloak/keycloak.types.d.ts +60 -0
- package/build/keycloak/keycloak.types.d.ts.map +1 -0
- package/build/keycloak/keycloak.types.js +2 -0
- package/build/keycloak/keycloak.types.js.map +1 -0
- package/build/keycloak/services/jwks-cache.service.d.ts +64 -0
- package/build/keycloak/services/jwks-cache.service.d.ts.map +1 -0
- package/build/keycloak/services/jwks-cache.service.js +176 -0
- package/build/keycloak/services/jwks-cache.service.js.map +1 -0
- package/build/keycloak/services/keycloak-token-validation.service.d.ts +88 -0
- package/build/keycloak/services/keycloak-token-validation.service.d.ts.map +1 -0
- package/build/keycloak/services/keycloak-token-validation.service.js +243 -0
- package/build/keycloak/services/keycloak-token-validation.service.js.map +1 -0
- package/build/package.json +72 -0
- package/package.json +93 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ModuleMetadata } from '@nestjs/common';
|
|
2
|
+
import type { InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
|
|
3
|
+
import type { KeycloakModuleOptions } from './keycloak.types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Async options for KeycloakModule configuration
|
|
6
|
+
*/
|
|
7
|
+
export interface KeycloakModuleAsyncOptions {
|
|
8
|
+
imports?: ModuleMetadata['imports'];
|
|
9
|
+
useFactory: (...args: unknown[]) => Promise<KeycloakModuleOptions> | KeycloakModuleOptions;
|
|
10
|
+
inject?: Array<InjectionToken | OptionalFactoryDependency>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=keycloak.interfaces.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keycloak.interfaces.d.ts","sourceRoot":"","sources":["../../src/keycloak/keycloak.interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAChF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,0BAA0B;IAC1C,OAAO,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IACpC,UAAU,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,qBAAqB,CAAC,GAAG,qBAAqB,CAAC;IAC3F,MAAM,CAAC,EAAE,KAAK,CAAC,cAAc,GAAG,yBAAyB,CAAC,CAAC;CAC3D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keycloak.interfaces.js","sourceRoot":"","sources":["../../src/keycloak/keycloak.interfaces.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { DynamicModule } from '@nestjs/common';
|
|
2
|
+
import type { KeycloakModuleOptions } from './keycloak.types.js';
|
|
3
|
+
import type { KeycloakModuleAsyncOptions } from './keycloak.interfaces.js';
|
|
4
|
+
/**
|
|
5
|
+
* Keycloak Token Validation Module
|
|
6
|
+
*
|
|
7
|
+
* Provides Keycloak token validation for NestJS resource servers. Supports two validation modes:
|
|
8
|
+
* - **Online mode (default)**: Uses Keycloak's token introspection endpoint for real-time token validation
|
|
9
|
+
* - **Offline mode (opt-in)**: Validates JWTs using cached JWKS (JSON Web Key Set) for stateless validation
|
|
10
|
+
*
|
|
11
|
+
* Export the `JwtAuthGuard` and decorate controllers/routes with `@UseGuards(JwtAuthGuard)` to enable
|
|
12
|
+
* token validation. The guard respects `@Public()` to bypass authentication on specific endpoints.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* // Online mode (default)
|
|
17
|
+
* KeycloakModule.forRoot({
|
|
18
|
+
* authServerUrl: 'https://keycloak.example.com',
|
|
19
|
+
* clientId: 'my-client',
|
|
20
|
+
* clientSecret: 'secret',
|
|
21
|
+
* validationMode: 'online'
|
|
22
|
+
* })
|
|
23
|
+
*
|
|
24
|
+
* // Offline mode (JWKS-based)
|
|
25
|
+
* KeycloakModule.forRoot({
|
|
26
|
+
* authServerUrl: 'https://keycloak.example.com',
|
|
27
|
+
* clientId: 'my-client',
|
|
28
|
+
* validationMode: 'offline'
|
|
29
|
+
* })
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare class KeycloakModule {
|
|
33
|
+
/**
|
|
34
|
+
* Register Keycloak module with static configuration
|
|
35
|
+
*
|
|
36
|
+
* @param options - Configuration options for the Keycloak module
|
|
37
|
+
* @returns Dynamic module configuration with KeycloakTokenValidationService and JwksCacheService
|
|
38
|
+
*/
|
|
39
|
+
static forRoot(options: KeycloakModuleOptions): DynamicModule;
|
|
40
|
+
/**
|
|
41
|
+
* Register Keycloak module with asynchronous configuration
|
|
42
|
+
*
|
|
43
|
+
* Defers module configuration until runtime using a factory function.
|
|
44
|
+
* Useful for reading configuration from environment variables or other async sources.
|
|
45
|
+
*
|
|
46
|
+
* @param options - Async factory configuration
|
|
47
|
+
* @param options.useFactory - Function that returns KeycloakModuleOptions or a promise that resolves to it
|
|
48
|
+
* @param options.inject - Optional array of providers to inject into the factory function
|
|
49
|
+
* @param options.imports - Optional array of modules to import for dependency injection
|
|
50
|
+
* @returns Dynamic module configuration with KeycloakTokenValidationService and JwksCacheService.
|
|
51
|
+
* Note: JwksCacheService is always provided in async mode (validationMode is not known at
|
|
52
|
+
* module definition time). In online mode, it initializes but skips the JWKS fetch.
|
|
53
|
+
*/
|
|
54
|
+
static forRootAsync(options: KeycloakModuleAsyncOptions): DynamicModule;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=keycloak.module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keycloak.module.d.ts","sourceRoot":"","sources":["../../src/keycloak/keycloak.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGvD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AAI3E;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBACa,cAAc;IAC1B;;;;;OAKG;WACW,OAAO,CAAC,OAAO,EAAE,qBAAqB,GAAG,aAAa;IAuBpE;;;;;;;;;;;;;OAaG;WACW,YAAY,CAAC,OAAO,EAAE,0BAA0B,GAAG,aAAa;CAgB9E"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
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;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var KeycloakModule_1;
|
|
8
|
+
import { Module } from '@nestjs/common';
|
|
9
|
+
import { JwtModule } from '@nestjs/jwt';
|
|
10
|
+
import { KEYCLOAK_MODULE_OPTIONS } from './keycloak.constants.js';
|
|
11
|
+
import { JwksCacheService } from './services/jwks-cache.service.js';
|
|
12
|
+
import { KeycloakTokenValidationService } from './services/keycloak-token-validation.service.js';
|
|
13
|
+
/**
|
|
14
|
+
* Keycloak Token Validation Module
|
|
15
|
+
*
|
|
16
|
+
* Provides Keycloak token validation for NestJS resource servers. Supports two validation modes:
|
|
17
|
+
* - **Online mode (default)**: Uses Keycloak's token introspection endpoint for real-time token validation
|
|
18
|
+
* - **Offline mode (opt-in)**: Validates JWTs using cached JWKS (JSON Web Key Set) for stateless validation
|
|
19
|
+
*
|
|
20
|
+
* Export the `JwtAuthGuard` and decorate controllers/routes with `@UseGuards(JwtAuthGuard)` to enable
|
|
21
|
+
* token validation. The guard respects `@Public()` to bypass authentication on specific endpoints.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* // Online mode (default)
|
|
26
|
+
* KeycloakModule.forRoot({
|
|
27
|
+
* authServerUrl: 'https://keycloak.example.com',
|
|
28
|
+
* clientId: 'my-client',
|
|
29
|
+
* clientSecret: 'secret',
|
|
30
|
+
* validationMode: 'online'
|
|
31
|
+
* })
|
|
32
|
+
*
|
|
33
|
+
* // Offline mode (JWKS-based)
|
|
34
|
+
* KeycloakModule.forRoot({
|
|
35
|
+
* authServerUrl: 'https://keycloak.example.com',
|
|
36
|
+
* clientId: 'my-client',
|
|
37
|
+
* validationMode: 'offline'
|
|
38
|
+
* })
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
let KeycloakModule = KeycloakModule_1 = class KeycloakModule {
|
|
42
|
+
/**
|
|
43
|
+
* Register Keycloak module with static configuration
|
|
44
|
+
*
|
|
45
|
+
* @param options - Configuration options for the Keycloak module
|
|
46
|
+
* @returns Dynamic module configuration with KeycloakTokenValidationService and JwksCacheService
|
|
47
|
+
*/
|
|
48
|
+
static forRoot(options) {
|
|
49
|
+
const isOffline = options.validationMode === 'offline';
|
|
50
|
+
const providers = [
|
|
51
|
+
{
|
|
52
|
+
provide: KEYCLOAK_MODULE_OPTIONS,
|
|
53
|
+
useValue: options,
|
|
54
|
+
},
|
|
55
|
+
KeycloakTokenValidationService,
|
|
56
|
+
...(isOffline ? [JwksCacheService] : []),
|
|
57
|
+
];
|
|
58
|
+
return {
|
|
59
|
+
module: KeycloakModule_1,
|
|
60
|
+
imports: [JwtModule.register({})],
|
|
61
|
+
providers,
|
|
62
|
+
exports: [
|
|
63
|
+
KeycloakTokenValidationService,
|
|
64
|
+
...(isOffline ? [JwksCacheService] : []),
|
|
65
|
+
KEYCLOAK_MODULE_OPTIONS,
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Register Keycloak module with asynchronous configuration
|
|
71
|
+
*
|
|
72
|
+
* Defers module configuration until runtime using a factory function.
|
|
73
|
+
* Useful for reading configuration from environment variables or other async sources.
|
|
74
|
+
*
|
|
75
|
+
* @param options - Async factory configuration
|
|
76
|
+
* @param options.useFactory - Function that returns KeycloakModuleOptions or a promise that resolves to it
|
|
77
|
+
* @param options.inject - Optional array of providers to inject into the factory function
|
|
78
|
+
* @param options.imports - Optional array of modules to import for dependency injection
|
|
79
|
+
* @returns Dynamic module configuration with KeycloakTokenValidationService and JwksCacheService.
|
|
80
|
+
* Note: JwksCacheService is always provided in async mode (validationMode is not known at
|
|
81
|
+
* module definition time). In online mode, it initializes but skips the JWKS fetch.
|
|
82
|
+
*/
|
|
83
|
+
static forRootAsync(options) {
|
|
84
|
+
return {
|
|
85
|
+
module: KeycloakModule_1,
|
|
86
|
+
imports: [JwtModule.register({}), ...(options.imports ?? [])],
|
|
87
|
+
providers: [
|
|
88
|
+
{
|
|
89
|
+
provide: KEYCLOAK_MODULE_OPTIONS,
|
|
90
|
+
useFactory: options.useFactory,
|
|
91
|
+
inject: options.inject ?? [],
|
|
92
|
+
},
|
|
93
|
+
JwksCacheService,
|
|
94
|
+
KeycloakTokenValidationService,
|
|
95
|
+
],
|
|
96
|
+
exports: [KeycloakTokenValidationService, JwksCacheService, KEYCLOAK_MODULE_OPTIONS],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
KeycloakModule = KeycloakModule_1 = __decorate([
|
|
101
|
+
Module({})
|
|
102
|
+
], KeycloakModule);
|
|
103
|
+
export { KeycloakModule };
|
|
104
|
+
//# sourceMappingURL=keycloak.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keycloak.module.js","sourceRoot":"","sources":["../../src/keycloak/keycloak.module.ts"],"names":[],"mappings":";;;;;;;AAAA,OAAO,EAAE,MAAM,EAAiB,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAGlE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,8BAA8B,EAAE,MAAM,iDAAiD,CAAC;AAEjG;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEI,IAAM,cAAc,sBAApB,MAAM,cAAc;IAC1B;;;;;OAKG;IACI,MAAM,CAAC,OAAO,CAAC,OAA8B;QACnD,MAAM,SAAS,GAAG,OAAO,CAAC,cAAc,KAAK,SAAS,CAAC;QACvD,MAAM,SAAS,GAAG;YACjB;gBACC,OAAO,EAAE,uBAAuB;gBAChC,QAAQ,EAAE,OAAO;aACjB;YACD,8BAA8B;YAC9B,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SACxC,CAAC;QAEF,OAAO;YACN,MAAM,EAAE,gBAAc;YACtB,OAAO,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YACjC,SAAS;YACT,OAAO,EAAE;gBACR,8BAA8B;gBAC9B,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBACxC,uBAAuB;aACvB;SACD,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;OAaG;IACI,MAAM,CAAC,YAAY,CAAC,OAAmC;QAC7D,OAAO;YACN,MAAM,EAAE,gBAAc;YACtB,OAAO,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;YAC7D,SAAS,EAAE;gBACV;oBACC,OAAO,EAAE,uBAAuB;oBAChC,UAAU,EAAE,OAAO,CAAC,UAAU;oBAC9B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE;iBAC5B;gBACD,gBAAgB;gBAChB,8BAA8B;aAC9B;YACD,OAAO,EAAE,CAAC,8BAA8B,EAAE,gBAAgB,EAAE,uBAAuB,CAAC;SACpF,CAAC;IACH,CAAC;CACD,CAAA;AA5DY,cAAc;IAD1B,MAAM,CAAC,EAAE,CAAC;GACE,cAAc,CA4D1B"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface KeycloakModuleOptions {
|
|
2
|
+
/** Keycloak realm base URL — e.g. 'https://auth.example.com/realms/myrealm' */
|
|
3
|
+
authServerUrl: string;
|
|
4
|
+
/** Realm name */
|
|
5
|
+
realm: string;
|
|
6
|
+
/** This service's Keycloak client ID — used for audience validation and client role extraction */
|
|
7
|
+
clientId: string;
|
|
8
|
+
/**
|
|
9
|
+
* Token validation mode.
|
|
10
|
+
* - 'online' (default): validate via Keycloak introspection — authoritative, detects revocation immediately
|
|
11
|
+
* - 'offline': validate JWT locally using JWKS — fast, no network hop, does not detect revocation
|
|
12
|
+
*/
|
|
13
|
+
validationMode?: 'online' | 'offline';
|
|
14
|
+
/** Required when validationMode is 'online' (the default). Client secret for the introspection endpoint. */
|
|
15
|
+
clientSecret?: string;
|
|
16
|
+
/** JWKS cache TTL in milliseconds. Used in offline mode only. Default: 300000 (5 minutes). */
|
|
17
|
+
jwksCacheTtlMs?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Expected token issuer. Must match the 'iss' claim exactly.
|
|
20
|
+
* Defaults to authServerUrl.
|
|
21
|
+
*/
|
|
22
|
+
issuer?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface KeycloakTokenClaims {
|
|
25
|
+
sub: string;
|
|
26
|
+
iss: string;
|
|
27
|
+
aud: string | string[];
|
|
28
|
+
exp: number;
|
|
29
|
+
iat: number;
|
|
30
|
+
jti?: string;
|
|
31
|
+
azp?: string;
|
|
32
|
+
session_state?: string;
|
|
33
|
+
email?: string;
|
|
34
|
+
email_verified?: boolean;
|
|
35
|
+
preferred_username?: string;
|
|
36
|
+
name?: string;
|
|
37
|
+
given_name?: string;
|
|
38
|
+
family_name?: string;
|
|
39
|
+
realm_access?: {
|
|
40
|
+
roles: string[];
|
|
41
|
+
};
|
|
42
|
+
resource_access?: Record<string, {
|
|
43
|
+
roles: string[];
|
|
44
|
+
}>;
|
|
45
|
+
scope?: string;
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
export interface KeycloakUser {
|
|
49
|
+
/** The user's unique ID (sub claim) */
|
|
50
|
+
id: string;
|
|
51
|
+
email?: string;
|
|
52
|
+
/** preferred_username claim */
|
|
53
|
+
username?: string;
|
|
54
|
+
name?: string;
|
|
55
|
+
/** Roles from realm_access.roles */
|
|
56
|
+
realmRoles: string[];
|
|
57
|
+
/** Roles from resource_access[clientId].roles */
|
|
58
|
+
clientRoles: string[];
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=keycloak.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keycloak.types.d.ts","sourceRoot":"","sources":["../../src/keycloak/keycloak.types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACrC,+EAA+E;IAC/E,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,kGAAkG;IAClG,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,cAAc,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACtC,4GAA4G;IAC5G,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8FAA8F;IAC9F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC5B,uCAAuC;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,iDAAiD;IACjD,WAAW,EAAE,MAAM,EAAE,CAAC;CACtB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keycloak.types.js","sourceRoot":"","sources":["../../src/keycloak/keycloak.types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import type { KeycloakModuleOptions } from '../keycloak.types.js';
|
|
3
|
+
/**
|
|
4
|
+
* JWKS Cache Service
|
|
5
|
+
*
|
|
6
|
+
* Fetches and caches JWKS (JSON Web Key Set) from Keycloak for offline token validation.
|
|
7
|
+
* Used exclusively in offline validation mode to verify JWT signatures locally without
|
|
8
|
+
* contacting the Keycloak introspection endpoint.
|
|
9
|
+
*
|
|
10
|
+
* Caches keys with automatic expiration (default 5 minutes). On key rotation or cache expiry,
|
|
11
|
+
* automatically re-fetches the latest JWKS from Keycloak. Prevents concurrent fetches to avoid
|
|
12
|
+
* stampedes.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* constructor(private jwksCache: JwksCacheService) {}
|
|
17
|
+
*
|
|
18
|
+
* async validateJwt(token: string): Promise<boolean> {
|
|
19
|
+
* const decoded = jwt.decode(token, { complete: true });
|
|
20
|
+
* const key = await this.jwksCache.getKey(decoded.header.kid);
|
|
21
|
+
* return jwt.verify(token, key);
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare class JwksCacheService implements OnModuleInit {
|
|
26
|
+
private readonly keyCache;
|
|
27
|
+
private cacheExpiresAt;
|
|
28
|
+
private fetchPromise;
|
|
29
|
+
private logger?;
|
|
30
|
+
private readonly options;
|
|
31
|
+
constructor(options: KeycloakModuleOptions);
|
|
32
|
+
private initializeLogger;
|
|
33
|
+
onModuleInit(): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Get a public key from cache by key ID (kid)
|
|
36
|
+
*
|
|
37
|
+
* Checks the cache for the requested key. If found and not expired, returns it immediately.
|
|
38
|
+
* If not found or cache is expired, automatically re-fetches all keys from Keycloak.
|
|
39
|
+
* Prevents concurrent fetches with an internal lock.
|
|
40
|
+
*
|
|
41
|
+
* On key rotation, the next key request for a missing `kid` will trigger a refresh and
|
|
42
|
+
* cache the new key set.
|
|
43
|
+
*
|
|
44
|
+
* @param kid - The Key ID (from JWT header) to retrieve
|
|
45
|
+
* @returns The PEM-encoded public key (SPKI format)
|
|
46
|
+
* @throws {UnauthorizedException} If the key ID is not found after re-fetch attempt
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* try {
|
|
51
|
+
* const key = await this.jwksCache.getKey('abc123');
|
|
52
|
+
* // Use key for JWT verification
|
|
53
|
+
* } catch (error) {
|
|
54
|
+
* // Handle unknown key ID
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
getKey(kid: string): Promise<string>;
|
|
59
|
+
private fetchJwks;
|
|
60
|
+
private doFetch;
|
|
61
|
+
private convertJwkToPem;
|
|
62
|
+
private log;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=jwks-cache.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwks-cache.service.d.ts","sourceRoot":"","sources":["../../../src/keycloak/services/jwks-cache.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,YAAY,EAAiC,MAAM,gBAAgB,CAAC;AAIzF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAgBlE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBACa,gBAAiB,YAAW,YAAY;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAE3D,OAAO,CAAC,cAAc,CAAa;IAEnC,OAAO,CAAC,YAAY,CAA8B;IAElD,OAAO,CAAC,MAAM,CAAC,CAAY;IAE3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;gBAGd,OAAO,EAAE,qBAAqB;IAMhE,OAAO,CAAC,gBAAgB;IAQX,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ1C;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACU,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YA2BnC,SAAS;YAaT,OAAO;IA6BrB,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,GAAG;CASX"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
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;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
var JwksCacheService_1;
|
|
14
|
+
import { Injectable, Inject, UnauthorizedException } from '@nestjs/common';
|
|
15
|
+
import { createPublicKey } from 'node:crypto';
|
|
16
|
+
import { AppLogger, getErrorMessage } from '@pawells/nestjs-shared/common';
|
|
17
|
+
import { KEYCLOAK_MODULE_OPTIONS } from '../keycloak.constants.js';
|
|
18
|
+
const DEFAULT_JWKS_CACHE_TTL_MS = 300_000;
|
|
19
|
+
/**
|
|
20
|
+
* JWKS Cache Service
|
|
21
|
+
*
|
|
22
|
+
* Fetches and caches JWKS (JSON Web Key Set) from Keycloak for offline token validation.
|
|
23
|
+
* Used exclusively in offline validation mode to verify JWT signatures locally without
|
|
24
|
+
* contacting the Keycloak introspection endpoint.
|
|
25
|
+
*
|
|
26
|
+
* Caches keys with automatic expiration (default 5 minutes). On key rotation or cache expiry,
|
|
27
|
+
* automatically re-fetches the latest JWKS from Keycloak. Prevents concurrent fetches to avoid
|
|
28
|
+
* stampedes.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* constructor(private jwksCache: JwksCacheService) {}
|
|
33
|
+
*
|
|
34
|
+
* async validateJwt(token: string): Promise<boolean> {
|
|
35
|
+
* const decoded = jwt.decode(token, { complete: true });
|
|
36
|
+
* const key = await this.jwksCache.getKey(decoded.header.kid);
|
|
37
|
+
* return jwt.verify(token, key);
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
let JwksCacheService = JwksCacheService_1 = class JwksCacheService {
|
|
42
|
+
keyCache = new Map();
|
|
43
|
+
cacheExpiresAt = 0;
|
|
44
|
+
fetchPromise = null;
|
|
45
|
+
logger;
|
|
46
|
+
options;
|
|
47
|
+
constructor(options) {
|
|
48
|
+
this.options = options;
|
|
49
|
+
this.initializeLogger();
|
|
50
|
+
}
|
|
51
|
+
initializeLogger() {
|
|
52
|
+
try {
|
|
53
|
+
this.logger = new AppLogger(undefined, JwksCacheService_1.name);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Logger unavailable, fall back to console
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async onModuleInit() {
|
|
60
|
+
// Only fetch JWKS if in offline validation mode
|
|
61
|
+
if (this.options.validationMode !== 'offline') {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
await this.fetchJwks();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get a public key from cache by key ID (kid)
|
|
68
|
+
*
|
|
69
|
+
* Checks the cache for the requested key. If found and not expired, returns it immediately.
|
|
70
|
+
* If not found or cache is expired, automatically re-fetches all keys from Keycloak.
|
|
71
|
+
* Prevents concurrent fetches with an internal lock.
|
|
72
|
+
*
|
|
73
|
+
* On key rotation, the next key request for a missing `kid` will trigger a refresh and
|
|
74
|
+
* cache the new key set.
|
|
75
|
+
*
|
|
76
|
+
* @param kid - The Key ID (from JWT header) to retrieve
|
|
77
|
+
* @returns The PEM-encoded public key (SPKI format)
|
|
78
|
+
* @throws {UnauthorizedException} If the key ID is not found after re-fetch attempt
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* try {
|
|
83
|
+
* const key = await this.jwksCache.getKey('abc123');
|
|
84
|
+
* // Use key for JWT verification
|
|
85
|
+
* } catch (error) {
|
|
86
|
+
* // Handle unknown key ID
|
|
87
|
+
* }
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
async getKey(kid) {
|
|
91
|
+
// Check if key is in cache and not expired
|
|
92
|
+
if (this.keyCache.has(kid) && Date.now() < this.cacheExpiresAt) {
|
|
93
|
+
const key = this.keyCache.get(kid);
|
|
94
|
+
if (key) {
|
|
95
|
+
return key;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Key not found or cache expired, re-fetch
|
|
99
|
+
try {
|
|
100
|
+
await this.fetchJwks();
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
this.log('warn', `Failed to re-fetch JWKS during key lookup: ${getErrorMessage(error)}`);
|
|
104
|
+
}
|
|
105
|
+
// Check cache again after re-fetch — only return if cache is still valid
|
|
106
|
+
if (this.keyCache.has(kid) && Date.now() < this.cacheExpiresAt) {
|
|
107
|
+
const key = this.keyCache.get(kid);
|
|
108
|
+
if (key) {
|
|
109
|
+
return key;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
throw new UnauthorizedException('Unknown signing key');
|
|
113
|
+
}
|
|
114
|
+
async fetchJwks() {
|
|
115
|
+
// If a fetch is already in-flight, await it instead of making another request
|
|
116
|
+
if (this.fetchPromise !== null) {
|
|
117
|
+
await this.fetchPromise;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.fetchPromise = this.doFetch().finally(() => {
|
|
121
|
+
this.fetchPromise = null;
|
|
122
|
+
});
|
|
123
|
+
await this.fetchPromise;
|
|
124
|
+
}
|
|
125
|
+
async doFetch() {
|
|
126
|
+
try {
|
|
127
|
+
const jwksUrl = `${this.options.authServerUrl}/realms/${this.options.realm}/protocol/openid-connect/certs`;
|
|
128
|
+
const response = await fetch(jwksUrl);
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
throw new Error(`JWKS fetch failed with status ${response.status}`);
|
|
131
|
+
}
|
|
132
|
+
const jwksData = await response.json();
|
|
133
|
+
if (!Array.isArray(jwksData.keys)) {
|
|
134
|
+
throw new Error('Invalid JWKS response: keys is not an array');
|
|
135
|
+
}
|
|
136
|
+
this.keyCache.clear();
|
|
137
|
+
for (const jwk of jwksData.keys) {
|
|
138
|
+
const pem = this.convertJwkToPem(jwk);
|
|
139
|
+
this.keyCache.set(jwk.kid, pem);
|
|
140
|
+
}
|
|
141
|
+
const ttlMs = this.options.jwksCacheTtlMs ?? DEFAULT_JWKS_CACHE_TTL_MS;
|
|
142
|
+
this.cacheExpiresAt = Date.now() + ttlMs;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
this.log('warn', `Failed to fetch JWKS: ${getErrorMessage(error)}`);
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
convertJwkToPem(jwk) {
|
|
150
|
+
const key = createPublicKey({
|
|
151
|
+
key: jwk,
|
|
152
|
+
format: 'jwk',
|
|
153
|
+
});
|
|
154
|
+
return key.export({
|
|
155
|
+
type: 'spki',
|
|
156
|
+
format: 'pem',
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
log(level, message) {
|
|
160
|
+
if (this.logger) {
|
|
161
|
+
if (level === 'warn') {
|
|
162
|
+
this.logger.warn(message);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
this.logger.info(message);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
JwksCacheService = JwksCacheService_1 = __decorate([
|
|
171
|
+
Injectable(),
|
|
172
|
+
__param(0, Inject(KEYCLOAK_MODULE_OPTIONS)),
|
|
173
|
+
__metadata("design:paramtypes", [Object])
|
|
174
|
+
], JwksCacheService);
|
|
175
|
+
export { JwksCacheService };
|
|
176
|
+
//# sourceMappingURL=jwks-cache.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwks-cache.service.js","sourceRoot":"","sources":["../../../src/keycloak/services/jwks-cache.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { JwtService } from '@nestjs/jwt';
|
|
2
|
+
import type { KeycloakModuleOptions, KeycloakTokenClaims, KeycloakUser } from '../keycloak.types.js';
|
|
3
|
+
import { JwksCacheService } from './jwks-cache.service.js';
|
|
4
|
+
export interface TokenValidationResult {
|
|
5
|
+
valid: boolean;
|
|
6
|
+
claims?: KeycloakTokenClaims;
|
|
7
|
+
error?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Keycloak Token Validation Service
|
|
11
|
+
*
|
|
12
|
+
* Validates JWT tokens issued by Keycloak in two modes:
|
|
13
|
+
* - **Online mode (default)**: Calls Keycloak's token introspection endpoint to validate the token
|
|
14
|
+
* (requires real-time network access to Keycloak)
|
|
15
|
+
* - **Offline mode**: Validates JWTs locally using JWKS (no network call; suitable for high-traffic scenarios)
|
|
16
|
+
*
|
|
17
|
+
* After successful validation, extracts user identity and roles from token claims.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* // Online mode validation
|
|
22
|
+
* const result = await this.validationService.validateToken(token);
|
|
23
|
+
* if (result.valid) {
|
|
24
|
+
* const user = this.validationService.extractUser(result.claims!);
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* // Offline mode uses JWKS-based verification (faster, no network call)
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare class KeycloakTokenValidationService {
|
|
31
|
+
private logger?;
|
|
32
|
+
private readonly options;
|
|
33
|
+
private readonly jwtService;
|
|
34
|
+
private readonly jwksCacheService?;
|
|
35
|
+
constructor(options: KeycloakModuleOptions, jwtService: JwtService, jwksCacheService?: JwksCacheService);
|
|
36
|
+
private initializeLogger;
|
|
37
|
+
/**
|
|
38
|
+
* Validate a JWT token issued by Keycloak
|
|
39
|
+
*
|
|
40
|
+
* Routes to the appropriate validation mode based on configuration:
|
|
41
|
+
* - **Online**: Calls the Keycloak introspection endpoint (requires network access)
|
|
42
|
+
* - **Offline**: Verifies JWT signature using cached JWKS (no network call)
|
|
43
|
+
*
|
|
44
|
+
* Both modes verify token expiration and audience/issuer claims.
|
|
45
|
+
*
|
|
46
|
+
* @param token - The JWT to validate (Bearer token without "Bearer " prefix)
|
|
47
|
+
* @returns Result object with validation status and optional claims on success, or error code on failure
|
|
48
|
+
* @returns `{ valid: true, claims: KeycloakTokenClaims }` on success
|
|
49
|
+
* @returns `{ valid: false, error: string }` on failure (includes error codes like 'token_expired', 'invalid_issuer', etc.)
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const result = await this.validateToken(jwtToken);
|
|
54
|
+
* if (result.valid && result.claims) {
|
|
55
|
+
* const user = this.extractUser(result.claims);
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
validateToken(token: string): Promise<TokenValidationResult>;
|
|
60
|
+
private validateTokenOnline;
|
|
61
|
+
private validateTokenOffline;
|
|
62
|
+
/**
|
|
63
|
+
* Extract user identity and roles from validated token claims
|
|
64
|
+
*
|
|
65
|
+
* Maps Keycloak token claims to a simplified `KeycloakUser` object.
|
|
66
|
+
* Extracts both realm-level roles (`realm_access.roles`) and client-specific roles
|
|
67
|
+
* (`resource_access[clientId].roles`).
|
|
68
|
+
*
|
|
69
|
+
* @param claims - The validated Keycloak token claims
|
|
70
|
+
* @returns User object with ID, email, username, name, and both realm and client roles
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* const user = this.extractUser(claims);
|
|
75
|
+
* // {
|
|
76
|
+
* // id: 'user-uuid',
|
|
77
|
+
* // email: 'user@example.com',
|
|
78
|
+
* // username: 'john_doe',
|
|
79
|
+
* // name: 'John Doe',
|
|
80
|
+
* // realmRoles: ['admin', 'user'],
|
|
81
|
+
* // clientRoles: ['read', 'write']
|
|
82
|
+
* // }
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
extractUser(claims: KeycloakTokenClaims): KeycloakUser;
|
|
86
|
+
private log;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=keycloak-token-validation.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keycloak-token-validation.service.d.ts","sourceRoot":"","sources":["../../../src/keycloak/services/keycloak-token-validation.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,OAAO,KAAK,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACrG,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,MAAM,WAAW,qBAAqB;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAID;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBACa,8BAA8B;IAC1C,OAAO,CAAC,MAAM,CAAC,CAAY;IAE3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAEhD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IAExC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAmB;gBAGnB,OAAO,EAAE,qBAAqB,EAC/D,UAAU,EAAE,UAAU,EACV,gBAAgB,CAAC,EAAE,gBAAgB;IAQhD,OAAO,CAAC,gBAAgB;IAQxB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACU,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;YAe3D,mBAAmB;YA8CnB,oBAAoB;IAiElC;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACI,WAAW,CAAC,MAAM,EAAE,mBAAmB,GAAG,YAAY;IAW7D,OAAO,CAAC,GAAG;CASX"}
|