@tenxyte/core 0.1.5 → 0.9.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.
@@ -1,122 +1,313 @@
1
- import { TenxyteHttpClient } from '../http/client';
2
- import { TokenPair } from '../types';
3
-
4
- export interface OtpRequestParams {
5
- email?: string;
6
- phone_country_code?: string;
7
- phone_number?: string;
8
- type: 'email_verification' | 'phone_verification' | 'password_reset';
9
- }
10
-
11
- export interface VerifyOtpEmailParams {
12
- email: string;
13
- code: string;
14
- }
15
-
16
- export interface VerifyOtpPhoneParams {
17
- phone_country_code: string;
18
- phone_number: string;
19
- code: string;
20
- }
21
-
22
- export interface Setup2FAResponse {
23
- qr_code_url: string;
24
- secret: string;
25
- backup_codes: string[];
26
- }
27
-
28
- export interface WebAuthnRegisterBeginResponse {
29
- publicKey: any; // CredentialCreationOptions
30
- }
31
-
32
- export interface WebAuthnAuthenticateBeginResponse {
33
- publicKey: any; // CredentialRequestOptions
34
- }
35
-
36
- export class SecurityModule {
37
- constructor(private client: TenxyteHttpClient) { }
38
-
39
- // --- OTP Verification --- //
40
-
41
- async requestOtp(data: OtpRequestParams): Promise<void> {
42
- return this.client.post<void>('/api/v1/auth/otp/request/', data);
43
- }
44
-
45
- async verifyOtpEmail(data: VerifyOtpEmailParams): Promise<void> {
46
- return this.client.post<void>('/api/v1/auth/otp/verify/email/', data);
47
- }
48
-
49
- async verifyOtpPhone(data: VerifyOtpPhoneParams): Promise<void> {
50
- return this.client.post<void>('/api/v1/auth/otp/verify/phone/', data);
51
- }
52
-
53
- // --- TOTP / 2FA --- //
54
-
55
- async get2FAStatus(): Promise<{ is_enabled: boolean; backup_codes_remaining: number }> {
56
- return this.client.get('/api/v1/auth/2fa/status/');
57
- }
58
-
59
- async setup2FA(): Promise<Setup2FAResponse> {
60
- return this.client.post<Setup2FAResponse>('/api/v1/auth/2fa/setup/');
61
- }
62
-
63
- async confirm2FA(totp_code: string): Promise<void> {
64
- return this.client.post<void>('/api/v1/auth/2fa/confirm/', { totp_code });
65
- }
66
-
67
- async disable2FA(totp_code: string, password?: string): Promise<void> {
68
- return this.client.post<void>('/api/v1/auth/2fa/disable/', { totp_code, password });
69
- }
70
-
71
- async regenerateBackupCodes(totp_code: string): Promise<{ backup_codes: string[] }> {
72
- return this.client.post('/api/v1/auth/2fa/backup-codes/', { totp_code });
73
- }
74
-
75
- // --- Password Management --- //
76
-
77
- async resetPasswordRequest(data: { email?: string; phone_country_code?: string; phone_number?: string }): Promise<void> {
78
- return this.client.post<void>('/api/v1/auth/password/reset/request/', data);
79
- }
80
-
81
- async resetPasswordConfirm(data: { otp_code: string; new_password: string; email?: string; phone_country_code?: string; phone_number?: string }): Promise<void> {
82
- return this.client.post<void>('/api/v1/auth/password/reset/confirm/', data);
83
- }
84
-
85
- async changePassword(data: { current_password: string; new_password: string }): Promise<void> {
86
- return this.client.post<void>('/api/v1/auth/password/change/', data);
87
- }
88
-
89
- async checkPasswordStrength(data: { password: string; email?: string }): Promise<{ score: number; feedback: string[] }> {
90
- return this.client.post('/api/v1/auth/password/strength/', data);
91
- }
92
-
93
- async getPasswordRequirements(): Promise<any> {
94
- return this.client.get('/api/v1/auth/password/requirements/');
95
- }
96
-
97
- // --- WebAuthn / Passkeys --- //
98
-
99
- async registerWebAuthnBegin(): Promise<WebAuthnRegisterBeginResponse> {
100
- return this.client.post<WebAuthnRegisterBeginResponse>('/api/v1/auth/webauthn/register/begin/');
101
- }
102
-
103
- async registerWebAuthnComplete(data: any): Promise<void> {
104
- return this.client.post<void>('/api/v1/auth/webauthn/register/complete/', data);
105
- }
106
-
107
- async authenticateWebAuthnBegin(data?: { email?: string }): Promise<WebAuthnAuthenticateBeginResponse> {
108
- return this.client.post<WebAuthnAuthenticateBeginResponse>('/api/v1/auth/webauthn/authenticate/begin/', data || {});
109
- }
110
-
111
- async authenticateWebAuthnComplete(data: any): Promise<TokenPair> {
112
- return this.client.post<TokenPair>('/api/v1/auth/webauthn/authenticate/complete/', data);
113
- }
114
-
115
- async listWebAuthnCredentials(): Promise<any[]> {
116
- return this.client.get<any[]>('/api/v1/auth/webauthn/credentials/');
117
- }
118
-
119
- async deleteWebAuthnCredential(credentialId: string): Promise<void> {
120
- return this.client.delete<void>(`/api/v1/auth/webauthn/credentials/${credentialId}/`);
121
- }
122
- }
1
+ import { TenxyteHttpClient } from '../http/client';
2
+ import { TenxyteUser, TokenPair } from '../types';
3
+ import { base64urlToBuffer, bufferToBase64url } from '../utils/base64url';
4
+
5
+ export class SecurityModule {
6
+ constructor(private client: TenxyteHttpClient) { }
7
+
8
+ // ─── 2FA (TOTP) Management ───
9
+
10
+ /**
11
+ * Get the current 2FA status for the authenticated user.
12
+ * @returns Information about whether 2FA is enabled and how many backup codes remain.
13
+ */
14
+ async get2FAStatus(): Promise<{ is_enabled: boolean; backup_codes_remaining: number }> {
15
+ return this.client.get('/api/v1/auth/2fa/status/');
16
+ }
17
+
18
+ /**
19
+ * Start the 2FA enrollment process.
20
+ * @returns The secret key and QR code URL to be scanned by an Authenticator app.
21
+ */
22
+ async setup2FA(): Promise<{
23
+ message: string;
24
+ secret: string;
25
+ manual_entry_key: string;
26
+ qr_code: string;
27
+ provisioning_uri: string;
28
+ backup_codes: string[];
29
+ warning: string;
30
+ }> {
31
+ return this.client.post('/api/v1/auth/2fa/setup/');
32
+ }
33
+
34
+ /**
35
+ * Confirm the 2FA setup by providing the first TOTP code generated by the Authenticator app.
36
+ * @param totpCode - The 6-digit code.
37
+ */
38
+ async confirm2FA(totpCode: string): Promise<{
39
+ message: string;
40
+ is_enabled: boolean;
41
+ enabled_at: string;
42
+ }> {
43
+ return this.client.post('/api/v1/auth/2fa/confirm/', { totp_code: totpCode });
44
+ }
45
+
46
+ /**
47
+ * Disable 2FA for the current user.
48
+ * Usually requires re-authentication or providing the active password/totp code.
49
+ * @param totpCode - The current 6-digit code to verify intent.
50
+ * @param password - (Optional) The user's password if required by backend policy.
51
+ */
52
+ async disable2FA(totpCode: string, password?: string): Promise<{
53
+ message: string;
54
+ is_enabled: boolean;
55
+ disabled_at: string;
56
+ backup_codes_invalidated: boolean;
57
+ }> {
58
+ return this.client.post('/api/v1/auth/2fa/disable/', { totp_code: totpCode, password });
59
+ }
60
+
61
+ /**
62
+ * Invalidate old backup codes and explicitly generate a new batch.
63
+ * @param totpCode - An active TOTP code to verify intent.
64
+ */
65
+ async regenerateBackupCodes(totpCode: string): Promise<{
66
+ message: string;
67
+ backup_codes: string[];
68
+ codes_count: number;
69
+ generated_at?: string;
70
+ warning: string;
71
+ }> {
72
+ return this.client.post('/api/v1/auth/2fa/backup-codes/', { totp_code: totpCode });
73
+ }
74
+
75
+ // ─── Verification OTP (Email / Phone) ───
76
+
77
+ /**
78
+ * Request an OTP code to be dispatched to the user's primary contact method.
79
+ * @param type - The channel type ('email' or 'phone').
80
+ */
81
+ async requestOtp(type: 'email' | 'phone'): Promise<{
82
+ message: string;
83
+ otp_id: number;
84
+ expires_at: string;
85
+ channel: 'email' | 'phone';
86
+ masked_recipient: string;
87
+ }> {
88
+ return this.client.post('/api/v1/auth/otp/request/', { type: type === 'email' ? 'email_verification' : 'phone_verification' });
89
+ }
90
+
91
+ /**
92
+ * Verify an email confirmation OTP.
93
+ * @param code - The numeric code received via email.
94
+ */
95
+ async verifyOtpEmail(code: string): Promise<{
96
+ message: string;
97
+ email_verified: boolean;
98
+ verified_at: string;
99
+ }> {
100
+ return this.client.post('/api/v1/auth/otp/verify/email/', { code });
101
+ }
102
+
103
+ /**
104
+ * Verify a phone confirmation OTP (SMS dispatch).
105
+ * @param code - The numeric code received via SMS.
106
+ */
107
+ async verifyOtpPhone(code: string): Promise<{
108
+ message: string;
109
+ phone_verified: boolean;
110
+ verified_at: string;
111
+ phone_number: string;
112
+ }> {
113
+ return this.client.post('/api/v1/auth/otp/verify/phone/', { code });
114
+ }
115
+
116
+ // ─── Password Sub-module ───
117
+
118
+ /**
119
+ * Triggers a password reset flow, dispatching an OTP to the target.
120
+ * @param target - Either an email address or a phone configuration payload.
121
+ */
122
+ async resetPasswordRequest(target: { email: string } | { phone_country_code: string; phone_number: string }): Promise<{ message: string }> {
123
+ return this.client.post('/api/v1/auth/password/reset/request/', target);
124
+ }
125
+
126
+ /**
127
+ * Confirm a password reset using the OTP dispatched by `resetPasswordRequest`.
128
+ * @param data - The OTP code and the new matching password fields.
129
+ */
130
+ async resetPasswordConfirm(data: {
131
+ email?: string;
132
+ phone_country_code?: string;
133
+ phone_number?: string;
134
+ otp_code: string;
135
+ new_password: string;
136
+ confirm_password: string;
137
+ }): Promise<{ message: string }> {
138
+ return this.client.post('/api/v1/auth/password/reset/confirm/', data);
139
+ }
140
+
141
+ /**
142
+ * Change password for an already authenticated user.
143
+ * @param currentPassword - The existing password to verify intent.
144
+ * @param newPassword - The distinct new password.
145
+ */
146
+ async changePassword(currentPassword: string, newPassword: string): Promise<{ message: string }> {
147
+ return this.client.post('/api/v1/auth/password/change/', {
148
+ current_password: currentPassword,
149
+ new_password: newPassword,
150
+ });
151
+ }
152
+
153
+ /**
154
+ * Evaluate the strength of a potential password against backend policies.
155
+ * @param password - The password string to test.
156
+ * @param email - (Optional) The user's email to ensure the password doesn't contain it.
157
+ */
158
+ async checkPasswordStrength(password: string, email?: string): Promise<{
159
+ score: number;
160
+ strength: string;
161
+ is_valid: boolean;
162
+ errors: string[];
163
+ requirements: {
164
+ min_length: number;
165
+ require_lowercase: boolean;
166
+ require_uppercase: boolean;
167
+ require_numbers: boolean;
168
+ require_special: boolean;
169
+ };
170
+ }> {
171
+ return this.client.post('/api/v1/auth/password/strength/', { password, email });
172
+ }
173
+
174
+ /**
175
+ * Fetch the password complexity requirements enforced by the Tenxyte backend.
176
+ */
177
+ async getPasswordRequirements(): Promise<{
178
+ requirements: Record<string, boolean | number>;
179
+ min_length: number;
180
+ max_length: number;
181
+ }> {
182
+ return this.client.get('/api/v1/auth/password/requirements/');
183
+ }
184
+
185
+ // ─── WebAuthn / Passkeys (FIDO2) ───
186
+
187
+ /**
188
+ * Register a new WebAuthn device (Passkey/Biometrics/Security Key) for the authenticated user.
189
+ * Integrates transparently with the browser `navigator.credentials` API.
190
+ * @param deviceName - Optional human-readable name for the device being registered.
191
+ */
192
+ async registerWebAuthn(deviceName?: string): Promise<{
193
+ message: string;
194
+ credential: {
195
+ id: number;
196
+ device_name: string;
197
+ created_at: string;
198
+ };
199
+ }> {
200
+ // 1. Begin registration
201
+ const optionsResponse = await this.client.post<any>('/api/v1/auth/webauthn/register/begin/');
202
+
203
+ // 2. Map options for the browser
204
+ const publicKeyOpts = optionsResponse.publicKey;
205
+ publicKeyOpts.challenge = base64urlToBuffer(publicKeyOpts.challenge);
206
+ publicKeyOpts.user.id = base64urlToBuffer(publicKeyOpts.user.id);
207
+ if (publicKeyOpts.excludeCredentials) {
208
+ publicKeyOpts.excludeCredentials.forEach((cred: any) => {
209
+ cred.id = base64urlToBuffer(cred.id);
210
+ });
211
+ }
212
+
213
+ // 3. Request credential creation from the Authenticator
214
+ const cred = await navigator.credentials.create({ publicKey: publicKeyOpts }) as PublicKeyCredential;
215
+ if (!cred) {
216
+ throw new Error('WebAuthn registration was aborted or failed.');
217
+ }
218
+
219
+ const response = cred.response as AuthenticatorAttestationResponse;
220
+
221
+ // 4. Complete registration on the backend
222
+ const completionPayload = {
223
+ device_name: deviceName,
224
+ credential: {
225
+ id: cred.id,
226
+ type: cred.type,
227
+ rawId: bufferToBase64url(cred.rawId),
228
+ response: {
229
+ attestationObject: bufferToBase64url(response.attestationObject),
230
+ clientDataJSON: bufferToBase64url(response.clientDataJSON),
231
+ },
232
+ },
233
+ };
234
+
235
+ return this.client.post('/api/v1/auth/webauthn/register/complete/', completionPayload);
236
+ }
237
+
238
+ /**
239
+ * Authenticate via WebAuthn (Passkey) without requiring a password.
240
+ * Integrates transparently with the browser `navigator.credentials` API.
241
+ * @param email - The email address identifying the user account (optional if discoverable credentials are used).
242
+ * @returns A session token pair and the user context upon successful cryptographic challenge verification.
243
+ */
244
+ async authenticateWebAuthn(email?: string): Promise<{
245
+ access: string;
246
+ refresh: string;
247
+ user: TenxyteUser;
248
+ message: string;
249
+ credential_used: string;
250
+ }> {
251
+ // 1. Begin authentication
252
+ const optionsResponse = await this.client.post<any>('/api/v1/auth/webauthn/authenticate/begin/', email ? { email } : {});
253
+
254
+ // 2. Map options for the browser
255
+ const publicKeyOpts = optionsResponse.publicKey;
256
+ publicKeyOpts.challenge = base64urlToBuffer(publicKeyOpts.challenge);
257
+ if (publicKeyOpts.allowCredentials) {
258
+ publicKeyOpts.allowCredentials.forEach((cred: any) => {
259
+ cred.id = base64urlToBuffer(cred.id);
260
+ });
261
+ }
262
+
263
+ // 3. Request assertion from the Authenticator
264
+ const cred = await navigator.credentials.get({ publicKey: publicKeyOpts }) as PublicKeyCredential;
265
+ if (!cred) {
266
+ throw new Error('WebAuthn authentication was aborted or failed.');
267
+ }
268
+
269
+ const response = cred.response as AuthenticatorAssertionResponse;
270
+
271
+ // 4. Complete authentication on the backend
272
+ const completionPayload = {
273
+ credential: {
274
+ id: cred.id,
275
+ type: cred.type,
276
+ rawId: bufferToBase64url(cred.rawId),
277
+ response: {
278
+ authenticatorData: bufferToBase64url(response.authenticatorData),
279
+ clientDataJSON: bufferToBase64url(response.clientDataJSON),
280
+ signature: bufferToBase64url(response.signature),
281
+ userHandle: response.userHandle ? bufferToBase64url(response.userHandle) : null,
282
+ },
283
+ },
284
+ };
285
+
286
+ return this.client.post('/api/v1/auth/webauthn/authenticate/complete/', completionPayload);
287
+ }
288
+
289
+ /**
290
+ * List all registered WebAuthn credentials for the active user.
291
+ */
292
+ async listWebAuthnCredentials(): Promise<{
293
+ credentials: Array<{
294
+ id: number;
295
+ device_name: string;
296
+ created_at: string;
297
+ last_used_at: string | null;
298
+ authenticator_type: string;
299
+ is_resident_key: boolean;
300
+ }>;
301
+ count: number;
302
+ }> {
303
+ return this.client.get('/api/v1/auth/webauthn/credentials/');
304
+ }
305
+
306
+ /**
307
+ * Delete a specific WebAuthn credential, removing its capability to sign in.
308
+ * @param credentialId - The internal ID of the credential to delete.
309
+ */
310
+ async deleteWebAuthnCredential(credentialId: number): Promise<void> {
311
+ return this.client.delete(`/api/v1/auth/webauthn/credentials/${credentialId}/`);
312
+ }
313
+ }
@@ -1,80 +1,95 @@
1
- import { TenxyteHttpClient } from '../http/client';
2
-
3
- export interface UpdateProfileParams {
4
- first_name?: string;
5
- last_name?: string;
6
- [key: string]: any; // Allow custom metadata updates
7
- }
8
-
9
- export interface AdminUpdateUserParams {
10
- first_name?: string;
11
- last_name?: string;
12
- is_active?: boolean;
13
- is_locked?: boolean;
14
- max_sessions?: number;
15
- max_devices?: number;
16
- }
17
-
18
- export class UserModule {
19
- constructor(private client: TenxyteHttpClient) { }
20
-
21
- // --- Standard Profile Actions --- //
22
-
23
- async getProfile(): Promise<any> {
24
- return this.client.get('/api/v1/auth/me/');
25
- }
26
-
27
- async updateProfile(data: UpdateProfileParams): Promise<any> {
28
- return this.client.patch('/api/v1/auth/me/', data);
29
- }
30
-
31
- /**
32
- * Upload an avatar using FormData.
33
- * Ensure the environment supports FormData (browser or Node.js v18+).
34
- * @param formData The FormData object containing the 'avatar' field.
35
- */
36
- async uploadAvatar(formData: FormData): Promise<any> {
37
- return this.client.patch('/api/v1/auth/me/', formData);
38
- }
39
-
40
- async deleteAccount(password: string, otpCode?: string): Promise<void> {
41
- return this.client.post<void>('/api/v1/auth/request-account-deletion/', {
42
- password,
43
- otp_code: otpCode
44
- });
45
- }
46
-
47
- // --- Admin Actions Mapping --- //
48
-
49
- async listUsers(params?: Record<string, any>): Promise<any[]> {
50
- return this.client.get<any[]>('/api/v1/auth/admin/users/', { params });
51
- }
52
-
53
- async getUser(userId: string): Promise<any> {
54
- return this.client.get(`/api/v1/auth/admin/users/${userId}/`);
55
- }
56
-
57
- async adminUpdateUser(userId: string, data: AdminUpdateUserParams): Promise<any> {
58
- return this.client.patch(`/api/v1/auth/admin/users/${userId}/`, data);
59
- }
60
-
61
- async adminDeleteUser(userId: string): Promise<void> {
62
- return this.client.delete<void>(`/api/v1/auth/admin/users/${userId}/`);
63
- }
64
-
65
- async banUser(userId: string, reason: string = ''): Promise<void> {
66
- return this.client.post<void>(`/api/v1/auth/admin/users/${userId}/ban/`, { reason });
67
- }
68
-
69
- async unbanUser(userId: string): Promise<void> {
70
- return this.client.post<void>(`/api/v1/auth/admin/users/${userId}/unban/`);
71
- }
72
-
73
- async lockUser(userId: string, durationMinutes: number = 30, reason: string = ''): Promise<void> {
74
- return this.client.post<void>(`/api/v1/auth/admin/users/${userId}/lock/`, { duration_minutes: durationMinutes, reason });
75
- }
76
-
77
- async unlockUser(userId: string): Promise<void> {
78
- return this.client.post<void>(`/api/v1/auth/admin/users/${userId}/unlock/`);
79
- }
80
- }
1
+ import { TenxyteHttpClient } from '../http/client';
2
+
3
+ export interface UpdateProfileParams {
4
+ first_name?: string;
5
+ last_name?: string;
6
+ [key: string]: any; // Allow custom metadata updates
7
+ }
8
+
9
+ export interface AdminUpdateUserParams {
10
+ first_name?: string;
11
+ last_name?: string;
12
+ is_active?: boolean;
13
+ is_locked?: boolean;
14
+ max_sessions?: number;
15
+ max_devices?: number;
16
+ }
17
+
18
+ export class UserModule {
19
+ constructor(private client: TenxyteHttpClient) { }
20
+
21
+ // --- Standard Profile Actions --- //
22
+
23
+ /** Retrieve your current comprehensive Profile metadata matching the active network bearer token. */
24
+ async getProfile(): Promise<any> {
25
+ return this.client.get('/api/v1/auth/me/');
26
+ }
27
+
28
+ /** Modify your active profile core details or injected application metadata. */
29
+ async updateProfile(data: UpdateProfileParams): Promise<any> {
30
+ return this.client.patch('/api/v1/auth/me/', data);
31
+ }
32
+
33
+ /**
34
+ * Upload an avatar using FormData.
35
+ * Ensure the environment supports FormData (browser or Node.js v18+).
36
+ * @param formData The FormData object containing the 'avatar' field.
37
+ */
38
+ async uploadAvatar(formData: FormData): Promise<any> {
39
+ return this.client.patch('/api/v1/auth/me/', formData);
40
+ }
41
+
42
+ /**
43
+ * Trigger self-deletion of an entire account data boundary.
44
+ * @param password - Requires the active system password as destructive proof of intent.
45
+ * @param otpCode - (Optional) If an OTP was queried prior to attempting account deletion.
46
+ */
47
+ async deleteAccount(password: string, otpCode?: string): Promise<void> {
48
+ return this.client.post<void>('/api/v1/auth/request-account-deletion/', {
49
+ password,
50
+ otp_code: otpCode
51
+ });
52
+ }
53
+
54
+ // --- Admin Actions Mapping --- //
55
+
56
+ /** (Admin only) Lists users paginated matching criteria. */
57
+ async listUsers(params?: Record<string, any>): Promise<any[]> {
58
+ return this.client.get<any[]>('/api/v1/auth/admin/users/', { params });
59
+ }
60
+
61
+ /** (Admin only) Gets deterministic data related to a remote unassociated user. */
62
+ async getUser(userId: string): Promise<any> {
63
+ return this.client.get(`/api/v1/auth/admin/users/${userId}/`);
64
+ }
65
+
66
+ /** (Admin only) Modifies configuration/details or capacity bounds related to a remote unassociated user. */
67
+ async adminUpdateUser(userId: string, data: AdminUpdateUserParams): Promise<any> {
68
+ return this.client.patch(`/api/v1/auth/admin/users/${userId}/`, data);
69
+ }
70
+
71
+ /** (Admin only) Force obliterate a User boundary. Can affect relational database stability if not bound carefully. */
72
+ async adminDeleteUser(userId: string): Promise<void> {
73
+ return this.client.delete<void>(`/api/v1/auth/admin/users/${userId}/`);
74
+ }
75
+
76
+ /** (Admin only) Apply a permanent suspension / ban state globally on a user token footprint. */
77
+ async banUser(userId: string, reason: string = ''): Promise<void> {
78
+ return this.client.post<void>(`/api/v1/auth/admin/users/${userId}/ban/`, { reason });
79
+ }
80
+
81
+ /** (Admin only) Recover a user footprint from a global ban state. */
82
+ async unbanUser(userId: string): Promise<void> {
83
+ return this.client.post<void>(`/api/v1/auth/admin/users/${userId}/unban/`);
84
+ }
85
+
86
+ /** (Admin only) Apply a temporary lock bounding block on a user interaction footprint. */
87
+ async lockUser(userId: string, durationMinutes: number = 30, reason: string = ''): Promise<void> {
88
+ return this.client.post<void>(`/api/v1/auth/admin/users/${userId}/lock/`, { duration_minutes: durationMinutes, reason });
89
+ }
90
+
91
+ /** (Admin only) Releases an arbitrary temporary system lock placed on a user bounds. */
92
+ async unlockUser(userId: string): Promise<void> {
93
+ return this.client.post<void>(`/api/v1/auth/admin/users/${userId}/unlock/`);
94
+ }
95
+ }