nbroad HF staff commited on
Commit
7e1e2b1
1 Parent(s): 07ded7d
Files changed (5) hide show
  1. Dockerfile +13 -0
  2. Readme.md +9 -0
  3. app.py +84 -0
  4. requirements.txt +5 -0
  5. 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>