Skip to content

Commit 1ba8b80

Browse files
committed
feat: wip user permissions management
1 parent 64b1b9b commit 1ba8b80

File tree

13 files changed

+501
-26
lines changed

13 files changed

+501
-26
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ The application is deployed on a CDN as a static SPA (Single Page Application).
4444
- A product is an item or service that is tested by a tester.
4545
- The seller is the person who sells the product.
4646
- Feedback is the opinion given by the tester about the product.
47-
<img width="1260" alt="Capture d’écran 2025-05-05 à 19 15 36" src="https://github.com/user-attachments/assets/f222c477-59ec-419d-987a-0a3276aa7412" />
47+
<img width="1260" alt="Statistics dashboard screenshots" src="https://github.com/user-attachments/assets/f222c477-59ec-419d-987a-0a3276aa7412" />
4848

4949
## Testing and Feedback Process
5050

client/package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@heroui/snippet": "^2.2.28",
3636
"@heroui/switch": "^2.2.24",
3737
"@heroui/system": "^2.4.23",
38+
"@heroui/checkbox": "^2.2.24",
3839
"@heroui/table": "^2.2.27",
3940
"@heroui/theme": "^2.4.23",
4041
"@heroui/toast": "^2.0.17",
@@ -86,4 +87,4 @@
8687
"vite": "^7.2.2",
8788
"vite-tsconfig-paths": "^5.1.4"
8889
}
89-
}
90+
}

client/src/components/auth0.tsx

Lines changed: 215 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,12 @@ export const LoginButton: FC<{ text?: string }> = ({ text }) => {
8282
export const LoginLink: FC<{
8383
text?: string;
8484
color?:
85-
| "primary"
86-
| "foreground"
87-
| "secondary"
88-
| "success"
89-
| "warning"
90-
| "danger";
85+
| "primary"
86+
| "foreground"
87+
| "secondary"
88+
| "success"
89+
| "warning"
90+
| "danger";
9191
}> = ({ text, color }) => {
9292
const { isAuthenticated, loginWithRedirect } = useAuth0();
9393
const { t } = useTranslation();
@@ -185,12 +185,12 @@ interface LogoutLinkProps extends LogoutButtonProps {
185185
* Button color
186186
*/
187187
color?:
188-
| "primary"
189-
| "foreground"
190-
| "secondary"
191-
| "success"
192-
| "warning"
193-
| "danger";
188+
| "primary"
189+
| "foreground"
190+
| "secondary"
191+
| "success"
192+
| "warning"
193+
| "danger";
194194
}
195195
/**
196196
* Renders a logout link for Auth0 authentication.
@@ -572,6 +572,209 @@ export const useSecuredApi = () => {
572572
postJson,
573573
deleteJson,
574574
hasPermission,
575+
// Auth0 Management helpers
576+
getAuth0ManagementToken: async () => {
577+
try {
578+
const tokenResp = await postJson(
579+
`${import.meta.env.API_BASE_URL}/__auth0/token`,
580+
{},
581+
);
582+
return tokenResp;
583+
} catch (error) {
584+
console.error("Failed to get Auth0 management token:", error);
585+
throw error;
586+
}
587+
},
588+
listAuth0Users: async (managementToken: string) => {
589+
try {
590+
const resp = await fetch(
591+
`https://${import.meta.env.AUTH0_DOMAIN}/api/v2/users?per_page=100`,
592+
{
593+
method: "GET",
594+
headers: {
595+
Authorization: `Bearer ${managementToken}`,
596+
"Content-Type": "application/json",
597+
},
598+
},
599+
);
600+
return await resp.json();
601+
} catch (error) {
602+
console.error("Failed to list Auth0 users:", error);
603+
throw error;
604+
}
605+
},
606+
listAuth0Roles: async (managementToken: string) => {
607+
try {
608+
const resp = await fetch(`https://${import.meta.env.AUTH0_DOMAIN}/api/v2/roles`, {
609+
method: "GET",
610+
headers: {
611+
Authorization: `Bearer ${managementToken}`,
612+
"Content-Type": "application/json",
613+
},
614+
});
615+
return await resp.json();
616+
} catch (error) {
617+
console.error("Failed to list Auth0 roles:", error);
618+
throw error;
619+
}
620+
},
621+
getUserRoles: async (managementToken: string, userId: string) => {
622+
try {
623+
const resp = await fetch(
624+
`https://${import.meta.env.AUTH0_DOMAIN}/api/v2/users/${encodeURIComponent(userId)}/roles`,
625+
{
626+
method: "GET",
627+
headers: {
628+
Authorization: `Bearer ${managementToken}`,
629+
"Content-Type": "application/json",
630+
},
631+
},
632+
);
633+
return await resp.json();
634+
} catch (error) {
635+
console.error("Failed to get user roles:", error);
636+
throw error;
637+
}
638+
},
639+
addUserToRole: async (managementToken: string, roleId: string, userId: string) => {
640+
try {
641+
const resp = await fetch(
642+
`https://${import.meta.env.AUTH0_DOMAIN}/api/v2/roles/${encodeURIComponent(roleId)}/users`,
643+
{
644+
method: "POST",
645+
headers: {
646+
Authorization: `Bearer ${managementToken}`,
647+
"Content-Type": "application/json",
648+
},
649+
body: JSON.stringify({ users: [userId] }),
650+
},
651+
);
652+
return await resp.json();
653+
} catch (error) {
654+
console.error("Failed to add user to role:", error);
655+
throw error;
656+
}
657+
},
658+
removeUserFromRole: async (managementToken: string, roleId: string, userId: string) => {
659+
try {
660+
const resp = await fetch(
661+
`https://${import.meta.env.AUTH0_DOMAIN}/api/v2/roles/${encodeURIComponent(roleId)}/users`,
662+
{
663+
method: "DELETE",
664+
headers: {
665+
Authorization: `Bearer ${managementToken}`,
666+
"Content-Type": "application/json",
667+
},
668+
body: JSON.stringify({ users: [userId] }),
669+
},
670+
);
671+
return await resp.json();
672+
} catch (error) {
673+
console.error("Failed to remove user from role:", error);
674+
throw error;
675+
}
676+
},
677+
patchAuth0User: async (managementToken: string, userId: string, body: any) => {
678+
try {
679+
const resp = await fetch(`https://${import.meta.env.AUTH0_DOMAIN}/api/v2/users/${encodeURIComponent(userId)}`, {
680+
method: "PATCH",
681+
headers: {
682+
Authorization: `Bearer ${managementToken}`,
683+
"Content-Type": "application/json",
684+
},
685+
body: JSON.stringify(body),
686+
});
687+
return await resp.json();
688+
} catch (error) {
689+
console.error("Failed to patch Auth0 user:", error);
690+
throw error;
691+
}
692+
},
693+
deleteAuth0User: async (managementToken: string, userId: string) => {
694+
try {
695+
const resp = await fetch(`https://${import.meta.env.AUTH0_DOMAIN}/api/v2/users/${encodeURIComponent(userId)}`, {
696+
method: "DELETE",
697+
headers: {
698+
Authorization: `Bearer ${managementToken}`,
699+
"Content-Type": "application/json",
700+
},
701+
});
702+
return await resp.json();
703+
} catch (error) {
704+
console.error("Failed to delete Auth0 user:", error);
705+
throw error;
706+
}
707+
},
708+
getUserPermissions: async (managementToken: string, userId: string) => {
709+
try {
710+
const resp = await fetch(
711+
`https://${import.meta.env.AUTH0_DOMAIN}/api/v2/users/${encodeURIComponent(userId)}/permissions`,
712+
{
713+
method: "GET",
714+
headers: {
715+
Authorization: `Bearer ${managementToken}`,
716+
"Content-Type": "application/json",
717+
},
718+
},
719+
);
720+
return await resp.json();
721+
} catch (error) {
722+
console.error("Failed to get user permissions:", error);
723+
throw error;
724+
}
725+
},
726+
addPermissionToUser: async (managementToken: string, userId: string, permissionName: string) => {
727+
try {
728+
const resp = await fetch(
729+
`https://${import.meta.env.AUTH0_DOMAIN}/api/v2/users/${encodeURIComponent(userId)}/permissions`,
730+
{
731+
method: "POST",
732+
headers: {
733+
Authorization: `Bearer ${managementToken}`,
734+
"Content-Type": "application/json",
735+
},
736+
body: JSON.stringify({
737+
permissions: [
738+
{
739+
resource_server_identifier: import.meta.env.AUTH0_AUDIENCE,
740+
permission_name: permissionName,
741+
},
742+
],
743+
}),
744+
},
745+
);
746+
return await resp.json();
747+
} catch (error) {
748+
console.error("Failed to add permission to user:", error);
749+
throw error;
750+
}
751+
},
752+
removePermissionFromUser: async (managementToken: string, userId: string, permissionName: string) => {
753+
try {
754+
const resp = await fetch(
755+
`https://${import.meta.env.AUTH0_DOMAIN}/api/v2/users/${encodeURIComponent(userId)}/permissions`,
756+
{
757+
method: "DELETE",
758+
headers: {
759+
Authorization: `Bearer ${managementToken}`,
760+
"Content-Type": "application/json",
761+
},
762+
body: JSON.stringify({
763+
permissions: [
764+
{
765+
resource_server_identifier: import.meta.env.AUTH0_AUDIENCE,
766+
permission_name: permissionName,
767+
},
768+
],
769+
}),
770+
},
771+
);
772+
return await resp.json();
773+
} catch (error) {
774+
console.error("Failed to remove permission from user:", error);
775+
throw error;
776+
}
777+
},
575778
};
576779
};
577780

client/src/config/site.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import AddNewUser from "@/pages/add-new-user";
66
import OldestReadyToRefundPage from "@/pages/oldest-ready-to-refund";
77
import ManageDatabasePage from "@/pages/manage-database";
88
import StatsPage from "@/pages/stats";
9+
import UsersAndPermissionsPage from "@/pages/users-and-permissions";
910

1011
export const siteConfig = () => {
1112
const t = i18next.t;
@@ -70,6 +71,12 @@ export const siteConfig = () => {
7071
permission: import.meta.env.BACKUP_PERMISSION,
7172
component: ManageDatabasePage,
7273
},
74+
{
75+
label: i18next.t("users-and-permissions"),
76+
href: "/users-and-permissions",
77+
permission: import.meta.env.ADMIN_AUTH0_PERMISSION,
78+
component: UsersAndPermissionsPage,
79+
}
7380
],
7481
links: {
7582
github: "https://github.com/sctg-development/feedback-flow",

client/src/locales/base/ar-SA.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,5 +247,6 @@
247247
"feedback-text": "نص التعليق",
248248
"publication-date": "تاريخ النشر",
249249
"publication-screenshot": "إثبات النشر",
250-
"order-date": "تاريخ الطلب"
251-
}
250+
"order-date": "تاريخ الطلب",
251+
"users-and-permissions": "المستخدمون والأذونات"
252+
}

client/src/locales/base/en-US.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,5 +247,6 @@
247247
"feedback-text": "Feedback Text",
248248
"publication-date": "Publication Date",
249249
"publication-screenshot": "Publication Proof",
250-
"order-date": "Order date"
251-
}
250+
"order-date": "Order date",
251+
"users-and-permissions": "Users and permissions"
252+
}

client/src/locales/base/es-ES.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,5 +247,6 @@
247247
"feedback-text": "Texto del comentario",
248248
"publication-date": "Fecha de publicación",
249249
"publication-screenshot": "Prueba de publicación",
250-
"order-date": "fecha de pedido"
251-
}
250+
"order-date": "fecha de pedido",
251+
"users-and-permissions": "Usuarios y permisos"
252+
}

client/src/locales/base/fr-FR.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,5 +247,6 @@
247247
"feedback-text": "Texte du commentaire",
248248
"publication-date": "Date de publication",
249249
"publication-screenshot": "Preuve de publication",
250-
"order-date": "Date de commande"
251-
}
250+
"order-date": "Date de commande",
251+
"users-and-permissions": "Utilisateurs et autorisations"
252+
}

client/src/locales/base/he-IL.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,5 +247,6 @@
247247
"feedback-text": "טקסט המשוב",
248248
"publication-date": "תאריך הפרסום",
249249
"publication-screenshot": "הוכחת פרסום",
250-
"order-date": "תאריך הזמנה"
251-
}
250+
"order-date": "תאריך הזמנה",
251+
"users-and-permissions": "משתמשים והרשאות"
252+
}

0 commit comments

Comments
 (0)