@xemahq/identity-client 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,313 @@
1
+ # @xemahq/identity-client
2
+
3
+ TypeScript client library and NestJS module for integrating microservices with Identity API. Handles service registration, credential management, and JWT token lifecycle automatically.
4
+
5
+ ## Features
6
+
7
+ - **Automatic Service Registration** — Background registration with identity-api on startup
8
+ - **Token Management** — Automatic caching, refresh at 90% lifetime, concurrent request deduplication
9
+ - **NestJS Module** — Drop-in `IdentityBootstrapModule` with async configuration
10
+ - **Standalone Client** — `IdentityApiClient` for direct API calls outside NestJS
11
+ - **Non-Blocking Startup** — Services start immediately; registration happens in the background
12
+ - **Exponential Backoff** — Resilient retry logic (5s → 5min cap) if identity-api is unavailable
13
+ - **Pre-configured Credentials** — Optional bypass for registration when credentials are provided via env vars
14
+ - **Zero Dependencies** — Uses native `fetch` (no axios)
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @xemahq/identity-client
20
+ ```
21
+
22
+ Requires `.npmrc` with GitHub Packages registry:
23
+
24
+ ```
25
+ @xemahq:registry=https://npm.pkg.github.com
26
+ //npm.pkg.github.com/:_authToken=${NPM_TOKEN}
27
+ ```
28
+
29
+ ## Quick Start (NestJS)
30
+
31
+ ### 1. Import the Module
32
+
33
+ ```typescript
34
+ import { Module } from '@nestjs/common';
35
+ import { ConfigModule, ConfigService } from '@nestjs/config';
36
+ import { IdentityBootstrapModule } from '@xemahq/identity-client';
37
+
38
+ @Module({
39
+ imports: [
40
+ ConfigModule.forRoot({ isGlobal: true }),
41
+ IdentityBootstrapModule.forRootAsync({
42
+ imports: [ConfigModule],
43
+ useFactory: (config: ConfigService) => ({
44
+ identityApiUrl: config.getOrThrow('IDENTITY_API_URL'),
45
+ identityApiInternalToken: config.getOrThrow('IDENTITY_API_INTERNAL_TOKEN'),
46
+ serviceName: 'my-service',
47
+ serviceDisplayName: 'My Service',
48
+ }),
49
+ inject: [ConfigService],
50
+ }),
51
+ ],
52
+ })
53
+ export class AppModule {}
54
+ ```
55
+
56
+ ### 2. Environment Variables
57
+
58
+ ```env
59
+ IDENTITY_API_URL=http://identity-api:3053
60
+ IDENTITY_API_INTERNAL_TOKEN=your-identity-api-token
61
+ ```
62
+
63
+ ### 3. Use the Service
64
+
65
+ ```typescript
66
+ import { Injectable } from '@nestjs/common';
67
+ import { IdentityBootstrapService } from '@xemahq/identity-client';
68
+ import { HttpService } from '@nestjs/axios';
69
+ import { firstValueFrom } from 'rxjs';
70
+
71
+ @Injectable()
72
+ export class MyService {
73
+ constructor(
74
+ private readonly identityBootstrap: IdentityBootstrapService,
75
+ private readonly httpService: HttpService,
76
+ ) {}
77
+
78
+ async callOtherService() {
79
+ // Get a valid JWT access token (cached, auto-refreshed)
80
+ const token = await this.identityBootstrap.getAccessToken();
81
+
82
+ const response = await firstValueFrom(
83
+ this.httpService.get('http://other-service/resource', {
84
+ headers: { Authorization: `Bearer ${token}` },
85
+ }),
86
+ );
87
+
88
+ return response.data;
89
+ }
90
+ }
91
+ ```
92
+
93
+ ## Integration with Event Hub
94
+
95
+ The primary use case is providing JWT tokens to `@xemahq/event-hub-client` via the `tokenProvider` option:
96
+
97
+ ```typescript
98
+ import { IdentityBootstrapModule, IdentityBootstrapService } from '@xemahq/identity-client';
99
+ import { EventHubModule } from '@xemahq/event-hub-client';
100
+
101
+ @Module({
102
+ imports: [
103
+ ConfigModule.forRoot({ isGlobal: true }),
104
+
105
+ // 1. Register with identity-api
106
+ IdentityBootstrapModule.forRootAsync({
107
+ imports: [ConfigModule],
108
+ useFactory: (config: ConfigService) => ({
109
+ identityApiUrl: config.getOrThrow('IDENTITY_API_URL'),
110
+ identityApiInternalToken: config.getOrThrow('IDENTITY_API_INTERNAL_TOKEN'),
111
+ serviceName: 'my-service',
112
+ serviceDisplayName: 'My Service',
113
+ }),
114
+ inject: [ConfigService],
115
+ }),
116
+
117
+ // 2. Register with event-hub, using identity-api for tokens
118
+ EventHubModule.forRootAsync({
119
+ imports: [ConfigModule],
120
+ useFactory: (config: ConfigService, identityBootstrap: IdentityBootstrapService) => ({
121
+ eventHubUrl: config.getOrThrow('EVENT_HUB_URL'),
122
+ eventHubInternalToken: config.getOrThrow('EVENT_HUB_INTERNAL_TOKEN'),
123
+ serviceId: 'my-service',
124
+ serviceName: 'My Service',
125
+ schemaVersion: '1.0.0',
126
+ dataTypes: ['order'],
127
+ tokenProvider: () => identityBootstrap.getAccessToken(),
128
+ }),
129
+ inject: [ConfigService, IdentityBootstrapService],
130
+ }),
131
+ ],
132
+ })
133
+ export class AppModule {}
134
+ ```
135
+
136
+ ## API Reference
137
+
138
+ ### IdentityBootstrapModule
139
+
140
+ NestJS module for automatic service registration and token management.
141
+
142
+ ```typescript
143
+ IdentityBootstrapModule.forRootAsync({
144
+ imports: [ConfigModule],
145
+ useFactory: (config: ConfigService) => ({
146
+ // Required
147
+ identityApiUrl: string; // Base URL of identity-api
148
+ identityApiInternalToken: string; // Internal bearer token for registration
149
+ serviceName: string; // Service ID (becomes OAuth2 client_id)
150
+ serviceDisplayName: string; // Human-readable name
151
+
152
+ // Optional
153
+ serviceDescription?: string; // Service description
154
+ maxRegistrationAttempts?: number; // Max retries (default: Infinity)
155
+ baseRetryDelay?: number; // Backoff base delay in ms (default: 5000)
156
+ maxRetryDelay?: number; // Backoff cap in ms (default: 300000)
157
+
158
+ // Pre-configured credentials (skip auto-registration)
159
+ clientId?: string; // OAuth2 client_id
160
+ clientSecret?: string; // OAuth2 client_secret
161
+ tokenEndpoint?: string; // Keycloak token endpoint URL
162
+ }),
163
+ inject: [ConfigService],
164
+ });
165
+ ```
166
+
167
+ The module registers globally and exports `IdentityBootstrapService`.
168
+
169
+ ### IdentityBootstrapService
170
+
171
+ Manages service account lifecycle. Injected automatically when using `IdentityBootstrapModule`.
172
+
173
+ ```typescript
174
+ class IdentityBootstrapService {
175
+ // Get a valid JWT access token (cached, auto-refreshed)
176
+ getAccessToken(): Promise<string>;
177
+
178
+ // Wait for registration to complete (with optional timeout)
179
+ waitForReady(timeoutMs?: number): Promise<boolean>;
180
+
181
+ // Check if credentials are available
182
+ hasCredentials(): boolean;
183
+
184
+ // Get the service's OAuth2 client_id
185
+ getClientId(): string | null;
186
+ }
187
+ ```
188
+
189
+ **Startup behavior:**
190
+ 1. If `clientId`, `clientSecret`, and `tokenEndpoint` are provided → uses them directly (no registration)
191
+ 2. Otherwise, starts background registration with identity-api:
192
+ - Calls `POST /internal/services/register` with `regenerateSecret: false`
193
+ - If the service already exists and no secret is returned, retries with `regenerateSecret: true`
194
+ - Verifies credentials by obtaining an initial token
195
+ - Retries with exponential backoff on failure
196
+
197
+ ### IdentityApiClient
198
+
199
+ Standalone HTTP client for direct identity-api calls. Does not require NestJS.
200
+
201
+ ```typescript
202
+ import { IdentityApiClient } from '@xemahq/identity-client';
203
+
204
+ const client = new IdentityApiClient({
205
+ baseUrl: 'http://identity-api:3053',
206
+ internalToken: 'your-token',
207
+ timeoutMs: 10000, // optional, default: 10000
208
+ });
209
+
210
+ // Register a service and get credentials
211
+ const credentials = await client.registerService({
212
+ name: 'my-service',
213
+ displayName: 'My Service',
214
+ serviceType: 'BACKEND',
215
+ regenerateSecret: false,
216
+ });
217
+ // → { clientId, clientSecret, tokenEndpoint, roles }
218
+
219
+ // Full response with metadata
220
+ const response = await client.registerServiceFull({
221
+ name: 'my-service',
222
+ displayName: 'My Service',
223
+ serviceType: 'BACKEND',
224
+ regenerateSecret: false,
225
+ });
226
+ // → { id, clientSecret, tokenEndpoint, roles, alreadyExisted, secretRegenerated }
227
+
228
+ // Verify organization membership
229
+ const membership = await client.verifyMembership(userId, orgId);
230
+ // → { isMember, role }
231
+
232
+ // Create system organization (idempotent)
233
+ const org = await client.ensureSystemOrg({
234
+ name: 'system',
235
+ displayName: 'System Organization',
236
+ });
237
+
238
+ // Get system organization by name
239
+ const systemOrg = await client.getSystemOrg('system');
240
+ ```
241
+
242
+ ### Error Handling
243
+
244
+ All client methods throw `IdentityApiError` on failure:
245
+
246
+ ```typescript
247
+ import { IdentityApiError } from '@xemahq/identity-client';
248
+
249
+ try {
250
+ await client.registerService({ ... });
251
+ } catch (error) {
252
+ if (error instanceof IdentityApiError) {
253
+ console.error(error.statusCode); // HTTP status
254
+ console.error(error.message); // Error message
255
+ }
256
+ }
257
+ ```
258
+
259
+ ## Pre-configured Credentials
260
+
261
+ For production multi-replica deployments, you can skip auto-registration by providing credentials via environment variables. This avoids the secret regeneration race condition when multiple pods start simultaneously.
262
+
263
+ ```typescript
264
+ IdentityBootstrapModule.forRootAsync({
265
+ imports: [ConfigModule],
266
+ useFactory: (config: ConfigService) => ({
267
+ identityApiUrl: config.getOrThrow('IDENTITY_API_URL'),
268
+ identityApiInternalToken: config.getOrThrow('IDENTITY_API_INTERNAL_TOKEN'),
269
+ serviceName: 'my-service',
270
+ serviceDisplayName: 'My Service',
271
+ // Pre-configured credentials — skip registration entirely
272
+ clientId: config.get('SERVICE_CLIENT_ID'),
273
+ clientSecret: config.get('SERVICE_CLIENT_SECRET'),
274
+ tokenEndpoint: config.get('SERVICE_TOKEN_ENDPOINT'),
275
+ }),
276
+ inject: [ConfigService],
277
+ });
278
+ ```
279
+
280
+ When all three (`clientId`, `clientSecret`, `tokenEndpoint`) are provided, `IdentityBootstrapService` validates them on startup and enters ready state immediately. If validation fails, the module throws (hard fail).
281
+
282
+ ## Exports
283
+
284
+ ```typescript
285
+ // Classes
286
+ export { IdentityApiClient } from './client';
287
+ export { IdentityApiError } from './client';
288
+
289
+ // NestJS module
290
+ export { IdentityBootstrapModule } from './nestjs';
291
+ export { IdentityBootstrapService } from './nestjs';
292
+ export { IDENTITY_BOOTSTRAP_CONFIG } from './nestjs';
293
+
294
+ // Types
295
+ export type {
296
+ IdentityApiClientOptions,
297
+ IdentityBootstrapConfig,
298
+ IdentityBootstrapModuleAsyncOptions,
299
+ RegisterServiceRequest,
300
+ RegisterServiceResponse,
301
+ ServiceCredentials,
302
+ ServiceType,
303
+ VerifyMembershipParams,
304
+ VerifyMembershipResponse,
305
+ EnsureSystemOrgRequest,
306
+ Organization,
307
+ ApiEnvelope,
308
+ } from './types';
309
+ ```
310
+
311
+ ## License
312
+
313
+ MIT
@@ -0,0 +1,22 @@
1
+ import type { EnsureSystemOrgRequest, IdentityApiClientOptions, OnBehalfOfTokenRequest, OnBehalfOfTokenResponse, Organization, RegisterServiceRequest, RegisterServiceResponse, ServiceCredentials, VerifyMembershipResponse } from './types';
2
+ export declare class IdentityApiClient {
3
+ private readonly baseUrl;
4
+ private readonly internalToken;
5
+ private readonly timeoutMs;
6
+ constructor(options: IdentityApiClientOptions);
7
+ registerService(request: RegisterServiceRequest): Promise<ServiceCredentials>;
8
+ registerServiceFull(request: RegisterServiceRequest): Promise<RegisterServiceResponse>;
9
+ verifyMembership(userId: string, orgId: string, realm?: string): Promise<VerifyMembershipResponse>;
10
+ ensureSystemOrg(request: EnsureSystemOrgRequest): Promise<Organization>;
11
+ getSystemOrg(name: string): Promise<Organization>;
12
+ listOrgIds(realm: string): Promise<string[]>;
13
+ mintOnBehalfOfToken(request: OnBehalfOfTokenRequest): Promise<OnBehalfOfTokenResponse>;
14
+ private get;
15
+ private post;
16
+ private request;
17
+ }
18
+ export declare class IdentityApiError extends Error {
19
+ readonly statusCode: number;
20
+ readonly responseBody?: string | undefined;
21
+ constructor(message: string, statusCode: number, responseBody?: string | undefined);
22
+ }
package/dist/client.js ADDED
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IdentityApiError = exports.IdentityApiClient = void 0;
4
+ class IdentityApiClient {
5
+ baseUrl;
6
+ internalToken;
7
+ timeoutMs;
8
+ constructor(options) {
9
+ this.baseUrl = options.baseUrl.replace(/\/$/, '');
10
+ this.internalToken = options.internalToken;
11
+ this.timeoutMs = options.timeoutMs ?? 10_000;
12
+ }
13
+ async registerService(request) {
14
+ const body = {
15
+ serviceType: 'BACKEND',
16
+ ...request,
17
+ };
18
+ const response = await this.post('/internal/services/register', body);
19
+ return {
20
+ clientId: response.id,
21
+ clientSecret: response.clientSecret,
22
+ tokenEndpoint: response.tokenEndpoint,
23
+ roles: response.roles,
24
+ keycloakBaseUrl: response.keycloakBaseUrl,
25
+ allowedRealms: response.allowedRealms,
26
+ primaryRealm: response.primaryRealm,
27
+ internalTokenScope: response.internalTokenScope,
28
+ };
29
+ }
30
+ async registerServiceFull(request) {
31
+ const body = {
32
+ serviceType: 'BACKEND',
33
+ ...request,
34
+ };
35
+ return this.post('/internal/services/register', body);
36
+ }
37
+ async verifyMembership(userId, orgId, realm) {
38
+ const params = new URLSearchParams({ userId, orgId });
39
+ if (realm) {
40
+ params.set('realm', realm);
41
+ }
42
+ return this.get(`/internal/verify-membership?${params.toString()}`);
43
+ }
44
+ async ensureSystemOrg(request) {
45
+ return this.post('/internal/organizations/ensure-system', request);
46
+ }
47
+ async getSystemOrg(name) {
48
+ return this.get(`/internal/organizations/system?name=${encodeURIComponent(name)}`);
49
+ }
50
+ async listOrgIds(realm) {
51
+ return this.get(`/internal/organizations/all-ids?realm=${encodeURIComponent(realm)}`);
52
+ }
53
+ async mintOnBehalfOfToken(request) {
54
+ return this.post('/internal/tokens/on-behalf-of', request);
55
+ }
56
+ async get(path) {
57
+ const response = await this.request('GET', path);
58
+ const envelope = (await response.json());
59
+ return envelope.data;
60
+ }
61
+ async post(path, body) {
62
+ const response = await this.request('POST', path, body);
63
+ const envelope = (await response.json());
64
+ return envelope.data;
65
+ }
66
+ async request(method, path, body) {
67
+ const url = `${this.baseUrl}${path}`;
68
+ const controller = new AbortController();
69
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
70
+ try {
71
+ const response = await fetch(url, {
72
+ method,
73
+ headers: {
74
+ 'Content-Type': 'application/json',
75
+ Authorization: `Bearer ${this.internalToken}`,
76
+ },
77
+ body: body ? JSON.stringify(body) : undefined,
78
+ signal: controller.signal,
79
+ });
80
+ if (!response.ok) {
81
+ const text = await response.text().catch(() => '');
82
+ throw new IdentityApiError(`identity-api ${method} ${path} failed: ${response.status} ${response.statusText}`, response.status, text);
83
+ }
84
+ return response;
85
+ }
86
+ catch (error) {
87
+ if (error instanceof IdentityApiError)
88
+ throw error;
89
+ if (error.name === 'AbortError') {
90
+ throw new IdentityApiError(`identity-api ${method} ${path} timed out after ${this.timeoutMs}ms`, 0);
91
+ }
92
+ throw new IdentityApiError(`identity-api ${method} ${path} failed: ${error instanceof Error ? error.message : String(error)}`, 0);
93
+ }
94
+ finally {
95
+ clearTimeout(timeout);
96
+ }
97
+ }
98
+ }
99
+ exports.IdentityApiClient = IdentityApiClient;
100
+ class IdentityApiError extends Error {
101
+ statusCode;
102
+ responseBody;
103
+ constructor(message, statusCode, responseBody) {
104
+ super(message);
105
+ this.statusCode = statusCode;
106
+ this.responseBody = responseBody;
107
+ this.name = 'IdentityApiError';
108
+ }
109
+ }
110
+ exports.IdentityApiError = IdentityApiError;
111
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { IdentityApiClient, IdentityApiError } from './client';
2
+ export { OrgRole, ORG_ADMIN_ROLES, } from './types';
3
+ export type { ApiEnvelope, EnsureSystemOrgRequest, IdentityApiClientOptions, Organization, RegisterServiceRequest, RegisterServiceResponse, ServiceCredentials, ServiceType, VerifyMembershipParams, VerifyMembershipResponse, } from './types';
4
+ export { IdentityBootstrapModule, IdentityBootstrapModuleAsyncOptions, IdentityBootstrapService, IdentityBootstrapConfig, IDENTITY_BOOTSTRAP_CONFIG, } from './nestjs';
5
+ export { OrgAuthModule, OrgAdminGuard, ORG_AUTH_CONFIG, type OrgAuthConfig, type OrgAuthModuleAsyncOptions, } from './nestjs';
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ORG_AUTH_CONFIG = exports.OrgAdminGuard = exports.OrgAuthModule = exports.IDENTITY_BOOTSTRAP_CONFIG = exports.IdentityBootstrapService = exports.IdentityBootstrapModule = exports.ORG_ADMIN_ROLES = exports.OrgRole = exports.IdentityApiError = exports.IdentityApiClient = void 0;
4
+ var client_1 = require("./client");
5
+ Object.defineProperty(exports, "IdentityApiClient", { enumerable: true, get: function () { return client_1.IdentityApiClient; } });
6
+ Object.defineProperty(exports, "IdentityApiError", { enumerable: true, get: function () { return client_1.IdentityApiError; } });
7
+ var types_1 = require("./types");
8
+ Object.defineProperty(exports, "OrgRole", { enumerable: true, get: function () { return types_1.OrgRole; } });
9
+ Object.defineProperty(exports, "ORG_ADMIN_ROLES", { enumerable: true, get: function () { return types_1.ORG_ADMIN_ROLES; } });
10
+ var nestjs_1 = require("./nestjs");
11
+ Object.defineProperty(exports, "IdentityBootstrapModule", { enumerable: true, get: function () { return nestjs_1.IdentityBootstrapModule; } });
12
+ Object.defineProperty(exports, "IdentityBootstrapService", { enumerable: true, get: function () { return nestjs_1.IdentityBootstrapService; } });
13
+ Object.defineProperty(exports, "IDENTITY_BOOTSTRAP_CONFIG", { enumerable: true, get: function () { return nestjs_1.IDENTITY_BOOTSTRAP_CONFIG; } });
14
+ var nestjs_2 = require("./nestjs");
15
+ Object.defineProperty(exports, "OrgAuthModule", { enumerable: true, get: function () { return nestjs_2.OrgAuthModule; } });
16
+ Object.defineProperty(exports, "OrgAdminGuard", { enumerable: true, get: function () { return nestjs_2.OrgAdminGuard; } });
17
+ Object.defineProperty(exports, "ORG_AUTH_CONFIG", { enumerable: true, get: function () { return nestjs_2.ORG_AUTH_CONFIG; } });
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,mCAA+D;AAAtD,2GAAA,iBAAiB,OAAA;AAAE,0GAAA,gBAAgB,OAAA;AAC5C,iCAGiB;AAFf,gGAAA,OAAO,OAAA;AACP,wGAAA,eAAe,OAAA;AAgBjB,mCAMkB;AALhB,iHAAA,uBAAuB,OAAA;AAEvB,kHAAA,wBAAwB,OAAA;AAExB,mHAAA,yBAAyB,OAAA;AAI3B,mCAMkB;AALhB,uGAAA,aAAa,OAAA;AACb,uGAAA,aAAa,OAAA;AACb,yGAAA,eAAe,OAAA"}
@@ -0,0 +1,13 @@
1
+ import { DynamicModule, Type, ForwardReference, InjectionToken } from '@nestjs/common';
2
+ import { IdentityBootstrapConfig } from './identity-bootstrap.service';
3
+ type ModuleImport = Type<unknown> | ForwardReference<unknown> | DynamicModule | Promise<DynamicModule>;
4
+ export interface IdentityBootstrapModuleAsyncOptions {
5
+ imports?: ModuleImport[];
6
+ useFactory: (...args: any[]) => Promise<IdentityBootstrapConfig> | IdentityBootstrapConfig;
7
+ inject?: InjectionToken[];
8
+ }
9
+ export declare class IdentityBootstrapModule {
10
+ static forRoot(config: IdentityBootstrapConfig): DynamicModule;
11
+ static forRootAsync(options: IdentityBootstrapModuleAsyncOptions): DynamicModule;
12
+ }
13
+ export {};
@@ -0,0 +1,45 @@
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 IdentityBootstrapModule_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.IdentityBootstrapModule = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const identity_bootstrap_service_1 = require("./identity-bootstrap.service");
13
+ let IdentityBootstrapModule = IdentityBootstrapModule_1 = class IdentityBootstrapModule {
14
+ static forRoot(config) {
15
+ const configProvider = {
16
+ provide: identity_bootstrap_service_1.IDENTITY_BOOTSTRAP_CONFIG,
17
+ useValue: config,
18
+ };
19
+ return {
20
+ module: IdentityBootstrapModule_1,
21
+ global: true,
22
+ providers: [configProvider, identity_bootstrap_service_1.IdentityBootstrapService],
23
+ exports: [identity_bootstrap_service_1.IdentityBootstrapService],
24
+ };
25
+ }
26
+ static forRootAsync(options) {
27
+ const configProvider = {
28
+ provide: identity_bootstrap_service_1.IDENTITY_BOOTSTRAP_CONFIG,
29
+ useFactory: options.useFactory,
30
+ inject: options.inject || [],
31
+ };
32
+ return {
33
+ module: IdentityBootstrapModule_1,
34
+ global: true,
35
+ imports: options.imports || [],
36
+ providers: [configProvider, identity_bootstrap_service_1.IdentityBootstrapService],
37
+ exports: [identity_bootstrap_service_1.IdentityBootstrapService],
38
+ };
39
+ }
40
+ };
41
+ exports.IdentityBootstrapModule = IdentityBootstrapModule;
42
+ exports.IdentityBootstrapModule = IdentityBootstrapModule = IdentityBootstrapModule_1 = __decorate([
43
+ (0, common_1.Module)({})
44
+ ], IdentityBootstrapModule);
45
+ //# sourceMappingURL=identity-bootstrap.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-bootstrap.module.js","sourceRoot":"","sources":["../../src/nestjs/identity-bootstrap.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAOwB;AACxB,6EAIsC;AA+D/B,IAAM,uBAAuB,+BAA7B,MAAM,uBAAuB;IAIlC,MAAM,CAAC,OAAO,CAAC,MAA+B;QAC5C,MAAM,cAAc,GAAa;YAC/B,OAAO,EAAE,sDAAyB;YAClC,QAAQ,EAAE,MAAM;SACjB,CAAC;QAEF,OAAO;YACL,MAAM,EAAE,yBAAuB;YAC/B,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,CAAC,cAAc,EAAE,qDAAwB,CAAC;YACrD,OAAO,EAAE,CAAC,qDAAwB,CAAC;SACpC,CAAC;IACJ,CAAC;IAKD,MAAM,CAAC,YAAY,CACjB,OAA4C;QAE5C,MAAM,cAAc,GAAa;YAC/B,OAAO,EAAE,sDAAyB;YAClC,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE;SAC7B,CAAC;QAEF,OAAO;YACL,MAAM,EAAE,yBAAuB;YAC/B,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE;YAC9B,SAAS,EAAE,CAAC,cAAc,EAAE,qDAAwB,CAAC;YACrD,OAAO,EAAE,CAAC,qDAAwB,CAAC;SACpC,CAAC;IACJ,CAAC;CACF,CAAA;AAtCY,0DAAuB;kCAAvB,uBAAuB;IADnC,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,uBAAuB,CAsCnC"}
@@ -0,0 +1,76 @@
1
+ import { OnModuleInit, OnModuleDestroy } from '@nestjs/common';
2
+ export declare const IDENTITY_BOOTSTRAP_CONFIG: unique symbol;
3
+ export interface IdentityBootstrapConfig {
4
+ identityApiUrl: string;
5
+ identityApiInternalToken: string;
6
+ serviceName: string;
7
+ serviceDisplayName: string;
8
+ serviceDescription?: string;
9
+ keycloakUrl?: string;
10
+ keycloakRealm?: string;
11
+ clientId?: string;
12
+ clientSecret?: string;
13
+ tokenEndpoint?: string;
14
+ maxRegistrationAttempts?: number;
15
+ baseRetryDelay?: number;
16
+ maxRetryDelay?: number;
17
+ }
18
+ export declare class IdentityBootstrapService implements OnModuleInit, OnModuleDestroy {
19
+ private readonly config;
20
+ private readonly logger;
21
+ private readonly client;
22
+ private clientId;
23
+ private clientSecret;
24
+ private tokenEndpoint;
25
+ private keycloakBaseUrl;
26
+ private allowedRealms;
27
+ private primaryRealm;
28
+ private registrationTokenScope;
29
+ private accessToken;
30
+ private tokenExpiresAt;
31
+ private tokenRefreshPromise;
32
+ private lastCredentialFetchAt;
33
+ private static readonly RECOVERY_DEBOUNCE_MS;
34
+ private static readonly DISTRIBUTED_RECOVERY_DEBOUNCE_MS;
35
+ private readonly userTokenCache;
36
+ private readonly userTokenInflight;
37
+ private registrationStarted;
38
+ private registrationAttempts;
39
+ private registrationTimer;
40
+ private readonly maxRegistrationAttempts;
41
+ private readonly baseRetryDelay;
42
+ private readonly maxRetryDelay;
43
+ private readyResolve;
44
+ private readonly readyPromise;
45
+ private isReady;
46
+ constructor(config: IdentityBootstrapConfig);
47
+ onModuleInit(): Promise<void>;
48
+ onModuleDestroy(): void;
49
+ getAccessToken(): Promise<string>;
50
+ getUserAccessToken(params: {
51
+ userId: string;
52
+ orgId: string;
53
+ realm?: string;
54
+ audience?: string;
55
+ }): Promise<string>;
56
+ private mintUserAccessToken;
57
+ waitForReady(timeoutMs?: number): Promise<boolean>;
58
+ hasCredentials(): boolean;
59
+ getClientId(): string | null;
60
+ getKeycloakBaseUrl(): string | null;
61
+ getAllowedRealms(): string[];
62
+ isMember(orgId: string, userId: string, realm?: string): Promise<boolean>;
63
+ listOrgIds(): Promise<string[]>;
64
+ private startBackgroundRegistration;
65
+ private attemptRegistration;
66
+ private requireRegistrationMetadata;
67
+ private resolveUserTokenRealm;
68
+ private scheduleRetry;
69
+ private clientSecretWasSelfRegistered;
70
+ private refreshToken;
71
+ private refreshTokenWithRecovery;
72
+ private fetchCurrentCredentials;
73
+ private checkCredentialRecoveryState;
74
+ private recordCredentialRecovery;
75
+ private resolveReady;
76
+ }