1
0
forked from baron/baron-sso

callback 검증 보강. seed-tenant 추가보강

This commit is contained in:
2026-05-11 11:03:11 +09:00
parent f46a7cc088
commit 9a64a16cb9
28 changed files with 2832 additions and 133 deletions

View File

@@ -0,0 +1,13 @@
import { matchRoutes } from "react-router-dom";
import { describe, expect, it } from "vitest";
import { DEVFRONT_AUTH_CALLBACK_PATH } from "../lib/authConfig";
import { devFrontRoutes } from "./routes";
describe("devfront routes", () => {
it("accepts the auth callback path used by the OIDC redirect URI", () => {
const matches = matchRoutes(devFrontRoutes, DEVFRONT_AUTH_CALLBACK_PATH);
expect(matches).not.toBeNull();
expect(matches?.at(-1)?.route.path).toBe(DEVFRONT_AUTH_CALLBACK_PATH);
});
});

View File

@@ -1,4 +1,8 @@
import { Navigate, createBrowserRouter } from "react-router-dom";
import {
Navigate,
type RouteObject,
createBrowserRouter,
} from "react-router-dom";
import AppLayout from "../components/layout/AppLayout";
import AuditLogsPage from "../features/audit/AuditLogsPage";
import AuthCallbackPage from "../features/auth/AuthCallbackPage";
@@ -11,42 +15,45 @@ import ClientRelationsPage from "../features/clients/ClientRelationsPage";
import ClientsPage from "../features/clients/ClientsPage";
import DeveloperRequestPage from "../features/developer-request/DeveloperRequestPage";
import ProfilePage from "../features/profile/ProfilePage";
import { DEVFRONT_AUTH_CALLBACK_PATH } from "../lib/authConfig";
export const devFrontRoutes: RouteObject[] = [
{
path: "/login",
element: <LoginPage />,
},
{
path: DEVFRONT_AUTH_CALLBACK_PATH,
element: <AuthCallbackPage />,
},
{
path: "/",
element: <AuthGuard />,
children: [
{
element: <AppLayout />,
children: [
{ index: true, element: <Navigate to="/clients" replace /> },
{ path: "clients", element: <ClientsPage /> },
{ path: "clients/new", element: <ClientGeneralPage /> },
{ path: "clients/:id", element: <ClientDetailsPage /> },
{ path: "clients/:id/consents", element: <ClientConsentsPage /> },
{ path: "clients/:id/settings", element: <ClientGeneralPage /> },
{
path: "clients/:id/relationships",
element: <ClientRelationsPage />,
},
{ path: "developer-requests", element: <DeveloperRequestPage /> },
{ path: "audit-logs", element: <AuditLogsPage /> },
{ path: "profile", element: <ProfilePage /> },
],
},
],
},
];
export const router = createBrowserRouter(
[
{
path: "/login",
element: <LoginPage />,
},
{
path: "/auth/callback",
element: <AuthCallbackPage />,
},
{
path: "/",
element: <AuthGuard />,
children: [
{
element: <AppLayout />,
children: [
{ index: true, element: <Navigate to="/clients" replace /> },
{ path: "clients", element: <ClientsPage /> },
{ path: "clients/new", element: <ClientGeneralPage /> },
{ path: "clients/:id", element: <ClientDetailsPage /> },
{ path: "clients/:id/consents", element: <ClientConsentsPage /> },
{ path: "clients/:id/settings", element: <ClientGeneralPage /> },
{
path: "clients/:id/relationships",
element: <ClientRelationsPage />,
},
{ path: "developer-requests", element: <DeveloperRequestPage /> },
{ path: "audit-logs", element: <AuditLogsPage /> },
{ path: "profile", element: <ProfilePage /> },
],
},
],
},
],
devFrontRoutes,
// React Router v7 플래그 사전 적용 (현재 타입 정의에 없어 any 캐스팅)
{
future: {

View File

@@ -1,14 +1,25 @@
import { UserManager, WebStorageStateStore } from "oidc-client-ts";
import type { AuthProviderProps } from "react-oidc-context";
import {
buildDevFrontAuthRedirectUris,
resolveDevFrontPublicOrigin,
} from "./authConfig";
const devFrontPublicOrigin = resolveDevFrontPublicOrigin(
import.meta.env.VITE_DEVFRONT_PUBLIC_URL,
window.location.origin,
);
const devFrontRedirectUris =
buildDevFrontAuthRedirectUris(devFrontPublicOrigin);
export const oidcConfig: AuthProviderProps = {
authority: import.meta.env.VITE_OIDC_AUTHORITY || "https://sso.hmac.kr/oidc", // Gateway Proxy URL
client_id: import.meta.env.VITE_OIDC_CLIENT_ID || "devfront",
redirect_uri: `${window.location.origin}/auth/callback`,
redirect_uri: devFrontRedirectUris.redirectUri,
response_type: "code",
scope: "openid offline_access profile email", // offline_access for refresh token
post_logout_redirect_uri: window.location.origin,
popup_redirect_uri: `${window.location.origin}/auth/callback`,
post_logout_redirect_uri: devFrontRedirectUris.postLogoutRedirectUri,
popup_redirect_uri: devFrontRedirectUris.popupRedirectUri,
userStore: new WebStorageStateStore({ store: window.localStorage }),
automaticSilentRenew: false,
};

View File

@@ -0,0 +1,29 @@
import { describe, expect, it } from "vitest";
import {
DEVFRONT_AUTH_CALLBACK_PATH,
buildDevFrontAuthRedirectUris,
resolveDevFrontPublicOrigin,
} from "./authConfig";
describe("devfront auth config", () => {
it("builds callback URLs from the public origin", () => {
expect(buildDevFrontAuthRedirectUris("https://sdev.hmac.kr")).toEqual({
redirectUri: "https://sdev.hmac.kr/auth/callback",
postLogoutRedirectUri: "https://sdev.hmac.kr",
popupRedirectUri: "https://sdev.hmac.kr/auth/callback",
});
});
it("uses the browser origin when the configured origin is empty or invalid", () => {
expect(resolveDevFrontPublicOrigin("", "http://localhost:5173")).toBe(
"http://localhost:5173",
);
expect(
resolveDevFrontPublicOrigin("not a url", "http://localhost:5173"),
).toBe("http://localhost:5173");
});
it("keeps the callback path aligned with the registered redirect path", () => {
expect(DEVFRONT_AUTH_CALLBACK_PATH).toBe("/auth/callback");
});
});

View File

@@ -0,0 +1,33 @@
export interface DevFrontAuthRedirectUris {
redirectUri: string;
postLogoutRedirectUri: string;
popupRedirectUri: string;
}
export const DEVFRONT_AUTH_CALLBACK_PATH = "/auth/callback";
export function resolveDevFrontPublicOrigin(
configuredOrigin: string | undefined,
browserOrigin: string,
) {
const trimmed = configuredOrigin?.trim();
if (!trimmed) {
return browserOrigin;
}
try {
return new URL(trimmed).origin;
} catch {
return browserOrigin;
}
}
export function buildDevFrontAuthRedirectUris(
publicOrigin: string,
): DevFrontAuthRedirectUris {
return {
redirectUri: `${publicOrigin}${DEVFRONT_AUTH_CALLBACK_PATH}`,
postLogoutRedirectUri: publicOrigin,
popupRedirectUri: `${publicOrigin}${DEVFRONT_AUTH_CALLBACK_PATH}`,
};
}