Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions apps/website/content/blocks/form.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ previewImageUrl: /images/form-preview.svg

### Type: Login

<Demo name="forms/login-form">
<Demo name="forms/login-form" showResponsiveToggle>
```json doc-gen:file
{
"file": "./src/components/demo/examples/forms/login-form.tsx",
Expand All @@ -23,7 +23,7 @@ previewImageUrl: /images/form-preview.svg

### Type: SignUp

<Demo name="forms/signup-form">
<Demo name="forms/signup-form" showResponsiveToggle>
```json doc-gen:file
{
"file": "./src/components/demo/examples/forms/signup-form.tsx",
Expand All @@ -40,7 +40,7 @@ previewImageUrl: /images/form-preview.svg

### Type: Authentication

<Demo name="forms/authentication-form">
<Demo name="forms/authentication-form" showResponsiveToggle>
```json doc-gen:file
{
"file": "./src/components/demo/examples/forms/authentication-form.tsx",
Expand All @@ -57,7 +57,7 @@ previewImageUrl: /images/form-preview.svg

### Type: Research

<Demo name="forms/research-form">
<Demo name="forms/research-form" showResponsiveToggle>
```json doc-gen:file
{
"file": "./src/components/demo/examples/forms/research-form.tsx",
Expand All @@ -74,7 +74,7 @@ previewImageUrl: /images/form-preview.svg

### Type: Filter

<Demo name="forms/filter-form">
<Demo name="forms/filter-form" showResponsiveToggle>
```json doc-gen:file
{
"file": "./src/components/demo/examples/forms/filter-form.tsx",
Expand All @@ -91,7 +91,7 @@ previewImageUrl: /images/form-preview.svg

### Type: BottomSheetFilter

<Demo name="forms/sheet-form">
<Demo name="forms/sheet-form" showResponsiveToggle>
```json doc-gen:file
{
"file": "./src/components/demo/examples/forms/sheet-form.tsx",
Expand Down
12 changes: 6 additions & 6 deletions apps/website/content/blocks/nav-bar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ previewImageUrl: /images/nav-bar-preview.svg

### Left Aligned Navbar

<Demo name="navbars/block01">
<Demo name="navbars/block01" showResponsiveToggle>
```json doc-gen:file
{
"file": "./src/components/demo/examples/navbars/block01.tsx",
Expand All @@ -23,7 +23,7 @@ previewImageUrl: /images/nav-bar-preview.svg

### Center Aligned Navbar

<Demo name="navbars/block02">
<Demo name="navbars/block02" showResponsiveToggle>
```json doc-gen:file
{
"file": "./src/components/demo/examples/navbars/block02.tsx",
Expand All @@ -40,7 +40,7 @@ previewImageUrl: /images/nav-bar-preview.svg

### Right Aligned Navbar

<Demo name="navbars/block03">
<Demo name="navbars/block03" showResponsiveToggle>
```json doc-gen:file
{
"file": "./src/components/demo/examples/navbars/block03.tsx",
Expand All @@ -57,7 +57,7 @@ previewImageUrl: /images/nav-bar-preview.svg

### Navbar with Login Button

<Demo name="navbars/block04">
<Demo name="navbars/block04" showResponsiveToggle>
```json doc-gen:file
{
"file": "./src/components/demo/examples/navbars/block04.tsx",
Expand All @@ -74,7 +74,7 @@ previewImageUrl: /images/nav-bar-preview.svg

### Logo Centered Navbar

<Demo name="navbars/block05">
<Demo name="navbars/block05" showResponsiveToggle>
```json doc-gen:file
{
"file": "./src/components/demo/examples/navbars/block05.tsx",
Expand All @@ -91,7 +91,7 @@ previewImageUrl: /images/nav-bar-preview.svg

### Page Navigation Navbar

<Demo name="navbars/block06">
<Demo name="navbars/block06" showResponsiveToggle>
```json doc-gen:file
{
"file": "./src/components/demo/examples/navbars/block06.tsx",
Expand Down
2 changes: 1 addition & 1 deletion apps/website/source.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default defineConfig({
inline: 'tailing-curly-colon',
themes: {
light: 'github-light',
dark: 'slack-dark',
dark: 'github-dark',
},
},
},
Expand Down
122 changes: 122 additions & 0 deletions apps/website/src/app/preview/[component]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
'use client';

import * as React from 'react';
import { Suspense } from 'react';

import { Text } from '@vapor-ui/core';

interface PreviewPageProps {
searchParams: Promise<{
path?: string;
}>;
}

const ComponentError = ({
componentPath,
error,
}: {
componentPath: string | undefined;
error: string;
}) => {
return (
<div className="p-5 text-center text-v-danger">
<Text typography="heading1" foreground="danger-100" render={<h1 />} className="mb-4">
Component not found
</Text>
<Text typography="body2" foreground="danger-100">
{error}
</Text>
{componentPath && (
<Text typography="body2" foreground="danger-100">
{componentPath}
</Text>
)}
</div>
);
};

function ComponentLoading() {
return (
<div className="p-5 text-center text-gray-500">
<div className="animate-spin w-6 h-6 border-2 border-v-gray-300 border-t-v-blue-500 rounded-full mx-auto mb-2" />
<p>Loading component...</p>
</div>
);
}

function useDynamicComponent(componentPath?: string) {
const [component, setComponent] = React.useState<React.ComponentType | null>(null);
const [error, setError] = React.useState<string | null>(null);
const [isLoading, setIsLoading] = React.useState(true);

React.useEffect(() => {
if (!componentPath) {
setError('No component path provided');
setIsLoading(false);
return;
}
Comment on lines +53 to +57
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

보안 취약점: componentPath를 동적 import에 사용하기 전에 유효성 검사를 추가해야 합니다. 현재 코드는 searchParams에서 받은 경로를 그대로 사용하므로 경로 조작(Path Traversal) 공격에 취약할 수 있습니다.

componentPath../와 같은 문자가 포함되어 있는지 확인하고, 허용된 문자(예: 영문, 숫자, -, /, _)만으로 구성되었는지 검증하는 로직을 이 부분에 추가하는 것을 권장합니다.


const loadComponent = async () => {
try {
setIsLoading(true);
setError(null);

const componentModule = await import(
`~/components/demo/examples/${componentPath}.tsx`
);

if (componentModule.default) {
setComponent(() => componentModule.default);
} else {
setError(`Component "${componentPath}" does not have a default export`);
}
} catch (err) {
setError(`Could not load component: ${componentPath}. ${(err as Error).message}`);
} finally {
setIsLoading(false);
}
};

loadComponent();
}, [componentPath]);

return { component, error, isLoading };
}

function DynamicComponent({ componentPath }: { componentPath?: string }) {
const { component: Component, error, isLoading } = useDynamicComponent(componentPath);

if (error) {
return <ComponentError componentPath={componentPath} error={error} />;
}

if (isLoading || !Component) {
return <ComponentLoading />;
}

return <Component />;
}
function isValidComponentPath(path?: string): boolean {
if (!path) return false;

const pathTraversalPattern = /\.\./;
if (pathTraversalPattern.test(path)) return false;

const validPathPattern = /^[a-zA-Z0-9/_-]+$/;
return validPathPattern.test(path);
}

export default function Page({ searchParams }: PreviewPageProps) {
const resolvedSearchParams = React.use(searchParams);
const componentPath = resolvedSearchParams.path;

if (!isValidComponentPath(componentPath)) {
return <ComponentError componentPath={componentPath} error="Invalid component path" />;
}

return (
<Suspense fallback={<div>Loading...</div>}>
<DynamicComponent componentPath={componentPath} />
</Suspense>
);
}
Loading
Loading