password requirements and opengraph for discord,slack etc

This commit is contained in:
spitkov 2024-09-16 11:49:09 +02:00
parent 79d0adfcf8
commit 740300edaf
7 changed files with 218 additions and 80 deletions

66
app.py
View File

@ -264,20 +264,27 @@ def redirect_vanity(vanity, password=None):
return render_template('password_prompt.html', vanity=vanity, error=None)
if content_type == 'url':
return redirect(content_data)
return render_template('og_shorturl.html', long_url=content_data, username=username, created_at=created_at)
elif content_type == 'file':
file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], content_data)
app.logger.info(f"Attempting to serve file: {file_path}")
file_path = os.path.join(app.config['UPLOAD_FOLDER'], content_data)
if os.path.exists(file_path):
if 'download' in request.path:
return send_file(file_path, as_attachment=True)
else:
return send_file(file_path)
else:
app.logger.error(f"File not found: {file_path}")
return "File not found", 404
file_size = os.path.getsize(file_path)
file_extension = os.path.splitext(content_data)[1].lower()
is_image = file_extension in ['.jpg', '.jpeg', '.png', '.gif']
return render_template('og_file.html',
filename=content_data,
file_size=file_size,
username=username,
created_at=created_at,
is_image=is_image,
file_url=url_for('redirect_vanity', vanity=vanity, _external=True))
elif content_type == 'pastebin':
return render_pastebin(content_data, created_at, user_id, username, vanity, is_private)
first_lines = '\n'.join(content_data.split('\n')[:5]) # Get first 5 lines
return render_template('og_pastebin.html',
vanity=vanity,
first_lines=first_lines,
username=username,
created_at=created_at)
app.logger.error(f"Content not found for vanity: {vanity}")
return "Not found", 404
@ -330,17 +337,6 @@ def raw_vanity(vanity, password=None):
return content_data, 200, {'Content-Type': 'text/plain; charset=utf-8'}
return 'Not Found', 404
# Replace the LoginForm and RegistrationForm classes with simple classes
class LoginForm:
def __init__(self, username, password, remember):
self.username = username
self.password = password
self.remember = remember
class RegistrationForm:
def __init__(self, username, password):
self.username = username
self.password = password
@app.route('/login', methods=['GET', 'POST'])
def login():
@ -348,16 +344,15 @@ def login():
username = request.form['username']
password = request.form['password']
remember = 'remember' in request.form
form = LoginForm(username, password, remember)
db = get_db()
cursor = db.cursor()
cursor.execute("SELECT * FROM users WHERE username = ?", (form.username,))
cursor.execute("SELECT * FROM users WHERE username = ?", (username,))
user = cursor.fetchone()
if user and User.verify_password(user[2], form.password):
if user and User.verify_password(user[2], password):
user_obj = User(user[0], user[1], user[2], user[3])
login_user(user_obj, remember=form.remember)
return redirect(url_for('user_files', username=form.username))
return "Invalid username or password"
login_user(user_obj, remember=remember)
return jsonify({'success': True, 'redirect': url_for('user_files', username=username)})
return jsonify({'success': False, 'error': 'Invalid username or password'})
return render_template('login.html')
@app.route('/register', methods=['GET', 'POST'])
@ -365,23 +360,26 @@ def register():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
api_key = User.generate_api_key() # Generate API key
if len(password) < 5 or not any(c.isupper() for c in password):
return jsonify({'success': False, 'error': 'Password does not meet requirements'})
api_key = User.generate_api_key()
db = get_db()
cursor = db.cursor()
cursor.execute("SELECT * FROM users WHERE username = ?", (username,))
if cursor.fetchone():
return "Username already exists"
return jsonify({'success': False, 'error': 'Username already exists'})
hashed_password = User.hash_password(password)
cursor.execute("INSERT INTO users (username, password_hash, api_key) VALUES (?, ?, ?)",
(username, hashed_password, api_key))
db.commit()
# Create user directory
user_folder = os.path.join(app.config['UPLOAD_FOLDER'], username)
if not os.path.exists(user_folder):
os.makedirs(user_folder)
return redirect(url_for('login'))
return jsonify({'success': True, 'redirect': url_for('login')})
return render_template('register.html')
@app.route('/logout')
@ -819,7 +817,7 @@ def generate_sharex_config():
base_url = request.url_root.replace('http://', 'https://', 1).rstrip('/')
config = {
"Version": "13.7.0",
"Name": "aCloud",
"Name": "sxbin",
"DestinationType": "ImageUploader, TextUploader, FileUploader, URLShortener",
"RequestMethod": "POST",
"RequestURL": f"{base_url}/api/upload",
@ -836,7 +834,7 @@ def generate_sharex_config():
response = make_response(json.dumps(config, indent=2))
response.headers.set('Content-Type', 'application/json')
response.headers.set('Content-Disposition', 'attachment', filename='aCloud_ShareX.sxcu')
response.headers.set('Content-Disposition', 'attachment', filename='sxbin_ShareX.sxcu')
return response
@app.route('/api/upload', methods=['POST'])

View File

@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>aCloud - File Sharing Service</title>
<title>sxbin - File Sharing Service</title>
<style>
body {
font-family: Arial, sans-serif;
@ -110,6 +110,10 @@
font-size: 1.2em;
margin-bottom: 20px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.cursor {
display: inline-block;
@ -399,7 +403,7 @@
<script>
document.addEventListener("DOMContentLoaded", function() {
const message1 = "Welcome to aCloud.";
const message1 = "Welcome to sxbin.gay";
const message2 = "\nA simple toolbox for file uploading,\nURL shortening and pastebin.";
const typewriterTextElement = document.getElementById('typewriter-text');
const cursorElement = document.getElementById('cursor');

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - aCloud</title>
<title>Login - sxbin</title>
<style>
body {
font-family: Arial, sans-serif;
@ -97,37 +97,54 @@
background-color: #2a2a2a;
color: #f0f0f0;
}
.error-message {
background-color: #ff6b6b;
color: white;
padding: 10px;
border-radius: 5px;
margin-bottom: 10px;
display: none;
}
</style>
</head>
<body>
<a href="/" class="back-button">&#8592;</a>
<div class="content">
<div class="container">
<h2>Login</h2>
<form method="POST">
<div>
<h2>Login to sxbin</h2>
<div id="error-message" class="error-message"></div>
<form id="login-form" method="POST">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<div class="remember-me">
<input type="checkbox" id="remember" name="remember">
<label for="remember">Remember me</label>
</div>
<button type="submit">Login</button>
<label>
<input type="checkbox" name="remember"> Remember me
</label>
<input type="submit" value="Login">
</form>
<p>Don't have an account? <a href="{{ url_for('register') }}">Register here</a></p>
<p>Don't have an account? <a href="{{ url_for('register') }}">Register</a></p>
</div>
</div>
<footer class="footer">
<p>
Source code: <a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git</a> |
<a href="https://office.bence.lol/form/#/2/form/view/z5Cf3CL6tZtPjzKsbcEPync6JE3iyMl22h6thUQg1a4/" target="_blank">Suggestions & Bugs</a> |
<a href="https://office.bence.lol/kanban/#/2/kanban/view/hx6RTcpN0pR7hc1HHkMzG4awMoMdHjR2zbHjG7Xh+wU/embed/" target="_blank">Todo List</a>
</p>
</footer>
<script>
document.getElementById('login-form').addEventListener('submit', function(e) {
e.preventDefault();
fetch('/login', {
method: 'POST',
body: new FormData(this),
})
.then(response => response.json())
.then(data => {
if (data.success) {
window.location.href = data.redirect;
} else {
document.getElementById('error-message').textContent = data.error;
document.getElementById('error-message').style.display = 'block';
}
});
});
</script>
</body>
</html>

26
templates/og_file.html Normal file
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ filename }} - sxbin</title>
<meta property="og:title" content="{{ filename }}">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ request.url }}">
<meta property="og:description" content="File size: {{ file_size|filesizeformat }} | Uploaded by: {{ username }} | Date: {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}">
{% if is_image %}
<meta property="og:image" content="{{ file_url }}">
{% endif %}
<meta property="og:site_name" content="sxbin">
<meta property="theme-color" content="#4CAF50">
</head>
<body>
<h1>{{ filename }}</h1>
<p>File size: {{ file_size|filesizeformat }}</p>
<p>Uploaded by: {{ username }}</p>
<p>Date: {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}</p>
{% if is_image %}
<img src="{{ file_url }}" alt="{{ filename }}">
{% endif %}
</body>
</html>

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pastebin {{ vanity }} - sxbin</title>
<meta property="og:title" content="Pastebin {{ vanity }}">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ request.url }}">
<meta property="og:description" content="{{ first_lines|truncate(200) }}">
<meta property="og:site_name" content="sxbin">
<meta property="theme-color" content="#4CAF50">
</head>
<body>
<h1>Pastebin {{ vanity }}</h1>
<pre>{{ first_lines }}</pre>
<p>Created by: {{ username }}</p>
<p>Date: {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}</p>
</body>
</html>

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shortened URL - sxbin</title>
<meta property="og:title" content="Shortened URL">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ request.url }}">
<meta property="og:description" content="Shortened link created by {{ username }} on {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}">
<meta property="og:site_name" content="sxbin">
<meta property="theme-color" content="#4CAF50">
<meta http-equiv="refresh" content="0;url={{ long_url }}">
</head>
<body>
<h1>Shortened URL</h1>
<p>Redirecting to: {{ long_url }}</p>
<p>Created by: {{ username }}</p>
<p>Date: {{ created_at.strftime('%Y-%m-
%d %H:%M:%S') }}</p>
</body>
</html>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register - aCloud</title>
<title>Register - sxbin</title>
<style>
body {
font-family: Arial, sans-serif;
@ -89,33 +89,83 @@
background-color: #2a2a2a;
color: #f0f0f0;
}
.error-message {
background-color: #ff6b6b;
color: white;
padding: 10px;
border-radius: 5px;
margin-bottom: 10px;
display: none;
}
.password-requirement {
font-size: 0.9em;
margin-top: 5px;
transition: color 0.3s ease;
}
.requirement-not-met {
color: #ff6b6b;
}
.requirement-met {
color: #4CAF50;
}
</style>
</head>
<body>
<a href="/" class="back-button">&#8592;</a>
<div class="content">
<div class="container">
<h2>Register</h2>
<form method="POST">
<div>
<h2>Register for sxbin</h2>
<div id="error-message" class="error-message"></div>
<form id="register-form" method="POST">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit">Register</button>
<div id="password-length" class="password-requirement requirement-not-met">At least 5 characters</div>
<div id="password-uppercase" class="password-requirement requirement-not-met">At least one uppercase letter</div>
<input type="submit" value="Register" id="submit-button" disabled>
</form>
<p>Already have an account? <a href="{{ url_for('login') }}">Login here</a></p>
<p>Already have an account? <a href="{{ url_for('login') }}">Login</a></p>
</div>
</div>
<footer class="footer">
<p>
Source code: <a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git</a> |
<a href="https://office.bence.lol/form/#/2/form/view/z5Cf3CL6tZtPjzKsbcEPync6JE3iyMl22h6thUQg1a4/" target="_blank">Suggestions & Bugs</a> |
<a href="https://office.bence.lol/kanban/#/2/kanban/view/hx6RTcpN0pR7hc1HHkMzG4awMoMdHjR2zbHjG7Xh+wU/embed/" target="_blank">Todo List</a>
</p>
</footer>
<script>
const passwordInput = document.getElementById('password');
const lengthRequirement = document.getElementById('password-length');
const uppercaseRequirement = document.getElementById('password-uppercase');
const submitButton = document.getElementById('submit-button');
passwordInput.addEventListener('input', function() {
const password = this.value;
const meetsLengthRequirement = password.length >= 5;
const meetsUppercaseRequirement = /[A-Z]/.test(password);
lengthRequirement.className = meetsLengthRequirement ? 'password-requirement requirement-met' : 'password-requirement requirement-not-met';
uppercaseRequirement.className = meetsUppercaseRequirement ? 'password-requirement requirement-met' : 'password-requirement requirement-not-met';
submitButton.disabled = !(meetsLengthRequirement && meetsUppercaseRequirement);
});
document.getElementById('register-form').addEventListener('submit', function(e) {
e.preventDefault();
fetch('/register', {
method: 'POST',
body: new FormData(this),
})
.then(response => response.json())
.then(data => {
if (data.success) {
window.location.href = data.redirect;
} else {
document.getElementById('error-message').textContent = data.error;
document.getElementById('error-message').style.display = 'block';
}
});
});
</script>
</body>
</html>