Added signup form and functionality

This commit is contained in:
2025-02-17 18:02:07 +01:00
parent 809b9dc638
commit 93bb9335a2
4 changed files with 165 additions and 37 deletions

View File

@ -3,6 +3,7 @@ from flask_login import login_user, logout_user
from app.models.users import User from app.models.users import User
from datetime import datetime, timedelta from datetime import datetime, timedelta
from app.models import db from app.models import db
import re
import sys import sys
@ -43,3 +44,40 @@ def logout():
logout_user() logout_user()
flash('You have been logged out.', 'info') flash('You have been logged out.', 'info')
return redirect(url_for('main.login_route')) return redirect(url_for('main.login_route'))
def signup():
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')
# Server-side validation
if len(username) < 5:
flash('Username must be at least 5 characters long', 'danger')
return redirect(url_for('main.login_route'))
email_regex = r'^[a-zA-Z][a-zA-Z0-9._-]*@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(email_regex, email):
flash('Please enter a valid email address', 'danger')
return redirect(url_for('main.login_route'))
password_regex = r'^(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{8,}$'
if not re.match(password_regex, password):
flash('Password must be at least 8 characters long and contain a number and a special character', 'danger')
return redirect(url_for('main.login_route'))
# Check if username or email already exists
if User.query.filter_by(username=username).first():
flash('Username already exists', 'danger')
return redirect(url_for('main.login_route'))
if User.query.filter_by(email=email).first():
flash('Email already exists', 'danger')
return redirect(url_for('main.login_route'))
# Create new user
new_user = User(username=username, email=email, password=password)
db.session.add(new_user)
db.session.commit()
flash('Account created successfully! Please login.', 'success')
return redirect(url_for('main.login_route'))

View File

@ -7,4 +7,5 @@ class User(db.Model, UserMixin):
username = db.Column(db.String(80), unique=True, nullable=False) username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False) email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False) password = db.Column(db.String(120), nullable=False)
failed_login_attempts = db.Column(db.Integer, default=0, nullable=False)
last_failed_login_attempt = db.Column(db.DateTime, default=None, nullable=True)

View File

@ -1,8 +1,9 @@
from flask import Blueprint from flask import Blueprint
from flask_login import login_required from flask_login import login_required
from app.controllers.auth_controller import login, logout from app.controllers.auth_controller import login, logout, signup
from app.controllers.home_controller import home from app.controllers.home_controller import home
main = Blueprint('main', __name__) main = Blueprint('main', __name__)
@main.route('/') @main.route('/')
@ -17,3 +18,7 @@ def login_route():
@login_required @login_required
def logout_route(): def logout_route():
return logout() return logout()
@main.route('/signup', methods=['POST'])
def signup_route():
return signup()

View File

@ -50,8 +50,8 @@
border: 1px solid #D9D9D9; border: 1px solid #D9D9D9;
border-radius: 6px; border-radius: 6px;
background-color: #D9D9D9; background-color: #D9D9D9;
color: white; color: rgb(0, 0, 0);
font-size: 16px; font-size: 20px;
box-sizing: border-box; box-sizing: border-box;
} }
@ -81,19 +81,6 @@
letter-spacing: 3px; letter-spacing: 3px;
} }
@media (max-width: 768px) {
.login-container {
width: 95%;
padding: 20px;
}
}
.error-message p {
color: red;
}
.signup-container { .signup-container {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -105,6 +92,19 @@
color: #8f8f8f; color: #8f8f8f;
margin-top: 14px; margin-top: 14px;
align-items: center; align-items: center;
cursor: pointer;
}
.signup u:hover {
color: #fff;
}
.hidden {
display: none;
}
.error-message p {
color: red;
} }
</style> </style>
{% endblock %} {% endblock %}
@ -112,30 +112,57 @@
{% block content %} {% block content %}
<div class="spotlight"></div> <div class="spotlight"></div>
<div class="container"> <div class="container">
<div class="login-signup-container">
</div>
<div class="login-container"> <div class="login-container">
<h2>Login</h2> <!-- Login Form -->
<p class="login-description">Enter your credentials below to login to your account</p> <div id="loginForm">
<h2>Login</h2>
<p class="login-description">Enter your credentials below to login to your account</p>
<form method="POST" action="{{ url_for('main.login_route') }}"> <form method="POST" action="{{ url_for('main.login_route') }}" id="login-form">
<div class="form-group"> <div class="form-group">
<label for="username">Username</label> <label for="username">Username</label>
<input type="text" id="username" name="username" required> <input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required class="password-input">
</div>
<button type="submit">Login</button>
</form>
<div class="signup-container">
<p class="signup">Don't have an account? <u onclick="toggleForms()">Sign up</u></p>
</div> </div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required class="password-input">
</div>
<button type="submit">Login</button>
</form>
<div class="signup-container">
<p class="signup">Dont have an account? <u>Sign up</u></p>
</div> </div>
<!-- Signup Form -->
<div id="signupForm" class="hidden">
<h2>Sign Up</h2>
<p class="login-description">Create your account to get started</p>
<form method="POST" action="{{ url_for('main.signup_route') }}" id="signup-form" onsubmit="return validateForm()">
<div class="form-group">
<label for="signup-username">Username</label>
<input type="text" id="signup-username" name="username" required>
</div>
<div class="form-group">
<label for="signup-email">Email</label>
<input type="email" id="signup-email" name="email" required>
</div>
<div class="form-group">
<label for="signup-password">Password</label>
<input type="password" id="signup-password" name="password" required class="password-input">
</div>
<button type="submit">Sign Up</button>
</form>
<div class="signup-container">
<p class="signup">Already have an account? <u onclick="toggleForms()">Login</u></p>
</div>
</div>
{% with messages = get_flashed_messages() %} {% with messages = get_flashed_messages() %}
{% if messages %} {% if messages %}
@ -148,4 +175,61 @@
{% endwith %} {% endwith %}
</div> </div>
</div> </div>
<script>
function toggleForms() {
const loginForm = document.getElementById('loginForm');
const signupForm = document.getElementById('signupForm');
loginForm.classList.toggle('hidden');
signupForm.classList.toggle('hidden');
}
function validateForm() {
const username = document.getElementById('signup-username').value;
const email = document.getElementById('signup-email').value;
const password = document.getElementById('signup-password').value;
// Username validation
if (username.length < 5) {
flash('Username must be at least 5 characters long');
return false;
}
// Email validation
const emailRegex = /^[a-zA-Z][a-zA-Z0-9._-]*@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailRegex.test(email)) {
flash('Please enter a valid email address');
return false;
}
// Password validation
const passwordRegex = /^(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{8,}$/;
if (!passwordRegex.test(password)) {
flash('Password must be at least 8 characters long and contain a number and a special character');
return false;
}
return true;
}
function flash(message) {
const errorDiv = document.querySelector('.error-message');
if (!errorDiv) {
const newErrorDiv = document.createElement('div');
newErrorDiv.className = 'error-message';
const p = document.createElement('p');
p.className = 'text-danger';
p.textContent = message;
newErrorDiv.appendChild(p);
document.querySelector('.login-container').appendChild(newErrorDiv);
} else {
const p = errorDiv.querySelector('p') || document.createElement('p');
p.className = 'text-danger';
p.textContent = message;
if (!errorDiv.contains(p)) {
errorDiv.appendChild(p);
}
}
}
</script>
{% endblock %} {% endblock %}