Spaces:
Sleeping
Sleeping
first
Browse files- Dockerfile +13 -0
- Readme.md +9 -0
- app.py +84 -0
- requirements.txt +5 -0
- static/index.html +124 -0
Dockerfile
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.11
|
2 |
+
|
3 |
+
RUN useradd -m -u 1000 user
|
4 |
+
USER user
|
5 |
+
ENV PATH="/home/user/.local/bin:$PATH"
|
6 |
+
|
7 |
+
WORKDIR /app
|
8 |
+
|
9 |
+
COPY --chown=user ./requirements.txt requirements.txt
|
10 |
+
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
11 |
+
|
12 |
+
COPY --chown=user . /app
|
13 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
Readme.md
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
title: Dropbox
|
3 |
+
emoji: 💻
|
4 |
+
colorFrom: green
|
5 |
+
colorTo: gray
|
6 |
+
sdk: docker
|
7 |
+
pinned: false
|
8 |
+
license: apache-2.0
|
9 |
+
---
|
app.py
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI, HTTPException, Request
|
2 |
+
from fastapi.responses import FileResponse
|
3 |
+
from fastapi.staticfiles import StaticFiles
|
4 |
+
from fastapi.middleware.cors import CORSMiddleware
|
5 |
+
from fastapi.middleware.trustedhost import TrustedHostMiddleware
|
6 |
+
from pydantic import BaseModel
|
7 |
+
import os
|
8 |
+
from huggingface_hub import HfApi
|
9 |
+
import time
|
10 |
+
|
11 |
+
from dotenv import load_dotenv
|
12 |
+
|
13 |
+
load_dotenv()
|
14 |
+
|
15 |
+
api = HfApi(token=os.getenv("HF_TOKEN"))
|
16 |
+
|
17 |
+
PASSWORD = os.getenv("PASSWORD")
|
18 |
+
|
19 |
+
app = FastAPI()
|
20 |
+
|
21 |
+
repo_url = os.environ["HF_SPACE_ID"].replace("/", "-")
|
22 |
+
|
23 |
+
app.add_middleware(
|
24 |
+
TrustedHostMiddleware,
|
25 |
+
allowed_hosts=["localhost", f"{repo_url}.hf.space"] # Replace with your actual HF space URL
|
26 |
+
)
|
27 |
+
|
28 |
+
|
29 |
+
app.add_middleware(
|
30 |
+
CORSMiddleware,
|
31 |
+
allow_origins=["http://localhost:7860", f"https://{repo_url}.hf.space"], # Replace with your actual HF space URL
|
32 |
+
allow_credentials=True,
|
33 |
+
allow_methods=["*"],
|
34 |
+
allow_headers=["*"],
|
35 |
+
)
|
36 |
+
# Rate limiting
|
37 |
+
class RateLimiter:
|
38 |
+
def __init__(self, max_attempts: int = 5, window_seconds: int = 300):
|
39 |
+
self.max_attempts = max_attempts
|
40 |
+
self.window_seconds = window_seconds
|
41 |
+
self.attempts = {}
|
42 |
+
|
43 |
+
async def check_rate_limit(self, ip: str) -> bool:
|
44 |
+
now = time.time()
|
45 |
+
if ip in self.attempts:
|
46 |
+
attempts = [t for t in self.attempts[ip] if now - t < self.window_seconds]
|
47 |
+
self.attempts[ip] = attempts
|
48 |
+
if len(attempts) >= self.max_attempts:
|
49 |
+
raise HTTPException(
|
50 |
+
status_code=429,
|
51 |
+
detail=f"Too many attempts. Try again in {self.window_seconds} seconds"
|
52 |
+
)
|
53 |
+
else:
|
54 |
+
self.attempts[ip] = []
|
55 |
+
self.attempts[ip].append(now)
|
56 |
+
return True
|
57 |
+
|
58 |
+
rate_limiter = RateLimiter()
|
59 |
+
|
60 |
+
class PasswordCheck(BaseModel):
|
61 |
+
password: str
|
62 |
+
|
63 |
+
@app.post("/api/verify-password")
|
64 |
+
async def verify_password(password_check: PasswordCheck, request: Request):
|
65 |
+
await rate_limiter.check_rate_limit(request.client.host)
|
66 |
+
|
67 |
+
if password_check.password == PASSWORD:
|
68 |
+
# Return list of available items
|
69 |
+
items = api.list_repo_files(repo_id=os.environ["HF_DATASET_ID"], repo_type="dataset")
|
70 |
+
return sorted(items)
|
71 |
+
raise HTTPException(status_code=401, detail="Invalid password")
|
72 |
+
|
73 |
+
@app.get("/api/download/{item_name}")
|
74 |
+
async def download_item(item_name: str, request: Request):
|
75 |
+
await rate_limiter.check_rate_limit(request.client.host)
|
76 |
+
|
77 |
+
filepath = api.hf_hub_download(repo_id=os.environ["HF_DATASET_ID"], filename=item_name, repo_type="dataset")
|
78 |
+
return FileResponse(filepath, filename=item_name)
|
79 |
+
|
80 |
+
app.mount("/", StaticFiles(directory="static", html=True), name="static")
|
81 |
+
|
82 |
+
if __name__ == "__main__":
|
83 |
+
import uvicorn
|
84 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
requirements.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
uvicorn
|
2 |
+
fastapi
|
3 |
+
pydantic
|
4 |
+
huggingface_hub
|
5 |
+
python-dotenv
|
static/index.html
ADDED
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html>
|
3 |
+
<head>
|
4 |
+
<title>Protected File Download</title>
|
5 |
+
<style>
|
6 |
+
.hidden {
|
7 |
+
display: none;
|
8 |
+
}
|
9 |
+
.container {
|
10 |
+
max-width: 600px;
|
11 |
+
margin: 50px auto;
|
12 |
+
padding: 20px;
|
13 |
+
font-family: Arial, sans-serif;
|
14 |
+
}
|
15 |
+
.error {
|
16 |
+
color: red;
|
17 |
+
margin-top: 10px;
|
18 |
+
}
|
19 |
+
.success {
|
20 |
+
color: green;
|
21 |
+
margin-top: 10px;
|
22 |
+
}
|
23 |
+
button, input, select {
|
24 |
+
margin: 10px 0;
|
25 |
+
padding: 8px;
|
26 |
+
width: 100%;
|
27 |
+
}
|
28 |
+
</style>
|
29 |
+
</head>
|
30 |
+
<body>
|
31 |
+
<div class="container">
|
32 |
+
<div id="passwordSection">
|
33 |
+
<h2>Enter Password</h2>
|
34 |
+
<input type="password" id="password" placeholder="Enter password">
|
35 |
+
<button onclick="checkPassword()">Submit</button>
|
36 |
+
<div id="passwordError" class="error hidden"></div>
|
37 |
+
</div>
|
38 |
+
|
39 |
+
<div id="itemSection" class="hidden">
|
40 |
+
<h2>Select Item</h2>
|
41 |
+
<select id="itemSelect">
|
42 |
+
<option value="">Choose an item...</option>
|
43 |
+
</select>
|
44 |
+
<button id="downloadButton" class="hidden" onclick="downloadItem()">Download</button>
|
45 |
+
</div>
|
46 |
+
</div>
|
47 |
+
|
48 |
+
<script>
|
49 |
+
// Add event listener for Enter key
|
50 |
+
document.getElementById('password').addEventListener('keypress', function(event) {
|
51 |
+
if (event.key === 'Enter') {
|
52 |
+
event.preventDefault();
|
53 |
+
checkPassword();
|
54 |
+
}
|
55 |
+
});
|
56 |
+
|
57 |
+
async function checkPassword() {
|
58 |
+
const password = document.getElementById('password').value;
|
59 |
+
// sleep for 2 seconds
|
60 |
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
61 |
+
try {
|
62 |
+
const response = await fetch('/api/verify-password', {
|
63 |
+
method: 'POST',
|
64 |
+
headers: {
|
65 |
+
'Content-Type': 'application/json',
|
66 |
+
},
|
67 |
+
body: JSON.stringify({ password: password })
|
68 |
+
});
|
69 |
+
|
70 |
+
if (response.ok) {
|
71 |
+
const items = await response.json();
|
72 |
+
showItemSection(items);
|
73 |
+
} else {
|
74 |
+
document.getElementById('passwordError').textContent = 'Invalid password';
|
75 |
+
document.getElementById('passwordError').classList.remove('hidden');
|
76 |
+
}
|
77 |
+
} catch (error) {
|
78 |
+
console.error('Error:', error);
|
79 |
+
document.getElementById('passwordError').textContent = 'Error connecting to server';
|
80 |
+
document.getElementById('passwordError').classList.remove('hidden');
|
81 |
+
}
|
82 |
+
}
|
83 |
+
|
84 |
+
function showItemSection(items) {
|
85 |
+
document.getElementById('passwordSection').classList.add('hidden');
|
86 |
+
document.getElementById('itemSection').classList.remove('hidden');
|
87 |
+
|
88 |
+
const select = document.getElementById('itemSelect');
|
89 |
+
items.forEach(item => {
|
90 |
+
const option = document.createElement('option');
|
91 |
+
option.value = item;
|
92 |
+
option.textContent = item;
|
93 |
+
select.appendChild(option);
|
94 |
+
});
|
95 |
+
|
96 |
+
select.addEventListener('change', function() {
|
97 |
+
const downloadButton = document.getElementById('downloadButton');
|
98 |
+
downloadButton.classList.toggle('hidden', !this.value);
|
99 |
+
});
|
100 |
+
}
|
101 |
+
|
102 |
+
async function downloadItem() {
|
103 |
+
const item = document.getElementById('itemSelect').value;
|
104 |
+
try {
|
105 |
+
const response = await fetch(`/api/download/${item}`);
|
106 |
+
if (!response.ok) throw new Error('Download failed');
|
107 |
+
|
108 |
+
const blob = await response.blob();
|
109 |
+
const url = window.URL.createObjectURL(blob);
|
110 |
+
const a = document.createElement('a');
|
111 |
+
a.href = url;
|
112 |
+
a.download = item;
|
113 |
+
document.body.appendChild(a);
|
114 |
+
a.click();
|
115 |
+
document.body.removeChild(a);
|
116 |
+
window.URL.revokeObjectURL(url);
|
117 |
+
} catch (error) {
|
118 |
+
console.error('Error:', error);
|
119 |
+
alert('Error downloading file');
|
120 |
+
}
|
121 |
+
}
|
122 |
+
</script>
|
123 |
+
</body>
|
124 |
+
</html>
|