@authhero/react-admin 0.10.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/.eslintrc.js +21 -0
- package/.vercelignore +4 -0
- package/CHANGELOG.md +56 -0
- package/LICENSE +21 -0
- package/README.md +50 -0
- package/index.html +125 -0
- package/package.json +61 -0
- package/prettier.config.js +1 -0
- package/public/favicon.ico +0 -0
- package/public/manifest.json +15 -0
- package/src/App.spec.tsx +42 -0
- package/src/App.tsx +232 -0
- package/src/AuthCallback.tsx +138 -0
- package/src/Layout.tsx +12 -0
- package/src/TenantsApp.tsx +115 -0
- package/src/auth0DataProvider.ts +1242 -0
- package/src/authProvider.ts +521 -0
- package/src/components/CertificateErrorDialog.tsx +116 -0
- package/src/components/DomainSelector.tsx +401 -0
- package/src/components/TenantAppBar.tsx +83 -0
- package/src/components/TenantLayout.tsx +25 -0
- package/src/components/TenantsAppBar.tsx +21 -0
- package/src/components/TenantsLayout.tsx +28 -0
- package/src/components/activity/ActivityDashboard.tsx +381 -0
- package/src/components/activity/index.ts +1 -0
- package/src/components/branding/BrandingList.tsx +0 -0
- package/src/components/branding/BrandingShow.tsx +0 -0
- package/src/components/branding/ThemesTab.tsx +286 -0
- package/src/components/branding/edit.tsx +149 -0
- package/src/components/branding/hooks/useThemesData.ts +123 -0
- package/src/components/branding/index.ts +2 -0
- package/src/components/branding/list.tsx +12 -0
- package/src/components/clients/create.tsx +12 -0
- package/src/components/clients/edit.tsx +1285 -0
- package/src/components/clients/index.ts +3 -0
- package/src/components/clients/list.tsx +37 -0
- package/src/components/common/DateAgo.tsx +6 -0
- package/src/components/common/JsonOutput.tsx +26 -0
- package/src/components/common/index.ts +1 -0
- package/src/components/connections/create.tsx +35 -0
- package/src/components/connections/edit.tsx +212 -0
- package/src/components/connections/index.ts +3 -0
- package/src/components/connections/list.tsx +15 -0
- package/src/components/custom-domains/create.tsx +26 -0
- package/src/components/custom-domains/edit.tsx +101 -0
- package/src/components/custom-domains/index.ts +3 -0
- package/src/components/custom-domains/list.tsx +16 -0
- package/src/components/flows/create.tsx +30 -0
- package/src/components/flows/edit.tsx +238 -0
- package/src/components/flows/index.ts +3 -0
- package/src/components/flows/list.tsx +15 -0
- package/src/components/forms/FlowEditor.tsx +1363 -0
- package/src/components/forms/NodeEditor.tsx +1119 -0
- package/src/components/forms/RichTextEditor.tsx +145 -0
- package/src/components/forms/create.tsx +30 -0
- package/src/components/forms/edit.tsx +256 -0
- package/src/components/forms/index.ts +3 -0
- package/src/components/forms/list.tsx +16 -0
- package/src/components/hooks/create.tsx +96 -0
- package/src/components/hooks/edit.tsx +114 -0
- package/src/components/hooks/index.ts +3 -0
- package/src/components/hooks/list.tsx +17 -0
- package/src/components/listActions/PostListActions.tsx +10 -0
- package/src/components/logs/LogIcon.tsx +32 -0
- package/src/components/logs/LogShow.tsx +82 -0
- package/src/components/logs/LogType.tsx +38 -0
- package/src/components/logs/index.ts +4 -0
- package/src/components/logs/list.tsx +41 -0
- package/src/components/organizations/create.tsx +13 -0
- package/src/components/organizations/edit.tsx +682 -0
- package/src/components/organizations/index.ts +3 -0
- package/src/components/organizations/list.tsx +21 -0
- package/src/components/resource-servers/create.tsx +87 -0
- package/src/components/resource-servers/edit.tsx +121 -0
- package/src/components/resource-servers/index.ts +3 -0
- package/src/components/resource-servers/list.tsx +47 -0
- package/src/components/roles/create.tsx +12 -0
- package/src/components/roles/edit.tsx +426 -0
- package/src/components/roles/index.ts +3 -0
- package/src/components/roles/list.tsx +24 -0
- package/src/components/sessions/edit.tsx +101 -0
- package/src/components/sessions/index.ts +3 -0
- package/src/components/sessions/list.tsx +20 -0
- package/src/components/sessions/show.tsx +113 -0
- package/src/components/settings/edit.tsx +236 -0
- package/src/components/settings/index.ts +2 -0
- package/src/components/settings/list.tsx +14 -0
- package/src/components/tenants/create.tsx +20 -0
- package/src/components/tenants/edit.tsx +54 -0
- package/src/components/tenants/index.ts +2 -0
- package/src/components/tenants/list.tsx +67 -0
- package/src/components/themes/edit.tsx +200 -0
- package/src/components/themes/index.ts +2 -0
- package/src/components/themes/list.tsx +12 -0
- package/src/components/users/create.tsx +144 -0
- package/src/components/users/edit.tsx +1711 -0
- package/src/components/users/index.ts +3 -0
- package/src/components/users/list.tsx +35 -0
- package/src/data.json +121 -0
- package/src/dataProvider.ts +97 -0
- package/src/index.tsx +106 -0
- package/src/lib/logs.ts +21 -0
- package/src/types/reactflow.d.ts +86 -0
- package/src/utils/domainUtils.ts +169 -0
- package/src/utils/tokenUtils.ts +75 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +37 -0
- package/tsconfig.node.json +10 -0
- package/vercel.json +17 -0
- package/vite.config.ts +30 -0
|
@@ -0,0 +1,1242 @@
|
|
|
1
|
+
import { fetchUtils, DataProvider } from "ra-core";
|
|
2
|
+
import { UpdateParams } from "react-admin";
|
|
3
|
+
import { createManagementClient } from "./authProvider";
|
|
4
|
+
import { ManagementClient } from "auth0";
|
|
5
|
+
|
|
6
|
+
// Add this at the top of the file with other imports
|
|
7
|
+
function stringify(obj: Record<string, any>): string {
|
|
8
|
+
return Object.entries(obj)
|
|
9
|
+
.filter(([_, value]) => value !== undefined)
|
|
10
|
+
.map(
|
|
11
|
+
([key, value]) =>
|
|
12
|
+
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`,
|
|
13
|
+
)
|
|
14
|
+
.join("&");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function removeExtraFields(params: UpdateParams) {
|
|
18
|
+
// delete params.data?.id; // this is required for patch... but not for put?
|
|
19
|
+
delete params.data?.tenant_id;
|
|
20
|
+
delete params.data?.updated_at;
|
|
21
|
+
delete params.data?.created_at;
|
|
22
|
+
delete params.data?.identities;
|
|
23
|
+
|
|
24
|
+
// hmmmmm, this is an issue we have here with mismatching structure?
|
|
25
|
+
// seems like we need to modify our endpoints to accept connections.
|
|
26
|
+
// TBD with Markus
|
|
27
|
+
delete params.data?.connections;
|
|
28
|
+
|
|
29
|
+
// actually Auth0 does not require this for patching. seems dangerous not to rely on an auto-id
|
|
30
|
+
// as may get rejected for having the same id
|
|
31
|
+
delete params.data?.id;
|
|
32
|
+
// for user we don't want to include this
|
|
33
|
+
delete params.data?.user_id;
|
|
34
|
+
|
|
35
|
+
// extra user fields
|
|
36
|
+
delete params.data?.last_login;
|
|
37
|
+
delete params.data?.provider;
|
|
38
|
+
|
|
39
|
+
// Remove empty properties
|
|
40
|
+
Object.keys(params.data).forEach((key) => {
|
|
41
|
+
if (params.data[key] === undefined) {
|
|
42
|
+
delete params.data[key];
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return params;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseResource(resourcePath: string) {
|
|
50
|
+
return resourcePath.split("/").pop() || resourcePath;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Helper to normalize SDK response format variations
|
|
54
|
+
function normalizeSDKResponse(
|
|
55
|
+
result: any,
|
|
56
|
+
resourceKey: string,
|
|
57
|
+
): { data: any[]; total: number } {
|
|
58
|
+
const response = (result as any).response || {};
|
|
59
|
+
|
|
60
|
+
// Handle direct array format
|
|
61
|
+
if (Array.isArray(response)) {
|
|
62
|
+
return { data: response, total: response.length };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Handle SDK wrapper format with resource key
|
|
66
|
+
if (response[resourceKey]) {
|
|
67
|
+
return {
|
|
68
|
+
data: response[resourceKey],
|
|
69
|
+
total: response.total || response.length || response[resourceKey].length,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Handle result itself being the array
|
|
74
|
+
if (Array.isArray(result)) {
|
|
75
|
+
return { data: result, total: result.length };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Fallback to empty array
|
|
79
|
+
return { data: [], total: 0 };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Helper to create headers with tenant ID
|
|
83
|
+
function createHeaders(tenantId?: string): Headers {
|
|
84
|
+
const headers = new Headers();
|
|
85
|
+
if (tenantId) {
|
|
86
|
+
headers.set("tenant-id", tenantId);
|
|
87
|
+
}
|
|
88
|
+
return headers;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Helper to handle singleton resource fetching
|
|
92
|
+
async function fetchSingleton(
|
|
93
|
+
resource: string,
|
|
94
|
+
fetcher: () => Promise<any>,
|
|
95
|
+
): Promise<{ data: any[]; total: number }> {
|
|
96
|
+
try {
|
|
97
|
+
const result = await fetcher();
|
|
98
|
+
const data = (result as any).response || result;
|
|
99
|
+
// Spread data first, then override id to ensure it matches the resource name
|
|
100
|
+
return {
|
|
101
|
+
data: [{ ...data, id: resource }],
|
|
102
|
+
total: 1,
|
|
103
|
+
};
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(`Error in getList for ${resource}:`, error);
|
|
106
|
+
return {
|
|
107
|
+
data: [{ id: resource }],
|
|
108
|
+
total: 1,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Maps react-admin queries to the auth0 management api
|
|
115
|
+
* Uses HTTP client for all API calls with custom headers for tenant isolation
|
|
116
|
+
*/
|
|
117
|
+
export default (
|
|
118
|
+
apiUrl: string,
|
|
119
|
+
httpClient = fetchUtils.fetchJson,
|
|
120
|
+
tenantId?: string,
|
|
121
|
+
domain?: string,
|
|
122
|
+
): DataProvider => {
|
|
123
|
+
// Get or create management client for SDK calls
|
|
124
|
+
let managementClientPromise: Promise<ManagementClient> | null = null;
|
|
125
|
+
const getManagementClient = async () => {
|
|
126
|
+
if (!managementClientPromise) {
|
|
127
|
+
// Extract API domain from apiUrl
|
|
128
|
+
const apiDomain = apiUrl.replace(/^https?:\/\//, "").replace(/\/.*$/, "");
|
|
129
|
+
// Pass both API domain and OAuth domain for authentication
|
|
130
|
+
managementClientPromise = createManagementClient(
|
|
131
|
+
apiDomain,
|
|
132
|
+
tenantId,
|
|
133
|
+
domain,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return managementClientPromise;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
getList: async (resourcePath, params) => {
|
|
141
|
+
const resource = parseResource(resourcePath);
|
|
142
|
+
const { page = 1, perPage } = params.pagination || {};
|
|
143
|
+
const { field, order } = params.sort || {};
|
|
144
|
+
const managementClient = await getManagementClient();
|
|
145
|
+
|
|
146
|
+
// SDK resource handlers configuration
|
|
147
|
+
const sdkHandlers: Record<
|
|
148
|
+
string,
|
|
149
|
+
{
|
|
150
|
+
fetch: (client: ManagementClient) => Promise<any>;
|
|
151
|
+
resourceKey: string;
|
|
152
|
+
idKey: string;
|
|
153
|
+
}
|
|
154
|
+
> = {
|
|
155
|
+
users: {
|
|
156
|
+
fetch: (client) =>
|
|
157
|
+
client.users.list({
|
|
158
|
+
page: page - 1,
|
|
159
|
+
per_page: perPage,
|
|
160
|
+
sort:
|
|
161
|
+
field && order
|
|
162
|
+
? `${field}:${order === "DESC" ? "-1" : "1"}`
|
|
163
|
+
: undefined,
|
|
164
|
+
q: params.filter?.q,
|
|
165
|
+
include_totals: true,
|
|
166
|
+
}),
|
|
167
|
+
resourceKey: "users",
|
|
168
|
+
idKey: "user_id",
|
|
169
|
+
},
|
|
170
|
+
clients: {
|
|
171
|
+
fetch: (client) =>
|
|
172
|
+
client.clients.list({
|
|
173
|
+
page: page - 1,
|
|
174
|
+
per_page: perPage,
|
|
175
|
+
include_totals: true,
|
|
176
|
+
}),
|
|
177
|
+
resourceKey: "clients",
|
|
178
|
+
idKey: "client_id",
|
|
179
|
+
},
|
|
180
|
+
connections: {
|
|
181
|
+
fetch: (client) => client.connections.list(),
|
|
182
|
+
resourceKey: "connections",
|
|
183
|
+
idKey: "id",
|
|
184
|
+
},
|
|
185
|
+
roles: {
|
|
186
|
+
fetch: (client) =>
|
|
187
|
+
client.roles.list({
|
|
188
|
+
page: page - 1,
|
|
189
|
+
per_page: perPage,
|
|
190
|
+
}),
|
|
191
|
+
resourceKey: "roles",
|
|
192
|
+
idKey: "id",
|
|
193
|
+
},
|
|
194
|
+
"resource-servers": {
|
|
195
|
+
fetch: (client) => client.resourceServers.list(),
|
|
196
|
+
resourceKey: "resource_servers",
|
|
197
|
+
idKey: "id",
|
|
198
|
+
},
|
|
199
|
+
organizations: {
|
|
200
|
+
fetch: (client) =>
|
|
201
|
+
client.organizations.list({
|
|
202
|
+
from: String((page - 1) * (perPage || 10)),
|
|
203
|
+
take: perPage || 10,
|
|
204
|
+
}),
|
|
205
|
+
resourceKey: "organizations",
|
|
206
|
+
idKey: "id",
|
|
207
|
+
},
|
|
208
|
+
logs: {
|
|
209
|
+
fetch: (client) => {
|
|
210
|
+
// Build the query string, combining search query and IP filter
|
|
211
|
+
let query = params.filter?.q || "";
|
|
212
|
+
if (params.filter?.ip) {
|
|
213
|
+
const ipQuery = `ip:${params.filter.ip}`;
|
|
214
|
+
query = query ? `${query} AND ${ipQuery}` : ipQuery;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return client.logs.list({
|
|
218
|
+
page: page - 1,
|
|
219
|
+
per_page: perPage,
|
|
220
|
+
q: query || undefined,
|
|
221
|
+
sort:
|
|
222
|
+
field && order
|
|
223
|
+
? `${field}:${order === "DESC" ? "-1" : "1"}`
|
|
224
|
+
: undefined,
|
|
225
|
+
include_totals: true,
|
|
226
|
+
});
|
|
227
|
+
},
|
|
228
|
+
resourceKey: "logs",
|
|
229
|
+
idKey: "log_id",
|
|
230
|
+
},
|
|
231
|
+
rules: {
|
|
232
|
+
fetch: (client) => client.rules.list(),
|
|
233
|
+
resourceKey: "rules",
|
|
234
|
+
idKey: "id",
|
|
235
|
+
},
|
|
236
|
+
"client-grants": {
|
|
237
|
+
fetch: (client) =>
|
|
238
|
+
client.clientGrants.list({
|
|
239
|
+
page: page - 1,
|
|
240
|
+
per_page: perPage,
|
|
241
|
+
...(params.filter?.client_id && {
|
|
242
|
+
client_id: params.filter.client_id,
|
|
243
|
+
}),
|
|
244
|
+
}),
|
|
245
|
+
resourceKey: "client_grants",
|
|
246
|
+
idKey: "id",
|
|
247
|
+
},
|
|
248
|
+
forms: {
|
|
249
|
+
fetch: (client) => client.forms.list(),
|
|
250
|
+
resourceKey: "forms",
|
|
251
|
+
idKey: "id",
|
|
252
|
+
},
|
|
253
|
+
"custom-domains": {
|
|
254
|
+
fetch: (client: any) => client.customDomains.list(),
|
|
255
|
+
resourceKey: "custom_domains",
|
|
256
|
+
idKey: "custom_domain_id",
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// Handle SDK resources
|
|
261
|
+
const handler = sdkHandlers[resource];
|
|
262
|
+
if (handler) {
|
|
263
|
+
const result = await handler.fetch(managementClient);
|
|
264
|
+
const { data, total } = normalizeSDKResponse(
|
|
265
|
+
result,
|
|
266
|
+
handler.resourceKey,
|
|
267
|
+
);
|
|
268
|
+
return {
|
|
269
|
+
data: data.map((item: any) => ({
|
|
270
|
+
id: item[handler.idKey] || item.id,
|
|
271
|
+
...item,
|
|
272
|
+
})),
|
|
273
|
+
total,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Handle singleton resources
|
|
278
|
+
if (resource === "branding") {
|
|
279
|
+
return fetchSingleton(resource, () => managementClient.branding.get());
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (resource === "settings") {
|
|
283
|
+
return fetchSingleton(resource, () =>
|
|
284
|
+
managementClient.tenants.settings.get(),
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Handle stats/daily endpoint
|
|
289
|
+
if (resourcePath === "stats/daily") {
|
|
290
|
+
const headers = createHeaders(tenantId);
|
|
291
|
+
const query: any = {};
|
|
292
|
+
if (params.filter?.from) query.from = params.filter.from;
|
|
293
|
+
if (params.filter?.to) query.to = params.filter.to;
|
|
294
|
+
|
|
295
|
+
const url = `${apiUrl}/api/v2/stats/daily${Object.keys(query).length ? `?${stringify(query)}` : ""}`;
|
|
296
|
+
try {
|
|
297
|
+
const res = await httpClient(url, { headers });
|
|
298
|
+
// Stats endpoint returns an array directly
|
|
299
|
+
const data = Array.isArray(res.json) ? res.json : [];
|
|
300
|
+
return {
|
|
301
|
+
data: data.map((item: any, index: number) => ({
|
|
302
|
+
id: item.date || index,
|
|
303
|
+
...item,
|
|
304
|
+
})),
|
|
305
|
+
total: data.length,
|
|
306
|
+
};
|
|
307
|
+
} catch (error) {
|
|
308
|
+
console.error("Error fetching daily stats:", error);
|
|
309
|
+
return { data: [], total: 0 };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Use HTTP client for all other list operations
|
|
314
|
+
const headers = createHeaders(tenantId);
|
|
315
|
+
|
|
316
|
+
const query: any = {
|
|
317
|
+
include_totals: true,
|
|
318
|
+
page: page - 1,
|
|
319
|
+
per_page: perPage,
|
|
320
|
+
sort:
|
|
321
|
+
field && order
|
|
322
|
+
? `${field}:${order === "DESC" ? "-1" : "1"}`
|
|
323
|
+
: undefined,
|
|
324
|
+
q: params.filter?.q,
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const url = `${apiUrl}/api/v2/${resourcePath}?${stringify(query)}`;
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
const res = await httpClient(url, { headers });
|
|
331
|
+
|
|
332
|
+
// Handle case where API returns an array directly (like custom_domains)
|
|
333
|
+
if (Array.isArray(res.json)) {
|
|
334
|
+
return {
|
|
335
|
+
data: res.json.map((item) => ({
|
|
336
|
+
id: item.custom_domain_id || item.id,
|
|
337
|
+
...item,
|
|
338
|
+
})),
|
|
339
|
+
total: res.json.length,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Handle standard case where API returns an object with a property named after the resource
|
|
344
|
+
return {
|
|
345
|
+
data:
|
|
346
|
+
res.json[resource]?.map((item: any) => ({
|
|
347
|
+
id: item.custom_domain_id || item.id,
|
|
348
|
+
...item,
|
|
349
|
+
})) || [],
|
|
350
|
+
total: res.json.total || res.json.length || 0,
|
|
351
|
+
};
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.error("Error in getList:", error);
|
|
354
|
+
throw error;
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
getOne: async (resource, params) => {
|
|
359
|
+
const managementClient = await getManagementClient();
|
|
360
|
+
|
|
361
|
+
// SDK resource handlers for getOne
|
|
362
|
+
const sdkGetHandlers: Record<
|
|
363
|
+
string,
|
|
364
|
+
{ fetch: (id: string) => Promise<any>; idKey: string }
|
|
365
|
+
> = {
|
|
366
|
+
users: {
|
|
367
|
+
fetch: (id) => managementClient.users.get(id),
|
|
368
|
+
idKey: "user_id",
|
|
369
|
+
},
|
|
370
|
+
clients: {
|
|
371
|
+
fetch: (id) => managementClient.clients.get(id),
|
|
372
|
+
idKey: "client_id",
|
|
373
|
+
},
|
|
374
|
+
"custom-domains": {
|
|
375
|
+
fetch: (id) => (managementClient as any).customDomains.get(id),
|
|
376
|
+
idKey: "custom_domain_id",
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const handler = sdkGetHandlers[resource];
|
|
381
|
+
if (handler) {
|
|
382
|
+
const result = await handler.fetch(params.id as string);
|
|
383
|
+
return {
|
|
384
|
+
data: {
|
|
385
|
+
id: result[handler.idKey] || result.id,
|
|
386
|
+
...result,
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Handle singleton resources
|
|
392
|
+
if (resource === "branding") {
|
|
393
|
+
const result = await managementClient.branding.get();
|
|
394
|
+
return {
|
|
395
|
+
data: {
|
|
396
|
+
...result,
|
|
397
|
+
id: resource,
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (resource === "settings") {
|
|
403
|
+
const result = await managementClient.tenants.settings.get();
|
|
404
|
+
return {
|
|
405
|
+
data: {
|
|
406
|
+
...result,
|
|
407
|
+
id: resource,
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Handle stats/active-users endpoint
|
|
413
|
+
if (resource === "stats/active-users") {
|
|
414
|
+
const headers = createHeaders(tenantId);
|
|
415
|
+
try {
|
|
416
|
+
const res = await httpClient(`${apiUrl}/api/v2/stats/active-users`, {
|
|
417
|
+
headers,
|
|
418
|
+
});
|
|
419
|
+
// API returns a number directly
|
|
420
|
+
const count = typeof res.json === "number" ? res.json : 0;
|
|
421
|
+
return {
|
|
422
|
+
data: {
|
|
423
|
+
id: "count",
|
|
424
|
+
count,
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
} catch (error) {
|
|
428
|
+
console.error("Error fetching active users:", error);
|
|
429
|
+
return {
|
|
430
|
+
data: {
|
|
431
|
+
id: "count",
|
|
432
|
+
count: 0,
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Special handling for tenants - fetch from list and find by ID
|
|
439
|
+
if (resource === "tenants") {
|
|
440
|
+
const headers = createHeaders(tenantId);
|
|
441
|
+
|
|
442
|
+
try {
|
|
443
|
+
const res = await httpClient(`${apiUrl}/api/v2/tenants`, {
|
|
444
|
+
headers,
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
const tenants = res.json.tenants || [];
|
|
448
|
+
const tenant = tenants.find(
|
|
449
|
+
(t: any) => t.id === params.id || t.tenant_id === params.id,
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
if (tenant) {
|
|
453
|
+
return {
|
|
454
|
+
data: {
|
|
455
|
+
id: tenant.id || tenant.tenant_id,
|
|
456
|
+
...tenant,
|
|
457
|
+
},
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return {
|
|
462
|
+
data: {
|
|
463
|
+
id: params.id,
|
|
464
|
+
name: params.id,
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
} catch (error) {
|
|
468
|
+
console.warn(`Could not fetch tenant ${params.id}:`, error);
|
|
469
|
+
return {
|
|
470
|
+
data: {
|
|
471
|
+
id: params.id,
|
|
472
|
+
name: params.id,
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// HTTP for other resources
|
|
479
|
+
const headers = createHeaders(tenantId);
|
|
480
|
+
return httpClient(`${apiUrl}/api/v2/${resource}/${params.id}`, {
|
|
481
|
+
headers,
|
|
482
|
+
}).then(({ json }) => ({
|
|
483
|
+
data: {
|
|
484
|
+
id: json.id,
|
|
485
|
+
...json,
|
|
486
|
+
},
|
|
487
|
+
}));
|
|
488
|
+
},
|
|
489
|
+
|
|
490
|
+
getMany: (resourcePath, params) => {
|
|
491
|
+
const query = `id:(${params.ids.join(" ")})`;
|
|
492
|
+
|
|
493
|
+
const url = `${apiUrl}/api/v2/${resourcePath}?q=${query}`;
|
|
494
|
+
return httpClient(url).then(({ json }) => ({
|
|
495
|
+
data: {
|
|
496
|
+
id: json.id,
|
|
497
|
+
...json,
|
|
498
|
+
},
|
|
499
|
+
}));
|
|
500
|
+
},
|
|
501
|
+
|
|
502
|
+
getManyReference: async (resource, params) => {
|
|
503
|
+
const { page, perPage } = params.pagination;
|
|
504
|
+
const { field, order } = params.sort;
|
|
505
|
+
const managementClient = await getManagementClient();
|
|
506
|
+
|
|
507
|
+
// Build common query params for pagination
|
|
508
|
+
const buildPaginationParams = () => ({
|
|
509
|
+
page: page - 1,
|
|
510
|
+
per_page: perPage,
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
// Sessions nested under users
|
|
514
|
+
if (resource === "sessions") {
|
|
515
|
+
// Sessions are user-specific, use HTTP
|
|
516
|
+
const headers = createHeaders(tenantId);
|
|
517
|
+
const res = await httpClient(
|
|
518
|
+
`${apiUrl}/api/v2/users/${params.id}/sessions?${stringify({
|
|
519
|
+
include_totals: true,
|
|
520
|
+
...buildPaginationParams(),
|
|
521
|
+
sort: `${field}:${order === "DESC" ? "-1" : "1"}`,
|
|
522
|
+
})}`,
|
|
523
|
+
{ headers },
|
|
524
|
+
);
|
|
525
|
+
return {
|
|
526
|
+
data: res.json.sessions.map((item: any) => ({
|
|
527
|
+
id: item.id,
|
|
528
|
+
...item,
|
|
529
|
+
})),
|
|
530
|
+
total: res.json.length || 0,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Permissions nested under users
|
|
535
|
+
if (resource === "permissions" && params.target === "user_id") {
|
|
536
|
+
const result = await managementClient.users.permissions.list(
|
|
537
|
+
params.id as string,
|
|
538
|
+
buildPaginationParams(),
|
|
539
|
+
);
|
|
540
|
+
const permissions = (result as any).response || result;
|
|
541
|
+
const permissionsArray = Array.isArray(permissions)
|
|
542
|
+
? permissions
|
|
543
|
+
: permissions.permissions || [];
|
|
544
|
+
return {
|
|
545
|
+
data: permissionsArray.map((item: any) => ({
|
|
546
|
+
id: `${item.resource_server_identifier}:${item.permission_name}`,
|
|
547
|
+
...item,
|
|
548
|
+
})),
|
|
549
|
+
total: permissionsArray.length || 0,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Permissions nested under roles
|
|
554
|
+
if (resource === "permissions" && params.target === "role_id") {
|
|
555
|
+
const result = await managementClient.roles.permissions.list(
|
|
556
|
+
params.id as string,
|
|
557
|
+
buildPaginationParams(),
|
|
558
|
+
);
|
|
559
|
+
const permissions = (result as any).response || result;
|
|
560
|
+
const permissionsArray = Array.isArray(permissions)
|
|
561
|
+
? permissions
|
|
562
|
+
: permissions.permissions || [];
|
|
563
|
+
return {
|
|
564
|
+
data: permissionsArray.map((item: any) => ({
|
|
565
|
+
id: `${item.resource_server_identifier}:${item.permission_name}`,
|
|
566
|
+
...item,
|
|
567
|
+
})),
|
|
568
|
+
total: permissionsArray.length || 0,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Roles nested under users
|
|
573
|
+
if (resource === "roles" && params.target === "user_id") {
|
|
574
|
+
const result = await managementClient.users.roles.list(
|
|
575
|
+
params.id as string,
|
|
576
|
+
);
|
|
577
|
+
const roles = (result as any).response || result;
|
|
578
|
+
const rolesArray = Array.isArray(roles) ? roles : [];
|
|
579
|
+
return {
|
|
580
|
+
data: rolesArray.map((item: any) => ({
|
|
581
|
+
id: item.id,
|
|
582
|
+
...item,
|
|
583
|
+
})),
|
|
584
|
+
total: rolesArray.length,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Organization members
|
|
589
|
+
if (
|
|
590
|
+
resource === "organization-members" &&
|
|
591
|
+
params.target === "organization_id"
|
|
592
|
+
) {
|
|
593
|
+
const result = await managementClient.organizations.members.list(
|
|
594
|
+
params.id as string,
|
|
595
|
+
{
|
|
596
|
+
from: String((page - 1) * perPage),
|
|
597
|
+
take: perPage,
|
|
598
|
+
},
|
|
599
|
+
);
|
|
600
|
+
const response = (result as any).response || result;
|
|
601
|
+
const membersData = Array.isArray(response)
|
|
602
|
+
? response
|
|
603
|
+
: response.members || [];
|
|
604
|
+
const total = response.total || membersData.length;
|
|
605
|
+
|
|
606
|
+
return {
|
|
607
|
+
data: membersData.map((item: any) => ({
|
|
608
|
+
id: `${params.id}_${item.user_id}`,
|
|
609
|
+
organization_id: params.id,
|
|
610
|
+
...item,
|
|
611
|
+
})),
|
|
612
|
+
total,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Organization invitations
|
|
617
|
+
if (
|
|
618
|
+
resource === "organization-invitations" &&
|
|
619
|
+
params.target === "organization_id"
|
|
620
|
+
) {
|
|
621
|
+
const result = await managementClient.organizations.invitations.list(
|
|
622
|
+
params.id as string,
|
|
623
|
+
{
|
|
624
|
+
page: page - 1,
|
|
625
|
+
per_page: perPage,
|
|
626
|
+
},
|
|
627
|
+
);
|
|
628
|
+
const response = (result as any).response || result;
|
|
629
|
+
const invitationsData = Array.isArray(response)
|
|
630
|
+
? response
|
|
631
|
+
: response.invitations || [];
|
|
632
|
+
const total = response.total || invitationsData.length;
|
|
633
|
+
|
|
634
|
+
return {
|
|
635
|
+
data: invitationsData.map((item: any) => ({
|
|
636
|
+
id: item.id,
|
|
637
|
+
organization_id: params.id,
|
|
638
|
+
...item,
|
|
639
|
+
})),
|
|
640
|
+
total,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// User organizations
|
|
645
|
+
if (resource === "user-organizations" && params.target === "user_id") {
|
|
646
|
+
const result = await managementClient.users.organizations.list(
|
|
647
|
+
params.id as string,
|
|
648
|
+
{
|
|
649
|
+
page: page - 1,
|
|
650
|
+
per_page: perPage,
|
|
651
|
+
},
|
|
652
|
+
);
|
|
653
|
+
const response = (result as any).response || result;
|
|
654
|
+
|
|
655
|
+
let organizationsData: any[];
|
|
656
|
+
let total: number;
|
|
657
|
+
|
|
658
|
+
if (Array.isArray(response)) {
|
|
659
|
+
organizationsData = response;
|
|
660
|
+
total = response.length;
|
|
661
|
+
} else if (response.organizations) {
|
|
662
|
+
organizationsData = response.organizations;
|
|
663
|
+
total = response.total || organizationsData.length;
|
|
664
|
+
} else {
|
|
665
|
+
organizationsData = [];
|
|
666
|
+
total = 0;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return {
|
|
670
|
+
data: organizationsData.map((org: any) => ({
|
|
671
|
+
id: org.id || org.organization_id,
|
|
672
|
+
user_id: params.id,
|
|
673
|
+
name: org.name,
|
|
674
|
+
display_name: org.display_name,
|
|
675
|
+
branding: org.branding,
|
|
676
|
+
metadata: org.metadata || {},
|
|
677
|
+
token_quota: org.token_quota,
|
|
678
|
+
created_at: org.created_at,
|
|
679
|
+
updated_at: org.updated_at,
|
|
680
|
+
...org,
|
|
681
|
+
})),
|
|
682
|
+
total,
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Client grants filtered by client_id
|
|
687
|
+
if (resource === "client-grants" && params.target === "client_id") {
|
|
688
|
+
const result = await managementClient.clientGrants.list({
|
|
689
|
+
...buildPaginationParams(),
|
|
690
|
+
client_id: params.id as string,
|
|
691
|
+
});
|
|
692
|
+
const response = (result as any).response || result;
|
|
693
|
+
const grantsData = Array.isArray(response)
|
|
694
|
+
? response
|
|
695
|
+
: response.client_grants || [];
|
|
696
|
+
const total = response.total || grantsData.length;
|
|
697
|
+
|
|
698
|
+
return {
|
|
699
|
+
data: grantsData.map((item: any) => ({
|
|
700
|
+
id: item.id,
|
|
701
|
+
...item,
|
|
702
|
+
})),
|
|
703
|
+
total,
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// Logs filtered by user_id
|
|
708
|
+
if (resource === "logs" && params.target === "user_id") {
|
|
709
|
+
const result = await managementClient.logs.list({
|
|
710
|
+
page: page - 1,
|
|
711
|
+
per_page: perPage,
|
|
712
|
+
q: `user_id:${params.id}`,
|
|
713
|
+
sort:
|
|
714
|
+
field && order
|
|
715
|
+
? `${field}:${order === "DESC" ? "-1" : "1"}`
|
|
716
|
+
: undefined,
|
|
717
|
+
include_totals: true,
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
const normalized = normalizeSDKResponse(result, "logs");
|
|
721
|
+
return {
|
|
722
|
+
data: normalized.data.map((log: any) => ({ id: log.log_id, ...log })),
|
|
723
|
+
total: normalized.total,
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Default implementation for other resources - use HTTP fallback
|
|
728
|
+
const headers = createHeaders(tenantId);
|
|
729
|
+
const res = await httpClient(
|
|
730
|
+
`${apiUrl}/api/v2/${resource}?${stringify({
|
|
731
|
+
include_totals: true,
|
|
732
|
+
...buildPaginationParams(),
|
|
733
|
+
sort: `${field}:${order === "DESC" ? "-1" : "1"}`,
|
|
734
|
+
q: `user_id:${params.id}`,
|
|
735
|
+
})}`,
|
|
736
|
+
{ headers },
|
|
737
|
+
);
|
|
738
|
+
|
|
739
|
+
return {
|
|
740
|
+
data: res.json[resource].map((item: any) => ({
|
|
741
|
+
id: item.id,
|
|
742
|
+
...item,
|
|
743
|
+
})),
|
|
744
|
+
total: res.json.total,
|
|
745
|
+
};
|
|
746
|
+
},
|
|
747
|
+
|
|
748
|
+
update: async (resource, params) => {
|
|
749
|
+
const cleanParams = removeExtraFields(params);
|
|
750
|
+
const managementClient = await getManagementClient();
|
|
751
|
+
const headers = createHeaders(tenantId);
|
|
752
|
+
|
|
753
|
+
// Handle singleton resources
|
|
754
|
+
if (resource === "settings") {
|
|
755
|
+
const result = await managementClient.tenants.settings.update(
|
|
756
|
+
cleanParams.data,
|
|
757
|
+
);
|
|
758
|
+
return {
|
|
759
|
+
data: { ...result, id: resource },
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Special handling for branding to update theme data separately
|
|
764
|
+
if (resource === "branding") {
|
|
765
|
+
// Update branding
|
|
766
|
+
const brandingResult = await managementClient.branding.update(
|
|
767
|
+
cleanParams.data,
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
// Update themes if provided
|
|
771
|
+
const result: any = {
|
|
772
|
+
id: resource,
|
|
773
|
+
...brandingResult,
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
if (cleanParams.data.themes) {
|
|
777
|
+
const themeUpdateResult = await (
|
|
778
|
+
managementClient.branding.themes as any
|
|
779
|
+
).default.patch(cleanParams.data.themes);
|
|
780
|
+
result.themes =
|
|
781
|
+
(themeUpdateResult as any).response || themeUpdateResult;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
return { data: result };
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// SDK-handled resources
|
|
788
|
+
if (resource === "users") {
|
|
789
|
+
const result = await managementClient.users.update(
|
|
790
|
+
params.id as string,
|
|
791
|
+
cleanParams.data,
|
|
792
|
+
);
|
|
793
|
+
return {
|
|
794
|
+
data: {
|
|
795
|
+
id: result.user_id,
|
|
796
|
+
...result,
|
|
797
|
+
},
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if (resource === "clients") {
|
|
802
|
+
const result = await managementClient.clients.update(
|
|
803
|
+
params.id as string,
|
|
804
|
+
cleanParams.data,
|
|
805
|
+
);
|
|
806
|
+
return {
|
|
807
|
+
data: {
|
|
808
|
+
id: result.client_id,
|
|
809
|
+
...result,
|
|
810
|
+
},
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
if (resource === "connections") {
|
|
815
|
+
const result = await managementClient.connections.update(
|
|
816
|
+
params.id as string,
|
|
817
|
+
cleanParams.data,
|
|
818
|
+
);
|
|
819
|
+
return {
|
|
820
|
+
data: {
|
|
821
|
+
id: result.id,
|
|
822
|
+
...result,
|
|
823
|
+
},
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (resource === "roles") {
|
|
828
|
+
const result = await managementClient.roles.update(
|
|
829
|
+
params.id as string,
|
|
830
|
+
cleanParams.data,
|
|
831
|
+
);
|
|
832
|
+
return {
|
|
833
|
+
data: {
|
|
834
|
+
id: result.id,
|
|
835
|
+
...result,
|
|
836
|
+
},
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (resource === "resource-servers") {
|
|
841
|
+
const result = await managementClient.resourceServers.update(
|
|
842
|
+
params.id as string,
|
|
843
|
+
cleanParams.data,
|
|
844
|
+
);
|
|
845
|
+
return {
|
|
846
|
+
data: {
|
|
847
|
+
id: result.id,
|
|
848
|
+
...result,
|
|
849
|
+
},
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
if (resource === "organizations") {
|
|
854
|
+
const result = await managementClient.organizations.update(
|
|
855
|
+
params.id as string,
|
|
856
|
+
cleanParams.data,
|
|
857
|
+
);
|
|
858
|
+
return {
|
|
859
|
+
data: {
|
|
860
|
+
id: result.id,
|
|
861
|
+
...result,
|
|
862
|
+
},
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
if (resource === "rules") {
|
|
867
|
+
const result = await managementClient.rules.update(
|
|
868
|
+
params.id as string,
|
|
869
|
+
cleanParams.data,
|
|
870
|
+
);
|
|
871
|
+
return {
|
|
872
|
+
data: {
|
|
873
|
+
id: result.id,
|
|
874
|
+
...result,
|
|
875
|
+
},
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
if (resource === "client-grants") {
|
|
880
|
+
const result = await managementClient.clientGrants.update(
|
|
881
|
+
params.id as string,
|
|
882
|
+
cleanParams.data,
|
|
883
|
+
);
|
|
884
|
+
return {
|
|
885
|
+
data: {
|
|
886
|
+
id: result.id,
|
|
887
|
+
...result,
|
|
888
|
+
},
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
if (resource === "custom-domains") {
|
|
893
|
+
const result = await (managementClient as any).customDomains.update(
|
|
894
|
+
params.id as string,
|
|
895
|
+
cleanParams.data,
|
|
896
|
+
);
|
|
897
|
+
return {
|
|
898
|
+
data: {
|
|
899
|
+
id: result.custom_domain_id || result.id,
|
|
900
|
+
...result,
|
|
901
|
+
},
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// HTTP fallback for other resources
|
|
906
|
+
return httpClient(`${apiUrl}/api/v2/${resource}/${params.id}`, {
|
|
907
|
+
headers,
|
|
908
|
+
method: "PATCH",
|
|
909
|
+
body: JSON.stringify(cleanParams.data),
|
|
910
|
+
}).then(({ json }) => {
|
|
911
|
+
if (!json.id) {
|
|
912
|
+
json.id = json[`${resource}_id`];
|
|
913
|
+
delete json[`${resource}_id`];
|
|
914
|
+
}
|
|
915
|
+
return { data: json };
|
|
916
|
+
});
|
|
917
|
+
},
|
|
918
|
+
|
|
919
|
+
updateMany: () => Promise.reject("not supporting updateMany"),
|
|
920
|
+
|
|
921
|
+
create: async (resource, params) => {
|
|
922
|
+
const headers = new Headers({ "content-type": "application/json" });
|
|
923
|
+
if (tenantId) headers.set("tenant-id", tenantId);
|
|
924
|
+
const managementClient = await getManagementClient();
|
|
925
|
+
|
|
926
|
+
// Helper for POST requests
|
|
927
|
+
const post = async (endpoint: string, body: any) =>
|
|
928
|
+
httpClient(`${apiUrl}/api/v2/${endpoint}`, {
|
|
929
|
+
method: "POST",
|
|
930
|
+
body: JSON.stringify(body),
|
|
931
|
+
headers,
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
// SDK resource handlers for create
|
|
935
|
+
const sdkCreateHandlers: Record<
|
|
936
|
+
string,
|
|
937
|
+
{ create: (data: any) => Promise<any>; idKey: string }
|
|
938
|
+
> = {
|
|
939
|
+
users: {
|
|
940
|
+
create: (data) => managementClient.users.create(data),
|
|
941
|
+
idKey: "user_id",
|
|
942
|
+
},
|
|
943
|
+
clients: {
|
|
944
|
+
create: (data) => managementClient.clients.create(data),
|
|
945
|
+
idKey: "client_id",
|
|
946
|
+
},
|
|
947
|
+
connections: {
|
|
948
|
+
create: (data) => managementClient.connections.create(data),
|
|
949
|
+
idKey: "id",
|
|
950
|
+
},
|
|
951
|
+
roles: {
|
|
952
|
+
create: (data) => managementClient.roles.create(data),
|
|
953
|
+
idKey: "id",
|
|
954
|
+
},
|
|
955
|
+
"resource-servers": {
|
|
956
|
+
create: (data) => managementClient.resourceServers.create(data),
|
|
957
|
+
idKey: "id",
|
|
958
|
+
},
|
|
959
|
+
organizations: {
|
|
960
|
+
create: (data) => managementClient.organizations.create(data),
|
|
961
|
+
idKey: "id",
|
|
962
|
+
},
|
|
963
|
+
rules: {
|
|
964
|
+
create: (data) => managementClient.rules.create(data),
|
|
965
|
+
idKey: "id",
|
|
966
|
+
},
|
|
967
|
+
"client-grants": {
|
|
968
|
+
create: (data) => managementClient.clientGrants.create(data),
|
|
969
|
+
idKey: "id",
|
|
970
|
+
},
|
|
971
|
+
"custom-domains": {
|
|
972
|
+
create: (data) =>
|
|
973
|
+
(managementClient as any).customDomains.create(data),
|
|
974
|
+
idKey: "custom_domain_id",
|
|
975
|
+
},
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
const handler = sdkCreateHandlers[resource];
|
|
979
|
+
if (handler) {
|
|
980
|
+
const result = await handler.create(params.data);
|
|
981
|
+
return {
|
|
982
|
+
data: {
|
|
983
|
+
id: result[handler.idKey] || result.id,
|
|
984
|
+
...result,
|
|
985
|
+
},
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Organization invitations
|
|
990
|
+
if (resource === "organization-invitations") {
|
|
991
|
+
const { organization_id, ...inviteData } = params.data;
|
|
992
|
+
const res = await post(
|
|
993
|
+
`organizations/${organization_id}/invitations`,
|
|
994
|
+
inviteData,
|
|
995
|
+
);
|
|
996
|
+
return {
|
|
997
|
+
data: {
|
|
998
|
+
id: res.json.id,
|
|
999
|
+
organization_id,
|
|
1000
|
+
...res.json,
|
|
1001
|
+
},
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// Organization members
|
|
1006
|
+
if (resource === "organization-members") {
|
|
1007
|
+
const { organization_id, user_id, user_ids } = params.data;
|
|
1008
|
+
const usersToAdd = user_ids || [user_id];
|
|
1009
|
+
const res = await post(`organizations/${organization_id}/members`, {
|
|
1010
|
+
members: usersToAdd,
|
|
1011
|
+
});
|
|
1012
|
+
return {
|
|
1013
|
+
data: {
|
|
1014
|
+
id: `${organization_id}_${usersToAdd.join("_")}`,
|
|
1015
|
+
organization_id,
|
|
1016
|
+
user_id: user_id || usersToAdd[0],
|
|
1017
|
+
members: usersToAdd,
|
|
1018
|
+
...res.json,
|
|
1019
|
+
},
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// User organizations (same endpoint as org members)
|
|
1024
|
+
if (resource === "user-organizations") {
|
|
1025
|
+
const { organization_id, user_id, user_ids } = params.data;
|
|
1026
|
+
const usersToAdd = user_ids || [user_id];
|
|
1027
|
+
const res = await post(`organizations/${organization_id}/members`, {
|
|
1028
|
+
members: usersToAdd,
|
|
1029
|
+
});
|
|
1030
|
+
return {
|
|
1031
|
+
data: {
|
|
1032
|
+
id: organization_id,
|
|
1033
|
+
user_id: user_id || usersToAdd[0],
|
|
1034
|
+
members: usersToAdd,
|
|
1035
|
+
organization_id,
|
|
1036
|
+
...res.json,
|
|
1037
|
+
},
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// User roles assignment
|
|
1042
|
+
const userRolesMatch = resource.match(/^users\/([^/]+)\/roles$/);
|
|
1043
|
+
if (userRolesMatch) {
|
|
1044
|
+
const res = await post(resource, params.data);
|
|
1045
|
+
const userId = userRolesMatch[1];
|
|
1046
|
+
const roleIds = params.data?.roles || [];
|
|
1047
|
+
return {
|
|
1048
|
+
data: {
|
|
1049
|
+
id: `${userId}_${roleIds.join("_")}`,
|
|
1050
|
+
user_id: userId,
|
|
1051
|
+
roles: roleIds,
|
|
1052
|
+
...res.json,
|
|
1053
|
+
},
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// Default create (for endpoints not in SDK)
|
|
1058
|
+
const res = await post(resource, params.data);
|
|
1059
|
+
return {
|
|
1060
|
+
data: {
|
|
1061
|
+
...res.json,
|
|
1062
|
+
id: res.json.id,
|
|
1063
|
+
},
|
|
1064
|
+
};
|
|
1065
|
+
},
|
|
1066
|
+
|
|
1067
|
+
delete: async (resource, params) => {
|
|
1068
|
+
const managementClient = await getManagementClient();
|
|
1069
|
+
const headers = new Headers({ "content-type": "application/json" });
|
|
1070
|
+
if (tenantId) headers.set("tenant-id", tenantId);
|
|
1071
|
+
|
|
1072
|
+
// Helper for DELETE requests
|
|
1073
|
+
const del = async (endpoint: string, body?: any) =>
|
|
1074
|
+
httpClient(`${apiUrl}/api/v2/${endpoint}`, {
|
|
1075
|
+
method: "DELETE",
|
|
1076
|
+
headers,
|
|
1077
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
// Organization invitations
|
|
1081
|
+
if (resource === "organization-invitations") {
|
|
1082
|
+
const invitation_id = params.id;
|
|
1083
|
+
const organization_id = params.previousData?.organization_id;
|
|
1084
|
+
|
|
1085
|
+
if (!organization_id || !invitation_id) {
|
|
1086
|
+
throw new Error(
|
|
1087
|
+
"Missing organization_id or invitation_id for invitation deletion",
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
const res = await del(
|
|
1092
|
+
`organizations/${organization_id}/invitations/${invitation_id}`,
|
|
1093
|
+
);
|
|
1094
|
+
return { data: res.json || { id: invitation_id } };
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// Organization members
|
|
1098
|
+
if (resource === "organization-members") {
|
|
1099
|
+
let organization_id, user_ids;
|
|
1100
|
+
|
|
1101
|
+
if (params.previousData?.members) {
|
|
1102
|
+
organization_id = params.id;
|
|
1103
|
+
user_ids = params.previousData.members;
|
|
1104
|
+
} else if (typeof params.id === "string" && params.id.includes("_")) {
|
|
1105
|
+
[organization_id, ...user_ids] = params.id.split("_");
|
|
1106
|
+
} else if (params.previousData) {
|
|
1107
|
+
organization_id = params.previousData.organization_id || params.id;
|
|
1108
|
+
user_ids = params.previousData.user_ids || [
|
|
1109
|
+
params.previousData.user_id,
|
|
1110
|
+
];
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
if (!organization_id || !user_ids || user_ids.length === 0) {
|
|
1114
|
+
throw new Error(
|
|
1115
|
+
"Missing organization_id or user_id(s) for organization member deletion",
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
const res = await del(`organizations/${organization_id}/members`, {
|
|
1120
|
+
members: user_ids,
|
|
1121
|
+
});
|
|
1122
|
+
return { data: res.json };
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// User organizations
|
|
1126
|
+
if (resource === "user-organizations") {
|
|
1127
|
+
let organization_id, user_id;
|
|
1128
|
+
|
|
1129
|
+
if (params.previousData) {
|
|
1130
|
+
user_id = params.previousData.user_id;
|
|
1131
|
+
organization_id = params.id;
|
|
1132
|
+
} else if (typeof params.id === "string" && params.id.includes("_")) {
|
|
1133
|
+
[user_id, organization_id] = params.id.split("_");
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
if (!organization_id || !user_id) {
|
|
1137
|
+
throw new Error(
|
|
1138
|
+
"Missing organization_id or user_id for user organization deletion",
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
await managementClient.organizations.members.delete(organization_id, {
|
|
1143
|
+
members: [user_id],
|
|
1144
|
+
});
|
|
1145
|
+
return { data: { id: params.id } };
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
// Custom-domains using SDK
|
|
1149
|
+
if (resource === "custom-domains") {
|
|
1150
|
+
await (managementClient as any).customDomains.delete(
|
|
1151
|
+
params.id as string,
|
|
1152
|
+
);
|
|
1153
|
+
return { data: { id: params.id } };
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Nested permissions/roles detection
|
|
1157
|
+
const isNestedPermissionsDelete =
|
|
1158
|
+
/(^|\/)users\/[^/]+\/permissions$/.test(resource) ||
|
|
1159
|
+
/(^|\/)roles\/[^/]+\/permissions$/.test(resource);
|
|
1160
|
+
const isNestedRolesDelete = /(^|\/)users\/[^/]+\/roles$/.test(resource);
|
|
1161
|
+
const isOrgMemberRolesDelete =
|
|
1162
|
+
/(^|\/)organizations\/[^/]+\/members\/[^/]+\/roles$/.test(resource);
|
|
1163
|
+
|
|
1164
|
+
const hasId =
|
|
1165
|
+
params?.id !== undefined &&
|
|
1166
|
+
params?.id !== null &&
|
|
1167
|
+
String(params.id) !== "";
|
|
1168
|
+
|
|
1169
|
+
const shouldAppendId =
|
|
1170
|
+
hasId &&
|
|
1171
|
+
!(
|
|
1172
|
+
isNestedPermissionsDelete ||
|
|
1173
|
+
isNestedRolesDelete ||
|
|
1174
|
+
isOrgMemberRolesDelete
|
|
1175
|
+
);
|
|
1176
|
+
|
|
1177
|
+
const resourceUrl = shouldAppendId
|
|
1178
|
+
? `${resource}/${encodeURIComponent(String(params.id))}`
|
|
1179
|
+
: resource;
|
|
1180
|
+
|
|
1181
|
+
let body: any = undefined;
|
|
1182
|
+
|
|
1183
|
+
if (isNestedPermissionsDelete) {
|
|
1184
|
+
const prev: any = params?.previousData ?? {};
|
|
1185
|
+
const parsedFromId = (() => {
|
|
1186
|
+
if (!hasId)
|
|
1187
|
+
return {
|
|
1188
|
+
resource_server_identifier: undefined,
|
|
1189
|
+
permission_name: undefined,
|
|
1190
|
+
};
|
|
1191
|
+
try {
|
|
1192
|
+
const decoded = decodeURIComponent(String(params.id));
|
|
1193
|
+
const [rsi, pname] = decoded.split(":");
|
|
1194
|
+
return { resource_server_identifier: rsi, permission_name: pname };
|
|
1195
|
+
} catch {
|
|
1196
|
+
return {
|
|
1197
|
+
resource_server_identifier: undefined,
|
|
1198
|
+
permission_name: undefined,
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
})();
|
|
1202
|
+
|
|
1203
|
+
body = {
|
|
1204
|
+
permissions: [
|
|
1205
|
+
{
|
|
1206
|
+
permission_name:
|
|
1207
|
+
prev.permission_name ?? parsedFromId.permission_name,
|
|
1208
|
+
resource_server_identifier:
|
|
1209
|
+
prev.resource_server_identifier ??
|
|
1210
|
+
parsedFromId.resource_server_identifier,
|
|
1211
|
+
},
|
|
1212
|
+
],
|
|
1213
|
+
};
|
|
1214
|
+
} else if (isNestedRolesDelete || isOrgMemberRolesDelete) {
|
|
1215
|
+
const roles = Array.isArray(params?.previousData?.roles)
|
|
1216
|
+
? params.previousData.roles
|
|
1217
|
+
: hasId
|
|
1218
|
+
? [String(params.id)]
|
|
1219
|
+
: [];
|
|
1220
|
+
body = { roles };
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// Update headers for nested endpoints
|
|
1224
|
+
if (
|
|
1225
|
+
!isNestedPermissionsDelete &&
|
|
1226
|
+
!isNestedRolesDelete &&
|
|
1227
|
+
!isOrgMemberRolesDelete
|
|
1228
|
+
) {
|
|
1229
|
+
headers.set("Content-Type", "text/plain");
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
const res = await httpClient(`${apiUrl}/api/v2/${resourceUrl}`, {
|
|
1233
|
+
method: "DELETE",
|
|
1234
|
+
headers,
|
|
1235
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
1236
|
+
});
|
|
1237
|
+
return { data: res.json };
|
|
1238
|
+
},
|
|
1239
|
+
|
|
1240
|
+
deleteMany: () => Promise.reject("not supporting deleteMany"),
|
|
1241
|
+
};
|
|
1242
|
+
};
|