Spaces:
Sleeping
Sleeping
Sami
commited on
Commit
·
b57a95c
0
Parent(s):
Initial deployment
Browse files- .gitattributes +32 -0
- .gitignore +11 -0
- Dockerfile +16 -0
- README.md +75 -0
- README_HF.md +20 -0
- app.py +196 -0
- requirements.txt +4 -0
- templates/login.html +112 -0
.gitattributes
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
27 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
28 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
29 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
30 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
31 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
32 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
__pycache__/
|
2 |
+
*.py[cod]
|
3 |
+
*$py.class
|
4 |
+
.DS_Store
|
5 |
+
.env
|
6 |
+
.venv
|
7 |
+
venv/
|
8 |
+
ENV/
|
9 |
+
create_space.py
|
10 |
+
deploy.sh
|
11 |
+
setup_hf_space.py
|
Dockerfile
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.9-slim
|
2 |
+
|
3 |
+
WORKDIR /app
|
4 |
+
|
5 |
+
COPY . .
|
6 |
+
|
7 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
8 |
+
|
9 |
+
# Create necessary directories
|
10 |
+
RUN mkdir -p static templates
|
11 |
+
|
12 |
+
# Make port 7860 available (default for HF Spaces)
|
13 |
+
EXPOSE 7860
|
14 |
+
|
15 |
+
# Command to run the application
|
16 |
+
CMD ["python", "app.py"]
|
README.md
ADDED
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Secure Balance Academy Viewer
|
2 |
+
|
3 |
+
This is a secure implementation of the Balance Academy web application that prevents users from downloading, copying, or inspecting the HTML content.
|
4 |
+
|
5 |
+
## Features
|
6 |
+
|
7 |
+
- Authentication system with token-based access
|
8 |
+
- Prevention of right-click context menu
|
9 |
+
- Detection and prevention of developer tools
|
10 |
+
- Content made uncopyable
|
11 |
+
- Dynamic watermarking with timestamp
|
12 |
+
- Security headers for additional protection
|
13 |
+
|
14 |
+
## Usage
|
15 |
+
|
16 |
+
Simply click the "Access Secure Content" button to view the protected content. Your session will automatically expire after 1 hour.
|
17 |
+
|
18 |
+
## Security Notice
|
19 |
+
|
20 |
+
The protections implemented in this application are designed to discourage casual copying and downloading but cannot prevent determined technical users from accessing the content. For the most sensitive content, consider additional server-side protection measures
|
21 |
+
|
22 |
+
## Setup Instructions
|
23 |
+
|
24 |
+
### Local Development
|
25 |
+
|
26 |
+
1. Install the required dependencies:
|
27 |
+
```
|
28 |
+
pip install -r requirements.txt
|
29 |
+
```
|
30 |
+
|
31 |
+
2. Run the Flask application:
|
32 |
+
```
|
33 |
+
python app.py
|
34 |
+
```
|
35 |
+
|
36 |
+
3. Open your browser and navigate to `http://localhost:7860`
|
37 |
+
|
38 |
+
### Deployment on Hugging Face Spaces
|
39 |
+
|
40 |
+
1. Create a new Space on Hugging Face with the "Gradio" template
|
41 |
+
2. Upload all the files in this directory to your Space
|
42 |
+
3. Add the following to your `requirements.txt`:
|
43 |
+
```
|
44 |
+
flask==2.3.3
|
45 |
+
Werkzeug==2.3.7
|
46 |
+
gunicorn==21.2.0
|
47 |
+
Flask-Session==0.5.0
|
48 |
+
```
|
49 |
+
4. Add the following to your `app.py` at the top:
|
50 |
+
```python
|
51 |
+
# This file will be used by Hugging Face Spaces
|
52 |
+
```
|
53 |
+
5. Commit and push your changes
|
54 |
+
|
55 |
+
## How It Works
|
56 |
+
|
57 |
+
The application serves the HTML content with added JavaScript protections that:
|
58 |
+
|
59 |
+
1. Disable right-clicking to prevent context menu access
|
60 |
+
2. Intercept keyboard shortcuts that could be used to save or inspect the page
|
61 |
+
3. Detect when developer tools are opened and replace page content with a warning
|
62 |
+
4. Make text selection and copying difficult
|
63 |
+
5. Add a dynamic watermark that updates with the current time
|
64 |
+
|
65 |
+
Additionally, the server adds security headers to every response to prevent certain attacks and restricts how the content can be loaded or framed.
|
66 |
+
|
67 |
+
## Limitations
|
68 |
+
|
69 |
+
While these protections make it more difficult for casual users to copy or download the content, they are not foolproof against determined technical users. No client-side protection can be 100% effective against someone with technical knowledge, as the browser must ultimately receive and render the content.
|
70 |
+
|
71 |
+
For the most sensitive content, consider server-side rendering of partial content or delivering content as images rather than HTML.
|
72 |
+
|
73 |
+
## License
|
74 |
+
|
75 |
+
This project is licensed under the terms of the LICENSE file included in the repository.
|
README_HF.md
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Secure Balance Academy Viewer
|
2 |
+
|
3 |
+
This is a secure implementation of the Balance Academy web application that prevents users from downloading, copying, or inspecting the HTML content.
|
4 |
+
|
5 |
+
## Features
|
6 |
+
|
7 |
+
- Authentication system with token-based access
|
8 |
+
- Prevention of right-click context menu
|
9 |
+
- Detection and prevention of developer tools
|
10 |
+
- Content made uncopyable
|
11 |
+
- Dynamic watermarking with timestamp
|
12 |
+
- Security headers for additional protection
|
13 |
+
|
14 |
+
## Usage
|
15 |
+
|
16 |
+
Simply click the "Access Secure Content" button to view the protected content. Your session will automatically expire after 1 hour.
|
17 |
+
|
18 |
+
## Security Notice
|
19 |
+
|
20 |
+
The protections implemented in this application are designed to discourage casual copying and downloading but cannot prevent determined technical users from accessing the content. For the most sensitive content, consider additional server-side protection measures.
|
app.py
ADDED
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from flask import Flask, render_template, send_from_directory, Response
|
2 |
+
import os
|
3 |
+
import uuid
|
4 |
+
import time
|
5 |
+
from functools import wraps
|
6 |
+
|
7 |
+
app = Flask(__name__, static_folder='static')
|
8 |
+
|
9 |
+
# Configure session-based token security
|
10 |
+
app.config['SECRET_KEY'] = str(uuid.uuid4())
|
11 |
+
app.config['SESSION_TYPE'] = 'filesystem'
|
12 |
+
app.config['PERMANENT_SESSION_LIFETIME'] = 1800 # 30 minutes
|
13 |
+
|
14 |
+
# Store for valid tokens and their expiry times (in a real app, use a proper database)
|
15 |
+
VALID_TOKENS = {}
|
16 |
+
TOKEN_EXPIRY = 3600 # 1 hour
|
17 |
+
|
18 |
+
# Security headers for all responses
|
19 |
+
@app.after_request
|
20 |
+
def add_security_headers(response):
|
21 |
+
# Prevent content from being framed by other sites
|
22 |
+
response.headers['X-Frame-Options'] = 'DENY'
|
23 |
+
|
24 |
+
# Prevent browsers from performing MIME sniffing
|
25 |
+
response.headers['X-Content-Type-Options'] = 'nosniff'
|
26 |
+
|
27 |
+
# Enable XSS protection in browsers
|
28 |
+
response.headers['X-XSS-Protection'] = '1; mode=block'
|
29 |
+
|
30 |
+
# Content Security Policy to restrict resources
|
31 |
+
response.headers['Content-Security-Policy'] = "default-src 'self' https://cdn.tailwindcss.com https://cdnjs.cloudflare.com https://fonts.googleapis.com https://fonts.gstatic.com; img-src 'self' data:; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://fonts.googleapis.com https://cdn.tailwindcss.com; script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com https://cdnjs.cloudflare.com https://cdn.jsdelivr.net"
|
32 |
+
|
33 |
+
# Cache control - prevent caching
|
34 |
+
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
|
35 |
+
response.headers['Pragma'] = 'no-cache'
|
36 |
+
response.headers['Expires'] = '0'
|
37 |
+
|
38 |
+
return response
|
39 |
+
|
40 |
+
# Load the HTML content
|
41 |
+
with open(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dist', 'index.html'), 'r') as f:
|
42 |
+
html_content = f.read()
|
43 |
+
|
44 |
+
# Add anti-inspection/download JavaScript to the HTML
|
45 |
+
def add_protection_scripts(html):
|
46 |
+
# Insert scripts right before the closing body tag
|
47 |
+
protection_scripts = """
|
48 |
+
<script>
|
49 |
+
// Disable right-click context menu
|
50 |
+
document.addEventListener('contextmenu', e => e.preventDefault());
|
51 |
+
|
52 |
+
// Disable keyboard shortcuts that could be used to save the page
|
53 |
+
document.addEventListener('keydown', function(e) {
|
54 |
+
// Ctrl/Cmd + S (Save)
|
55 |
+
if ((e.ctrlKey || e.metaKey) && e.keyCode === 83) {
|
56 |
+
e.preventDefault();
|
57 |
+
return false;
|
58 |
+
}
|
59 |
+
|
60 |
+
// Ctrl/Cmd + P (Print, which can be used to save as PDF)
|
61 |
+
if ((e.ctrlKey || e.metaKey) && e.keyCode === 80) {
|
62 |
+
e.preventDefault();
|
63 |
+
return false;
|
64 |
+
}
|
65 |
+
|
66 |
+
// Ctrl/Cmd + Shift + I or F12 (Developer Tools)
|
67 |
+
if (((e.ctrlKey || e.metaKey) && e.shiftKey && e.keyCode === 73) || e.keyCode === 123) {
|
68 |
+
e.preventDefault();
|
69 |
+
return false;
|
70 |
+
}
|
71 |
+
|
72 |
+
// Ctrl/Cmd + U (View Source)
|
73 |
+
if ((e.ctrlKey || e.metaKey) && e.keyCode === 85) {
|
74 |
+
e.preventDefault();
|
75 |
+
return false;
|
76 |
+
}
|
77 |
+
});
|
78 |
+
|
79 |
+
// Detect and prevent developer tools from opening with periodic check
|
80 |
+
(function() {
|
81 |
+
function detectDevTools() {
|
82 |
+
const widthThreshold = window.outerWidth - window.innerWidth > 160;
|
83 |
+
const heightThreshold = window.outerHeight - window.innerHeight > 160;
|
84 |
+
|
85 |
+
if (widthThreshold || heightThreshold) {
|
86 |
+
document.body.innerHTML = '<div style="text-align: center; padding: 50px;"><h1>Developer tools detected</h1><p>Please close developer tools to view this content.</p></div>';
|
87 |
+
}
|
88 |
+
}
|
89 |
+
|
90 |
+
// Check periodically
|
91 |
+
setInterval(detectDevTools, 1000);
|
92 |
+
})();
|
93 |
+
|
94 |
+
// Make content uncopyable
|
95 |
+
document.addEventListener('selectstart', e => e.preventDefault());
|
96 |
+
document.addEventListener('copy', e => e.preventDefault());
|
97 |
+
|
98 |
+
// Add a watermark with user info and timestamp
|
99 |
+
(function() {
|
100 |
+
const watermark = document.createElement('div');
|
101 |
+
watermark.style.position = 'fixed';
|
102 |
+
watermark.style.top = '0';
|
103 |
+
watermark.style.left = '0';
|
104 |
+
watermark.style.width = '100%';
|
105 |
+
watermark.style.height = '100%';
|
106 |
+
watermark.style.pointerEvents = 'none';
|
107 |
+
watermark.style.display = 'flex';
|
108 |
+
watermark.style.justifyContent = 'center';
|
109 |
+
watermark.style.alignItems = 'center';
|
110 |
+
watermark.style.zIndex = '10000';
|
111 |
+
|
112 |
+
const watermarkText = document.createElement('p');
|
113 |
+
watermarkText.style.transform = 'rotate(-45deg)';
|
114 |
+
watermarkText.style.fontSize = '20px';
|
115 |
+
watermarkText.style.color = 'rgba(0, 0, 0, 0.1)';
|
116 |
+
watermarkText.style.userSelect = 'none';
|
117 |
+
|
118 |
+
// Update watermark with current time every minute
|
119 |
+
function updateWatermark() {
|
120 |
+
const date = new Date();
|
121 |
+
watermarkText.textContent = 'Balance Academy - ' + date.toLocaleString();
|
122 |
+
}
|
123 |
+
|
124 |
+
updateWatermark();
|
125 |
+
setInterval(updateWatermark, 60000);
|
126 |
+
|
127 |
+
watermark.appendChild(watermarkText);
|
128 |
+
document.body.appendChild(watermark);
|
129 |
+
})();
|
130 |
+
|
131 |
+
// Additional anti-inspection code, tries to detect if devtools is open
|
132 |
+
(function() {
|
133 |
+
let devtools = function() {};
|
134 |
+
devtools.toString = function() {
|
135 |
+
document.body.innerHTML = '<div style="text-align: center; padding: 50px;"><h1>Developer tools detected</h1><p>Please close developer tools to view this content.</p></div>';
|
136 |
+
return 'Dev tools usage detected!';
|
137 |
+
};
|
138 |
+
console.log('%c', devtools);
|
139 |
+
})();
|
140 |
+
</script>
|
141 |
+
"""
|
142 |
+
return html.replace('</body>', protection_scripts + '</body>')
|
143 |
+
|
144 |
+
protected_html = add_protection_scripts(html_content)
|
145 |
+
|
146 |
+
# Create a simple token-based authentication system
|
147 |
+
def generate_token():
|
148 |
+
token = str(uuid.uuid4())
|
149 |
+
VALID_TOKENS[token] = time.time() + TOKEN_EXPIRY
|
150 |
+
return token
|
151 |
+
|
152 |
+
def is_valid_token(token):
|
153 |
+
if token in VALID_TOKENS:
|
154 |
+
if time.time() < VALID_TOKENS[token]:
|
155 |
+
return True
|
156 |
+
else:
|
157 |
+
# Token expired
|
158 |
+
del VALID_TOKENS[token]
|
159 |
+
return False
|
160 |
+
|
161 |
+
def clean_expired_tokens():
|
162 |
+
current_time = time.time()
|
163 |
+
expired = [token for token, expiry in VALID_TOKENS.items() if current_time > expiry]
|
164 |
+
for token in expired:
|
165 |
+
del VALID_TOKENS[token]
|
166 |
+
|
167 |
+
# Authentication decorator
|
168 |
+
def token_required(f):
|
169 |
+
@wraps(f)
|
170 |
+
def decorated_function(*args, **kwargs):
|
171 |
+
token = kwargs.get('token')
|
172 |
+
if not token or not is_valid_token(token):
|
173 |
+
return Response('Unauthorized access', 401)
|
174 |
+
return f(*args, **kwargs)
|
175 |
+
return decorated_function
|
176 |
+
|
177 |
+
# Routes
|
178 |
+
@app.route('/')
|
179 |
+
def index():
|
180 |
+
token = generate_token()
|
181 |
+
clean_expired_tokens()
|
182 |
+
return render_template('login.html', token=token)
|
183 |
+
|
184 |
+
@app.route('/view/<token>')
|
185 |
+
@token_required
|
186 |
+
def view_content(token):
|
187 |
+
return protected_html
|
188 |
+
|
189 |
+
@app.route('/static/<path:filename>')
|
190 |
+
def serve_static(filename):
|
191 |
+
return send_from_directory(app.static_folder, filename)
|
192 |
+
|
193 |
+
if __name__ == '__main__':
|
194 |
+
os.makedirs(os.path.join(os.path.dirname(__file__), 'static'), exist_ok=True)
|
195 |
+
os.makedirs(os.path.join(os.path.dirname(__file__), 'templates'), exist_ok=True)
|
196 |
+
app.run(host='0.0.0.0', port=7860, debug=False)
|
requirements.txt
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
flask==2.3.3
|
2 |
+
Werkzeug==2.3.7
|
3 |
+
gunicorn==21.2.0
|
4 |
+
Flask-Session==0.5.0
|
templates/login.html
ADDED
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Secure Access - Balance Academy</title>
|
7 |
+
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
8 |
+
<style>
|
9 |
+
body {
|
10 |
+
font-family: 'Nunito', sans-serif;
|
11 |
+
background-color: #f3f4f6;
|
12 |
+
display: flex;
|
13 |
+
justify-content: center;
|
14 |
+
align-items: center;
|
15 |
+
height: 100vh;
|
16 |
+
margin: 0;
|
17 |
+
padding: 0;
|
18 |
+
}
|
19 |
+
|
20 |
+
.login-container {
|
21 |
+
background-color: white;
|
22 |
+
border-radius: 8px;
|
23 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
24 |
+
padding: 2rem;
|
25 |
+
width: 100%;
|
26 |
+
max-width: 400px;
|
27 |
+
text-align: center;
|
28 |
+
}
|
29 |
+
|
30 |
+
.login-logo {
|
31 |
+
font-size: 1.5rem;
|
32 |
+
font-weight: 700;
|
33 |
+
color: #0D6EFD;
|
34 |
+
margin-bottom: 1.5rem;
|
35 |
+
}
|
36 |
+
|
37 |
+
.login-title {
|
38 |
+
font-size: 1.25rem;
|
39 |
+
margin-bottom: 1rem;
|
40 |
+
color: #4b5563;
|
41 |
+
}
|
42 |
+
|
43 |
+
.login-description {
|
44 |
+
color: #6b7280;
|
45 |
+
margin-bottom: 1.5rem;
|
46 |
+
font-size: 0.875rem;
|
47 |
+
}
|
48 |
+
|
49 |
+
.login-button {
|
50 |
+
background-color: #0D6EFD;
|
51 |
+
color: white;
|
52 |
+
border: none;
|
53 |
+
border-radius: 4px;
|
54 |
+
padding: 0.75rem 1.5rem;
|
55 |
+
font-weight: 600;
|
56 |
+
cursor: pointer;
|
57 |
+
transition: background-color 0.2s;
|
58 |
+
width: 100%;
|
59 |
+
font-size: 1rem;
|
60 |
+
}
|
61 |
+
|
62 |
+
.login-button:hover {
|
63 |
+
background-color: #0b5ed7;
|
64 |
+
}
|
65 |
+
|
66 |
+
.terms {
|
67 |
+
margin-top: 1.5rem;
|
68 |
+
font-size: 0.75rem;
|
69 |
+
color: #6b7280;
|
70 |
+
}
|
71 |
+
|
72 |
+
.disclaimer {
|
73 |
+
margin-top: 1rem;
|
74 |
+
font-size: 0.75rem;
|
75 |
+
color: #ef4444;
|
76 |
+
}
|
77 |
+
</style>
|
78 |
+
</head>
|
79 |
+
<body>
|
80 |
+
<div class="login-container">
|
81 |
+
<div class="login-logo">Balance Academy</div>
|
82 |
+
<h1 class="login-title">Secure Access Portal</h1>
|
83 |
+
<p class="login-description">
|
84 |
+
This content is protected and requires secure access. By continuing, you agree not to download, copy, or distribute this content.
|
85 |
+
</p>
|
86 |
+
<a href="/view/{{ token }}">
|
87 |
+
<button class="login-button">Access Secure Content</button>
|
88 |
+
</a>
|
89 |
+
<p class="terms">
|
90 |
+
By accessing this content, you agree to our terms of use and privacy policy.
|
91 |
+
Your access is being logged and monitored.
|
92 |
+
</p>
|
93 |
+
<p class="disclaimer">
|
94 |
+
WARNING: Attempts to bypass security measures or extract content are prohibited and may result in legal action.
|
95 |
+
</p>
|
96 |
+
</div>
|
97 |
+
|
98 |
+
<script>
|
99 |
+
// Disable right-click
|
100 |
+
document.addEventListener('contextmenu', e => e.preventDefault());
|
101 |
+
|
102 |
+
// Disable keyboard shortcuts
|
103 |
+
document.addEventListener('keydown', function(e) {
|
104 |
+
if ((e.ctrlKey || e.metaKey) &&
|
105 |
+
(e.keyCode === 83 || e.keyCode === 80 || e.keyCode === 85)) {
|
106 |
+
e.preventDefault();
|
107 |
+
return false;
|
108 |
+
}
|
109 |
+
});
|
110 |
+
</script>
|
111 |
+
</body>
|
112 |
+
</html>
|