Spaces:
Running
Running
try to avoid iframe on other website
Browse files- app/layout.tsx +2 -0
- components/editor/pages/index.tsx +0 -9
- components/iframe-detector.tsx +75 -0
- components/iframe-warning-modal.tsx +61 -0
- middleware.ts +48 -1
app/layout.tsx
CHANGED
@@ -10,6 +10,7 @@ import MY_TOKEN_KEY from "@/lib/get-cookie-name";
|
|
10 |
import { apiServer } from "@/lib/api";
|
11 |
import AppContext from "@/components/contexts/app-context";
|
12 |
import Script from "next/script";
|
|
|
13 |
|
14 |
const inter = Inter({
|
15 |
variable: "--font-inter-sans",
|
@@ -100,6 +101,7 @@ export default async function RootLayout({
|
|
100 |
<body
|
101 |
className={`${inter.variable} ${ptSans.variable} antialiased bg-black dark h-[100dvh] overflow-hidden`}
|
102 |
>
|
|
|
103 |
<Toaster richColors position="bottom-center" />
|
104 |
<TanstackProvider>
|
105 |
<AppContext me={data}>{children}</AppContext>
|
|
|
10 |
import { apiServer } from "@/lib/api";
|
11 |
import AppContext from "@/components/contexts/app-context";
|
12 |
import Script from "next/script";
|
13 |
+
import IframeDetector from "@/components/iframe-detector";
|
14 |
|
15 |
const inter = Inter({
|
16 |
variable: "--font-inter-sans",
|
|
|
101 |
<body
|
102 |
className={`${inter.variable} ${ptSans.variable} antialiased bg-black dark h-[100dvh] overflow-hidden`}
|
103 |
>
|
104 |
+
<IframeDetector />
|
105 |
<Toaster richColors position="bottom-center" />
|
106 |
<TanstackProvider>
|
107 |
<AppContext me={data}>{children}</AppContext>
|
components/editor/pages/index.tsx
CHANGED
@@ -1,12 +1,10 @@
|
|
1 |
import { Page } from "@/types";
|
2 |
-
// import { PlusIcon } from "lucide-react";
|
3 |
import { ListPagesItem } from "./page";
|
4 |
|
5 |
export function ListPages({
|
6 |
pages,
|
7 |
currentPage,
|
8 |
onSelectPage,
|
9 |
-
// onNewPage,
|
10 |
onDeletePage,
|
11 |
}: {
|
12 |
pages: Array<Page>;
|
@@ -27,13 +25,6 @@ export function ListPages({
|
|
27 |
index={i}
|
28 |
/>
|
29 |
))}
|
30 |
-
{/* <button
|
31 |
-
className="max-h-14 min-h-14 pl-2 pr-4 py-4 text-neutral-400 cursor-pointer text-sm hover:bg-neutral-900 flex items-center justify-center gap-1 text-nowrap"
|
32 |
-
onClick={onNewPage}
|
33 |
-
>
|
34 |
-
<PlusIcon className="h-3" />
|
35 |
-
New Page
|
36 |
-
</button> */}
|
37 |
</div>
|
38 |
);
|
39 |
}
|
|
|
1 |
import { Page } from "@/types";
|
|
|
2 |
import { ListPagesItem } from "./page";
|
3 |
|
4 |
export function ListPages({
|
5 |
pages,
|
6 |
currentPage,
|
7 |
onSelectPage,
|
|
|
8 |
onDeletePage,
|
9 |
}: {
|
10 |
pages: Array<Page>;
|
|
|
25 |
index={i}
|
26 |
/>
|
27 |
))}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
</div>
|
29 |
);
|
30 |
}
|
components/iframe-detector.tsx
ADDED
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client";
|
2 |
+
|
3 |
+
import { useEffect, useState } from "react";
|
4 |
+
import IframeWarningModal from "./iframe-warning-modal";
|
5 |
+
|
6 |
+
export default function IframeDetector() {
|
7 |
+
const [showWarning, setShowWarning] = useState(false);
|
8 |
+
|
9 |
+
useEffect(() => {
|
10 |
+
// Helper function to check if a hostname is from allowed domains
|
11 |
+
const isAllowedDomain = (hostname: string) => {
|
12 |
+
const host = hostname.toLowerCase();
|
13 |
+
return (
|
14 |
+
host.endsWith(".huggingface.co") ||
|
15 |
+
host.endsWith(".hf.co") ||
|
16 |
+
host === "huggingface.co" ||
|
17 |
+
host === "hf.co"
|
18 |
+
);
|
19 |
+
};
|
20 |
+
|
21 |
+
// Check if the current window is in an iframe
|
22 |
+
const isInIframe = () => {
|
23 |
+
try {
|
24 |
+
return window.self !== window.top;
|
25 |
+
} catch {
|
26 |
+
// If we can't access window.top due to cross-origin restrictions,
|
27 |
+
// we're likely in an iframe
|
28 |
+
return true;
|
29 |
+
}
|
30 |
+
};
|
31 |
+
|
32 |
+
// Additional check: compare window location with parent location
|
33 |
+
const isEmbedded = () => {
|
34 |
+
try {
|
35 |
+
return window.location !== window.parent.location;
|
36 |
+
} catch {
|
37 |
+
// Cross-origin iframe
|
38 |
+
return true;
|
39 |
+
}
|
40 |
+
};
|
41 |
+
|
42 |
+
// Check if we're in an iframe from a non-allowed domain
|
43 |
+
const shouldShowWarning = () => {
|
44 |
+
if (!isInIframe() && !isEmbedded()) {
|
45 |
+
return false; // Not in an iframe
|
46 |
+
}
|
47 |
+
|
48 |
+
try {
|
49 |
+
// Try to get the parent's hostname
|
50 |
+
const parentHostname = window.parent.location.hostname;
|
51 |
+
return !isAllowedDomain(parentHostname);
|
52 |
+
} catch {
|
53 |
+
// Cross-origin iframe - try to get referrer instead
|
54 |
+
try {
|
55 |
+
if (document.referrer) {
|
56 |
+
const referrerUrl = new URL(document.referrer);
|
57 |
+
return !isAllowedDomain(referrerUrl.hostname);
|
58 |
+
}
|
59 |
+
} catch {
|
60 |
+
// If we can't determine the parent domain, assume it's not allowed
|
61 |
+
}
|
62 |
+
return true;
|
63 |
+
}
|
64 |
+
};
|
65 |
+
|
66 |
+
if (shouldShowWarning()) {
|
67 |
+
// Show warning modal instead of redirecting immediately
|
68 |
+
setShowWarning(true);
|
69 |
+
}
|
70 |
+
}, []);
|
71 |
+
|
72 |
+
return (
|
73 |
+
<IframeWarningModal isOpen={showWarning} onOpenChange={setShowWarning} />
|
74 |
+
);
|
75 |
+
}
|
components/iframe-warning-modal.tsx
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client";
|
2 |
+
|
3 |
+
import {
|
4 |
+
Dialog,
|
5 |
+
DialogContent,
|
6 |
+
DialogDescription,
|
7 |
+
DialogFooter,
|
8 |
+
DialogHeader,
|
9 |
+
DialogTitle,
|
10 |
+
} from "@/components/ui/dialog";
|
11 |
+
import { Button } from "@/components/ui/button";
|
12 |
+
import { ExternalLink, AlertTriangle } from "lucide-react";
|
13 |
+
|
14 |
+
interface IframeWarningModalProps {
|
15 |
+
isOpen: boolean;
|
16 |
+
onOpenChange: (open: boolean) => void;
|
17 |
+
}
|
18 |
+
|
19 |
+
export default function IframeWarningModal({
|
20 |
+
isOpen,
|
21 |
+
onOpenChange,
|
22 |
+
}: IframeWarningModalProps) {
|
23 |
+
const handleVisitSite = () => {
|
24 |
+
window.top!.location.href = "https://deepsite.hf.co";
|
25 |
+
};
|
26 |
+
|
27 |
+
return (
|
28 |
+
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
29 |
+
<DialogContent className="sm:max-w-md">
|
30 |
+
<DialogHeader>
|
31 |
+
<div className="flex items-center gap-2">
|
32 |
+
<AlertTriangle className="h-5 w-5 text-red-500" />
|
33 |
+
<DialogTitle>Unauthorized Embedding</DialogTitle>
|
34 |
+
</div>
|
35 |
+
<DialogDescription className="text-left">
|
36 |
+
You're viewing DeepSite through an unauthorized iframe. For the
|
37 |
+
best experience and security, please visit the official website
|
38 |
+
directly.
|
39 |
+
</DialogDescription>
|
40 |
+
</DialogHeader>
|
41 |
+
|
42 |
+
<div className="bg-muted/50 rounded-lg p-4 space-y-2">
|
43 |
+
<p className="text-sm font-medium">Why visit the official site?</p>
|
44 |
+
<ul className="text-sm text-muted-foreground space-y-1">
|
45 |
+
<li>• Better performance and security</li>
|
46 |
+
<li>• Full functionality access</li>
|
47 |
+
<li>• Latest features and updates</li>
|
48 |
+
<li>• Proper authentication support</li>
|
49 |
+
</ul>
|
50 |
+
</div>
|
51 |
+
|
52 |
+
<DialogFooter className="flex-col sm:flex-row gap-2">
|
53 |
+
<Button onClick={handleVisitSite} className="w-full sm:w-auto">
|
54 |
+
<ExternalLink className="mr-2 h-4 w-4" />
|
55 |
+
Visit Deepsite.hf.co
|
56 |
+
</Button>
|
57 |
+
</DialogFooter>
|
58 |
+
</DialogContent>
|
59 |
+
</Dialog>
|
60 |
+
);
|
61 |
+
}
|
middleware.ts
CHANGED
@@ -4,7 +4,54 @@ import type { NextRequest } from "next/server";
|
|
4 |
export function middleware(request: NextRequest) {
|
5 |
const headers = new Headers(request.headers);
|
6 |
headers.set("x-current-host", request.nextUrl.host);
|
7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
}
|
9 |
|
10 |
export const config = {
|
|
|
4 |
export function middleware(request: NextRequest) {
|
5 |
const headers = new Headers(request.headers);
|
6 |
headers.set("x-current-host", request.nextUrl.host);
|
7 |
+
|
8 |
+
// Check if the request is coming from an iframe
|
9 |
+
const referer = request.headers.get("referer");
|
10 |
+
const currentHost = request.nextUrl.host;
|
11 |
+
const currentOrigin = `${request.nextUrl.protocol}//${currentHost}`;
|
12 |
+
|
13 |
+
// Helper function to check if a URL is from allowed domains
|
14 |
+
const isAllowedDomain = (url: string) => {
|
15 |
+
try {
|
16 |
+
const urlObj = new URL(url);
|
17 |
+
const hostname = urlObj.hostname.toLowerCase();
|
18 |
+
return hostname.endsWith('.huggingface.co') ||
|
19 |
+
hostname.endsWith('.hf.co') ||
|
20 |
+
hostname === 'huggingface.co' ||
|
21 |
+
hostname === 'hf.co';
|
22 |
+
} catch {
|
23 |
+
return false;
|
24 |
+
}
|
25 |
+
};
|
26 |
+
|
27 |
+
// If there's a referer and it's not from the same origin, check if it's allowed
|
28 |
+
if (referer && !referer.startsWith(currentOrigin)) {
|
29 |
+
// Additional check: look for iframe-specific headers or indicators
|
30 |
+
const secFetchDest = request.headers.get("sec-fetch-dest");
|
31 |
+
const secFetchMode = request.headers.get("sec-fetch-mode");
|
32 |
+
|
33 |
+
// If the request is for a document within an iframe context
|
34 |
+
if (secFetchDest === "iframe" ||
|
35 |
+
(secFetchDest === "document" && secFetchMode === "navigate" && referer)) {
|
36 |
+
|
37 |
+
// Check if the referer is from an allowed domain
|
38 |
+
if (!isAllowedDomain(referer)) {
|
39 |
+
return NextResponse.redirect("https://deepsite.hf.co");
|
40 |
+
}
|
41 |
+
}
|
42 |
+
}
|
43 |
+
|
44 |
+
// Set headers to prevent framing
|
45 |
+
const response = NextResponse.next({ headers });
|
46 |
+
|
47 |
+
// Allow embedding only from Hugging Face domains
|
48 |
+
response.headers.set("X-Frame-Options", "SAMEORIGIN");
|
49 |
+
response.headers.set(
|
50 |
+
"Content-Security-Policy",
|
51 |
+
"frame-ancestors 'self' *.huggingface.co *.hf.co huggingface.co hf.co;"
|
52 |
+
);
|
53 |
+
|
54 |
+
return response;
|
55 |
}
|
56 |
|
57 |
export const config = {
|