From 1bb1120015cb0da1bc597e29237fdb87a1e37cb6 Mon Sep 17 00:00:00 2001 From: kyy Date: Fri, 27 Feb 2026 11:21:29 +0900 Subject: [PATCH 1/8] =?UTF-8?q?=EB=8B=A4=EA=B5=AD=EC=96=B4(i18n)=20?= =?UTF-8?q?=EB=B2=88=EC=97=AD=20=EB=A6=AC=EC=86=8C=EC=8A=A4=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devfront/src/locales/en.toml | 42 ++++++++++++++++++------------ devfront/src/locales/ko.toml | 22 +++++++++++----- devfront/src/locales/template.toml | 8 ++++++ locales/en.toml | 23 ++++++++++------ locales/ko.toml | 21 ++++++++++----- locales/template.toml | 7 +++++ 6 files changed, 84 insertions(+), 39 deletions(-) diff --git a/devfront/src/locales/en.toml b/devfront/src/locales/en.toml index 45da9846..dcceb9bf 100644 --- a/devfront/src/locales/en.toml +++ b/devfront/src/locales/en.toml @@ -227,25 +227,25 @@ subtitle = "Subtitle" [msg.dev.clients.details] copy_client_id = "Client ID copied." -copy_client_secret = "Copy Client Secret" +copy_client_secret = "Client Secret copied." copy_endpoint = "{{label}} copied." load_error = "Error loading client: {{error}}" loading = "Loading client..." missing_id = "Client ID is required." redirect_saved = "Redirect URIs saved." -rotate_confirm = "Rotate Confirm" -rotate_error = "Rotate Error" -save_error = "Save Error" -secret_rotated = "Secret Rotated" +rotate_confirm = "Warning: Rotating the Client Secret will invalidate the existing secret immediately.\nConnected applications may experience downtime. Do you want to proceed?" +rotate_error = "Failed to rotate secret: {{error}}" +save_error = "Failed to save: {{error}}" +secret_rotated = "Client Secret has been rotated." secret_unavailable = "SECRET_NOT_AVAILABLE" -subtitle = "Subtitle" +subtitle = "Manage OIDC credentials and endpoints." [msg.dev.clients.details.redirect] -description = "Description" +description = "A list of allowed URLs to redirect users to after successful authentication. You can enter multiple URLs separated by commas." [msg.dev.clients.details.security] -footer = "Footer" -note = "Note" +footer = "We recommend verifying admin session TTL, applying rate limits, and setting up notifications for secret rotation." +note = "Keep endpoints read-only and ensure that secret rotation and copying are tracked in audit logs." [msg.dev.clients.general] load_error = "Error loading client: {{error}}" @@ -301,8 +301,8 @@ dev_scope = "Dev Scope" hydra_health = "Hydra Health" [msg.dev.sidebar] -notice = "Notice" -notice_detail = "Notice Detail" +notice = "Developer Console" +notice_detail = "Register and manage client applications." [msg.info] saved_success = "Saved successfully." @@ -978,6 +978,13 @@ active_grants = "Active Grants" avg_scopes = "Avg. Scopes per User" total_scopes = "Total Scopes Issued" +[ui.dev.clients.stats] +total = "Total Applications" +active_sessions = "Active Sessions" +auth_failures = "Auth Failures (24h)" +realtime = "Realtime" +stable = "Stable" + [ui.dev.clients.consents.table] action = "Action" first_granted = "First Granted" @@ -990,24 +997,24 @@ user = "User" [ui.dev.clients.details] [ui.dev.clients.details.breadcrumb] -current = "Current" -section = "Applications" +current = "App Details" +section = "Connected Applications" [ui.dev.clients.details.credentials] client_id = "Client ID" client_secret = "Client Secret" -title = "Title" +title = "Client Credentials" [ui.dev.clients.details.endpoints] read_only = "Read Only" -title = "Title" +title = "OIDC Endpoints" [ui.dev.clients.details.redirect] callback_label = "Callback Label" label = "Redirect URIs" placeholder = "https://your-app.com/callback, http://localhost:3000/auth/callback" save = "Save" -title = "Title" +title = "Redirection Settings" [ui.dev.clients.details.secret] hide = "Hide" @@ -1015,7 +1022,7 @@ rotate = "Rotate" show = "Show" [ui.dev.clients.details.security] -title = "Title" +title = "Security Note" [ui.dev.clients.details.tab] connection = "Federation" @@ -1144,6 +1151,7 @@ unknown = "Unknown" expired = "Session expired" expiring = "Expiring soon: {{minutes}}m {{seconds}}s left" remaining = "Expires in: {{minutes}}m {{seconds}}s" +refresh = "Refresh session expiry" [ui.userfront] app_title = "Baron SW Portal" diff --git a/devfront/src/locales/ko.toml b/devfront/src/locales/ko.toml index d832d1e4..69bc9dae 100644 --- a/devfront/src/locales/ko.toml +++ b/devfront/src/locales/ko.toml @@ -926,10 +926,10 @@ admin = "Admin" user = "User" [ui.common.status] -active = "Active" -blocked = "Blocked" +active = "활성" +blocked = "차단됨" failure = "실패" -inactive = "Inactive" +inactive = "비활성" ok = "정상" pending = "준비 중" success = "성공" @@ -979,6 +979,13 @@ active_grants = "활성 권한" avg_scopes = "사용자당 평균 권한 수" total_scopes = "전체 부여된 권한 수" +[ui.dev.clients.stats] +total = "총 애플리케이션" +active_sessions = "활성 세션" +auth_failures = "인증 실패 (24h)" +realtime = "실시간" +stable = "안정" + [ui.dev.clients.consents.table] action = "작업" first_granted = "최초 동의" @@ -1020,14 +1027,14 @@ title = "보안 메모" [ui.dev.clients.details.tab] connection = "연동 설정" -consents = "Consent & Users" -settings = "Settings" +consents = "동의 및 사용자" +settings = "설정" [ui.dev.clients.general] create = "앱 생성" display_new = "연동 앱 추가" -title_create = "Create Client" -title_edit = "Client Settings" +title_create = "연동 앱 생성" +title_edit = "연동 앱 설정" [ui.dev.clients.federation] title = "Identity Federation" @@ -1145,6 +1152,7 @@ unknown = "확인 불가" expired = "세션 만료" expiring = "만료 임박: {{minutes}}분 {{seconds}}초 남음" remaining = "만료 예정: {{minutes}}분 {{seconds}}초 남음" +refresh = "세션 만료 시간 갱신" [ui.userfront] app_title = "Baron SW 포탈" diff --git a/devfront/src/locales/template.toml b/devfront/src/locales/template.toml index b1cb4d35..ed9939a1 100644 --- a/devfront/src/locales/template.toml +++ b/devfront/src/locales/template.toml @@ -990,6 +990,13 @@ active_grants = "" avg_scopes = "" total_scopes = "" +[ui.dev.clients.stats] +total = "" +active_sessions = "" +auth_failures = "" +realtime = "" +stable = "" + [ui.dev.clients.consents.table] action = "" first_granted = "" @@ -1156,6 +1163,7 @@ unknown = "" expired = "" expiring = "" remaining = "" +refresh = "" [ui.userfront] app_title = "" diff --git a/locales/en.toml b/locales/en.toml index 4ac4562c..ec495d5f 100644 --- a/locales/en.toml +++ b/locales/en.toml @@ -359,8 +359,8 @@ dev_scope = "Dev Scope" hydra_health = "Hydra Health" [msg.dev.sidebar] -notice = "Notice" -notice_detail = "Notice Detail" +notice = "Developer Console" +notice_detail = "Register and manage client applications." [msg.info] saved_success = "Saved successfully." @@ -1137,6 +1137,13 @@ active_grants = "Active Grants" avg_scopes = "Avg. Scopes per User" total_scopes = "Total Scopes Issued" +[ui.dev.clients.stats] +total = "Total Applications" +active_sessions = "Active Sessions" +auth_failures = "Auth Failures (24h)" +realtime = "Realtime" +stable = "Stable" + [ui.dev.clients.consents.table] action = "Action" first_granted = "First Granted" @@ -1149,24 +1156,24 @@ user = "User" [ui.dev.clients.details] [ui.dev.clients.details.breadcrumb] -current = "Current" -section = "Applications" +current = "App Details" +section = "Connected Applications" [ui.dev.clients.details.credentials] client_id = "Client ID" client_secret = "Client Secret" -title = "Title" +title = "Client Credentials" [ui.dev.clients.details.endpoints] read_only = "Read Only" -title = "Title" +title = "OIDC Endpoints" [ui.dev.clients.details.redirect] callback_label = "Callback Label" label = "Redirect URIs" placeholder = "https://your-app.com/callback, http://localhost:3000/auth/callback" save = "Save" -title = "Title" +title = "Redirection Settings" [ui.dev.clients.details.secret] hide = "Hide" @@ -1174,7 +1181,7 @@ rotate = "Rotate" show = "Show" [ui.dev.clients.details.security] -title = "Title" +title = "Security Note" [ui.dev.clients.details.tab] connection = "Federation" diff --git a/locales/ko.toml b/locales/ko.toml index d69953e6..59767ceb 100644 --- a/locales/ko.toml +++ b/locales/ko.toml @@ -1071,10 +1071,10 @@ admin = "Admin" user = "User" [ui.common.status] -active = "Active" -blocked = "Blocked" +active = "활성" +blocked = "차단됨" failure = "실패" -inactive = "Inactive" +inactive = "비활성" ok = "정상" pending = "준비 중" success = "성공" @@ -1137,6 +1137,13 @@ active_grants = "Active Grants" avg_scopes = "Avg. Scopes per User" total_scopes = "Total Scopes Issued" +[ui.dev.clients.stats] +total = "총 애플리케이션" +active_sessions = "활성 세션" +auth_failures = "인증 실패 (24h)" +realtime = "실시간" +stable = "안정" + [ui.dev.clients.consents.table] action = "Action" first_granted = "First Granted" @@ -1178,14 +1185,14 @@ title = "보안 메모" [ui.dev.clients.details.tab] connection = "연동 설정" -consents = "Consent & Users" -settings = "Settings" +consents = "동의 및 사용자" +settings = "설정" [ui.dev.clients.general] create = "앱 생성" display_new = "연동 앱 추가" -title_create = "Create Client" -title_edit = "Client Settings" +title_create = "연동 앱 생성" +title_edit = "연동 앱 설정" [ui.dev.clients.federation] title = "Identity Federation" diff --git a/locales/template.toml b/locales/template.toml index ecd149df..9a8fb8ce 100644 --- a/locales/template.toml +++ b/locales/template.toml @@ -999,6 +999,13 @@ active_grants = "" avg_scopes = "" total_scopes = "" +[ui.dev.clients.stats] +total = "" +active_sessions = "" +auth_failures = "" +realtime = "" +stable = "" + [ui.dev.clients.consents.table] action = "" first_granted = "" From cba839ad1ef1c2f9c79060409e5e938848a57924 Mon Sep 17 00:00:00 2001 From: kyy Date: Fri, 27 Feb 2026 11:22:14 +0900 Subject: [PATCH 2/8] =?UTF-8?q?App=20Layout=20=ED=85=8D=EC=8A=A4=ED=8A=B8(?= =?UTF-8?q?UI)=20=EC=9D=BC=EC=B9=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devfront/src/components/layout/AppLayout.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/devfront/src/components/layout/AppLayout.tsx b/devfront/src/components/layout/AppLayout.tsx index a5163929..8fa80e47 100644 --- a/devfront/src/components/layout/AppLayout.tsx +++ b/devfront/src/components/layout/AppLayout.tsx @@ -211,11 +211,11 @@ function AppLayout() {
-

{t("msg.dev.sidebar.notice", "개발자 전용 콘솔입니다.")}

+

{t("msg.dev.sidebar.notice", "Developer Console")}

{t( "msg.dev.sidebar.notice_detail", - "클라이언트 애플리케이션 등록 및 관리를 수행할 수 있습니다.", + "Register and manage client applications.", )}

@@ -301,9 +301,18 @@ function AppLayout() { {isRefreshingSession ? t( "ui.dev.session.refreshing", - "세션 만료 시간 갱신 중...", + "Refreshing...", ) - : t("ui.dev.session.refresh", "세션 만료 시간 갱신")} + : t("ui.dev.session.refresh", "Refresh session expiry")} + + ) : null} From f6647230f710af2ab2c0519dc3f1fb6da17968e9 Mon Sep 17 00:00:00 2001 From: kyy Date: Fri, 27 Feb 2026 11:22:33 +0900 Subject: [PATCH 3/8] =?UTF-8?q?User=20Consent=20=ED=95=84=ED=84=B0(?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5)=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/clients/ClientConsentsPage.tsx | 150 ++++++++++++------ 1 file changed, 104 insertions(+), 46 deletions(-) diff --git a/devfront/src/features/clients/ClientConsentsPage.tsx b/devfront/src/features/clients/ClientConsentsPage.tsx index 1f44f2f6..ee9a31ab 100644 --- a/devfront/src/features/clients/ClientConsentsPage.tsx +++ b/devfront/src/features/clients/ClientConsentsPage.tsx @@ -34,8 +34,8 @@ function ClientConsentsPage() { const clientId = params.id ?? ""; const [subjectInput, setSubjectInput] = useState(""); const [subject, setSubject] = useState(""); - const [statusFilter, setStatusFilter] = useState("all"); - const [scopeFilter, setScopeFilter] = useState("all"); + const [statusFilter, setStatusFilter] = useState([]); + const [scopeFilter, setScopeFilter] = useState([]); const [isAdvancedFilterOpen, setIsAdvancedFilterOpen] = useState(false); const { data: clientData } = useQuery({ @@ -49,8 +49,8 @@ function ClientConsentsPage() { error, refetch, } = useQuery({ - queryKey: ["consents", clientId, subject, statusFilter], - queryFn: () => fetchConsents(subject, clientId, statusFilter), + queryKey: ["consents", clientId, subject], + queryFn: () => fetchConsents(subject, clientId, "all"), enabled: clientId.length > 0, }); const revokeMutation = useMutation({ @@ -77,7 +77,12 @@ function ClientConsentsPage() { const rows = consentsData?.items ?? []; const allScopes = Array.from(new Set(rows.flatMap((r) => r.grantedScopes))); const filteredRows = rows.filter((row) => { - return scopeFilter === "all" || row.grantedScopes.includes(scopeFilter); + const matchStatus = + statusFilter.length === 0 || statusFilter.includes(row.status); + const matchScope = + scopeFilter.length === 0 || + scopeFilter.some((s) => row.grantedScopes.includes(s)); + return matchStatus && matchScope; }); const handleExportCSV = () => { @@ -130,6 +135,30 @@ function ClientConsentsPage() { document.body.removeChild(link); }; + const handleStatusFilterChange = (status: string, checked: boolean) => { + if (checked) { + setStatusFilter((prev) => [...prev, status]); + } else { + setStatusFilter((prev) => prev.filter((s) => s !== status)); + } + }; + + const handleScopeFilterChange = (scope: string, checked: boolean) => { + if (checked) { + setScopeFilter((prev) => [...prev, scope]); + } else { + setScopeFilter((prev) => prev.filter((s) => s !== scope)); + } + }; + + const handleAllScopesChange = (checked: boolean) => { + if (checked) { + setScopeFilter(allScopes); + } else { + setScopeFilter([]); + } + }; + return (
@@ -175,7 +204,7 @@ function ClientConsentsPage() {
{clientData?.client?.status === "active" @@ -252,59 +281,88 @@ function ClientConsentsPage() {
{isAdvancedFilterOpen && ( -
-
- +
+
+ {t("ui.dev.clients.consents.status_label", "Status:")} - + handleStatusFilterChange("active", e.target.checked) + } + /> {t("ui.common.status.active", "Active")} - - - + +
-
- +
+ {t("ui.dev.clients.consents.scope_label", "Scope:")} - 0 + } + onChange={(e) => handleAllScopesChange(e.target.checked)} + /> + ALL + + )} {allScopes.map((scope) => ( - + ))} - +
- +
+ +
)} From c584c28f1af1913c9a931457313565ba55beafb3 Mon Sep 17 00:00:00 2001 From: kyy Date: Fri, 27 Feb 2026 11:23:06 +0900 Subject: [PATCH 4/8] =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=B1=83=EC=A7=80=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC(UI)=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devfront/src/components/ui/badge.tsx | 2 ++ devfront/src/features/clients/ClientDetailsPage.tsx | 10 +++++----- devfront/src/features/clients/ClientGeneralPage.tsx | 6 +++--- devfront/src/features/clients/ClientsPage.tsx | 8 ++++---- .../features/clients/routes/ClientFederationPage.tsx | 2 +- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/devfront/src/components/ui/badge.tsx b/devfront/src/components/ui/badge.tsx index 8ef586fd..ab13e2ce 100644 --- a/devfront/src/components/ui/badge.tsx +++ b/devfront/src/components/ui/badge.tsx @@ -17,6 +17,8 @@ const badgeVariants = cva( "border-transparent bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300", warning: "border-transparent bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-200", + info: + "border-transparent bg-blue-500 text-white hover:bg-blue-500/90", }, }, defaultVariants: { diff --git a/devfront/src/features/clients/ClientDetailsPage.tsx b/devfront/src/features/clients/ClientDetailsPage.tsx index a372fef0..e76fc71e 100644 --- a/devfront/src/features/clients/ClientDetailsPage.tsx +++ b/devfront/src/features/clients/ClientDetailsPage.tsx @@ -185,11 +185,11 @@ function ClientDetailsPage() {
- {t("ui.dev.clients.details.breadcrumb.section", "Apps")} + {t("ui.dev.clients.details.breadcrumb.section", "Connected Applications")} / - {t("ui.dev.clients.details.breadcrumb.current", "클라이언트 상세")} + {t("ui.dev.clients.details.breadcrumb.current", "App Details")}
@@ -200,12 +200,12 @@ function ClientDetailsPage() {

{t( "msg.dev.clients.details.subtitle", - "OIDC 자격 증명과 엔드포인트를 관리합니다.", + "Manage OIDC credentials and endpoints.", )}

{data.client.status === "active" @@ -241,7 +241,7 @@ function ClientDetailsPage() {

{t( "ui.dev.clients.details.credentials.title", - "클라이언트 자격 증명", + "Client Credentials", )}

diff --git a/devfront/src/features/clients/ClientGeneralPage.tsx b/devfront/src/features/clients/ClientGeneralPage.tsx index 349bba58..b648ced7 100644 --- a/devfront/src/features/clients/ClientGeneralPage.tsx +++ b/devfront/src/features/clients/ClientGeneralPage.tsx @@ -263,7 +263,7 @@ function ClientGeneralPage() {
- {t("ui.dev.clients.general.breadcrumb.section", "Applications")} + {t("ui.dev.clients.general.breadcrumb.section", "Connected Applications")} / {displayName} @@ -276,7 +276,7 @@ function ClientGeneralPage() {
{!isCreate && ( {status === "active" @@ -292,7 +292,7 @@ function ClientGeneralPage() { to={`/clients/${clientId}`} className="whitespace-nowrap border-b-2 border-transparent text-muted-foreground hover:text-foreground" > - {t("ui.dev.clients.details.tab.connection", "Connection")} + {t("ui.dev.clients.details.tab.connection", "Federation")} {client.status === "active" diff --git a/devfront/src/features/clients/routes/ClientFederationPage.tsx b/devfront/src/features/clients/routes/ClientFederationPage.tsx index c61c20be..6700e7fe 100644 --- a/devfront/src/features/clients/routes/ClientFederationPage.tsx +++ b/devfront/src/features/clients/routes/ClientFederationPage.tsx @@ -267,7 +267,7 @@ export function ClientFederationPage() { From 6482e8d3e0c4b5322a9b55b48c652e7587c61978 Mon Sep 17 00:00:00 2001 From: kyy Date: Fri, 27 Feb 2026 12:38:04 +0900 Subject: [PATCH 5/8] =?UTF-8?q?=EC=95=A0=ED=94=8C=EB=A6=AC=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EB=B8=8C=EB=A0=88=EB=93=9C=ED=81=AC?= =?UTF-8?q?=EB=9F=BC=20=EA=B2=BD=EB=A1=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/clients/ClientDetailsPage.tsx | 47 ++++++++++++------- .../features/clients/ClientGeneralPage.tsx | 39 +++++++++++---- 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/devfront/src/features/clients/ClientDetailsPage.tsx b/devfront/src/features/clients/ClientDetailsPage.tsx index e76fc71e..060048c8 100644 --- a/devfront/src/features/clients/ClientDetailsPage.tsx +++ b/devfront/src/features/clients/ClientDetailsPage.tsx @@ -1,6 +1,6 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import type { AxiosError } from "axios"; -import { Eye, EyeOff, Link2, RefreshCw, Save, Shield } from "lucide-react"; +import { ArrowLeft, Eye, EyeOff, Link2, RefreshCw, Save, Shield } from "lucide-react"; import { useEffect, useState } from "react"; import { Link, useParams } from "react-router-dom"; import { Badge } from "../../components/ui/badge"; @@ -183,26 +183,39 @@ function ClientDetailsPage() { return (
-
- - {t("ui.dev.clients.details.breadcrumb.section", "Connected Applications")} +
+
-
-

- {data.client.name || data.client.id} -

-

- {t( - "msg.dev.clients.details.subtitle", - "Manage OIDC credentials and endpoints.", - )} -

+
+ +
+

+ {data.client.name || data.client.id} +

+

+ {t( + "msg.dev.clients.details.subtitle", + "Manage OIDC credentials and endpoints.", + )} +

+
-
- - {t("ui.dev.clients.general.breadcrumb.section", "Connected Applications")} + +
+ +

+ {isCreate + ? t("ui.dev.clients.general.title_create", "Create Client") + : t("ui.dev.clients.general.title_edit", "Client Settings")} +

-

- {isCreate - ? t("ui.dev.clients.general.title_create", "Create Client") - : t("ui.dev.clients.general.title_edit", "Client Settings")} -

{!isCreate && ( Date: Fri, 27 Feb 2026 14:25:01 +0900 Subject: [PATCH 6/8] =?UTF-8?q?=EB=B9=84=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20OIDC=20=EC=97=B0=EB=8F=99=20=EC=8B=9C=20?= =?UTF-8?q?=EB=B0=9C=EC=83=9D=ED=95=98=EB=8A=94=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EB=A3=A8=ED=94=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/oidc-login-challenge.spec.ts | 81 +++++++++++++++++++ .../auth/presentation/login_screen.dart | 22 +++-- 2 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 userfront-e2e/tests/oidc-login-challenge.spec.ts diff --git a/userfront-e2e/tests/oidc-login-challenge.spec.ts b/userfront-e2e/tests/oidc-login-challenge.spec.ts new file mode 100644 index 00000000..920a69b4 --- /dev/null +++ b/userfront-e2e/tests/oidc-login-challenge.spec.ts @@ -0,0 +1,81 @@ +import { expect, test, type Page, type Route } from '@playwright/test'; + +async function mockUserfrontApisForRepro( + page: Page, + options: { sessionStatus: number } = { sessionStatus: 401 }, +): Promise { + await page.route('**/api/v1/**', async (route: Route) => { + const requestUrl = new URL(route.request().url()); + const path = requestUrl.pathname; + + if (path.endsWith('/api/v1/user/me')) { + await route.fulfill({ + status: options.sessionStatus, + contentType: 'application/json', + body: JSON.stringify({ error: 'unauthorized' }), + }); + return; + } + + if (path.endsWith('/api/v1/client-log')) { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ ok: true }), + }); + return; + } + + // Default mock for other APIs + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({}), + }); + }); +} + +test.describe('Issue #345 Reproduction (Log-based Validation)', () => { + test('비로그인 상태에서 login_challenge와 함께 signin 진입 시 루프 없이 로그가 정상 출력되어야 한다', async ({ page }) => { + const logs: string[] = []; + page.on('console', msg => { + const text = msg.text(); + logs.push(text); + console.log(`[Browser] ${text}`); + }); + + const requests: string[] = []; + page.on('request', request => { + if (request.isNavigationRequest()) { + requests.push(request.url()); + } + }); + + await mockUserfrontApisForRepro(page, { sessionStatus: 401 }); + + const targetUrl = '/ko/signin?login_challenge=repro_challenge_12345'; + await page.goto(targetUrl); + + // WASM 앱 로딩 및 로직 실행 대기 + await page.waitForTimeout(7000); + + const currentUrl = page.url(); + const signinNavigations = requests.filter(url => url.includes('/signin')); + + // [검증 1] URL 유지 확인 + expect(currentUrl).toContain('login_challenge=repro_challenge_12345'); + + // [검증 2] 리다이렉트 루프 발생 여부 확인 (최초 진입 1회만 있어야 함) + expect(signinNavigations.length).toBeLessThanOrEqual(1); + + // [검증 3] 핵심 로직 로그 확인 (성공의 결정적 증거) + // 이전에는 여기서 Exception이 발생했으나, 이제는 아래 로그가 찍혀야 함 + const hasSuccessLog = logs.some(log => + log.includes('[Auth] OIDC auto-accept: No active session (status: 401)') + ); + + expect(hasSuccessLog).toBe(true); + + console.log('✅ 루프가 해결되었으며, 로그 검증을 통해 정상 동작을 확인했습니다.'); + }); +}); diff --git a/userfront/lib/features/auth/presentation/login_screen.dart b/userfront/lib/features/auth/presentation/login_screen.dart index 43f51370..5f23e16f 100644 --- a/userfront/lib/features/auth/presentation/login_screen.dart +++ b/userfront/lib/features/auth/presentation/login_screen.dart @@ -161,7 +161,12 @@ class _LoginScreenState extends ConsumerState final provider = pendingProvider ?? AuthTokenStore.getProvider() ?? 'ory'; try { - await AuthProxyService.checkCookieSession(); + final status = await AuthProxyService.getSessionStatus(useCookie: true); + if (status != 200) { + debugPrint("[Auth] Cookie session check: No active session (status: $status)"); + return; + } + if (!shouldPromoteCookieSession( currentToken: AuthTokenStore.getToken(), loginChallenge: loginChallenge, @@ -242,11 +247,16 @@ class _LoginScreenState extends ConsumerState } try { - await AuthProxyService.checkCookieSession(); - AuthTokenStore.setCookieMode( - provider: AuthTokenStore.getProvider() ?? 'ory', - ); - await _acceptOidcLoginAndRedirect(); + // 401 응답은 세션이 없는 정상적인 상태이므로 예외로 처리하지 않고 우아하게 중단합니다. + final status = await AuthProxyService.getSessionStatus(useCookie: true); + if (status == 200) { + AuthTokenStore.setCookieMode( + provider: AuthTokenStore.getProvider() ?? 'ory', + ); + await _acceptOidcLoginAndRedirect(); + } else { + debugPrint("[Auth] OIDC auto-accept: No active session (status: $status)"); + } } catch (e) { debugPrint("[Auth] OIDC auto-accept cookie check failed: $e"); } From 00e36b429a9e797dbe4e2b68523518d8dbeb918a Mon Sep 17 00:00:00 2001 From: kyy Date: Fri, 27 Feb 2026 14:46:25 +0900 Subject: [PATCH 7/8] =?UTF-8?q?i18n=20=EB=B0=8F=20flutter=20=EB=A6=B0?= =?UTF-8?q?=ED=8A=B8/=ED=8F=AC=EB=A9=A7=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devfront/src/components/layout/AppLayout.tsx | 5 +-- devfront/src/components/ui/badge.tsx | 3 +- .../features/clients/ClientConsentsPage.tsx | 4 ++- .../features/clients/ClientDetailsPage.tsx | 10 +++++- .../features/clients/ClientGeneralPage.tsx | 10 +++++- userfront/assets/translations/ko.toml | 6 ++-- .../auth/presentation/login_screen.dart | 8 +++-- userfront/pubspec.lock | 32 ++++++++++++------- 8 files changed, 52 insertions(+), 26 deletions(-) diff --git a/devfront/src/components/layout/AppLayout.tsx b/devfront/src/components/layout/AppLayout.tsx index 8fa80e47..c0324ac1 100644 --- a/devfront/src/components/layout/AppLayout.tsx +++ b/devfront/src/components/layout/AppLayout.tsx @@ -299,10 +299,7 @@ function AppLayout() { disabled={isRefreshingSession} > {isRefreshingSession - ? t( - "ui.dev.session.refreshing", - "Refreshing...", - ) + ? t("ui.dev.session.refreshing", "Refreshing...") : t("ui.dev.session.refresh", "Refresh session expiry")}