feat(admin): sync and authorize Keycloak users
Generic Container CI/CD / test-build-publish (push) Successful in 1m5s
Frontend CI/CD v2 / build-test-publish-and-deploy (push) Successful in 1m5s

This commit is contained in:
2026-11-04 13:35:29 +08:00
parent 7f1ab4a79c
commit ca1c41aa9d
10 changed files with 1077 additions and 143 deletions
+23
View File
@@ -115,6 +115,29 @@ describe("RefineContext access authentication", () => {
});
});
it("shows an authorization message when access-context rejects the account", async () => {
global.fetch = jest.fn().mockResolvedValue({
ok: false,
status: 403,
headers: new Headers(),
} as Response);
render(
<App>
<div>应用内容</div>
</App>,
);
expect(
await screen.findByText("账号尚未获得访问权限"),
).toBeInTheDocument();
expect(screen.queryByText("应用内容")).not.toBeInTheDocument();
expect(useAuthStore.getState()).toMatchObject({
sessionExpired: false,
sessionExpiryReason: null,
});
});
it("prioritizes an unauthenticated session over route permissions", async () => {
mockSessionState = { data: null, status: "unauthenticated" };
+14 -2
View File
@@ -9,7 +9,7 @@ import { RefineKbar, RefineKbarProvider } from "@refinedev/kbar";
import { RefineSnackbarProvider } from "@refinedev/mui";
import { SessionProvider, signIn, useSession } from "next-auth/react";
import { usePathname } from "next/navigation";
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import routerProvider from "@refinedev/nextjs-router";
import { NavigationLink } from "@components/loading/NavigationLink";
@@ -75,6 +75,7 @@ export const App = (props: React.PropsWithChildren<AppProps>) => {
const setAccessContext = useAccessStore((state) => state.setContext);
const setAccessLoading = useAccessStore((state) => state.setLoading);
const resetAccess = useAccessStore((state) => state.reset);
const [accessDenied, setAccessDenied] = useState(false);
const can = (permission: string) => permissions.includes(permission);
useEffect(() => {
@@ -103,21 +104,29 @@ export const App = (props: React.PropsWithChildren<AppProps>) => {
useEffect(() => {
if (status !== "authenticated") {
setAccessDenied(false);
resetAccess();
return;
}
let cancelled = false;
setAccessDenied(false);
setAccessLoading(true);
apiFetch(`${config.BACKEND_URL}/api/v1/access-context`, {
projectHeaderMode: currentProjectId ? "include" : "omit",
})
.then(async (response) => {
if (cancelled) return;
if (response.status === 403) {
setAccessDenied(true);
resetAccess();
return;
}
if (!response.ok) {
resetAccess();
return;
}
setAccessDenied(false);
setAccessContext(await response.json());
})
.catch(() => {
@@ -392,7 +401,10 @@ export const App = (props: React.PropsWithChildren<AppProps>) => {
}}
>
<SessionExpiryDialog expiresAt={data?.sessionExpiresAt} />
<RoutePermissionGuard authenticated={status === "authenticated"}>
<RoutePermissionGuard
authenticated={status === "authenticated"}
accessDenied={accessDenied}
>
{props.children}
</RoutePermissionGuard>
<RefineKbar />