1
0
forked from baron/baron-sso

Merge pull request 'RP scope 설정에 offline_access 안내 추가' (#1173) from feature/df-claim-tenant into dev

Reviewed-on: baron/baron-sso#1173
This commit is contained in:
2026-06-16 10:53:21 +09:00
6 changed files with 152 additions and 12 deletions

View File

@@ -450,6 +450,44 @@ 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("상세 안내 보기"),
);
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();

View File

@@ -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",
"상세 안내 보기",
)}
>
{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">

View File

@@ -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 details"
[ui.dev.clients.general.scopes.table]
description = "Scope Description"

View File

@@ -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 = "상세 안내 보기"
[ui.dev.clients.general.scopes.table]
description = "설명"

View File

@@ -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 = ""

View File

@@ -45,10 +45,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.1"
version: "1.4.0"
cli_config:
dependency: transitive
description:
@@ -268,6 +268,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.5"
js:
dependency: transitive
description:
name: js
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
leak_tracker:
dependency: transitive
description:
@@ -320,18 +328,18 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.19"
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.13.0"
version: "0.11.1"
meta:
dependency: transitive
description:
@@ -653,26 +661,26 @@ packages:
dependency: transitive
description:
name: test
sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7"
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
url: "https://pub.dev"
source: hosted
version: "1.30.0"
version: "1.26.3"
test_api:
dependency: transitive
description:
name: test_api
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev"
source: hosted
version: "0.7.10"
version: "0.7.7"
test_core:
dependency: transitive
description:
name: test_core
sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51"
sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0"
url: "https://pub.dev"
source: hosted
version: "0.6.16"
version: "0.6.12"
toml:
dependency: "direct main"
description: