@linagora/ldap-rest-client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1173 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ApiError: () => ApiError,
24
+ AuthenticationError: () => AuthenticationError,
25
+ AuthorizationError: () => AuthorizationError,
26
+ ConflictError: () => ConflictError,
27
+ GroupsResource: () => GroupsResource,
28
+ LdapRestClient: () => LdapRestClient,
29
+ LdapRestError: () => LdapRestError,
30
+ NetworkError: () => NetworkError,
31
+ NotFoundError: () => NotFoundError,
32
+ OrganizationsResource: () => OrganizationsResource,
33
+ RateLimitError: () => RateLimitError,
34
+ UsersResource: () => UsersResource,
35
+ ValidationError: () => ValidationError
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // src/config/ClientConfig.ts
40
+ var ConfigValidator = class {
41
+ static validate(config) {
42
+ if (!config.baseUrl || config.baseUrl.trim().length === 0) {
43
+ throw new Error("baseUrl is required");
44
+ }
45
+ if (config.auth?.type === "hmac") {
46
+ if (!config.auth.serviceId || config.auth.serviceId.trim().length === 0) {
47
+ throw new Error("serviceId is required for HMAC authentication");
48
+ }
49
+ if (!config.auth.secret || config.auth.secret.trim().length === 0) {
50
+ throw new Error("secret is required for HMAC authentication");
51
+ }
52
+ if (config.auth.secret.length < 32) {
53
+ console.warn(
54
+ `[LDAP-REST Client] Secret should be at least 32 characters (current: ${config.auth.secret.length})`
55
+ );
56
+ }
57
+ }
58
+ try {
59
+ new URL(config.baseUrl);
60
+ } catch {
61
+ throw new Error("baseUrl must be a valid URL");
62
+ }
63
+ if (config.timeout !== void 0 && (config.timeout <= 0 || !Number.isFinite(config.timeout))) {
64
+ throw new Error("timeout must be a positive number");
65
+ }
66
+ }
67
+ static normalize(config) {
68
+ return {
69
+ baseUrl: config.baseUrl.replace(/\/$/, ""),
70
+ auth: config.auth ?? { type: "cookie" },
71
+ timeout: config.timeout ?? 3e4,
72
+ logger: config.logger
73
+ };
74
+ }
75
+ static toHttpConfig(config) {
76
+ return {
77
+ baseUrl: config.baseUrl,
78
+ timeout: config.timeout
79
+ };
80
+ }
81
+ };
82
+
83
+ // src/lib/HmacAuth.ts
84
+ var import_crypto = require("crypto");
85
+ var HmacAuth = class {
86
+ /**
87
+ * Creates an HMAC authentication handler
88
+ *
89
+ * @param {HmacAuthConfig} config - HMAC authentication configuration containing serviceId and secret
90
+ */
91
+ constructor(config) {
92
+ this.config = config;
93
+ }
94
+ /**
95
+ * Generates authorization header for a request
96
+ *
97
+ * @param {SignatureParams} params - Request parameters to sign
98
+ * @returns {string} Authorization header value in format "HMAC-SHA256 service-id:timestamp:signature"
99
+ */
100
+ sign = (params) => {
101
+ const signature = this.generateSignature(params);
102
+ return `HMAC-SHA256 ${signature.serviceId}:${signature.timestamp}:${signature.signature}`;
103
+ };
104
+ /**
105
+ * Generates HMAC signature components
106
+ *
107
+ * @param {SignatureParams} params - Request parameters
108
+ * @returns {HmacSignature} Signature components including service ID, timestamp, and signature
109
+ * @private
110
+ */
111
+ generateSignature = (params) => {
112
+ const timestamp = Date.now();
113
+ const bodyHash = this.hashBody(params.method, params.body);
114
+ const signingString = `${params.method.toUpperCase()}|${params.path}|${timestamp}|${bodyHash}`;
115
+ const signature = this.computeHmac(signingString);
116
+ return {
117
+ serviceId: this.config.serviceId,
118
+ timestamp,
119
+ signature
120
+ };
121
+ };
122
+ /**
123
+ * Computes SHA256 hash of request body
124
+ *
125
+ * For GET/DELETE/HEAD requests, returns empty string.
126
+ * For other methods, returns SHA256 hash of body if provided.
127
+ *
128
+ * @param {string} method - HTTP method
129
+ * @param {string} [body] - Request body
130
+ * @returns {string} SHA256 hash as hex string or empty string
131
+ * @private
132
+ */
133
+ hashBody = (method, body) => {
134
+ const upperMethod = method.toUpperCase();
135
+ if (upperMethod === "GET" || upperMethod === "DELETE" || upperMethod === "HEAD") {
136
+ return "";
137
+ }
138
+ if (!body || body.trim().length === 0) {
139
+ return "";
140
+ }
141
+ return (0, import_crypto.createHash)("sha256").update(body, "utf8").digest("hex");
142
+ };
143
+ /**
144
+ * Computes HMAC-SHA256 signature
145
+ *
146
+ * @param {string} data - Data to sign
147
+ * @returns {string} HMAC-SHA256 signature as hex string
148
+ * @private
149
+ */
150
+ computeHmac = (data) => {
151
+ return (0, import_crypto.createHmac)("sha256", this.config.secret).update(data, "utf8").digest("hex");
152
+ };
153
+ };
154
+
155
+ // src/errors/LdapRestError.ts
156
+ var LdapRestError = class extends Error {
157
+ /** Error class name (automatically set to constructor name) */
158
+ name;
159
+ /** HTTP status code associated with the error (if applicable) */
160
+ statusCode;
161
+ /** Machine-readable error code for programmatic handling */
162
+ code;
163
+ /**
164
+ * Creates a new LDAP-REST error
165
+ *
166
+ * @param {string} message - Human-readable error message
167
+ * @param {number} [statusCode] - HTTP status code
168
+ * @param {string} [code] - Machine-readable error code
169
+ */
170
+ constructor(message, statusCode, code) {
171
+ super(message);
172
+ this.name = this.constructor.name;
173
+ this.statusCode = statusCode;
174
+ this.code = code;
175
+ Error.captureStackTrace(this, this.constructor);
176
+ }
177
+ };
178
+
179
+ // src/errors/ApiError.ts
180
+ var ApiError = class _ApiError extends LdapRestError {
181
+ /**
182
+ * Creates a new API error
183
+ *
184
+ * @param {string} message - Human-readable error message
185
+ * @param {number} statusCode - HTTP status code
186
+ * @param {string} code - Machine-readable error code
187
+ */
188
+ constructor(message, statusCode, code) {
189
+ super(message, statusCode, code);
190
+ }
191
+ /**
192
+ * Creates an ApiError from an API response body
193
+ *
194
+ * @param {number} statusCode - HTTP status code from response
195
+ * @param {{ error: string; code: string }} body - Response body containing error details
196
+ * @returns {ApiError} New ApiError instance
197
+ */
198
+ static fromResponse(statusCode, body) {
199
+ return new _ApiError(body.error, statusCode, body.code);
200
+ }
201
+ };
202
+ var ValidationError = class extends LdapRestError {
203
+ /**
204
+ * Creates a new validation error
205
+ *
206
+ * @param {string} message - Description of what validation failed
207
+ */
208
+ constructor(message) {
209
+ super(message, 400, "VALIDATION_ERROR");
210
+ }
211
+ };
212
+ var AuthenticationError = class extends LdapRestError {
213
+ /**
214
+ * Creates a new authentication error
215
+ *
216
+ * @param {string} message - Authentication failure reason
217
+ */
218
+ constructor(message) {
219
+ super(message, 401, "AUTHENTICATION_ERROR");
220
+ }
221
+ };
222
+ var AuthorizationError = class extends LdapRestError {
223
+ /**
224
+ * Creates a new authorization error
225
+ *
226
+ * @param {string} message - Authorization failure reason
227
+ */
228
+ constructor(message) {
229
+ super(message, 403, "AUTHORIZATION_ERROR");
230
+ }
231
+ };
232
+ var NotFoundError = class extends LdapRestError {
233
+ /**
234
+ * Creates a new not found error
235
+ *
236
+ * @param {string} message - Description of what was not found
237
+ * @param {string} [code] - Optional specific error code (defaults to 'NOT_FOUND')
238
+ */
239
+ constructor(message, code) {
240
+ super(message, 404, code || "NOT_FOUND");
241
+ }
242
+ };
243
+ var ConflictError = class extends LdapRestError {
244
+ /**
245
+ * Creates a new conflict error
246
+ *
247
+ * @param {string} message - Description of the conflict
248
+ * @param {string} [code] - Optional specific error code (e.g., 'USERNAME_EXISTS', 'EMAIL_EXISTS')
249
+ */
250
+ constructor(message, code) {
251
+ super(message, 409, code || "CONFLICT");
252
+ }
253
+ };
254
+ var RateLimitError = class extends LdapRestError {
255
+ /** Number of seconds to wait before retrying (from Retry-After header) */
256
+ retryAfter;
257
+ /**
258
+ * Creates a new rate limit error
259
+ *
260
+ * @param {string} message - Rate limit error message
261
+ * @param {number} [retryAfter] - Seconds to wait before retrying
262
+ */
263
+ constructor(message, retryAfter) {
264
+ super(message, 429, "RATE_LIMIT_EXCEEDED");
265
+ this.retryAfter = retryAfter;
266
+ }
267
+ };
268
+ var NetworkError = class extends LdapRestError {
269
+ /** Original error that caused the network failure */
270
+ cause;
271
+ /**
272
+ * Creates a new network error
273
+ *
274
+ * @param {string} message - Network error description
275
+ * @param {Error} [cause] - Original error that caused the failure
276
+ */
277
+ constructor(message, cause) {
278
+ super(message);
279
+ this.cause = cause;
280
+ }
281
+ };
282
+
283
+ // src/lib/HttpClient.ts
284
+ var HttpClient = class {
285
+ /**
286
+ * Creates an HTTP client instance
287
+ *
288
+ * @param {HttpConfig} config - HTTP configuration (baseUrl, timeout)
289
+ * @param {Auth | undefined} auth - Optional authentication handler (HMAC). If undefined, uses cookies.
290
+ * @param {Logger<unknown>} logger - Logger instance
291
+ */
292
+ constructor(config, auth, logger) {
293
+ this.config = config;
294
+ this.auth = auth;
295
+ this.logger = logger;
296
+ }
297
+ /**
298
+ * Makes an authenticated HTTP request
299
+ *
300
+ * @template T - Response type
301
+ * @param {RequestOptions} options - Request options
302
+ * @returns {Promise<T>} Parsed response body
303
+ * @throws {ApiError} When API returns an error
304
+ * @throws {NetworkError} When network request fails
305
+ */
306
+ request = async (options) => {
307
+ const url = `${this.config.baseUrl}${options.path}`;
308
+ const bodyString = options.body ? JSON.stringify(options.body) : void 0;
309
+ this.logger.debug("Sending request", {
310
+ method: options.method,
311
+ url,
312
+ hasBody: !!bodyString
313
+ });
314
+ const authHeader = this.auth?.sign({
315
+ method: options.method,
316
+ path: options.path,
317
+ body: bodyString
318
+ });
319
+ const headers = {
320
+ "Content-Type": "application/json",
321
+ ...options.headers
322
+ };
323
+ if (authHeader) {
324
+ headers.Authorization = authHeader;
325
+ }
326
+ const controller = new AbortController();
327
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
328
+ try {
329
+ const response = await fetch(url, {
330
+ method: options.method,
331
+ headers,
332
+ body: bodyString,
333
+ signal: controller.signal,
334
+ credentials: authHeader ? void 0 : "include"
335
+ });
336
+ clearTimeout(timeoutId);
337
+ this.logger.info("Request completed", {
338
+ method: options.method,
339
+ path: options.path,
340
+ status: response.status
341
+ });
342
+ return this.handleResponse(response);
343
+ } catch (error) {
344
+ clearTimeout(timeoutId);
345
+ if (error instanceof Error && error.name === "AbortError") {
346
+ this.logger.error("Request timeout", {
347
+ method: options.method,
348
+ path: options.path,
349
+ timeoutMs: this.config.timeout
350
+ });
351
+ throw new NetworkError(`Request timeout after ${this.config.timeout}ms`);
352
+ }
353
+ if (error instanceof NetworkError || error instanceof ApiError) {
354
+ throw error;
355
+ }
356
+ this.logger.error("Network request failed", {
357
+ method: options.method,
358
+ path: options.path,
359
+ error: error instanceof Error ? error.message : "Unknown error"
360
+ });
361
+ throw new NetworkError(
362
+ `Network request failed: ${error instanceof Error ? error.message : "Unknown error"}`,
363
+ error instanceof Error ? error : void 0
364
+ );
365
+ }
366
+ };
367
+ /**
368
+ * Handles HTTP response
369
+ *
370
+ * Maps HTTP status codes to specific error types and parses response body.
371
+ *
372
+ * @template T - Response type
373
+ * @param {Response} response - Fetch API response
374
+ * @returns {Promise<T>} Parsed response body
375
+ * @throws {ApiError} When API returns an error status
376
+ * @private
377
+ */
378
+ handleResponse = async (response) => {
379
+ if (response.ok) {
380
+ if (response.status === 204) {
381
+ return { success: true };
382
+ }
383
+ const contentType = response.headers.get("content-type");
384
+ if (!contentType?.includes("application/json")) {
385
+ throw new ApiError("Expected JSON response", response.status, "INVALID_RESPONSE");
386
+ }
387
+ return response.json();
388
+ }
389
+ let errorBody;
390
+ try {
391
+ const json = await response.json();
392
+ errorBody = json;
393
+ } catch {
394
+ }
395
+ const message = errorBody?.error ?? `HTTP ${response.status}: ${response.statusText}`;
396
+ const code = errorBody?.code ?? "UNKNOWN_ERROR";
397
+ switch (response.status) {
398
+ case 400:
399
+ throw new ValidationError(message);
400
+ case 401:
401
+ throw new AuthenticationError(message);
402
+ case 403:
403
+ throw new AuthorizationError(message);
404
+ case 404:
405
+ throw new NotFoundError(message, code);
406
+ case 409:
407
+ throw new ConflictError(message, code);
408
+ case 429: {
409
+ const retryAfter = response.headers.get("retry-after");
410
+ throw new RateLimitError(message, retryAfter ? parseInt(retryAfter, 10) : void 0);
411
+ }
412
+ default:
413
+ if (errorBody) {
414
+ throw ApiError.fromResponse(response.status, errorBody);
415
+ }
416
+ throw new ApiError(message, response.status, code);
417
+ }
418
+ };
419
+ /**
420
+ * Performs a GET request
421
+ *
422
+ * @template T - Response type
423
+ * @param {string} path - Request path
424
+ * @param {Record<string, string>} [headers] - Additional headers
425
+ * @returns {Promise<T>} Parsed response body
426
+ */
427
+ get = (path, headers) => {
428
+ return this.request({ method: "GET", path, headers });
429
+ };
430
+ /**
431
+ * Performs a POST request
432
+ *
433
+ * @template T - Response type
434
+ * @param {string} path - Request path
435
+ * @param {unknown} [body] - Request body
436
+ * @param {Record<string, string>} [headers] - Additional headers
437
+ * @returns {Promise<T>} Parsed response body
438
+ */
439
+ post = (path, body, headers) => {
440
+ return this.request({ method: "POST", path, body, headers });
441
+ };
442
+ /**
443
+ * Performs a PATCH request
444
+ *
445
+ * @template T - Response type
446
+ * @param {string} path - Request path
447
+ * @param {unknown} [body] - Request body
448
+ * @param {Record<string, string>} [headers] - Additional headers
449
+ * @returns {Promise<T>} Parsed response body
450
+ */
451
+ patch = (path, body, headers) => {
452
+ return this.request({ method: "PATCH", path, body, headers });
453
+ };
454
+ /**
455
+ * Performs a DELETE request
456
+ *
457
+ * @template T - Response type
458
+ * @param {string} path - Request path
459
+ * @param {Record<string, string>} [headers] - Additional headers
460
+ * @returns {Promise<T>} Parsed response body
461
+ */
462
+ delete = (path, headers) => {
463
+ return this.request({ method: "DELETE", path, headers });
464
+ };
465
+ };
466
+
467
+ // src/lib/index.ts
468
+ var import_tslog = require("tslog");
469
+
470
+ // src/LdapRestClient.ts
471
+ var import_tslog2 = require("tslog");
472
+
473
+ // src/resources/BaseResource.ts
474
+ var BaseResource = class {
475
+ /**
476
+ * Creates a base resource instance
477
+ *
478
+ * @param {HttpClient} http - HTTP client for making requests
479
+ * @protected
480
+ */
481
+ constructor(http) {
482
+ this.http = http;
483
+ }
484
+ /**
485
+ * Builds a query string from parameters
486
+ *
487
+ * Filters out undefined and null values, and properly encodes
488
+ * parameter names and values for URL use.
489
+ *
490
+ * @param {Record<string, string | number | boolean | undefined>} params - Query parameters
491
+ * @returns {string} Formatted query string with leading '?' or empty string
492
+ * @protected
493
+ *
494
+ * @example
495
+ * ```typescript
496
+ * buildQueryString({ field: 'username', value: 'john' })
497
+ * // Returns: "?field=username&value=john"
498
+ * ```
499
+ */
500
+ buildQueryString = (params) => {
501
+ const entries = Object.entries(params).filter(
502
+ ([_, value]) => value !== void 0 && value !== null
503
+ );
504
+ if (entries.length === 0) {
505
+ return "";
506
+ }
507
+ const queryParams = entries.map(
508
+ ([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`
509
+ );
510
+ return `?${queryParams.join("&")}`;
511
+ };
512
+ };
513
+
514
+ // src/resources/UsersResource.ts
515
+ var UsersResource = class extends BaseResource {
516
+ /**
517
+ * Creates a new user in the main LDAP branch
518
+ *
519
+ * @param {CreateUserRequest} data - User data including credentials and profile
520
+ * @returns {Promise<{ success: true }>} Success response
521
+ * @throws {ConflictError} When username/email/phone already exists
522
+ * @throws {ApiError} On other API errors
523
+ */
524
+ create = async (data) => {
525
+ return this.http.post("/api/v1/users", data);
526
+ };
527
+ /**
528
+ * Updates an existing user
529
+ *
530
+ * @param {string} userId - User identifier (username)
531
+ * @param {UpdateUserRequest} data - Fields to update
532
+ * @returns {Promise<{ success: true }>} Success response
533
+ * @throws {NotFoundError} When user is not found
534
+ * @throws {ApiError} On other API errors
535
+ */
536
+ update = async (userId, data) => {
537
+ return this.http.patch(`/api/v1/users/${encodeURIComponent(userId)}`, data);
538
+ };
539
+ /**
540
+ * Disables a user account
541
+ *
542
+ * Sets pwdAccountLockedTime to lock the account using LDAP PPolicy.
543
+ *
544
+ * @param {string} userId - User identifier (username)
545
+ * @returns {Promise<{ success: true }>} Success response
546
+ * @throws {NotFoundError} When user is not found
547
+ * @throws {ApiError} On other API errors
548
+ */
549
+ disable = async (userId) => {
550
+ return this.http.post(`/api/v1/users/${encodeURIComponent(userId)}/disable`);
551
+ };
552
+ /**
553
+ * Deletes a user
554
+ *
555
+ * Permanently removes the user from LDAP.
556
+ *
557
+ * @param {string} userId - User identifier (username)
558
+ * @returns {Promise<{ success: true }>} Success response
559
+ * @throws {NotFoundError} When user is not found
560
+ * @throws {ApiError} On other API errors
561
+ */
562
+ delete = async (userId) => {
563
+ return this.http.delete(`/api/v1/users/${encodeURIComponent(userId)}`);
564
+ };
565
+ /**
566
+ * Checks if a username, phone, or email is available
567
+ *
568
+ * @param {CheckAvailabilityParams} params - Field and value to check
569
+ * @returns {Promise<CheckAvailabilityResponse>} Availability status
570
+ * @throws {ApiError} On API errors
571
+ *
572
+ * @example
573
+ * ```typescript
574
+ * const result = await client.users.checkAvailability({
575
+ * field: 'username',
576
+ * value: 'johndoe'
577
+ * });
578
+ *
579
+ * ```
580
+ */
581
+ checkAvailability = async (params) => {
582
+ const query = this.buildQueryString(params);
583
+ return this.http.get(`/api/v1/users/check${query}`);
584
+ };
585
+ /**
586
+ * Fetches a user by identifier
587
+ *
588
+ * @param {FetchUserRequest} params - Fetch parameters (by, value, fields)
589
+ * @returns {Promise<User>} User data
590
+ * @throws {NotFoundError} When user is not found
591
+ * @throws {ApiError} On other API errors
592
+ *
593
+ * @example
594
+ * ```typescript
595
+ * const user = await client.users.fetch({
596
+ * by: 'username',
597
+ * value: 'johndoe',
598
+ * fields: 'cn,mail,mobile'
599
+ * });
600
+ * ```
601
+ */
602
+ fetch = async (params) => {
603
+ const query = this.buildQueryString(params);
604
+ return this.http.get(`/api/v1/users${query}`);
605
+ };
606
+ };
607
+
608
+ // src/resources/OrganizationsResource.ts
609
+ var OrganizationsResource = class extends BaseResource {
610
+ /**
611
+ * Creates a new organization with dedicated LDAP branch
612
+ *
613
+ * @param {CreateOrganizationRequest} data - Organization data
614
+ * @returns {Promise<CreateOrganizationResponse>} Organization details including baseDN
615
+ * @throws {ConflictError} When organization already exists
616
+ * @throws {ApiError} On other API errors
617
+ */
618
+ create = async (data) => {
619
+ return this.http.post("/api/v1/organizations", data);
620
+ };
621
+ /**
622
+ * Checks if an organization identifier is available
623
+ *
624
+ * @param {CheckAvailabilityParams} params - Field and value to check
625
+ * @returns {Promise<CheckAvailabilityResponse>} Availability status
626
+ * @throws {ApiError} On API errors
627
+ *
628
+ * @example
629
+ * ```typescript
630
+ * const result = await client.organizations.checkAvailability({
631
+ * field: 'domain',
632
+ * value: 'acme.example.com'
633
+ * });
634
+ * ```
635
+ */
636
+ checkAvailability = async (params) => {
637
+ const query = this.buildQueryString(params);
638
+ return this.http.get(`/api/v1/organizations/check${query}`);
639
+ };
640
+ /**
641
+ * Links a user as the first admin of an organization
642
+ *
643
+ * Associates an existing user account with an organization and grants
644
+ * admin privileges. Typically called after organization creation.
645
+ *
646
+ * @param {string} organizationId - Organization identifier
647
+ * @param {CreateAdminRequest} data - Admin user details (username and email)
648
+ * @returns {Promise<{ success: true }>} Success response
649
+ * @throws {NotFoundError} When organization or user is not found
650
+ * @throws {ConflictError} When user is already an admin
651
+ * @throws {ApiError} On other API errors
652
+ *
653
+ * @example
654
+ * ```typescript
655
+ * await client.organizations.createAdmin('org_abc123', {
656
+ * username: 'john.doe',
657
+ * mail: 'john.doe@acme.example.com'
658
+ * });
659
+ * ```
660
+ */
661
+ createAdmin = async (organizationId, data) => {
662
+ return this.http.post(
663
+ `/api/v1/organizations/${encodeURIComponent(organizationId)}/admin`,
664
+ data
665
+ );
666
+ };
667
+ /**
668
+ * Gets a list of organizations for the authenticated user
669
+ *
670
+ * Requires SSO cookie authentication. Returns organizations where the
671
+ * authenticated user has admin role.
672
+ *
673
+ * @returns {Promise<Organization[]>} Array of organizations
674
+ * @throws {ApiError} On API errors
675
+ *
676
+ * @example
677
+ * ```typescript
678
+ * const organizations = await client.organizations.list();
679
+ * ```
680
+ */
681
+ list = async () => {
682
+ return this.http.get("/api/v1/organizations");
683
+ };
684
+ /**
685
+ * Gets details of a specific organization
686
+ *
687
+ * Requires SSO cookie authentication.
688
+ *
689
+ * @param {string} organizationId - Organization identifier
690
+ * @returns {Promise<Organization>} Organization details
691
+ * @throws {NotFoundError} When organization is not found
692
+ * @throws {ApiError} On other API errors
693
+ *
694
+ * @example
695
+ * ```typescript
696
+ * const org = await client.organizations.get('org_abc123');
697
+ * ```
698
+ */
699
+ get = async (organizationId) => {
700
+ return this.http.get(`/api/v1/organizations/${encodeURIComponent(organizationId)}`);
701
+ };
702
+ /**
703
+ * Updates an organization
704
+ *
705
+ * Requires SSO cookie authentication and admin role.
706
+ *
707
+ * @param {string} organizationId - Organization identifier
708
+ * @param {UpdateOrganizationRequest} data - Fields to update
709
+ * @returns {Promise<{ success: true }>} Success response
710
+ * @throws {NotFoundError} When organization is not found
711
+ * @throws {ForbiddenError} When user lacks admin privileges
712
+ * @throws {ApiError} On other API errors
713
+ *
714
+ * @example
715
+ * ```typescript
716
+ * await client.organizations.update('org_abc123', {
717
+ * name: 'New Organization Name',
718
+ * status: 'active'
719
+ * });
720
+ * ```
721
+ */
722
+ update = async (organizationId, data) => {
723
+ return this.http.patch(`/api/v1/organizations/${encodeURIComponent(organizationId)}`, data);
724
+ };
725
+ // ===== B2B User Management Methods =====
726
+ /**
727
+ * Creates a new user in an organization's LDAP branch
728
+ *
729
+ * Requires SSO cookie authentication and admin role in the target organization.
730
+ *
731
+ * @param {string} organizationId - Organization identifier
732
+ * @param {CreateUserRequest} data - User data including credentials and profile
733
+ * @returns {Promise<CreateB2BUserResponse>} Response with user's baseDN
734
+ * @throws {ForbiddenError} When user lacks admin privileges
735
+ * @throws {NotFoundError} When organization is not found
736
+ * @throws {ConflictError} When username/email/phone already exists
737
+ * @throws {ApiError} On other API errors
738
+ *
739
+ * @example
740
+ * ```typescript
741
+ * const result = await client.organizations.createUser('org_abc123', {
742
+ * cn: 'john.doe',
743
+ * uid: 'john.doe',
744
+ * // ... other user fields
745
+ * });
746
+ * ```
747
+ */
748
+ createUser = async (organizationId, data) => {
749
+ return this.http.post(
750
+ `/api/v1/organizations/${encodeURIComponent(organizationId)}/users`,
751
+ data
752
+ );
753
+ };
754
+ /**
755
+ * Updates a user in an organization
756
+ *
757
+ * Requires SSO cookie authentication and admin role in the target organization.
758
+ *
759
+ * @param {string} organizationId - Organization identifier
760
+ * @param {string} userId - User identifier (username)
761
+ * @param {UpdateUserRequest} data - Fields to update
762
+ * @returns {Promise<{ success: true }>} Success response
763
+ * @throws {NotFoundError} When user or organization is not found
764
+ * @throws {ForbiddenError} When user lacks admin privileges
765
+ * @throws {ApiError} On other API errors
766
+ *
767
+ * @example
768
+ * ```typescript
769
+ * await client.organizations.updateUser('org_abc123', 'john.doe', {
770
+ * mobile: '+33687654321'
771
+ * });
772
+ * ```
773
+ */
774
+ updateUser = async (organizationId, userId, data) => {
775
+ return this.http.patch(
776
+ `/api/v1/organizations/${encodeURIComponent(organizationId)}/users/${encodeURIComponent(userId)}`,
777
+ data
778
+ );
779
+ };
780
+ /**
781
+ * Disables a user in an organization
782
+ *
783
+ * Locks the account by setting pwdAccountLockedTime using LDAP PPolicy.
784
+ * Requires SSO cookie authentication and admin role.
785
+ *
786
+ * @param {string} organizationId - Organization identifier
787
+ * @param {string} userId - User identifier (username)
788
+ * @returns {Promise<{ success: true }>} Success response
789
+ * @throws {NotFoundError} When user or organization is not found
790
+ * @throws {ForbiddenError} When user lacks admin privileges
791
+ * @throws {ApiError} On other API errors
792
+ *
793
+ * @example
794
+ * ```typescript
795
+ * await client.organizations.disableUser('org_abc123', 'john.doe');
796
+ * ```
797
+ */
798
+ disableUser = async (organizationId, userId) => {
799
+ return this.http.post(
800
+ `/api/v1/organizations/${encodeURIComponent(organizationId)}/users/${encodeURIComponent(userId)}/disable`
801
+ );
802
+ };
803
+ /**
804
+ * Deletes a user from an organization
805
+ *
806
+ * Permanently removes the user from the organization's LDAP branch.
807
+ * Requires SSO cookie authentication and admin role.
808
+ *
809
+ * @param {string} organizationId - Organization identifier
810
+ * @param {string} userId - User identifier (username)
811
+ * @returns {Promise<{ success: true }>} Success response
812
+ * @throws {NotFoundError} When user or organization is not found
813
+ * @throws {ForbiddenError} When user lacks admin privileges
814
+ * @throws {ApiError} On other API errors
815
+ *
816
+ * @example
817
+ * ```typescript
818
+ * await client.organizations.deleteUser('org_abc123', 'john.doe');
819
+ * ```
820
+ */
821
+ deleteUser = async (organizationId, userId) => {
822
+ return this.http.delete(
823
+ `/api/v1/organizations/${encodeURIComponent(organizationId)}/users/${encodeURIComponent(userId)}`
824
+ );
825
+ };
826
+ /**
827
+ * Fetches a user from an organization by identifier
828
+ *
829
+ * Requires SSO cookie authentication and admin role.
830
+ *
831
+ * @param {string} organizationId - Organization identifier
832
+ * @param {FetchUserRequest} params - Fetch parameters (by, value, fields)
833
+ * @returns {Promise<User>} User data
834
+ * @throws {NotFoundError} When user or organization is not found
835
+ * @throws {ForbiddenError} When user lacks admin privileges
836
+ * @throws {ApiError} On other API errors
837
+ *
838
+ * @example
839
+ * ```typescript
840
+ * const user = await client.organizations.getUser('org_abc123', {
841
+ * by: 'username',
842
+ * value: 'john.doe',
843
+ * fields: 'cn,mail,mobile'
844
+ * });
845
+ * ```
846
+ */
847
+ getUser = async (organizationId, params) => {
848
+ const query = this.buildQueryString(params);
849
+ return this.http.get(
850
+ `/api/v1/organizations/${encodeURIComponent(organizationId)}/users${query}`
851
+ );
852
+ };
853
+ /**
854
+ * Lists users in an organization with pagination and filtering
855
+ *
856
+ * Requires SSO cookie authentication and admin role.
857
+ *
858
+ * @param {string} organizationId - Organization identifier
859
+ * @param {ListUsersParams} params - Pagination and filter parameters
860
+ * @returns {Promise<ListUsersResponse>} Paginated list of users
861
+ * @throws {NotFoundError} When organization is not found
862
+ * @throws {ForbiddenError} When user lacks admin privileges
863
+ * @throws {ApiError} On other API errors
864
+ *
865
+ * @example
866
+ * ```typescript
867
+ * const result = await client.organizations.listUsers('org_abc123', {
868
+ * page: 1,
869
+ * limit: 20,
870
+ * status: 'active',
871
+ * search: 'john',
872
+ * sortBy: 'createdAt',
873
+ * sortOrder: 'desc'
874
+ * });
875
+ * ```
876
+ */
877
+ listUsers = async (organizationId, params) => {
878
+ const query = params ? this.buildQueryString(params) : "";
879
+ return this.http.get(
880
+ `/api/v1/organizations/${encodeURIComponent(organizationId)}/users${query}`
881
+ );
882
+ };
883
+ /**
884
+ * Checks if a username, phone, or email is available in an organization
885
+ *
886
+ * Requires SSO cookie authentication and admin role.
887
+ *
888
+ * @param {string} organizationId - Organization identifier
889
+ * @param {CheckAvailabilityParams} params - Field and value to check
890
+ * @returns {Promise<CheckAvailabilityResponse>} Availability status
891
+ * @throws {NotFoundError} When organization is not found
892
+ * @throws {ForbiddenError} When user lacks admin privileges
893
+ * @throws {ApiError} On other API errors
894
+ *
895
+ * @example
896
+ * ```typescript
897
+ * const result = await client.organizations.checkUserAvailability('org_abc123', {
898
+ * field: 'username',
899
+ * value: 'john.doe'
900
+ * });
901
+ * ```
902
+ */
903
+ checkUserAvailability = async (organizationId, params) => {
904
+ const query = this.buildQueryString(params);
905
+ return this.http.get(
906
+ `/api/v1/organizations/${encodeURIComponent(organizationId)}/users/check${query}`
907
+ );
908
+ };
909
+ /**
910
+ * Changes a user's role in an organization
911
+ *
912
+ * Available roles: admin, moderator, member.
913
+ * Requires SSO cookie authentication and admin role.
914
+ * Cannot change your own role or remove the last admin.
915
+ *
916
+ * @param {string} organizationId - Organization identifier
917
+ * @param {string} userId - User identifier (username)
918
+ * @param {ChangeUserRoleRequest} data - New role
919
+ * @returns {Promise<{ success: true }>} Success response
920
+ * @throws {NotFoundError} When user or organization is not found
921
+ * @throws {ForbiddenError} When attempting self-demotion or removing last admin
922
+ * @throws {ApiError} On other API errors
923
+ *
924
+ * @example
925
+ * ```typescript
926
+ * await client.organizations.changeUserRole('org_abc123', 'john.doe', {
927
+ * role: 'moderator'
928
+ * });
929
+ * ```
930
+ */
931
+ changeUserRole = async (organizationId, userId, data) => {
932
+ return this.http.patch(
933
+ `/api/v1/organizations/${encodeURIComponent(organizationId)}/users/${encodeURIComponent(userId)}/role`,
934
+ data
935
+ );
936
+ };
937
+ };
938
+
939
+ // src/resources/GroupsResource.ts
940
+ var GroupsResource = class extends BaseResource {
941
+ /**
942
+ * Creates a new group in an organization
943
+ *
944
+ * Requires SSO cookie authentication and admin role in the target organization.
945
+ *
946
+ * @param {string} organizationId - Organization identifier
947
+ * @param {CreateGroupRequest} data - Group data (name and optional description)
948
+ * @returns {Promise<CreateGroupResponse>} Created group details
949
+ * @throws {ForbiddenError} When user lacks admin privileges
950
+ * @throws {NotFoundError} When organization is not found
951
+ * @throws {ConflictError} When group name already exists
952
+ * @throws {ApiError} On other API errors
953
+ *
954
+ * @examples
955
+ * ```typescript
956
+ * const result = await client.groups.create('org_abc123', {
957
+ * name: 'engineering',
958
+ * description: 'Engineering team'
959
+ * });
960
+ * ```
961
+ */
962
+ create = async (organizationId, data) => {
963
+ return this.http.post(
964
+ `/api/v1/organizations/${encodeURIComponent(organizationId)}/groups`,
965
+ data
966
+ );
967
+ };
968
+ /**
969
+ * Lists all groups in an organization with pagination
970
+ *
971
+ * Requires SSO cookie authentication and admin role.
972
+ *
973
+ * @param {string} organizationId - Organization identifier
974
+ * @param {ListGroupsParams} params - Pagination parameters (page, limit)
975
+ * @returns {Promise<ListGroupsResponse>} Paginated list of groups
976
+ * @throws {NotFoundError} When organization is not found
977
+ * @throws {ForbiddenError} When user lacks admin privileges
978
+ * @throws {ApiError} On other API errors
979
+ *
980
+ * @example
981
+ * ```typescript
982
+ * const result = await client.groups.list('org_abc123', {
983
+ * page: 1,
984
+ * limit: 20
985
+ * });
986
+ * ```
987
+ */
988
+ list = async (organizationId, params) => {
989
+ const query = params ? this.buildQueryString(params) : "";
990
+ return this.http.get(
991
+ `/api/v1/organizations/${encodeURIComponent(organizationId)}/groups${query}`
992
+ );
993
+ };
994
+ /**
995
+ * Gets details of a specific group
996
+ *
997
+ * Requires SSO cookie authentication and admin role.
998
+ *
999
+ * @param {string} organizationId - Organization identifier
1000
+ * @param {string} groupId - Group identifier
1001
+ * @returns {Promise<Group>} Group details including members
1002
+ * @throws {NotFoundError} When organization or group is not found
1003
+ * @throws {ForbiddenError} When user lacks admin privileges
1004
+ * @throws {ApiError} On other API errors
1005
+ *
1006
+ * @example
1007
+ * ```typescript
1008
+ * const group = await client.groups.get('org_abc123', 'grp_xyz789');
1009
+ * ```
1010
+ */
1011
+ get = async (organizationId, groupId) => {
1012
+ return this.http.get(
1013
+ `/api/v1/organizations/${encodeURIComponent(organizationId)}/groups/${encodeURIComponent(groupId)}`
1014
+ );
1015
+ };
1016
+ /**
1017
+ * Updates a group's properties
1018
+ *
1019
+ * Requires SSO cookie authentication and admin role.
1020
+ *
1021
+ * @param {string} organizationId - Organization identifier
1022
+ * @param {string} groupId - Group identifier
1023
+ * @param {UpdateGroupRequest} data - Fields to update (name, description)
1024
+ * @returns {Promise<{ success: true }>} Success response
1025
+ * @throws {NotFoundError} When organization or group is not found
1026
+ * @throws {ForbiddenError} When user lacks admin privileges
1027
+ * @throws {ConflictError} When new group name already exists
1028
+ * @throws {ApiError} On other API errors
1029
+ *
1030
+ * @example
1031
+ * ```typescript
1032
+ * await client.groups.update('org_abc123', 'grp_xyz789', {
1033
+ * description: 'Updated description'
1034
+ * });
1035
+ * ```
1036
+ */
1037
+ update = async (organizationId, groupId, data) => {
1038
+ return this.http.patch(
1039
+ `/api/v1/organizations/${encodeURIComponent(organizationId)}/groups/${encodeURIComponent(groupId)}`,
1040
+ data
1041
+ );
1042
+ };
1043
+ /**
1044
+ * Deletes a group from an organization
1045
+ *
1046
+ * Permanently removes the group. Members are not deleted, only the group itself.
1047
+ * Requires SSO cookie authentication and admin role.
1048
+ *
1049
+ * @param {string} organizationId - Organization identifier
1050
+ * @param {string} groupId - Group identifier
1051
+ * @returns {Promise<{ success: true }>} Success response
1052
+ * @throws {NotFoundError} When organization or group is not found
1053
+ * @throws {ForbiddenError} When user lacks admin privileges
1054
+ * @throws {ApiError} On other API errors
1055
+ *
1056
+ * @example
1057
+ * ```typescript
1058
+ * await client.groups.delete('org_abc123', 'grp_xyz789');
1059
+ * ```
1060
+ */
1061
+ delete = async (organizationId, groupId) => {
1062
+ return this.http.delete(
1063
+ `/api/v1/organizations/${encodeURIComponent(organizationId)}/groups/${encodeURIComponent(groupId)}`
1064
+ );
1065
+ };
1066
+ /**
1067
+ * Adds users to a group
1068
+ *
1069
+ * Adds one or more users to the group's member list.
1070
+ * Requires SSO cookie authentication and admin role.
1071
+ *
1072
+ * @param {string} organizationId - Organization identifier
1073
+ * @param {string} groupId - Group identifier
1074
+ * @param {AddGroupMembersRequest} data - Usernames to add
1075
+ * @returns {Promise<{ success: true }>} Success response
1076
+ * @throws {NotFoundError} When organization, group, or users are not found
1077
+ * @throws {ForbiddenError} When user lacks admin privileges
1078
+ * @throws {ApiError} On other API errors
1079
+ *
1080
+ * @example
1081
+ * ```typescript
1082
+ * await client.groups.addMembers('org_abc123', 'grp_xyz789', {
1083
+ * usernames: ['alice.johnson', 'bob.wilson']
1084
+ * });
1085
+ * ```
1086
+ */
1087
+ addMembers = async (organizationId, groupId, data) => {
1088
+ return this.http.post(
1089
+ `/api/v1/organizations/${encodeURIComponent(organizationId)}/groups/${encodeURIComponent(groupId)}/members`,
1090
+ data
1091
+ );
1092
+ };
1093
+ /**
1094
+ * Removes a user from a group
1095
+ *
1096
+ * Removes a specific user from the group's member list.
1097
+ * Requires SSO cookie authentication and admin role.
1098
+ *
1099
+ * @param {string} organizationId - Organization identifier
1100
+ * @param {string} groupId - Group identifier
1101
+ * @param {string} userId - User identifier (username) to remove
1102
+ * @returns {Promise<{ success: true }>} Success response
1103
+ * @throws {NotFoundError} When organization, group, or user is not found
1104
+ * @throws {ForbiddenError} When user lacks admin privileges
1105
+ * @throws {ApiError} On other API errors
1106
+ *
1107
+ * @example
1108
+ * ```typescript
1109
+ * await client.groups.removeMember('org_abc123', 'grp_xyz789', 'bob.wilson');
1110
+ * ```
1111
+ */
1112
+ removeMember = async (organizationId, groupId, userId) => {
1113
+ return this.http.delete(
1114
+ `/api/v1/organizations/${encodeURIComponent(organizationId)}/groups/${encodeURIComponent(groupId)}/members/${encodeURIComponent(userId)}`
1115
+ );
1116
+ };
1117
+ };
1118
+
1119
+ // src/LdapRestClient.ts
1120
+ var LdapRestClient = class {
1121
+ users;
1122
+ organizations;
1123
+ groups;
1124
+ config;
1125
+ /**
1126
+ * Creates a new LDAP-REST client instance
1127
+ *
1128
+ * @param {ClientConfig} config - Client configuration
1129
+ * @throws {Error} When configuration is invalid
1130
+ */
1131
+ constructor(config) {
1132
+ ConfigValidator.validate(config);
1133
+ this.config = ConfigValidator.normalize(config);
1134
+ const logger = new import_tslog2.Logger({
1135
+ name: "LDAP-REST-CLIENT",
1136
+ minLevel: 0,
1137
+ ...this.config.logger
1138
+ });
1139
+ logger.info("Initializing LDAP-REST client", {
1140
+ baseUrl: this.config.baseUrl,
1141
+ authType: this.config.auth.type
1142
+ });
1143
+ const auth = this.config.auth.type === "hmac" ? new HmacAuth(this.config.auth) : void 0;
1144
+ const http = new HttpClient(ConfigValidator.toHttpConfig(this.config), auth, logger);
1145
+ this.users = new UsersResource(http);
1146
+ this.organizations = new OrganizationsResource(http);
1147
+ this.groups = new GroupsResource(http);
1148
+ }
1149
+ /**
1150
+ * Gets the configured base URL
1151
+ *
1152
+ * @returns {string} The base URL of the LDAP-REST API
1153
+ */
1154
+ getBaseUrl = () => {
1155
+ return this.config.baseUrl;
1156
+ };
1157
+ };
1158
+ // Annotate the CommonJS export names for ESM import in node:
1159
+ 0 && (module.exports = {
1160
+ ApiError,
1161
+ AuthenticationError,
1162
+ AuthorizationError,
1163
+ ConflictError,
1164
+ GroupsResource,
1165
+ LdapRestClient,
1166
+ LdapRestError,
1167
+ NetworkError,
1168
+ NotFoundError,
1169
+ OrganizationsResource,
1170
+ RateLimitError,
1171
+ UsersResource,
1172
+ ValidationError
1173
+ });