Skip to content

Commit

Permalink
Use browser router for Account Console (#22192)
Browse files Browse the repository at this point in the history
Closes #27442

Signed-off-by: Jon Koops <jonkoops@gmail.com>
  • Loading branch information
jonkoops committed Mar 4, 2024
1 parent be3e2fa commit 7afd75b
Show file tree
Hide file tree
Showing 14 changed files with 145 additions and 108 deletions.
5 changes: 2 additions & 3 deletions js/apps/account-ui/playwright.config.ts
@@ -1,4 +1,5 @@
import { defineConfig, devices } from "@playwright/test";
import { getRootPath } from "./src/utils/getRootPath";

/**
* See https://playwright.dev/docs/test-configuration.
Expand All @@ -11,9 +12,7 @@ export default defineConfig({
workers: 1,
reporter: process.env.CI ? [["github"], ["html"]] : "list",
use: {
baseURL: process.env.CI
? "http://localhost:8080/realms/master/account/"
: "http://localhost:8080/",
baseURL: `http://localhost:8080${getRootPath()}`,
trace: "on-first-retry",
},

Expand Down
2 changes: 2 additions & 0 deletions js/apps/account-ui/src/constants.ts
@@ -0,0 +1,2 @@
export const DEFAULT_REALM = "master";
export const ROOT_PATH = "/realms/:realm/account";
10 changes: 7 additions & 3 deletions js/apps/account-ui/src/environment.ts
@@ -1,3 +1,6 @@
import { matchPath } from "react-router-dom";
import { DEFAULT_REALM, ROOT_PATH } from "./constants";

export type Feature = {
isRegistrationEmailAsUsername: boolean;
isEditUserNameAllowed: boolean;
Expand Down Expand Up @@ -31,11 +34,12 @@ export type Environment = {
features: Feature;
};

// The default environment, used during development.
const realm = new URLSearchParams(window.location.search).get("realm");
// Detect the current realm from the URL.
const match = matchPath(ROOT_PATH, location.pathname);

const defaultEnvironment: Environment = {
authUrl: "http://localhost:8180",
realm: realm || "master",
realm: match?.params.realm ?? DEFAULT_REALM,
clientId: "security-admin-console-v2",
resourceUrl: "http://localhost:8080",
logo: "/logo.svg",
Expand Down
8 changes: 6 additions & 2 deletions js/apps/account-ui/src/main.tsx
Expand Up @@ -3,18 +3,22 @@ import "@patternfly/patternfly/patternfly-addons.css";

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { createHashRouter, RouterProvider } from "react-router-dom";
import { createBrowserRouter, RouterProvider } from "react-router-dom";

import { environment } from "./environment";
import { i18n } from "./i18n";
import { routes } from "./routes";
import { getRootPath } from "./utils/getRootPath";

// Initialize required components before rendering app.
await i18n.init();

const router = createHashRouter(routes);
const container = document.getElementById("app");
const root = createRoot(container!);

const basename = getRootPath(environment.realm);
const router = createBrowserRouter(routes, { basename });

root.render(
<StrictMode>
<RouterProvider router={router} />
Expand Down
5 changes: 5 additions & 0 deletions js/apps/account-ui/src/utils/getRootPath.ts
@@ -0,0 +1,5 @@
import { generatePath } from "react-router-dom";
import { DEFAULT_REALM, ROOT_PATH } from "../constants";

export const getRootPath = (realm = DEFAULT_REALM) =>
generatePath(ROOT_PATH, { realm });
32 changes: 17 additions & 15 deletions js/apps/account-ui/test/account-security/linked-accounts.spec.ts
@@ -1,18 +1,20 @@
import ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
import IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation";
import { expect, test } from "@playwright/test";
import { randomUUID } from "node:crypto";

import {
createIdentityProvider,
deleteIdentityProvider,
createClient,
deleteClient,
inRealm,
findClientByClientId,
createIdentityProvider,
createRandomUserWithPassword,
deleteClient,
deleteIdentityProvider,
deleteUser,
findClientByClientId,
inRealm,
} from "../admin-client";
import groupsIdPClient from "../realms/groups-idp.json" assert { type: "json" };
import ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
import IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation";
import { randomUUID } from "crypto";
import { getBaseUrl } from "../utils";

const realm = "groups";

Expand All @@ -30,7 +32,7 @@ test.describe("Account linking", () => {
groupIdPClientId = await createClient(
groupsIdPClient as ClientRepresentation,
);
const kc = process.env.KEYCLOAK_SERVER || "http://localhost:8080";
const baseUrl = getBaseUrl();
const idp: IdentityProviderRepresentation = {
alias: "master-idp",
providerId: "oidc",
Expand All @@ -39,12 +41,12 @@ test.describe("Account linking", () => {
clientId: "groups-idp",
clientSecret: "H0JaTc7VBu3HJR26vrzMxgidfJmgI5Dw",
validateSignature: "false",
tokenUrl: `${kc}/realms/master/protocol/openid-connect/token`,
jwksUrl: `${kc}/realms/master/protocol/openid-connect/certs`,
issuer: `${kc}/realms/master`,
authorizationUrl: `${kc}/realms/master/protocol/openid-connect/auth`,
logoutUrl: `${kc}/realms/master/protocol/openid-connect/logout`,
userInfoUrl: `${kc}/realms/master/protocol/openid-connect/userinfo`,
tokenUrl: `${baseUrl}/realms/master/protocol/openid-connect/token`,
jwksUrl: `${baseUrl}/realms/master/protocol/openid-connect/certs`,
issuer: `${baseUrl}/realms/master`,
authorizationUrl: `${baseUrl}/realms/master/protocol/openid-connect/auth`,
logoutUrl: `${baseUrl}/realms/master/protocol/openid-connect/logout`,
userInfoUrl: `${baseUrl}/realms/master/protocol/openid-connect/userinfo`,
},
};

Expand Down
20 changes: 13 additions & 7 deletions js/apps/account-ui/test/admin-client.ts
Expand Up @@ -5,9 +5,12 @@ import RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmR
import type { UserProfileConfig } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
import UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";

import { DEFAULT_REALM } from "../src/constants";
import { getBaseUrl } from "./utils";

const adminClient = new KeycloakAdminClient({
baseUrl: process.env.KEYCLOAK_SERVER || "http://127.0.0.1:8080",
realmName: "master",
baseUrl: getBaseUrl(),
realmName: DEFAULT_REALM,
});

await adminClient.auth({
Expand All @@ -18,9 +21,12 @@ await adminClient.auth({
});

export async function useTheme() {
const masterRealm = await adminClient.realms.findOne({ realm: "master" });
const masterRealm = await adminClient.realms.findOne({
realm: DEFAULT_REALM,
});

await adminClient.realms.update(
{ realm: "master" },
{ realm: DEFAULT_REALM },
{ ...masterRealm, accountTheme: "keycloak.v3" },
);
}
Expand Down Expand Up @@ -76,7 +82,7 @@ export async function importUserProfile(
await adminClient.users.updateProfile({ ...userProfile, realm });
}

export async function enableLocalization(realm: string) {
export async function enableLocalization(realm = DEFAULT_REALM) {
const realmRepresentation = await adminClient.realms.findOne({ realm });
await adminClient.realms.update(
{ realm },
Expand Down Expand Up @@ -121,9 +127,9 @@ export async function getUserByUsername(username: string, realm: string) {

export async function deleteUser(username: string) {
try {
const users = await adminClient.users.find({ username, realm });
const users = await adminClient.users.find({ username });
const { id } = users[0];
await adminClient.users.del({ id: id!, realm });
await adminClient.users.del({ id: id! });
} catch (error) {
console.error(error);
}
Expand Down
20 changes: 11 additions & 9 deletions js/apps/account-ui/test/applications.spec.ts
@@ -1,10 +1,12 @@
import { expect, test } from "@playwright/test";
import { getRootPath } from "../src/utils/getRootPath";
import { login } from "./login";
import { getAccountUrl, getAdminUrl } from "./utils";

test.describe("Applications test", () => {
test.beforeEach(async ({ page }) => {
// Sign out all devices before each test
await login(page, "admin", "admin", "master");
await login(page, "admin", "admin");
await page.getByTestId("accountSecurity").click();
await page.getByTestId("account-security/device-activity").click();

Expand All @@ -19,7 +21,7 @@ test.describe("Applications test", () => {
});

test("Single application", async ({ page }) => {
await login(page, "admin", "admin", "master");
await login(page, "admin", "admin");

await page.getByTestId("applications").click();

Expand All @@ -39,8 +41,8 @@ test.describe("Applications test", () => {
const page1 = await context1.newPage();
const page2 = await context2.newPage();

await login(page1, "admin", "admin", "master");
await login(page2, "admin", "admin", "master");
await login(page1, "admin", "admin");
await login(page2, "admin", "admin");

await page1.getByTestId("applications").click();

Expand All @@ -62,15 +64,15 @@ test.describe("Applications test", () => {
"Skip this test if not running with regular Keycloak",
);

await login(page, "admin", "admin", "master");
await login(page, "admin", "admin");

// go to admin console
await page.goto("/");
await expect(page).toHaveURL("http://localhost:8080/admin/master/console/");
await page.waitForURL("http://localhost:8080/admin/master/console/");
await expect(page).toHaveURL(getAdminUrl());
await page.waitForURL(getAdminUrl());

await page.goto("/realms/master/account");
await page.waitForURL("http://localhost:8080/realms/master/account/");
await page.goto(getRootPath());
await page.waitForURL(getAccountUrl());

await page.getByTestId("applications").click();

Expand Down
11 changes: 6 additions & 5 deletions js/apps/account-ui/test/login.ts
@@ -1,15 +1,16 @@
import { Page } from "@playwright/test";
import { DEFAULT_REALM } from "../src/constants";
import { getRootPath } from "../src/utils/getRootPath";

export const login = async (
page: Page,
username: string,
password: string,
realm?: string,
realm = DEFAULT_REALM,
) => {
if (realm)
await page.goto(
process.env.CI ? `/realms/${realm}/account` : `/?realm=${realm}`,
);
const rootPath = getRootPath(realm);

await page.goto(rootPath);
await page.getByLabel("Username").fill(username);
await page.getByLabel("Password", { exact: true }).fill(password);
await page.getByRole("button", { name: "Sign In" }).click();
Expand Down
6 changes: 3 additions & 3 deletions js/apps/account-ui/test/personal-info/personal-info.spec.ts
Expand Up @@ -18,7 +18,7 @@ test.describe("Personal info page", () => {
test("sets basic information", async ({ page }) => {
user = await createRandomUserWithPassword("user-" + randomUUID(), "pwd");

await login(page, user, "pwd", "master");
await login(page, user, "pwd");

await page.getByTestId("email").fill(`${user}@somewhere.com`);
await page.getByTestId("firstName").fill("Erik");
Expand Down Expand Up @@ -84,15 +84,15 @@ test.describe("Personal info with userprofile enabled", async () => {

// skip currently the locale is not part of the response
test.describe.skip("Realm localization", async () => {
test.beforeAll(() => enableLocalization("master"));
test.beforeAll(() => enableLocalization());

test("change locale", async ({ page }) => {
const user = await createRandomUserWithPassword(
"user-" + randomUUID(),
"pwd",
);

await login(page, user, "pwd", "master");
await login(page, user, "pwd");
await page
.locator("div")
.filter({ hasText: /^Deutsch$/ })
Expand Down
13 changes: 13 additions & 0 deletions js/apps/account-ui/test/utils.ts
@@ -0,0 +1,13 @@
import { getRootPath } from "../src/utils/getRootPath";

export function getBaseUrl(): string {
return process.env.KEYCLOAK_SERVER ?? "http://localhost:8080";
}

export function getAccountUrl() {
return getBaseUrl() + getRootPath();
}

export function getAdminUrl() {
return getBaseUrl() + "/admin/master/console/";
}
3 changes: 3 additions & 0 deletions js/apps/account-ui/vite.config.ts
Expand Up @@ -4,6 +4,8 @@ import { defineConfig, loadEnv } from "vite";
import { checker } from "vite-plugin-checker";
import dts from "vite-plugin-dts";

import { getRootPath } from "./src/utils/getRootPath";

// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
Expand All @@ -26,6 +28,7 @@ export default defineConfig(({ mode }) => {
base: "",
server: {
port: 8080,
open: getRootPath(),
},
build: {
...lib,
Expand Down
2 changes: 1 addition & 1 deletion js/apps/admin-ui/cypress/e2e/masthead_test.spec.ts
Expand Up @@ -18,7 +18,7 @@ describe("Masthead tests", () => {
it("Go to account console and back to admin console", () => {
sidebarPage.waitForPageLoad();
masthead.accountManagement();
cy.url().should("contain", "/realms/master/account/");
cy.url().should("contain", "/realms/master/account");
});

it("Sign out reachs to log in screen", () => {
Expand Down

0 comments on commit 7afd75b

Please sign in to comment.