@adatechnology/auth-keycloak 0.0.1
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 +81 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +15132 -0
- package/dist/index.mjs +15128 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +2 -0
- package/dist/src/keycloak.client.d.ts +20 -0
- package/dist/src/keycloak.client.js +140 -0
- package/dist/src/keycloak.http.interceptor.d.ts +11 -0
- package/dist/src/keycloak.http.interceptor.js +39 -0
- package/dist/src/keycloak.interface.d.ts +52 -0
- package/dist/src/keycloak.interface.js +1 -0
- package/dist/src/keycloak.module.d.ts +6 -0
- package/dist/src/keycloak.module.js +40 -0
- package/dist/src/keycloak.token.d.ts +3 -0
- package/dist/src/keycloak.token.js +3 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +26 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { HttpProviderInterface } from "@adatechnology/http-client";
|
|
2
|
+
import type { KeycloakClientInterface, KeycloakConfig, KeycloakTokenResponse } from "./keycloak.interface";
|
|
3
|
+
/**
|
|
4
|
+
* Minimal Keycloak client implementation without external shared infra dependencies.
|
|
5
|
+
*/
|
|
6
|
+
export declare class KeycloakClient implements KeycloakClientInterface {
|
|
7
|
+
private readonly config;
|
|
8
|
+
private readonly httpProvider;
|
|
9
|
+
private tokenCache;
|
|
10
|
+
private tokenPromise?;
|
|
11
|
+
constructor(config: KeycloakConfig, httpProvider: HttpProviderInterface);
|
|
12
|
+
getAccessToken(): Promise<string>;
|
|
13
|
+
getTokenWithCredentials(username: string, password: string): Promise<any>;
|
|
14
|
+
private requestToken;
|
|
15
|
+
refreshToken(refreshToken: string): Promise<KeycloakTokenResponse>;
|
|
16
|
+
validateToken(token: string): Promise<boolean>;
|
|
17
|
+
getUserInfo(token: string): Promise<any>;
|
|
18
|
+
clearTokenCache(): void;
|
|
19
|
+
private static maskToken;
|
|
20
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
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
|
+
import { Inject, Injectable } from "@nestjs/common";
|
|
14
|
+
import { HTTP_PROVIDER } from "@adatechnology/http-client";
|
|
15
|
+
/**
|
|
16
|
+
* Minimal Keycloak client implementation without external shared infra dependencies.
|
|
17
|
+
*/
|
|
18
|
+
let KeycloakClient = class KeycloakClient {
|
|
19
|
+
config;
|
|
20
|
+
httpProvider;
|
|
21
|
+
tokenCache = null;
|
|
22
|
+
tokenPromise = null;
|
|
23
|
+
constructor(config, httpProvider) {
|
|
24
|
+
this.config = config;
|
|
25
|
+
this.httpProvider = httpProvider;
|
|
26
|
+
}
|
|
27
|
+
async getAccessToken() {
|
|
28
|
+
// Check cache
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
if (this.tokenCache && now < this.tokenCache.expiresAt) {
|
|
31
|
+
return this.tokenCache.token;
|
|
32
|
+
}
|
|
33
|
+
if (this.tokenPromise)
|
|
34
|
+
return this.tokenPromise;
|
|
35
|
+
this.tokenPromise = (async () => {
|
|
36
|
+
try {
|
|
37
|
+
const tokenResponse = await this.requestToken();
|
|
38
|
+
const expiresAt = Date.now() + (tokenResponse.expires_in - 60) * 1000;
|
|
39
|
+
this.tokenCache = { token: tokenResponse.access_token, expiresAt };
|
|
40
|
+
return tokenResponse.access_token;
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
this.tokenPromise = null;
|
|
44
|
+
}
|
|
45
|
+
})();
|
|
46
|
+
return this.tokenPromise;
|
|
47
|
+
}
|
|
48
|
+
async getTokenWithCredentials(username, password) {
|
|
49
|
+
const tokenUrl = `${this.config.baseUrl}/realms/${this.config.realm}/protocol/openid-connect/token`;
|
|
50
|
+
const data = new URLSearchParams();
|
|
51
|
+
data.append("client_id", this.config.credentials.clientId);
|
|
52
|
+
data.append("grant_type", "password");
|
|
53
|
+
data.append("username", username);
|
|
54
|
+
data.append("password", password);
|
|
55
|
+
if (this.config.credentials.clientSecret) {
|
|
56
|
+
data.append("client_secret", this.config.credentials.clientSecret);
|
|
57
|
+
}
|
|
58
|
+
const response = await this.httpProvider.post(tokenUrl, data, {
|
|
59
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
60
|
+
});
|
|
61
|
+
return response.data;
|
|
62
|
+
}
|
|
63
|
+
async requestToken() {
|
|
64
|
+
const tokenUrl = `${this.config.baseUrl}/realms/${this.config.realm}/protocol/openid-connect/token`;
|
|
65
|
+
const data = new URLSearchParams();
|
|
66
|
+
data.append("client_id", this.config.credentials.clientId);
|
|
67
|
+
data.append("grant_type", this.config.credentials.grantType);
|
|
68
|
+
if (this.config.credentials.clientSecret) {
|
|
69
|
+
data.append("client_secret", this.config.credentials.clientSecret);
|
|
70
|
+
}
|
|
71
|
+
if (this.config.credentials.grantType === "password") {
|
|
72
|
+
if (this.config.credentials.username &&
|
|
73
|
+
this.config.credentials.password) {
|
|
74
|
+
data.append("username", this.config.credentials.username);
|
|
75
|
+
data.append("password", this.config.credentials.password);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const response = await this.httpProvider.post(tokenUrl, data, {
|
|
79
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
80
|
+
});
|
|
81
|
+
return response.data;
|
|
82
|
+
}
|
|
83
|
+
async refreshToken(refreshToken) {
|
|
84
|
+
const tokenUrl = `${this.config.baseUrl}/realms/${this.config.realm}/protocol/openid-connect/token`;
|
|
85
|
+
const data = new URLSearchParams();
|
|
86
|
+
data.append("client_id", this.config.credentials.clientId);
|
|
87
|
+
data.append("grant_type", "refresh_token");
|
|
88
|
+
data.append("refresh_token", refreshToken);
|
|
89
|
+
if (this.config.credentials.clientSecret) {
|
|
90
|
+
data.append("client_secret", this.config.credentials.clientSecret);
|
|
91
|
+
}
|
|
92
|
+
const response = await this.httpProvider.post(tokenUrl, data, {
|
|
93
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
94
|
+
});
|
|
95
|
+
const expiresAt = Date.now() + (response.data.expires_in - 60) * 1000;
|
|
96
|
+
this.tokenCache = { token: response.data.access_token, expiresAt };
|
|
97
|
+
return response.data;
|
|
98
|
+
}
|
|
99
|
+
async validateToken(token) {
|
|
100
|
+
try {
|
|
101
|
+
const introspectUrl = `${this.config.baseUrl}/realms/${this.config.realm}/protocol/openid-connect/token/introspect`;
|
|
102
|
+
const data = new URLSearchParams();
|
|
103
|
+
data.append("token", token);
|
|
104
|
+
data.append("client_id", this.config.credentials.clientId);
|
|
105
|
+
if (this.config.credentials.clientSecret) {
|
|
106
|
+
data.append("client_secret", this.config.credentials.clientSecret);
|
|
107
|
+
}
|
|
108
|
+
const response = await this.httpProvider.post(introspectUrl, data, {
|
|
109
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
110
|
+
});
|
|
111
|
+
return response.data.active === true;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async getUserInfo(token) {
|
|
118
|
+
const userInfoUrl = `${this.config.baseUrl}/realms/${this.config.realm}/protocol/openid-connect/userinfo`;
|
|
119
|
+
const response = await this.httpProvider.get(userInfoUrl, {
|
|
120
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
121
|
+
});
|
|
122
|
+
return response.data;
|
|
123
|
+
}
|
|
124
|
+
clearTokenCache() {
|
|
125
|
+
this.tokenCache = null;
|
|
126
|
+
}
|
|
127
|
+
static maskToken(token, visibleChars = 8) {
|
|
128
|
+
if (!token || typeof token !== "string")
|
|
129
|
+
return "";
|
|
130
|
+
return token.length <= visibleChars
|
|
131
|
+
? token
|
|
132
|
+
: `${token.slice(0, visibleChars)}...`;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
KeycloakClient = __decorate([
|
|
136
|
+
Injectable(),
|
|
137
|
+
__param(1, Inject(HTTP_PROVIDER)),
|
|
138
|
+
__metadata("design:paramtypes", [Object, Object])
|
|
139
|
+
], KeycloakClient);
|
|
140
|
+
export { KeycloakClient };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CallHandler, ExecutionContext, NestInterceptor } from "@nestjs/common";
|
|
2
|
+
import { Observable } from "rxjs";
|
|
3
|
+
import type { KeycloakClientInterface } from "./keycloak.interface";
|
|
4
|
+
/**
|
|
5
|
+
* HTTP interceptor that automatically adds Keycloak tokens to requests
|
|
6
|
+
*/
|
|
7
|
+
export declare class KeycloakHttpInterceptor implements NestInterceptor {
|
|
8
|
+
private readonly keycloakClient;
|
|
9
|
+
constructor(keycloakClient: KeycloakClientInterface);
|
|
10
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
import { Inject, Injectable, } from "@nestjs/common";
|
|
14
|
+
import { KEYCLOAK_CLIENT } from "./keycloak.token";
|
|
15
|
+
/**
|
|
16
|
+
* HTTP interceptor that automatically adds Keycloak tokens to requests
|
|
17
|
+
*/
|
|
18
|
+
let KeycloakHttpInterceptor = class KeycloakHttpInterceptor {
|
|
19
|
+
keycloakClient;
|
|
20
|
+
constructor(keycloakClient) {
|
|
21
|
+
this.keycloakClient = keycloakClient;
|
|
22
|
+
}
|
|
23
|
+
intercept(context, next) {
|
|
24
|
+
const request = context.switchToHttp().getRequest();
|
|
25
|
+
// Only add token for external API calls (not Keycloak itself)
|
|
26
|
+
if (request.url && !request.url.includes("keycloak")) {
|
|
27
|
+
// Note: In a real implementation, you might want to add the token here
|
|
28
|
+
// But since we're using HttpProvider, the token addition should be handled there
|
|
29
|
+
// This interceptor could be used for other HTTP client libraries
|
|
30
|
+
}
|
|
31
|
+
return next.handle();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
KeycloakHttpInterceptor = __decorate([
|
|
35
|
+
Injectable(),
|
|
36
|
+
__param(0, Inject(KEYCLOAK_CLIENT)),
|
|
37
|
+
__metadata("design:paramtypes", [Object])
|
|
38
|
+
], KeycloakHttpInterceptor);
|
|
39
|
+
export { KeycloakHttpInterceptor };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keycloak token response
|
|
3
|
+
*/
|
|
4
|
+
export interface KeycloakTokenResponse {
|
|
5
|
+
access_token: string;
|
|
6
|
+
expires_in: number;
|
|
7
|
+
refresh_expires_in: number;
|
|
8
|
+
refresh_token: string;
|
|
9
|
+
token_type: string;
|
|
10
|
+
'not-before-policy': number;
|
|
11
|
+
session_state: string;
|
|
12
|
+
scope: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Keycloak client credentials
|
|
16
|
+
*/
|
|
17
|
+
export interface KeycloakCredentials {
|
|
18
|
+
clientId: string;
|
|
19
|
+
clientSecret: string;
|
|
20
|
+
username?: string;
|
|
21
|
+
password?: string;
|
|
22
|
+
grantType: 'client_credentials' | 'password';
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Keycloak configuration
|
|
26
|
+
*/
|
|
27
|
+
export interface KeycloakConfig {
|
|
28
|
+
baseUrl: string;
|
|
29
|
+
realm: string;
|
|
30
|
+
credentials: KeycloakCredentials;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Keycloak client interface
|
|
34
|
+
*/
|
|
35
|
+
export interface KeycloakClientInterface {
|
|
36
|
+
/**
|
|
37
|
+
* Get access token
|
|
38
|
+
*/
|
|
39
|
+
getAccessToken(): Promise<string>;
|
|
40
|
+
/**
|
|
41
|
+
* Refresh access token
|
|
42
|
+
*/
|
|
43
|
+
refreshToken(refreshToken: string): Promise<KeycloakTokenResponse>;
|
|
44
|
+
/**
|
|
45
|
+
* Validate token
|
|
46
|
+
*/
|
|
47
|
+
validateToken(token: string): Promise<boolean>;
|
|
48
|
+
/**
|
|
49
|
+
* Get user info
|
|
50
|
+
*/
|
|
51
|
+
getUserInfo(token: string): Promise<any>;
|
|
52
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DynamicModule } from "@nestjs/common";
|
|
2
|
+
import type { AxiosRequestConfig, AxiosInstance } from "axios";
|
|
3
|
+
import { KeycloakConfig } from "./keycloak.interface";
|
|
4
|
+
export declare class KeycloakModule {
|
|
5
|
+
static forRoot(config: KeycloakConfig, httpConfig?: AxiosRequestConfig | AxiosInstance): DynamicModule;
|
|
6
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
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 { HTTP_PROVIDER, HttpModule } from "@adatechnology/http-client";
|
|
10
|
+
import { KeycloakClient } from "./keycloak.client";
|
|
11
|
+
import { KeycloakHttpInterceptor } from "./keycloak.http.interceptor";
|
|
12
|
+
import { KEYCLOAK_CLIENT, KEYCLOAK_HTTP_INTERCEPTOR } from "./keycloak.token";
|
|
13
|
+
import { KEYCLOAK_CONFIG } from "./keycloak.token";
|
|
14
|
+
let KeycloakModule = KeycloakModule_1 = class KeycloakModule {
|
|
15
|
+
static forRoot(config, httpConfig) {
|
|
16
|
+
return {
|
|
17
|
+
module: KeycloakModule_1,
|
|
18
|
+
global: true,
|
|
19
|
+
imports: [HttpModule.forRoot(httpConfig)],
|
|
20
|
+
providers: [
|
|
21
|
+
{ provide: KEYCLOAK_CONFIG, useValue: config },
|
|
22
|
+
{
|
|
23
|
+
provide: KEYCLOAK_CLIENT,
|
|
24
|
+
useFactory: (cfg, httpProvider) => new KeycloakClient(cfg, httpProvider),
|
|
25
|
+
inject: [KEYCLOAK_CONFIG, HTTP_PROVIDER],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
provide: KEYCLOAK_HTTP_INTERCEPTOR,
|
|
29
|
+
useFactory: (client) => new KeycloakHttpInterceptor(client),
|
|
30
|
+
inject: [KEYCLOAK_CLIENT],
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
exports: [KEYCLOAK_CLIENT, KEYCLOAK_HTTP_INTERCEPTOR],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
KeycloakModule = KeycloakModule_1 = __decorate([
|
|
38
|
+
Module({})
|
|
39
|
+
], KeycloakModule);
|
|
40
|
+
export { KeycloakModule };
|