forked from baron/baron-sso
RP scope 설정에 offline_access 안내 추가
This commit is contained in:
@@ -450,6 +450,46 @@ describe("ClientGeneralPage RP claims", () => {
|
||||
expect(scopeInputs.some((input) => input.value === "old_claim")).toBe(true);
|
||||
});
|
||||
|
||||
it("shows the offline_access guide in the scopes section and expands its details", async () => {
|
||||
const { container } = await renderPage();
|
||||
|
||||
expect(container.textContent).toContain(
|
||||
"Refresh token 사용 시 offline_access scope가 필요합니다.",
|
||||
);
|
||||
expect(container.textContent).toContain(
|
||||
"scope 목록에 offline_access를 포함하고",
|
||||
);
|
||||
|
||||
const guideToggleButton = Array.from(
|
||||
container.querySelectorAll("button"),
|
||||
).find((button) =>
|
||||
(button.getAttribute("aria-label") ?? "").includes(
|
||||
"offline_access 상세 안내 보기",
|
||||
),
|
||||
);
|
||||
expect(guideToggleButton).toBeDefined();
|
||||
|
||||
await act(async () => {
|
||||
guideToggleButton?.dispatchEvent(
|
||||
new MouseEvent("click", { bubbles: true }),
|
||||
);
|
||||
});
|
||||
await flush();
|
||||
|
||||
expect(container.textContent).toContain(
|
||||
"Hydra 기준으로 refresh token 발급 조건",
|
||||
);
|
||||
expect(container.textContent).toContain(
|
||||
"authorization request scope에 offline 또는 offline_access 포함",
|
||||
);
|
||||
expect(container.textContent).toContain(
|
||||
"consent accept의 granted_scope에 offline 또는 offline_access 포함",
|
||||
);
|
||||
expect(container.textContent).toContain(
|
||||
"client grant_types에 refresh_token 포함",
|
||||
);
|
||||
});
|
||||
|
||||
it("blocks saving a number RP claim default value that is not numeric", async () => {
|
||||
const { container } = await renderPage();
|
||||
|
||||
|
||||
@@ -639,6 +639,8 @@ function ClientGeneralPage() {
|
||||
const [headlessLoginEnabled, setHeadlessLoginEnabled] = useState(false);
|
||||
|
||||
const [isScopePickerOpen, setIsScopePickerOpen] = useState(false);
|
||||
const [isOfflineAccessGuideOpen, setIsOfflineAccessGuideOpen] =
|
||||
useState(false);
|
||||
const [scopes, setScopes] = useState<ScopeItem[]>(() => [
|
||||
{
|
||||
id: "1",
|
||||
@@ -1970,6 +1972,77 @@ function ClientGeneralPage() {
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="rounded-xl border border-amber-500/30 bg-amber-500/10 p-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold text-amber-900 dark:text-amber-100">
|
||||
<Info className="h-4 w-4" />
|
||||
<span>
|
||||
{t(
|
||||
"ui.dev.clients.general.scopes.offline_access_title",
|
||||
"Refresh token 사용 시 offline_access scope가 필요합니다.",
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs leading-relaxed text-amber-950/80 dark:text-amber-50/80">
|
||||
{t(
|
||||
"msg.dev.clients.general.scopes.offline_access_summary",
|
||||
"RP가 refresh token을 사용하려면 scope 목록에 offline_access를 포함하고, consent와 grant type 설정도 함께 맞아야 합니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 shrink-0 gap-1 text-amber-900 hover:bg-amber-500/10 hover:text-amber-950 dark:text-amber-100 dark:hover:bg-amber-500/20 dark:hover:text-amber-50"
|
||||
onClick={() => setIsOfflineAccessGuideOpen((prev) => !prev)}
|
||||
aria-expanded={isOfflineAccessGuideOpen}
|
||||
aria-label={t(
|
||||
"ui.dev.clients.general.scopes.offline_access_toggle",
|
||||
"offline_access 상세 안내 보기",
|
||||
)}
|
||||
>
|
||||
{isOfflineAccessGuideOpen ? (
|
||||
<X className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<Info className="h-3.5 w-3.5" />
|
||||
)}
|
||||
{t("ui.common.info", "상세 안내")}
|
||||
</Button>
|
||||
</div>
|
||||
{isOfflineAccessGuideOpen ? (
|
||||
<div className="mt-3 rounded-lg border border-amber-500/20 bg-background/70 p-3 text-xs leading-relaxed text-foreground shadow-sm">
|
||||
<p className="font-semibold">
|
||||
{t(
|
||||
"msg.dev.clients.general.scopes.offline_access_conditions_title",
|
||||
"Hydra 기준으로 refresh token 발급 조건",
|
||||
)}
|
||||
</p>
|
||||
<ul className="mt-2 list-disc space-y-1 pl-4">
|
||||
<li>
|
||||
{t(
|
||||
"msg.dev.clients.general.scopes.offline_access_condition_request",
|
||||
"authorization request scope에 offline 또는 offline_access 포함",
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
{t(
|
||||
"msg.dev.clients.general.scopes.offline_access_condition_consent",
|
||||
"consent accept의 granted_scope에 offline 또는 offline_access 포함",
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
{t(
|
||||
"msg.dev.clients.general.scopes.offline_access_condition_grant_type",
|
||||
"client grant_types에 refresh_token 포함",
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{isScopePickerOpen && (
|
||||
<div className="space-y-3 rounded-md border border-border bg-muted/10 p-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
|
||||
Reference in New Issue
Block a user