forked from baron/baron-sso
callback 검증 보강. seed-tenant 추가보강
This commit is contained in:
1014
devfront/package-lock.json
generated
1014
devfront/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@
|
||||
"lint": "biome check .",
|
||||
"preview": "vite preview",
|
||||
"test": "playwright test",
|
||||
"test:unit": "vitest run",
|
||||
"test:roles": "playwright test tests/devfront-role-switch-report.spec.ts",
|
||||
"test:ui": "playwright test --ui"
|
||||
},
|
||||
@@ -43,10 +44,12 @@
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"autoprefixer": "^10.4.23",
|
||||
"jsdom": "^28.1.0",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^8.0.3"
|
||||
"vite": "^8.0.3",
|
||||
"vitest": "^4.1.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,19 @@ set -eu
|
||||
|
||||
app_env="$(printf '%s' "${APP_ENV:-development}" | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
if [ -z "${VITE_DEVFRONT_PUBLIC_URL:-}" ] && [ -n "${DEVFRONT_URL:-}" ]; then
|
||||
export VITE_DEVFRONT_PUBLIC_URL="$DEVFRONT_URL"
|
||||
fi
|
||||
|
||||
if [ -z "${VITE_DEVFRONT_PUBLIC_URL:-}" ] && [ -n "${DEVFRONT_CALLBACK_URLS:-}" ]; then
|
||||
first_devfront_callback="${DEVFRONT_CALLBACK_URLS%%,*}"
|
||||
case "$first_devfront_callback" in
|
||||
http://*/auth/callback | https://*/auth/callback)
|
||||
export VITE_DEVFRONT_PUBLIC_URL="${first_devfront_callback%/auth/callback}"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
case "$app_env" in
|
||||
production|prod|stage|staging)
|
||||
mode="production"
|
||||
@@ -12,6 +25,11 @@ case "$app_env" in
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "${1:-}" = "--print-public-url" ]; then
|
||||
printf '%s\n' "${VITE_DEVFRONT_PUBLIC_URL:-}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "${1:-}" = "--print-mode" ]; then
|
||||
printf '%s\n' "$mode"
|
||||
exit 0
|
||||
|
||||
13
devfront/src/app/routes.test.tsx
Normal file
13
devfront/src/app/routes.test.tsx
Normal 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);
|
||||
});
|
||||
});
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
29
devfront/src/lib/authConfig.test.ts
Normal file
29
devfront/src/lib/authConfig.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
33
devfront/src/lib/authConfig.ts
Normal file
33
devfront/src/lib/authConfig.ts
Normal 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}`,
|
||||
};
|
||||
}
|
||||
11
devfront/vitest.config.ts
Normal file
11
devfront/vitest.config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
include: ["src/**/*.{test,spec}.{ts,tsx}"],
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user