Skip to content

Commit 6531dfa

Browse files
committed
quality: strong typing in users and permissions
1 parent 39bc58b commit 6531dfa

File tree

3 files changed

+71
-18
lines changed

3 files changed

+71
-18
lines changed

client/src/components/auth0.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { FC, ReactNode, useEffect, useState } from "react";
2727
import { useTranslation } from "react-i18next";
2828
import { Link } from "@heroui/link";
2929
import { createRemoteJWKSet, JWTPayload, jwtVerify } from "jose";
30+
import type { Auth0User, Auth0Permission, Auth0Role } from "@/types/data";
3031

3132
/**
3233
* Renders the user's profile name with a tooltip showing their username.
@@ -585,7 +586,7 @@ export const useSecuredApi = () => {
585586
throw error;
586587
}
587588
},
588-
listAuth0Users: async (managementToken: string) => {
589+
listAuth0Users: async (managementToken: string): Promise<Auth0User[] | null> => {
589590
try {
590591
const resp = await fetch(
591592
`https://${import.meta.env.AUTH0_DOMAIN}/api/v2/users?per_page=100`,
@@ -597,13 +598,13 @@ export const useSecuredApi = () => {
597598
},
598599
},
599600
);
600-
return await resp.json();
601+
return (await resp.json()) as Auth0User[];
601602
} catch (error) {
602603
console.error("Failed to list Auth0 users:", error);
603604
throw error;
604605
}
605606
},
606-
listAuth0Roles: async (managementToken: string) => {
607+
listAuth0Roles: async (managementToken: string): Promise<Auth0Role[] | null> => {
607608
try {
608609
const resp = await fetch(`https://${import.meta.env.AUTH0_DOMAIN}/api/v2/roles`, {
609610
method: "GET",
@@ -612,13 +613,13 @@ export const useSecuredApi = () => {
612613
"Content-Type": "application/json",
613614
},
614615
});
615-
return await resp.json();
616+
return (await resp.json()) as Auth0Role[];
616617
} catch (error) {
617618
console.error("Failed to list Auth0 roles:", error);
618619
throw error;
619620
}
620621
},
621-
getUserRoles: async (managementToken: string, userId: string) => {
622+
getUserRoles: async (managementToken: string, userId: string): Promise<Auth0Role[] | null> => {
622623
try {
623624
const resp = await fetch(
624625
`https://${import.meta.env.AUTH0_DOMAIN}/api/v2/users/${encodeURIComponent(userId)}/roles`,
@@ -630,13 +631,13 @@ export const useSecuredApi = () => {
630631
},
631632
},
632633
);
633-
return await resp.json();
634+
return (await resp.json()) as Auth0Role[];
634635
} catch (error) {
635636
console.error("Failed to get user roles:", error);
636637
throw error;
637638
}
638639
},
639-
addUserToRole: async (managementToken: string, roleId: string, userId: string) => {
640+
addUserToRole: async (managementToken: string, roleId: string, userId: string): Promise<any> => {
640641
try {
641642
const resp = await fetch(
642643
`https://${import.meta.env.AUTH0_DOMAIN}/api/v2/roles/${encodeURIComponent(roleId)}/users`,
@@ -714,7 +715,7 @@ export const useSecuredApi = () => {
714715
throw error;
715716
}
716717
},
717-
getUserPermissions: async (managementToken: string, userId: string) => {
718+
getUserPermissions: async (managementToken: string, userId: string): Promise<Auth0Permission[] | null> => {
718719
try {
719720
const resp = await fetch(
720721
`https://${import.meta.env.AUTH0_DOMAIN}/api/v2/users/${encodeURIComponent(userId)}/permissions`,
@@ -732,7 +733,7 @@ export const useSecuredApi = () => {
732733
throw error;
733734
}
734735
},
735-
addPermissionToUser: async (managementToken: string, userId: string, permissionName: string) => {
736+
addPermissionToUser: async (managementToken: string, userId: string, permissionName: string): Promise<Auth0Permission[] | null> => {
736737
try {
737738
const resp = await fetch(
738739
`https://${import.meta.env.AUTH0_DOMAIN}/api/v2/users/${encodeURIComponent(userId)}/permissions`,
@@ -758,7 +759,7 @@ export const useSecuredApi = () => {
758759
throw error;
759760
}
760761
},
761-
removePermissionFromUser: async (managementToken: string, userId: string, permissionName: string) => {
762+
removePermissionFromUser: async (managementToken: string, userId: string, permissionName: string): Promise<Auth0Permission[] | null> => {
762763
try {
763764
const resp = await fetch(
764765
`https://${import.meta.env.AUTH0_DOMAIN}/api/v2/users/${encodeURIComponent(userId)}/permissions`,

client/src/pages/users-and-permissions.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import { useEffect, useState } from "react";
2626
import DefaultLayout from "@/layouts/default";
2727
import { useSecuredApi } from "@/components/auth0";
28-
import { Auth0ManagementTokenApiResponse, Auth0ManagementTokenResponse } from "@/types/data";
28+
import { Auth0ManagementTokenApiResponse, Auth0ManagementTokenResponse, Auth0User, Auth0Permission } from "@/types/data";
2929
import { useTranslation } from "react-i18next";
3030
import { Button } from "@heroui/button";
3131
import { Checkbox } from "@heroui/checkbox";
@@ -39,14 +39,14 @@ export default function UsersAndPermissionsPage() {
3939
const [token, setToken] = useState<Auth0ManagementTokenResponse | null>(null);
4040
const { t } = useTranslation();
4141
// replaced message state and inline alert by HeroUI toasts
42-
const [users, setUsers] = useState<any[]>([]);
42+
const [users, setUsers] = useState<Auth0User[]>([]);
4343
// roles are not used yet because we work with direct permissions (not roles)
44-
const [editing, setEditing] = useState<Record<string, any>>({});
45-
const [selectedUser, setSelectedUser] = useState<any | null>(null);
44+
const [editing, setEditing] = useState<Record<string, Record<string, boolean>>>({});
45+
const [selectedUser, setSelectedUser] = useState<Auth0User | null>(null);
4646
const [modalOpen, setModalOpen] = useState(false);
4747
const [modalLoading, setModalLoading] = useState(false);
4848
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
49-
const [confirmDeleteUser, setConfirmDeleteUser] = useState<any | null>(null);
49+
const [confirmDeleteUser, setConfirmDeleteUser] = useState<Auth0User | null>(null);
5050
const [deletingUserId, setDeletingUserId] = useState<string | null>(null);
5151
useEffect(() => {
5252
// Fetch Auth0 Management API token for accessing Auth0 management endpoints
@@ -93,7 +93,7 @@ export default function UsersAndPermissionsPage() {
9393
if (edits.hasOwnProperty(key)) {
9494
const permName = permissionMappings[key];
9595
const userPerms = await getUserPermissions(mgmtToken, userId);
96-
const hasIt = (userPerms || []).some((p: any) => p.permission_name === permName);
96+
const hasIt = (userPerms || []).some((p: Auth0Permission) => p.permission_name === permName);
9797
if (edits[key] && !hasIt) {
9898
await addPermissionToUser(mgmtToken, userId, permName);
9999
} else if (!edits[key] && hasIt) {
@@ -143,10 +143,10 @@ export default function UsersAndPermissionsPage() {
143143
// Try exact match or partial match to support localhost vs absolute URL differences
144144
return rs === audience || rs.includes(audience) || audience.includes(rs) || rs.endsWith(audience) || audience.endsWith(rs);
145145
})
146-
.map((p: any) => p.permission_name);
146+
.map((p: Auth0Permission) => p.permission_name);
147147
// fallback: if nothing matched, use the whole list of permission names
148148
if (!permNames || permNames.length === 0) {
149-
permNames = (userPerms || []).map((p: any) => p.permission_name);
149+
permNames = (userPerms || []).map((p: Auth0Permission) => p.permission_name);
150150
}
151151
// Debug info removed for production readiness
152152
setEditing((prev) => ({

cloudflare-worker/src/types/data.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,3 +409,55 @@ export interface Auth0ManagementTokenResponse {
409409
* Union type for the API response: either the token or a standard error response
410410
*/
411411
export type Auth0ManagementTokenApiResponse = Auth0ManagementTokenResponse | ErrorResponse;
412+
413+
/**
414+
* Identity object from Auth0 user object (one of multiple identities in a federated login)
415+
*/
416+
export interface Auth0UserIdentity {
417+
/** Provider specific user ID (e.g. google-oauth2|123456789) */
418+
user_id: string;
419+
/** Identity provider (e.g. "google-oauth2", "auth0") */
420+
provider: string;
421+
/** Connection (e.g. "Username-Password-Authentication") */
422+
connection?: string;
423+
/** True if this identity is a social provider */
424+
isSocial?: boolean;
425+
}
426+
427+
/**
428+
* Lightweight representation of an Auth0 Management API user
429+
* Based on the official Auth0 Management API documentation (https://auth0.com/docs/api/management/v2/#!/Users/get_users)
430+
*/
431+
export interface Auth0User {
432+
user_id: string;
433+
email?: string;
434+
email_verified?: boolean;
435+
name?: string;
436+
nickname?: string;
437+
picture?: string;
438+
created_at?: string;
439+
updated_at?: string;
440+
last_login?: string;
441+
logins_count?: number;
442+
blocked?: boolean;
443+
identities?: Auth0UserIdentity[];
444+
user_metadata?: Record<string, any> | null;
445+
app_metadata?: Record<string, any> | null;
446+
}
447+
448+
/**
449+
* Representation of a single permission object returned by the Auth0 Management API
450+
*/
451+
export interface Auth0Permission {
452+
permission_name: string;
453+
resource_server_identifier: string;
454+
}
455+
456+
/**
457+
* Simplified Auth0 role object shape
458+
*/
459+
export interface Auth0Role {
460+
id: string;
461+
name: string;
462+
description?: string;
463+
}

0 commit comments

Comments
 (0)