@apito-io/js-admin-sdk 2.5.0 → 2.7.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/README.md +17 -7
- package/dist/index.d.mts +54 -12
- package/dist/index.d.ts +54 -12
- package/dist/index.js +39 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +42 -35
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/client.test.ts +10 -4
- package/src/client.ts +133 -44
- package/src/index.ts +3 -0
- package/src/types.ts +40 -10
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -277,17 +277,23 @@ describe('ApitoClient', () => {
|
|
|
277
277
|
|
|
278
278
|
it('should login when APITO_PROJECT_ID and credentials are set', async () => {
|
|
279
279
|
const projectId = process.env.APITO_PROJECT_ID;
|
|
280
|
-
const
|
|
280
|
+
const email = process.env.APITO_TENANT_EMAIL ?? '';
|
|
281
|
+
const phone = process.env.APITO_TENANT_PHONE ?? '';
|
|
281
282
|
const password = process.env.APITO_TENANT_PASSWORD;
|
|
282
|
-
if (!projectId || !
|
|
283
|
+
if (!projectId || !(email.trim() || phone.trim()) || !password) {
|
|
283
284
|
console.log(
|
|
284
|
-
'loginTenantUser skipped — set APITO_PROJECT_ID,
|
|
285
|
+
'loginTenantUser skipped — set APITO_PROJECT_ID, APITO_TENANT_PASSWORD, and APITO_TENANT_EMAIL and/or APITO_TENANT_PHONE'
|
|
285
286
|
);
|
|
286
287
|
return;
|
|
287
288
|
}
|
|
288
289
|
const client = getTestClient();
|
|
289
290
|
try {
|
|
290
|
-
const login = await client.loginTenantUser(
|
|
291
|
+
const login = await client.loginTenantUser({
|
|
292
|
+
projectId,
|
|
293
|
+
password,
|
|
294
|
+
...(email.trim() ? { email: email.trim() } : {}),
|
|
295
|
+
...(phone.trim() ? { phone: phone.trim() } : {}),
|
|
296
|
+
});
|
|
291
297
|
expect(login.token).toBeDefined();
|
|
292
298
|
expect(login.token.length).toBeGreaterThan(0);
|
|
293
299
|
console.log('✅ loginTenantUser token length:', login.token.length);
|
package/src/client.ts
CHANGED
|
@@ -16,6 +16,9 @@ import {
|
|
|
16
16
|
TenantUsersResponse,
|
|
17
17
|
TenantByDomainResponse,
|
|
18
18
|
TenantCatalogSearchRow,
|
|
19
|
+
TenantLoginParams,
|
|
20
|
+
CreateTenantUserParams,
|
|
21
|
+
UpdateTenantUserParams,
|
|
19
22
|
} from './types';
|
|
20
23
|
|
|
21
24
|
/**
|
|
@@ -145,21 +148,46 @@ export class ApitoClient implements InjectedDBOperationInterface {
|
|
|
145
148
|
}
|
|
146
149
|
|
|
147
150
|
/**
|
|
148
|
-
* Tenant catalog login (system GraphQL).
|
|
151
|
+
* Tenant catalog login (system GraphQL `loginTenantUser`). Password path: pass `password` and `email` or `phone` per project Authentication settings. Google OAuth path: `authMethod: 'google'` with `code` and `state` from the redirect; call `tenantGoogleOAuthState(projectId)` before opening Google to obtain `state`.
|
|
149
152
|
*/
|
|
150
|
-
async loginTenantUser(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
async loginTenantUser(params: TenantLoginParams): Promise<TenantLoginResponse> {
|
|
154
|
+
const authMethod = (params.authMethod ?? 'general').trim().toLowerCase() || 'general';
|
|
155
|
+
const variables: Record<string, any> = {
|
|
156
|
+
project_id: params.projectId,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
if (authMethod === 'google') {
|
|
160
|
+
variables.auth_method = 'google';
|
|
161
|
+
const code = (params.code ?? '').trim();
|
|
162
|
+
const state = (params.state ?? '').trim();
|
|
163
|
+
if (!code || !state) {
|
|
164
|
+
throw new ValidationError('code and state are required for Google login');
|
|
165
|
+
}
|
|
166
|
+
variables.code = code;
|
|
167
|
+
variables.state = state;
|
|
168
|
+
} else {
|
|
169
|
+
const password = (params.password ?? '').trim();
|
|
170
|
+
if (!password) {
|
|
171
|
+
throw new ValidationError('password is required');
|
|
172
|
+
}
|
|
173
|
+
variables.password = password;
|
|
174
|
+
const email = (params.email ?? '').trim();
|
|
175
|
+
const phone = (params.phone ?? '').trim();
|
|
176
|
+
if (!email && !phone) {
|
|
177
|
+
throw new ValidationError('email or phone is required');
|
|
178
|
+
}
|
|
179
|
+
if (email) variables.email = email;
|
|
180
|
+
if (phone) variables.phone = phone;
|
|
181
|
+
}
|
|
182
|
+
|
|
155
183
|
const query = `
|
|
156
|
-
query LoginTenantUser($project_id: String!, $
|
|
157
|
-
loginTenantUser(project_id: $project_id,
|
|
184
|
+
query LoginTenantUser($project_id: String!, $password: String, $auth_method: String, $email: String, $phone: String, $code: String, $state: String) {
|
|
185
|
+
loginTenantUser(project_id: $project_id, password: $password, auth_method: $auth_method, email: $email, phone: $phone, code: $code, state: $state) {
|
|
158
186
|
token
|
|
159
187
|
user {
|
|
160
188
|
id
|
|
161
|
-
username
|
|
162
189
|
email
|
|
190
|
+
phone
|
|
163
191
|
role
|
|
164
192
|
provider
|
|
165
193
|
tenant_id
|
|
@@ -170,7 +198,6 @@ export class ApitoClient implements InjectedDBOperationInterface {
|
|
|
170
198
|
}
|
|
171
199
|
}
|
|
172
200
|
`;
|
|
173
|
-
const variables: Record<string, any> = { project_id: projectId, username, password };
|
|
174
201
|
const response = await this.executeGraphQL(query, variables);
|
|
175
202
|
const raw = response.data?.loginTenantUser;
|
|
176
203
|
if (!raw?.token) {
|
|
@@ -183,37 +210,24 @@ export class ApitoClient implements InjectedDBOperationInterface {
|
|
|
183
210
|
}
|
|
184
211
|
|
|
185
212
|
/**
|
|
186
|
-
*
|
|
213
|
+
* Signed OAuth state for tenant Google login (system query `tenantGoogleOAuthState`). Use in the authorize URL together with project `google_client_id` and the configured redirect URI.
|
|
187
214
|
*/
|
|
188
|
-
async
|
|
215
|
+
async tenantGoogleOAuthState(projectId: string): Promise<{ state: string }> {
|
|
189
216
|
const query = `
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
user {
|
|
194
|
-
id
|
|
195
|
-
username
|
|
196
|
-
email
|
|
197
|
-
role
|
|
198
|
-
provider
|
|
199
|
-
tenant_id
|
|
200
|
-
status
|
|
201
|
-
created_at
|
|
202
|
-
updated_at
|
|
203
|
-
}
|
|
217
|
+
query TenantGoogleOAuthState($project_id: String!) {
|
|
218
|
+
tenantGoogleOAuthState(project_id: $project_id) {
|
|
219
|
+
state
|
|
204
220
|
}
|
|
205
221
|
}
|
|
206
222
|
`;
|
|
207
|
-
const variables = { project_id: projectId
|
|
223
|
+
const variables = { project_id: projectId };
|
|
208
224
|
const response = await this.executeGraphQL(query, variables);
|
|
209
|
-
const raw = response.data?.
|
|
210
|
-
|
|
211
|
-
|
|
225
|
+
const raw = response.data?.tenantGoogleOAuthState;
|
|
226
|
+
const state = typeof raw?.state === 'string' ? raw.state.trim() : '';
|
|
227
|
+
if (!state) {
|
|
228
|
+
throw new ValidationError('Invalid response format for tenantGoogleOAuthState');
|
|
212
229
|
}
|
|
213
|
-
return {
|
|
214
|
-
token: raw.token as string,
|
|
215
|
-
user: raw.user as TenantUser | undefined,
|
|
216
|
-
};
|
|
230
|
+
return { state };
|
|
217
231
|
}
|
|
218
232
|
|
|
219
233
|
/**
|
|
@@ -230,8 +244,8 @@ export class ApitoClient implements InjectedDBOperationInterface {
|
|
|
230
244
|
count
|
|
231
245
|
users {
|
|
232
246
|
id
|
|
233
|
-
username
|
|
234
247
|
email
|
|
248
|
+
phone
|
|
235
249
|
role
|
|
236
250
|
provider
|
|
237
251
|
tenant_id
|
|
@@ -289,21 +303,22 @@ export class ApitoClient implements InjectedDBOperationInterface {
|
|
|
289
303
|
}
|
|
290
304
|
|
|
291
305
|
/**
|
|
292
|
-
* Create a tenant catalog user (local password).
|
|
306
|
+
* Create a tenant catalog user (local password). Use `email` and/or `phone` per engine validation for the project identifier mode.
|
|
293
307
|
*/
|
|
294
308
|
async createTenantUser(
|
|
295
309
|
projectId: string,
|
|
296
|
-
|
|
297
|
-
email: string,
|
|
298
|
-
password: string,
|
|
299
|
-
role: string
|
|
310
|
+
params: CreateTenantUserParams
|
|
300
311
|
): Promise<TenantUser> {
|
|
312
|
+
const password = (params.password ?? '').trim();
|
|
313
|
+
if (!password) {
|
|
314
|
+
throw new ValidationError('password is required');
|
|
315
|
+
}
|
|
301
316
|
const query = `
|
|
302
|
-
mutation CreateTenantUser($project_id: String!, $
|
|
303
|
-
createTenantUser(project_id: $project_id,
|
|
317
|
+
mutation CreateTenantUser($project_id: String!, $password: String!, $role: String, $email: String, $phone: String) {
|
|
318
|
+
createTenantUser(project_id: $project_id, password: $password, role: $role, email: $email, phone: $phone) {
|
|
304
319
|
id
|
|
305
|
-
username
|
|
306
320
|
email
|
|
321
|
+
phone
|
|
307
322
|
role
|
|
308
323
|
provider
|
|
309
324
|
tenant_id
|
|
@@ -313,7 +328,16 @@ export class ApitoClient implements InjectedDBOperationInterface {
|
|
|
313
328
|
}
|
|
314
329
|
}
|
|
315
330
|
`;
|
|
316
|
-
const variables
|
|
331
|
+
const variables: Record<string, any> = {
|
|
332
|
+
project_id: projectId,
|
|
333
|
+
password,
|
|
334
|
+
};
|
|
335
|
+
const role = (params.role ?? '').trim();
|
|
336
|
+
if (role) variables.role = role;
|
|
337
|
+
const email = (params.email ?? '').trim();
|
|
338
|
+
if (email) variables.email = email;
|
|
339
|
+
const phone = (params.phone ?? '').trim();
|
|
340
|
+
if (phone) variables.phone = phone;
|
|
317
341
|
const response = await this.executeGraphQL(query, variables);
|
|
318
342
|
const u = response.data?.createTenantUser;
|
|
319
343
|
if (!u?.id) {
|
|
@@ -322,6 +346,71 @@ export class ApitoClient implements InjectedDBOperationInterface {
|
|
|
322
346
|
return u as TenantUser;
|
|
323
347
|
}
|
|
324
348
|
|
|
349
|
+
/**
|
|
350
|
+
* Update a tenant catalog user. Project scope is implied by the API key. Only include fields to change.
|
|
351
|
+
*/
|
|
352
|
+
async updateTenantUser(userId: string, params: UpdateTenantUserParams): Promise<TenantUser> {
|
|
353
|
+
const uid = (userId ?? '').trim();
|
|
354
|
+
if (!uid) {
|
|
355
|
+
throw new ValidationError('userId is required');
|
|
356
|
+
}
|
|
357
|
+
if (
|
|
358
|
+
params.email === undefined &&
|
|
359
|
+
params.phone === undefined &&
|
|
360
|
+
params.password === undefined &&
|
|
361
|
+
params.role === undefined
|
|
362
|
+
) {
|
|
363
|
+
throw new ValidationError('at least one field must be provided');
|
|
364
|
+
}
|
|
365
|
+
const query = `
|
|
366
|
+
mutation UpdateTenantUser($user_id: String!, $email: String, $phone: String, $password: String, $role: String) {
|
|
367
|
+
updateTenantUser(user_id: $user_id, email: $email, phone: $phone, password: $password, role: $role) {
|
|
368
|
+
id
|
|
369
|
+
email
|
|
370
|
+
phone
|
|
371
|
+
role
|
|
372
|
+
provider
|
|
373
|
+
tenant_id
|
|
374
|
+
status
|
|
375
|
+
created_at
|
|
376
|
+
updated_at
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
`;
|
|
380
|
+
const variables: Record<string, any> = { user_id: uid };
|
|
381
|
+
if (params.email !== undefined) variables.email = params.email;
|
|
382
|
+
if (params.phone !== undefined) variables.phone = params.phone;
|
|
383
|
+
if (params.password !== undefined) variables.password = params.password;
|
|
384
|
+
if (params.role !== undefined) variables.role = params.role;
|
|
385
|
+
const response = await this.executeGraphQL(query, variables);
|
|
386
|
+
const u = response.data?.updateTenantUser;
|
|
387
|
+
if (!u?.id) {
|
|
388
|
+
throw new ValidationError('Invalid response format for updateTenantUser');
|
|
389
|
+
}
|
|
390
|
+
return u as TenantUser;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Delete a tenant catalog user by id. Project scope is implied by the API key.
|
|
395
|
+
*/
|
|
396
|
+
async deleteTenantUser(userId: string): Promise<boolean> {
|
|
397
|
+
const uid = (userId ?? '').trim();
|
|
398
|
+
if (!uid) {
|
|
399
|
+
throw new ValidationError('userId is required');
|
|
400
|
+
}
|
|
401
|
+
const query = `
|
|
402
|
+
mutation DeleteTenantUser($user_id: String!) {
|
|
403
|
+
deleteTenantUser(user_id: $user_id)
|
|
404
|
+
}
|
|
405
|
+
`;
|
|
406
|
+
const response = await this.executeGraphQL(query, { user_id: uid });
|
|
407
|
+
const ok = response.data?.deleteTenantUser;
|
|
408
|
+
if (typeof ok !== 'boolean') {
|
|
409
|
+
throw new ValidationError('Invalid response format for deleteTenantUser');
|
|
410
|
+
}
|
|
411
|
+
return ok;
|
|
412
|
+
}
|
|
413
|
+
|
|
325
414
|
/**
|
|
326
415
|
* Get a single resource by model and ID
|
|
327
416
|
*/
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -89,8 +89,8 @@ export interface CreateAndUpdateRequest {
|
|
|
89
89
|
/** Tenant catalog user from engine system DB (pro_tenant_users). */
|
|
90
90
|
export interface TenantUser {
|
|
91
91
|
id: string;
|
|
92
|
-
username: string;
|
|
93
92
|
email?: string;
|
|
93
|
+
phone?: string;
|
|
94
94
|
role: string;
|
|
95
95
|
tenant_id: string;
|
|
96
96
|
provider?: string;
|
|
@@ -99,6 +99,40 @@ export interface TenantUser {
|
|
|
99
99
|
updated_at?: string;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/** Login via system GraphQL `loginTenantUser`. Password path: use `email` or `phone` per project settings. Google OAuth code path: `authMethod: 'google'`, `code`, `state` from redirect (get `state` first via `tenantGoogleOAuthState`). */
|
|
103
|
+
export interface TenantLoginParams {
|
|
104
|
+
projectId: string;
|
|
105
|
+
/** Required for general (password) login. */
|
|
106
|
+
password?: string;
|
|
107
|
+
email?: string;
|
|
108
|
+
phone?: string;
|
|
109
|
+
/** `general` (default) or `google`. */
|
|
110
|
+
authMethod?: string;
|
|
111
|
+
/** Google authorization code (with `authMethod: 'google'`). */
|
|
112
|
+
code?: string;
|
|
113
|
+
/** OAuth state from `tenantGoogleOAuthState` or callback (with `authMethod: 'google'`). */
|
|
114
|
+
state?: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface TenantGoogleOAuthStateResponse {
|
|
118
|
+
state: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface CreateTenantUserParams {
|
|
122
|
+
password: string;
|
|
123
|
+
role?: string;
|
|
124
|
+
email?: string;
|
|
125
|
+
phone?: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Optional fields for `updateTenantUser`; omitted keys are not sent. */
|
|
129
|
+
export interface UpdateTenantUserParams {
|
|
130
|
+
email?: string;
|
|
131
|
+
phone?: string;
|
|
132
|
+
password?: string;
|
|
133
|
+
role?: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
102
136
|
export interface TenantLoginResponse {
|
|
103
137
|
token: string;
|
|
104
138
|
user?: TenantUser;
|
|
@@ -158,17 +192,13 @@ export interface InjectedDBOperationInterface {
|
|
|
158
192
|
): Promise<GraphQLResponse>;
|
|
159
193
|
/** @param token Legacy; ignored. Auth uses client API key. */
|
|
160
194
|
generateTenantToken(tenantId: string, duration?: string, role?: string): Promise<string>;
|
|
161
|
-
loginTenantUser(
|
|
162
|
-
|
|
195
|
+
loginTenantUser(params: TenantLoginParams): Promise<TenantLoginResponse>;
|
|
196
|
+
tenantGoogleOAuthState(projectId: string): Promise<TenantGoogleOAuthStateResponse>;
|
|
163
197
|
searchTenantUsers(projectId: string, limit?: number, offset?: number): Promise<TenantUsersResponse>;
|
|
164
198
|
searchTenantsByDomain(projectId: string, domain: string): Promise<TenantByDomainResponse>;
|
|
165
|
-
createTenantUser(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
email: string,
|
|
169
|
-
password: string,
|
|
170
|
-
role: string
|
|
171
|
-
): Promise<TenantUser>;
|
|
199
|
+
createTenantUser(projectId: string, params: CreateTenantUserParams): Promise<TenantUser>;
|
|
200
|
+
updateTenantUser(userId: string, params: UpdateTenantUserParams): Promise<TenantUser>;
|
|
201
|
+
deleteTenantUser(userId: string): Promise<boolean>;
|
|
172
202
|
getSingleResource(model: string, id: string, singlePageData?: boolean): Promise<DefaultDocumentStructure>;
|
|
173
203
|
searchResources(model: string, filter?: Record<string, any>, aggregate?: boolean): Promise<SearchResult>;
|
|
174
204
|
getRelationDocuments(id: string, connection: Record<string, any>): Promise<SearchResult>;
|