@authhero/react-admin 0.20.0 → 0.21.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @authhero/react-admin
2
2
 
3
+ ## 0.21.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1423254: Fix casing in the ui for organizations
8
+
3
9
  ## 0.20.0
4
10
 
5
11
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authhero/react-admin",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "packageManager": "pnpm@10.20.0",
5
5
  "private": false,
6
6
  "repository": {
@@ -340,7 +340,7 @@ export default (
340
340
  if (Array.isArray(res.json)) {
341
341
  return {
342
342
  data: res.json.map((item) => ({
343
- id: item.custom_domain_id || item.id,
343
+ id: item.id,
344
344
  ...item,
345
345
  })),
346
346
  total: res.json.length,
@@ -351,7 +351,7 @@ export default (
351
351
  return {
352
352
  data:
353
353
  res.json[resource]?.map((item: any) => ({
354
- id: item.custom_domain_id || item.id,
354
+ id: item.id,
355
355
  ...item,
356
356
  })) || [],
357
357
  total: res.json.total || res.json.length || 0,
@@ -382,15 +382,30 @@ export default (
382
382
  fetch: (id) => (managementClient as any).customDomains.get(id),
383
383
  idKey: "custom_domain_id",
384
384
  },
385
+ flows: {
386
+ fetch: (id) => (managementClient as any).flows.get(id),
387
+ idKey: "id",
388
+ },
389
+ hooks: {
390
+ fetch: (id) => (managementClient as any).hooks.get(id),
391
+ idKey: "hook_id",
392
+ },
393
+ forms: {
394
+ fetch: (id) => managementClient.forms.get(id),
395
+ idKey: "id",
396
+ },
385
397
  };
386
398
 
387
399
  const handler = sdkGetHandlers[resource];
388
400
  if (handler) {
389
401
  const result = await handler.fetch(params.id as string);
402
+ // Unwrap SDK response wrapper if present
403
+ const data = (result as any).response || result;
404
+
390
405
  return {
391
406
  data: {
392
- id: result[handler.idKey] || result.id,
393
- ...result,
407
+ id: data[handler.idKey] || data.id,
408
+ ...data,
394
409
  },
395
410
  };
396
411
  }
@@ -926,8 +941,12 @@ export default (
926
941
  body: JSON.stringify(cleanParams.data),
927
942
  }).then(({ json }) => {
928
943
  if (!json.id) {
929
- json.id = json[`${resource}_id`];
930
- delete json[`${resource}_id`];
944
+ // Try singular form of resource name (e.g., hooks -> hook_id)
945
+ const singularResource = resource.endsWith("s")
946
+ ? resource.slice(0, -1)
947
+ : resource;
948
+ json.id =
949
+ json[`${singularResource}_id`] || json[`${resource}_id`] || json.id;
931
950
  }
932
951
  return { data: json };
933
952
  });
@@ -1073,10 +1092,17 @@ export default (
1073
1092
 
1074
1093
  // Default create (for endpoints not in SDK)
1075
1094
  const res = await post(resource, params.data);
1095
+ // Try singular form of resource name (e.g., hooks -> hook_id)
1096
+ const singularResource = resource.endsWith("s")
1097
+ ? resource.slice(0, -1)
1098
+ : resource;
1076
1099
  return {
1077
1100
  data: {
1078
1101
  ...res.json,
1079
- id: res.json.id,
1102
+ id:
1103
+ res.json[`${singularResource}_id`] ||
1104
+ res.json[`${resource}_id`] ||
1105
+ res.json.id,
1080
1106
  },
1081
1107
  };
1082
1108
  },
@@ -122,7 +122,9 @@ export const createAuth0ClientForOrg = (
122
122
  domain: string,
123
123
  organizationId: string,
124
124
  ) => {
125
- const cacheKey = `${domain}:${organizationId}`;
125
+ // Normalize organization ID to lowercase to avoid casing mismatches
126
+ const normalizedOrgId = organizationId.toLowerCase();
127
+ const cacheKey = `${domain}:${normalizedOrgId}`;
126
128
 
127
129
  // Check cache first
128
130
  if (auth0OrgClientCache.has(cacheKey)) {
@@ -147,12 +149,12 @@ export const createAuth0ClientForOrg = (
147
149
  useRefreshTokens: false,
148
150
  // Use organization-specific cache to isolate tokens
149
151
  // Note: Don't use cacheLocation when providing a custom cache
150
- cache: new OrgCache(organizationId),
152
+ cache: new OrgCache(normalizedOrgId),
151
153
  authorizationParams: {
152
154
  audience,
153
155
  redirect_uri: redirectUri,
154
156
  scope: "openid profile email auth:read auth:write",
155
- organization: organizationId,
157
+ organization: normalizedOrgId,
156
158
  },
157
159
  });
158
160
 
@@ -167,7 +169,11 @@ export const createManagementClient = async (
167
169
  tenantId?: string,
168
170
  oauthDomain?: string,
169
171
  ): Promise<ManagementClient> => {
170
- const cacheKey = tenantId ? `${apiDomain}:${tenantId}` : apiDomain;
172
+ // Normalize tenant ID to lowercase to avoid casing mismatches
173
+ const normalizedTenantId = tenantId?.toLowerCase();
174
+ const cacheKey = normalizedTenantId
175
+ ? `${apiDomain}:${normalizedTenantId}`
176
+ : apiDomain;
171
177
 
172
178
  // Check cache first
173
179
  if (managementClientCache.has(cacheKey)) {
@@ -193,18 +199,21 @@ export const createManagementClient = async (
193
199
  const storedFlag = sessionStorage.getItem("isSingleTenant");
194
200
  const isSingleTenant = storedFlag?.endsWith("|true") || storedFlag === "true";
195
201
 
196
- if (tenantId && !isSingleTenant) {
202
+ if (normalizedTenantId && !isSingleTenant) {
197
203
  // When accessing tenant-specific resources in MULTI-TENANT mode, use org-scoped token
198
204
  if (domainConfig.connectionMethod === "login") {
199
205
  // For OAuth login, use organization-scoped client
200
- const orgAuth0Client = createAuth0ClientForOrg(domainForAuth, tenantId);
206
+ const orgAuth0Client = createAuth0ClientForOrg(
207
+ domainForAuth,
208
+ normalizedTenantId,
209
+ );
201
210
  const audience =
202
211
  import.meta.env.VITE_AUTH0_AUDIENCE || "urn:authhero:management";
203
212
  try {
204
213
  token = await orgAuth0Client.getTokenSilently({
205
214
  authorizationParams: {
206
215
  audience,
207
- organization: tenantId,
216
+ organization: normalizedTenantId,
208
217
  },
209
218
  });
210
219
  } catch (error) {
@@ -216,7 +225,7 @@ export const createManagementClient = async (
216
225
  // Redirect to login with organization
217
226
  await orgAuth0Client.loginWithRedirect({
218
227
  authorizationParams: {
219
- organization: tenantId,
228
+ organization: normalizedTenantId,
220
229
  login_hint: user?.email,
221
230
  },
222
231
  appState: {
@@ -225,11 +234,13 @@ export const createManagementClient = async (
225
234
  });
226
235
 
227
236
  // This won't be reached as loginWithRedirect redirects the page
228
- throw new Error(`Redirecting to login for organization ${tenantId}`);
237
+ throw new Error(
238
+ `Redirecting to login for organization ${normalizedTenantId}`,
239
+ );
229
240
  }
230
241
  } else {
231
242
  // For token/client_credentials, use getOrganizationToken
232
- token = await getOrganizationToken(domainConfig, tenantId);
243
+ token = await getOrganizationToken(domainConfig, normalizedTenantId);
233
244
  }
234
245
  } else {
235
246
  // No tenantId - get non-org-scoped token for tenant management endpoints
@@ -244,7 +255,9 @@ export const createManagementClient = async (
244
255
  const managementClient = new ManagementClient({
245
256
  domain: apiDomain,
246
257
  token,
247
- headers: tenantId ? { "tenant-id": tenantId } : undefined,
258
+ headers: normalizedTenantId
259
+ ? { "tenant-id": normalizedTenantId }
260
+ : undefined,
248
261
  });
249
262
 
250
263
  managementClientCache.set(cacheKey, managementClient);
@@ -688,8 +701,11 @@ const authorizedHttpClient = (url: string, options: HttpOptions = {}) => {
688
701
  * @returns An HTTP client function that uses organization-scoped tokens
689
702
  */
690
703
  export const createOrganizationHttpClient = (organizationId: string) => {
704
+ // Normalize organization ID to lowercase to avoid casing mismatches
705
+ const normalizedOrgId = organizationId.toLowerCase();
706
+
691
707
  return (url: string, options: HttpOptions = {}) => {
692
- const requestKey = `${organizationId}:${url}-${JSON.stringify(options)}`;
708
+ const requestKey = `${normalizedOrgId}:${url}-${JSON.stringify(options)}`;
693
709
 
694
710
  // If there's already a pending request for this URL and options, return it
695
711
  if (pendingRequests.has(requestKey)) {
@@ -728,7 +744,7 @@ export const createOrganizationHttpClient = (organizationId: string) => {
728
744
  !activeSessions.has(formattedSelectedDomain)
729
745
  ) {
730
746
  clearInterval(checkInterval);
731
- createOrganizationHttpClient(organizationId)(url, options)
747
+ createOrganizationHttpClient(normalizedOrgId)(url, options)
732
748
  .then(resolve)
733
749
  .catch(reject);
734
750
  }
@@ -768,7 +784,7 @@ export const createOrganizationHttpClient = (organizationId: string) => {
768
784
  ) {
769
785
  // For token/client_credentials, use organization-scoped token
770
786
  // This includes the org_id claim for accessing tenant-specific resources
771
- request = getOrganizationToken(domainConfig, organizationId)
787
+ request = getOrganizationToken(domainConfig, normalizedOrgId)
772
788
  .catch((error) => {
773
789
  throw new Error(
774
790
  `Authentication failed: ${error.message}. Please configure your credentials in the domain selector.`,
@@ -838,7 +854,7 @@ export const createOrganizationHttpClient = (organizationId: string) => {
838
854
  // For OAuth login, use an organization-specific client with isolated cache
839
855
  const orgAuth0Client = createAuth0ClientForOrg(
840
856
  selectedDomain,
841
- organizationId,
857
+ normalizedOrgId,
842
858
  );
843
859
 
844
860
  // Use the management API audience for cross-tenant operations
@@ -851,7 +867,7 @@ export const createOrganizationHttpClient = (organizationId: string) => {
851
867
  .getTokenSilently({
852
868
  authorizationParams: {
853
869
  audience,
854
- organization: organizationId,
870
+ organization: normalizedOrgId,
855
871
  },
856
872
  })
857
873
  .catch(async (_error) => {
@@ -863,7 +879,7 @@ export const createOrganizationHttpClient = (organizationId: string) => {
863
879
  // Redirect to login with organization
864
880
  await orgAuth0Client.loginWithRedirect({
865
881
  authorizationParams: {
866
- organization: organizationId,
882
+ organization: normalizedOrgId,
867
883
  login_hint: user?.email,
868
884
  },
869
885
  appState: {
@@ -873,7 +889,7 @@ export const createOrganizationHttpClient = (organizationId: string) => {
873
889
 
874
890
  // This won't be reached as loginWithRedirect redirects the page
875
891
  throw new Error(
876
- `Redirecting to login for organization ${organizationId}`,
892
+ `Redirecting to login for organization ${normalizedOrgId}`,
877
893
  );
878
894
  })
879
895
  .then((token) => {
@@ -89,9 +89,16 @@ export const FlowEdit = () => {
89
89
  return (
90
90
  <Edit
91
91
  queryOptions={{
92
- select: (data) => ({
93
- data: parseFlowData(data.data as Record<string, unknown>),
94
- }),
92
+ select: (response) => {
93
+ const data = response?.data as Record<string, unknown>;
94
+ if (!data) {
95
+ console.error("FlowEdit: No data in response", response);
96
+ return response;
97
+ }
98
+ return {
99
+ data: parseFlowData(data),
100
+ };
101
+ },
95
102
  }}
96
103
  transform={(data: Record<string, unknown>) => {
97
104
  // Transform actions to include required fields
@@ -5,7 +5,7 @@ export function HookList() {
5
5
  return (
6
6
  <List actions={<PostListActions />}>
7
7
  <Datagrid rowClick="edit" bulkActionButtons={false}>
8
- <TextField source="id" />
8
+ <TextField source="hook_id" />
9
9
  <TextField source="trigger_id" />
10
10
  <TextField source="url" />
11
11
  <TextField source="form_id" label="Form" />
@@ -18,7 +18,8 @@ export class OrgCache implements ICache {
18
18
  private orgId: string;
19
19
 
20
20
  constructor(orgId: string) {
21
- this.orgId = orgId;
21
+ // Normalize organization ID to lowercase to avoid casing mismatches
22
+ this.orgId = orgId.toLowerCase();
22
23
  }
23
24
 
24
25
  public set<T = Cacheable>(key: string, entry: T) {
@@ -77,8 +78,9 @@ async function fetchTokenWithClientCredentials(
77
78
  };
78
79
 
79
80
  // Add organization if specified - this will include org_id in the token
81
+ // Normalize to lowercase to avoid casing mismatches
80
82
  if (organizationId) {
81
- body.organization = organizationId;
83
+ body.organization = organizationId.toLowerCase();
82
84
  }
83
85
 
84
86
  const response = await fetch(`https://proxy.authhe.ro/oauth/token`, {