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">
|
||||
|
||||
@@ -452,6 +452,11 @@ session_required_off = "Off: process logout using sub even if sid is missing."
|
||||
empty = "No scopes registered."
|
||||
subtitle = "Define the permission scopes this application can request."
|
||||
tenant = "Tenant access claim"
|
||||
offline_access_summary = "If the RP needs refresh tokens, include offline_access in the scope list and align the consent and grant type settings as well."
|
||||
offline_access_conditions_title = "Hydra conditions for issuing refresh tokens"
|
||||
offline_access_condition_request = "Include offline or offline_access in the authorization request scope."
|
||||
offline_access_condition_consent = "Include offline or offline_access in the consent accept granted_scope."
|
||||
offline_access_condition_grant_type = "Include refresh_token in the client grant_types."
|
||||
|
||||
[msg.dev.clients.general.id_token_claims]
|
||||
subtitle = "Manage RP-specific extension claims separately."
|
||||
@@ -1590,6 +1595,8 @@ add = "Scope Add"
|
||||
description_placeholder = "Description Placeholder"
|
||||
name_placeholder = "e.g. profile"
|
||||
title = "Scopes"
|
||||
offline_access_title = "offline_access scope is required when using refresh tokens."
|
||||
offline_access_toggle = "Show offline_access help"
|
||||
|
||||
[ui.dev.clients.general.scopes.table]
|
||||
description = "Scope Description"
|
||||
|
||||
@@ -452,6 +452,11 @@ session_required_off = "끄면: sid가 없어도 sub만으로 로그아웃 처
|
||||
empty = "등록된 스코프가 없습니다."
|
||||
subtitle = "이 앱이 요청할 수 있는 권한 범위를 정의합니다."
|
||||
tenant = "소속 테넌트 정보 접근"
|
||||
offline_access_summary = "RP가 refresh token을 사용하려면 scope 목록에 offline_access를 포함하고, consent와 grant type 설정도 함께 맞아야 합니다."
|
||||
offline_access_conditions_title = "Hydra 기준으로 refresh token 발급 조건"
|
||||
offline_access_condition_request = "authorization request scope에 offline 또는 offline_access 포함"
|
||||
offline_access_condition_consent = "consent accept의 granted_scope에 offline 또는 offline_access 포함"
|
||||
offline_access_condition_grant_type = "client grant_types에 refresh_token 포함"
|
||||
|
||||
[msg.dev.clients.general.id_token_claims]
|
||||
subtitle = "RP 전용 확장 claim을 구분해서 관리합니다."
|
||||
@@ -1589,6 +1594,8 @@ add = "스코프 추가"
|
||||
description_placeholder = "권한에 대한 설명"
|
||||
name_placeholder = "e.g. profile"
|
||||
title = "스코프"
|
||||
offline_access_title = "Refresh token 사용 시 offline_access scope가 필요합니다."
|
||||
offline_access_toggle = "offline_access 상세 안내 보기"
|
||||
|
||||
[ui.dev.clients.general.scopes.table]
|
||||
description = "설명"
|
||||
|
||||
@@ -499,6 +499,11 @@ session_required_off = ""
|
||||
empty = ""
|
||||
subtitle = ""
|
||||
tenant = ""
|
||||
offline_access_summary = ""
|
||||
offline_access_conditions_title = ""
|
||||
offline_access_condition_request = ""
|
||||
offline_access_condition_consent = ""
|
||||
offline_access_condition_grant_type = ""
|
||||
|
||||
[msg.dev.clients.general.security]
|
||||
private_help = ""
|
||||
@@ -1638,6 +1643,8 @@ add = ""
|
||||
description_placeholder = ""
|
||||
name_placeholder = ""
|
||||
title = ""
|
||||
offline_access_title = ""
|
||||
offline_access_toggle = ""
|
||||
|
||||
[ui.dev.clients.general.scopes.table]
|
||||
description = ""
|
||||
|
||||
Reference in New Issue
Block a user