diff --git a/pwa/components/admin/AppBar.tsx b/pwa/components/admin/AppBar.tsx
index 5e8385a0..b948f568 100644
--- a/pwa/components/admin/AppBar.tsx
+++ b/pwa/components/admin/AppBar.tsx
@@ -1,13 +1,16 @@
-import { useContext, useState } from "react";
-import { AppBar, AppBarClasses, UserMenu, Logout, useStore } from "react-admin";
+import { forwardRef, useContext, useState } from "react";
+import { AppBar, AppBarClasses, LogoutClasses, UserMenu, useTranslate, useStore } from "react-admin";
import { type AppBarProps } from "react-admin";
-import { Button, Menu, MenuItem, Typography } from "@mui/material";
+import {Button, ListItemIcon, ListItemText, Menu, MenuItem, Typography} from "@mui/material";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
+import ExitIcon from "@mui/icons-material/PowerSettingsNew";
+import { signOut, useSession } from "next-auth/react";
import DocContext from "../../components/admin/DocContext";
import HydraLogo from "../../components/admin/HydraLogo";
import OpenApiLogo from "../../components/admin/OpenApiLogo";
import Logo from "../../components/admin/Logo";
+import {OIDC_SERVER_URL} from "../../config/keycloak";
const DocTypeMenuButton = () => {
const [anchorEl, setAnchorEl] = useState(null);
@@ -62,11 +65,42 @@ const DocTypeMenuButton = () => {
);
};
+const Logout = forwardRef((props, ref) => {
+ const { data: session } = useSession();
+ const translate = useTranslate();
+
+ if (!session) {
+ return;
+ }
+
+ const handleClick = () => signOut({
+ // @ts-ignore
+ callbackUrl: `${OIDC_SERVER_URL}/protocol/openid-connect/logout?id_token_hint=${session.idToken}&post_logout_redirect_uri=${window.location.origin}`,
+ });
+
+ return (
+
+ );
+});
+
const CustomAppBar = ({ ...props }: AppBarProps) => {
return (
-
+
} {...props}>
Promise.resolve(),
logout: async () => {
- const session = await auth();
+ const { data: session } = useSession();
if (!session) {
return;
}
- await signOut(/*{
+ await signOut({
// @ts-ignore
callbackUrl: `${OIDC_SERVER_URL}/protocol/openid-connect/logout?id_token_hint=${session.idToken}&post_logout_redirect_uri=${window.location.origin}`,
- }*/);
+ });
},
checkError: async (error) => {
- const session = await auth();
+ const { data: session } = useSession();
const status = error.status;
// @ts-ignore
if (!session || session?.error === "RefreshAccessTokenError" || status === 401) {
@@ -33,7 +32,7 @@ const authProvider: AuthProvider = {
}
},
checkAuth: async () => {
- const session = await auth();
+ const { data: session } = useSession();
// @ts-ignore
if (!session || session?.error === "RefreshAccessTokenError") {
await signIn("keycloak");
@@ -46,7 +45,7 @@ const authProvider: AuthProvider = {
getPermissions: () => Promise.resolve(),
// @ts-ignore
getIdentity: async () => {
- const session = await auth();
+ const { data: session } = useSession();
return session ? Promise.resolve(session.user) : Promise.reject();
},
diff --git a/pwa/tests/User.spec.ts b/pwa/tests/User.spec.ts
index a03bd1a1..2ea33234 100644
--- a/pwa/tests/User.spec.ts
+++ b/pwa/tests/User.spec.ts
@@ -5,7 +5,7 @@ test.describe("User authentication", () => {
await bookPage.gotoList();
});
- test("I can log in @login", async ({ userPage, page }) => {
+ test("I can log in Books Store @login", async ({ userPage, page }) => {
await expect(page.getByText("Log in")).toBeVisible();
await expect(page.getByText("Sign out")).toHaveCount(0);
@@ -22,7 +22,7 @@ test.describe("User authentication", () => {
await expect(page.getByText("Sign out")).toBeVisible();
});
- test("I can sign out @login", async ({ userPage, page }) => {
+ test("I can sign out of Books Store @login", async ({ userPage, page }) => {
await page.getByText("Log in").click();
await userPage.login();
await page.getByText("Sign out").click();
diff --git a/pwa/tests/admin/User.spec.ts b/pwa/tests/admin/User.spec.ts
new file mode 100644
index 00000000..dd5920a9
--- /dev/null
+++ b/pwa/tests/admin/User.spec.ts
@@ -0,0 +1,24 @@
+import { expect, test } from "./test";
+
+test.describe("User authentication", () => {
+ test.beforeEach(async ({ bookPage }) => {
+ await bookPage.gotoList();
+ });
+
+ test("I can sign out of Admin @login", async ({ userPage, page }) => {
+ await page.getByLabel("Profile").click();
+ await page.getByRole("menu").getByText("Logout").waitFor({ state: "visible" });
+ await page.getByRole("menu").getByText("Logout").click();
+
+ await expect(page).toHaveURL(/\/$/);
+
+ // I should be logged out from Keycloak also
+ await page.goto("/admin");
+ await page.waitForURL(/\/oidc\/realms\/demo\/protocol\/openid-connect\/auth/);
+ // @ts-ignore assert declared on test.ts
+ await expect(page).toBeOnLoginPage();
+ await expect(page.locator("#kc-header-wrapper")).toContainText("API Platform - Demo");
+ await expect(page.locator("#kc-form-login")).toContainText("Login as user: john.doe@example.com");
+ await expect(page.locator("#kc-form-login")).toContainText("Login as admin: chuck.norris@example.com");
+ });
+});
diff --git a/pwa/tests/admin/pages/UserPage.ts b/pwa/tests/admin/pages/UserPage.ts
new file mode 100644
index 00000000..d6196b12
--- /dev/null
+++ b/pwa/tests/admin/pages/UserPage.ts
@@ -0,0 +1,4 @@
+import { AbstractPage } from "./AbstractPage";
+
+export class UserPage extends AbstractPage {
+}
diff --git a/pwa/tests/admin/test.ts b/pwa/tests/admin/test.ts
index a5acafa9..0c86fb58 100644
--- a/pwa/tests/admin/test.ts
+++ b/pwa/tests/admin/test.ts
@@ -1,12 +1,30 @@
-import { test as playwrightTest } from "@playwright/test";
+import { Page, test as playwrightTest } from "@playwright/test";
import { expect } from "../test";
import { BookPage } from "./pages/BookPage";
import { ReviewPage } from "./pages/ReviewPage";
+import { UserPage } from "./pages/UserPage";
+
+expect.extend({
+ toBeOnLoginPage(page: Page) {
+ if (page.url().match(/\/oidc\/realms\/demo\/protocol\/openid-connect\/auth/)) {
+ return {
+ message: () => "passed",
+ pass: true,
+ };
+ }
+
+ return {
+ message: () => `toBeOnLoginPage() assertion failed.\nExpected "/oidc/realms/demo/protocol/openid-connect/auth", got "${page.url()}".`,
+ pass: false,
+ };
+ },
+});
type Test = {
bookPage: BookPage,
reviewPage: ReviewPage,
+ userPage: UserPage,
}
export const test = playwrightTest.extend({
@@ -16,6 +34,9 @@ export const test = playwrightTest.extend({
reviewPage: async ({ page }, use) => {
await use(new ReviewPage(page));
},
+ userPage: async ({ page }, use) => {
+ await use(new UserPage(page));
+ },
});
export { expect };