mirror of
https://github.com/keycloak/keycloak.git
synced 2025-12-19 21:40:12 -06:00
[OID4VCI] Extend realm UI configuration by OID4VCI attributes (#41757)
* Extend realm UI configuration by OID4VCI attributes Closes #39533 Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com> * update: adjust tests in oid4vci-attributes.spec.ts based on feature availability Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com> * update: directly check OID4VCI feature from server info in tests before running Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com> * update: address comment(s) by @IngridPuppet Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com> * update: skip tests when oid4vci feature is not enabled Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com> * update: move oid4vc realm attributes setting to token tab Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com> * update: refactor tokens tab and restructure oid4vci attributes tests Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com> * update: apply review comments by @jonkoops Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com> * update: refactored tests to use createTestBed() Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com> * update: apply changes by @jonkoops Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com> * update: address review comments Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com> * update: change test format Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com> * update: enable oid4vc feature in worfklows Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com> * Update .github/workflows/stability-js-ci.yml Co-authored-by: Jon Koops <jonkoops@gmail.com> Signed-off-by: forkimenjeckayang <104195313+forkimenjeckayang@users.noreply.github.com> * fix: included required fields in tokens.ts as fix for CI tests Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com> * update: address more review comments Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com> --------- Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com> Signed-off-by: forkimenjeckayang <104195313+forkimenjeckayang@users.noreply.github.com> Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
committed by
GitHub
parent
1abddbf64c
commit
f3bd3dcd2e
2
.github/workflows/js-ci.yml
vendored
2
.github/workflows/js-ci.yml
vendored
@@ -230,7 +230,7 @@ jobs:
|
|||||||
- name: Start Keycloak server
|
- name: Start Keycloak server
|
||||||
run: |
|
run: |
|
||||||
tar xfvz keycloak-999.0.0-SNAPSHOT.tar.gz
|
tar xfvz keycloak-999.0.0-SNAPSHOT.tar.gz
|
||||||
keycloak-999.0.0-SNAPSHOT/bin/kc.sh start-dev --features=admin-fine-grained-authz:v2,transient-users,spiffe &> ~/server.log &
|
keycloak-999.0.0-SNAPSHOT/bin/kc.sh start-dev --features=admin-fine-grained-authz:v2,transient-users,spiffe,oid4vc-vci &> ~/server.log &
|
||||||
env:
|
env:
|
||||||
KC_BOOTSTRAP_ADMIN_USERNAME: admin
|
KC_BOOTSTRAP_ADMIN_USERNAME: admin
|
||||||
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
|
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
|
||||||
|
|||||||
2
.github/workflows/stability-js-ci.yml
vendored
2
.github/workflows/stability-js-ci.yml
vendored
@@ -119,7 +119,7 @@ jobs:
|
|||||||
- name: Start Keycloak server
|
- name: Start Keycloak server
|
||||||
run: |
|
run: |
|
||||||
tar xfvz keycloak-999.0.0-SNAPSHOT.tar.gz
|
tar xfvz keycloak-999.0.0-SNAPSHOT.tar.gz
|
||||||
keycloak-999.0.0-SNAPSHOT/bin/kc.sh start-dev --features=admin-fine-grained-authz:v2,transient-users &> ~/server.log &
|
keycloak-999.0.0-SNAPSHOT/bin/kc.sh start-dev --features=admin-fine-grained-authz:v2,transient-users,spiffe,oid4vc-vci &> ~/server.log &
|
||||||
env:
|
env:
|
||||||
KC_BOOTSTRAP_ADMIN_USERNAME: admin
|
KC_BOOTSTRAP_ADMIN_USERNAME: admin
|
||||||
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
|
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
|
||||||
|
|||||||
@@ -3556,3 +3556,9 @@ invalid_request_object=Invalid request object
|
|||||||
request_not_supported=Request not supported
|
request_not_supported=Request not supported
|
||||||
request_uri_not_supported=Request uri not supported
|
request_uri_not_supported=Request uri not supported
|
||||||
registration_not_supported=Registration not supported
|
registration_not_supported=Registration not supported
|
||||||
|
oid4vciAttributes=OID4VCI attributes
|
||||||
|
oid4vciNonceLifetime=OID4VCI Nonce Lifetime
|
||||||
|
oid4vciNonceLifetimeHelp=The lifetime of the OID4VCI nonce in seconds.
|
||||||
|
preAuthorizedCodeLifespan=Pre-Authorized Code Lifespan
|
||||||
|
preAuthorizedCodeLifespanHelp=The lifespan of the pre-authorized code in seconds.
|
||||||
|
oid4vciFormValidationError=Please ensure the OID4VCI attribute fields are filled with values 30 seconds or greater.
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||||
import {
|
import {
|
||||||
FormPanel,
|
|
||||||
HelpItem,
|
HelpItem,
|
||||||
KeycloakSelect,
|
KeycloakSelect,
|
||||||
SelectVariant,
|
SelectVariant,
|
||||||
|
ScrollForm,
|
||||||
|
useAlerts,
|
||||||
} from "@keycloak/keycloak-ui-shared";
|
} from "@keycloak/keycloak-ui-shared";
|
||||||
import {
|
import {
|
||||||
ActionGroup,
|
AlertVariant,
|
||||||
Button,
|
|
||||||
FormGroup,
|
FormGroup,
|
||||||
FormHelperText,
|
FormHelperText,
|
||||||
HelperText,
|
HelperText,
|
||||||
HelperTextItem,
|
HelperTextItem,
|
||||||
NumberInput,
|
NumberInput,
|
||||||
PageSection,
|
|
||||||
SelectOption,
|
SelectOption,
|
||||||
Switch,
|
Switch,
|
||||||
Text,
|
Text,
|
||||||
@@ -24,10 +23,13 @@ import { useState } from "react";
|
|||||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FormAccess } from "../components/form/FormAccess";
|
import { FormAccess } from "../components/form/FormAccess";
|
||||||
|
import { FixedButtonsGroup } from "../components/form/FixedButtonGroup";
|
||||||
|
import { convertAttributeNameToForm } from "../util";
|
||||||
import {
|
import {
|
||||||
TimeSelector,
|
TimeSelector,
|
||||||
toHumanFormat,
|
toHumanFormat,
|
||||||
} from "../components/time-selector/TimeSelector";
|
} from "../components/time-selector/TimeSelector";
|
||||||
|
import { TimeSelectorControl } from "../components/time-selector/TimeSelectorControl";
|
||||||
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||||
import { useWhoAmI } from "../context/whoami/WhoAmI";
|
import { useWhoAmI } from "../context/whoami/WhoAmI";
|
||||||
import { beerify, sortProviders } from "../util";
|
import { beerify, sortProviders } from "../util";
|
||||||
@@ -45,6 +47,7 @@ export const RealmSettingsTokensTab = ({
|
|||||||
save,
|
save,
|
||||||
}: RealmSettingsSessionsTabProps) => {
|
}: RealmSettingsSessionsTabProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { addAlert } = useAlerts();
|
||||||
const serverInfo = useServerInfo();
|
const serverInfo = useServerInfo();
|
||||||
const isFeatureEnabled = useIsFeatureEnabled();
|
const isFeatureEnabled = useIsFeatureEnabled();
|
||||||
const { whoAmI } = useWhoAmI();
|
const { whoAmI } = useWhoAmI();
|
||||||
@@ -59,6 +62,11 @@ export const RealmSettingsTokensTab = ({
|
|||||||
const { control, register, reset, formState, handleSubmit } =
|
const { control, register, reset, formState, handleSubmit } =
|
||||||
useFormContext<RealmRepresentation>();
|
useFormContext<RealmRepresentation>();
|
||||||
|
|
||||||
|
// Show a global error notification if validation fails
|
||||||
|
const onError = () => {
|
||||||
|
addAlert(t("oid4vciFormValidationError"), AlertVariant.danger);
|
||||||
|
};
|
||||||
|
|
||||||
const offlineSessionMaxEnabled = useWatch({
|
const offlineSessionMaxEnabled = useWatch({
|
||||||
control,
|
control,
|
||||||
name: "offlineSessionMaxLifespanEnabled",
|
name: "offlineSessionMaxLifespanEnabled",
|
||||||
@@ -77,9 +85,10 @@ export const RealmSettingsTokensTab = ({
|
|||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
const sections = [
|
||||||
<PageSection variant="light">
|
{
|
||||||
<FormPanel title={t("general")} className="kc-sso-session-template">
|
title: t("general"),
|
||||||
|
panel: (
|
||||||
<FormAccess
|
<FormAccess
|
||||||
isHorizontal
|
isHorizontal
|
||||||
role="manage-realm"
|
role="manage-realm"
|
||||||
@@ -236,11 +245,11 @@ export const RealmSettingsTokensTab = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</FormAccess>
|
</FormAccess>
|
||||||
</FormPanel>
|
),
|
||||||
<FormPanel
|
},
|
||||||
title={t("refreshTokens")}
|
{
|
||||||
className="kc-client-session-template"
|
title: t("refreshTokens"),
|
||||||
>
|
panel: (
|
||||||
<FormAccess
|
<FormAccess
|
||||||
isHorizontal
|
isHorizontal
|
||||||
role="manage-realm"
|
role="manage-realm"
|
||||||
@@ -308,11 +317,11 @@ export const RealmSettingsTokensTab = ({
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
</FormAccess>
|
</FormAccess>
|
||||||
</FormPanel>
|
),
|
||||||
<FormPanel
|
},
|
||||||
title={t("accessTokens")}
|
{
|
||||||
className="kc-offline-session-template"
|
title: t("accessTokens"),
|
||||||
>
|
panel: (
|
||||||
<FormAccess
|
<FormAccess
|
||||||
isHorizontal
|
isHorizontal
|
||||||
role="manage-realm"
|
role="manage-realm"
|
||||||
@@ -437,11 +446,11 @@ export const RealmSettingsTokensTab = ({
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
</FormAccess>
|
</FormAccess>
|
||||||
</FormPanel>
|
),
|
||||||
<FormPanel
|
},
|
||||||
className="kc-login-settings-template"
|
{
|
||||||
title={t("actionTokens")}
|
title: t("actionTokens"),
|
||||||
>
|
panel: (
|
||||||
<FormAccess
|
<FormAccess
|
||||||
isHorizontal
|
isHorizontal
|
||||||
role="manage-realm"
|
role="manage-realm"
|
||||||
@@ -618,21 +627,69 @@ export const RealmSettingsTokensTab = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<ActionGroup>
|
{!isFeatureEnabled(Feature.OpenId4VCI) && (
|
||||||
<Button
|
<FixedButtonsGroup
|
||||||
variant="primary"
|
name="tokens-tab"
|
||||||
type="submit"
|
isSubmit
|
||||||
data-testid="tokens-tab-save"
|
|
||||||
isDisabled={!formState.isDirty}
|
isDisabled={!formState.isDirty}
|
||||||
>
|
reset={() => reset(realm)}
|
||||||
{t("save")}
|
/>
|
||||||
</Button>
|
)}
|
||||||
<Button variant="link" onClick={() => reset(realm)}>
|
|
||||||
{t("revert")}
|
|
||||||
</Button>
|
|
||||||
</ActionGroup>
|
|
||||||
</FormAccess>
|
</FormAccess>
|
||||||
</FormPanel>
|
),
|
||||||
</PageSection>
|
},
|
||||||
|
{
|
||||||
|
title: t("oid4vciAttributes"),
|
||||||
|
isHidden: !isFeatureEnabled(Feature.OpenId4VCI),
|
||||||
|
panel: (
|
||||||
|
<FormAccess
|
||||||
|
isHorizontal
|
||||||
|
role="manage-realm"
|
||||||
|
className="pf-v5-u-mt-lg"
|
||||||
|
onSubmit={handleSubmit(save, onError)}
|
||||||
|
>
|
||||||
|
<TimeSelectorControl
|
||||||
|
name={convertAttributeNameToForm(
|
||||||
|
"attributes.vc.c-nonce-lifetime-seconds",
|
||||||
|
)}
|
||||||
|
label={t("oid4vciNonceLifetime")}
|
||||||
|
labelIcon={t("oid4vciNonceLifetimeHelp")}
|
||||||
|
controller={{
|
||||||
|
defaultValue: 60,
|
||||||
|
rules: { min: 30 },
|
||||||
|
}}
|
||||||
|
min={30}
|
||||||
|
units={["second", "minute", "hour"]}
|
||||||
|
/>
|
||||||
|
<TimeSelectorControl
|
||||||
|
name={convertAttributeNameToForm(
|
||||||
|
"attributes.preAuthorizedCodeLifespanS",
|
||||||
|
)}
|
||||||
|
label={t("preAuthorizedCodeLifespan")}
|
||||||
|
labelIcon={t("preAuthorizedCodeLifespanHelp")}
|
||||||
|
controller={{
|
||||||
|
defaultValue: 30,
|
||||||
|
rules: { min: 30 },
|
||||||
|
}}
|
||||||
|
min={30}
|
||||||
|
units={["second", "minute", "hour"]}
|
||||||
|
/>
|
||||||
|
<FixedButtonsGroup
|
||||||
|
name="tokens-tab"
|
||||||
|
isSubmit
|
||||||
|
isDisabled={!formState.isDirty}
|
||||||
|
reset={() => reset(realm)}
|
||||||
|
/>
|
||||||
|
</FormAccess>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollForm
|
||||||
|
label={t("jumpToSection")}
|
||||||
|
className="pf-v5-u-px-lg pf-v5-u-pb-lg"
|
||||||
|
sections={sections}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
199
js/apps/admin-ui/test/realm-settings/oid4vci-attributes.spec.ts
Normal file
199
js/apps/admin-ui/test/realm-settings/oid4vci-attributes.spec.ts
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
import { generatePath } from "react-router-dom";
|
||||||
|
import { toRealmSettings } from "../../src/realm-settings/routes/RealmSettings.tsx";
|
||||||
|
import { createTestBed } from "../support/testbed.ts";
|
||||||
|
import adminClient from "../utils/AdminClient.js";
|
||||||
|
import { SERVER_URL, ROOT_PATH } from "../utils/constants.ts";
|
||||||
|
import { login } from "../utils/login.js";
|
||||||
|
|
||||||
|
test("OID4VCI section visibility and jump link in Tokens tab", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const realm = await createTestBed();
|
||||||
|
await login(page, { to: toRealmSettings({ realm }) });
|
||||||
|
|
||||||
|
const tokensTab = page.getByTestId("rs-tokens-tab");
|
||||||
|
await tokensTab.click();
|
||||||
|
|
||||||
|
const oid4vciJumpLink = page.getByTestId("jump-link-oid4vci-attributes");
|
||||||
|
await expect(oid4vciJumpLink).toBeVisible();
|
||||||
|
|
||||||
|
await oid4vciJumpLink.click();
|
||||||
|
const oid4vciSection = page.getByRole("heading", {
|
||||||
|
name: "OID4VCI attributes",
|
||||||
|
});
|
||||||
|
await expect(oid4vciSection).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should render fields and save values with correct attribute keys", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const realm = await createTestBed();
|
||||||
|
await login(page, { to: toRealmSettings({ realm }) });
|
||||||
|
|
||||||
|
const tokensTab = page.getByTestId("rs-tokens-tab");
|
||||||
|
await tokensTab.click();
|
||||||
|
|
||||||
|
const oid4vciJumpLink = page.getByTestId("jump-link-oid4vci-attributes");
|
||||||
|
await oid4vciJumpLink.click();
|
||||||
|
|
||||||
|
const nonceField = page.getByTestId(
|
||||||
|
"attributes.vc🍺c-nonce-lifetime-seconds",
|
||||||
|
);
|
||||||
|
const preAuthField = page.getByTestId(
|
||||||
|
"attributes.preAuthorizedCodeLifespanS",
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(nonceField).toBeVisible();
|
||||||
|
await expect(preAuthField).toBeVisible();
|
||||||
|
|
||||||
|
await nonceField.fill("60");
|
||||||
|
await preAuthField.fill("120");
|
||||||
|
await page.getByTestId("tokens-tab-save").click();
|
||||||
|
await expect(
|
||||||
|
page.getByText("Realm successfully updated").first(),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
const realmData = await adminClient.getRealm(realm);
|
||||||
|
expect(realmData).toBeDefined();
|
||||||
|
// TimeSelector converts values based on selected unit (60 minutes = 3600 seconds, 120 seconds = 120 seconds)
|
||||||
|
expect(realmData?.attributes?.["vc.c-nonce-lifetime-seconds"]).toBe("3600");
|
||||||
|
expect(realmData?.attributes?.["preAuthorizedCodeLifespanS"]).toBe("120");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should persist values after page refresh", async ({ page }) => {
|
||||||
|
const realm = await createTestBed();
|
||||||
|
await login(page, { to: toRealmSettings({ realm }) });
|
||||||
|
|
||||||
|
const tokensTab = page.getByTestId("rs-tokens-tab");
|
||||||
|
await tokensTab.click();
|
||||||
|
|
||||||
|
const oid4vciJumpLink = page.getByTestId("jump-link-oid4vci-attributes");
|
||||||
|
await oid4vciJumpLink.click();
|
||||||
|
|
||||||
|
const nonceField = page.getByTestId(
|
||||||
|
"attributes.vc🍺c-nonce-lifetime-seconds",
|
||||||
|
);
|
||||||
|
const preAuthField = page.getByTestId(
|
||||||
|
"attributes.preAuthorizedCodeLifespanS",
|
||||||
|
);
|
||||||
|
|
||||||
|
await nonceField.fill("60");
|
||||||
|
await preAuthField.fill("120");
|
||||||
|
await page.getByTestId("tokens-tab-save").click();
|
||||||
|
await expect(
|
||||||
|
page.getByText("Realm successfully updated").first(),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Refresh the page
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
|
// Navigate back to realm settings using the same pattern as login
|
||||||
|
const url = new URL(generatePath(ROOT_PATH, { realm }), SERVER_URL);
|
||||||
|
url.hash = toRealmSettings({ realm }).pathname!;
|
||||||
|
await page.goto(url.toString());
|
||||||
|
|
||||||
|
// The TimeSelector component converts values based on units, so we need to check the actual saved values
|
||||||
|
const realmData = await adminClient.getRealm(realm);
|
||||||
|
expect(realmData?.attributes?.["vc.c-nonce-lifetime-seconds"]).toBeDefined();
|
||||||
|
expect(realmData?.attributes?.["preAuthorizedCodeLifespanS"]).toBeDefined();
|
||||||
|
|
||||||
|
// The values should be numbers representing seconds
|
||||||
|
const nonceValue = parseInt(
|
||||||
|
realmData?.attributes?.["vc.c-nonce-lifetime-seconds"] || "0",
|
||||||
|
);
|
||||||
|
const preAuthValue = parseInt(
|
||||||
|
realmData?.attributes?.["preAuthorizedCodeLifespanS"] || "0",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(nonceValue).toBeGreaterThan(0);
|
||||||
|
expect(preAuthValue).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should validate form fields and save valid values", async ({ page }) => {
|
||||||
|
const realm = await createTestBed();
|
||||||
|
await login(page, { to: toRealmSettings({ realm }) });
|
||||||
|
|
||||||
|
const tokensTab = page.getByTestId("rs-tokens-tab");
|
||||||
|
await tokensTab.click();
|
||||||
|
|
||||||
|
const oid4vciJumpLink = page.getByTestId("jump-link-oid4vci-attributes");
|
||||||
|
await oid4vciJumpLink.click();
|
||||||
|
|
||||||
|
const nonceField = page.getByTestId(
|
||||||
|
"attributes.vc🍺c-nonce-lifetime-seconds",
|
||||||
|
);
|
||||||
|
const preAuthField = page.getByTestId(
|
||||||
|
"attributes.preAuthorizedCodeLifespanS",
|
||||||
|
);
|
||||||
|
const saveButton = page.getByTestId("tokens-tab-save");
|
||||||
|
|
||||||
|
// Test that fields are visible and can be filled
|
||||||
|
await expect(nonceField).toBeVisible();
|
||||||
|
await expect(preAuthField).toBeVisible();
|
||||||
|
await expect(saveButton).toBeVisible();
|
||||||
|
|
||||||
|
// Test with valid values - this should work
|
||||||
|
await nonceField.clear();
|
||||||
|
await preAuthField.clear();
|
||||||
|
|
||||||
|
// Fill with smaller, more reasonable values for testing
|
||||||
|
await nonceField.fill("60");
|
||||||
|
await preAuthField.fill("120");
|
||||||
|
|
||||||
|
// Save button should be enabled when form has values
|
||||||
|
await expect(saveButton).toBeEnabled();
|
||||||
|
|
||||||
|
await saveButton.click();
|
||||||
|
await expect(
|
||||||
|
page.getByText("Realm successfully updated").first(),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Verify the values were saved correctly
|
||||||
|
const realmData = await adminClient.getRealm(realm);
|
||||||
|
expect(realmData?.attributes?.["vc.c-nonce-lifetime-seconds"]).toBeDefined();
|
||||||
|
expect(realmData?.attributes?.["preAuthorizedCodeLifespanS"]).toBeDefined();
|
||||||
|
|
||||||
|
// The values should be numbers representing seconds
|
||||||
|
const nonceValue = parseInt(
|
||||||
|
realmData?.attributes?.["vc.c-nonce-lifetime-seconds"] || "0",
|
||||||
|
);
|
||||||
|
const preAuthValue = parseInt(
|
||||||
|
realmData?.attributes?.["preAuthorizedCodeLifespanS"] || "0",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(nonceValue).toBeGreaterThan(0);
|
||||||
|
expect(preAuthValue).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should show validation error for values below minimum threshold", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const realm = await createTestBed();
|
||||||
|
await login(page, { to: toRealmSettings({ realm }) });
|
||||||
|
|
||||||
|
const tokensTab = page.getByTestId("rs-tokens-tab");
|
||||||
|
await tokensTab.click();
|
||||||
|
|
||||||
|
const oid4vciJumpLink = page.getByTestId("jump-link-oid4vci-attributes");
|
||||||
|
await oid4vciJumpLink.click();
|
||||||
|
|
||||||
|
const nonceField = page.getByTestId(
|
||||||
|
"attributes.vc🍺c-nonce-lifetime-seconds",
|
||||||
|
);
|
||||||
|
const preAuthField = page.getByTestId(
|
||||||
|
"attributes.preAuthorizedCodeLifespanS",
|
||||||
|
);
|
||||||
|
const saveButton = page.getByTestId("tokens-tab-save");
|
||||||
|
|
||||||
|
// Fill with values below the minimum threshold (29 seconds)
|
||||||
|
await nonceField.fill("29");
|
||||||
|
await preAuthField.fill("29");
|
||||||
|
|
||||||
|
await saveButton.click();
|
||||||
|
|
||||||
|
// Check for validation error message
|
||||||
|
const validationErrorText =
|
||||||
|
"Please ensure the OID4VCI attribute fields are filled with values 30 seconds or greater.";
|
||||||
|
await expect(page.getByText(validationErrorText).first()).toBeVisible();
|
||||||
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Page, expect } from "@playwright/test";
|
import { type Page, expect } from "@playwright/test";
|
||||||
import { changeTimeUnit, switchOn } from "../utils/form.ts";
|
import { changeTimeUnit, switchOn } from "../utils/form.ts";
|
||||||
|
|
||||||
export async function goToTokensTab(page: Page) {
|
export async function goToTokensTab(page: Page) {
|
||||||
|
|||||||
@@ -539,6 +539,11 @@ class AdminClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getServerInfo() {
|
||||||
|
await this.#login();
|
||||||
|
return await this.#client.serverInfo.find();
|
||||||
|
}
|
||||||
|
|
||||||
async copyFlow(
|
async copyFlow(
|
||||||
name: string,
|
name: string,
|
||||||
newName: string,
|
newName: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user