Added login functionality and login required

This commit is contained in:
2025-05-31 12:05:53 +02:00
parent c30d32b10f
commit 1ac625ccbd
10 changed files with 251 additions and 79 deletions

2
app.py
View File

@@ -12,7 +12,7 @@ app.register_blueprint(dash_blueprint, url_prefix="/dash")
# Default app route
@app.route("/")
def home():
return redirect(url_for("auth.demo"))
return redirect(url_for("auth.login"))
if __name__ == "__main__":

View File

@@ -1,5 +1,6 @@
from application import db, app
from application.dashboard.models import AllowedPlate, LoggedItem, datetime
from application.auth.models import User
with app.app_context():
AllowedPlate.query.delete()
@@ -11,3 +12,8 @@ with app.app_context():
db.session.add(LoggedItem("MUN389", datetime.now(), True))
db.session.add(LoggedItem("MUN389", datetime.now(), False))
db.session.commit()
with app.app_context():
User.query.delete()
db.session.add(User(username="admin", password="admin"))
db.session.commit()

View File

@@ -1,6 +1,7 @@
from flask import Flask
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
# from authlib.integrations.flask_client import OAuth
@@ -16,6 +17,20 @@ db = SQLAlchemy(app)
migrate = Migrate(app, db)
# Login manager
from application.auth.models import User
login_manager = LoginManager()
login_manager.init_app(app) # type: ignore
login_manager.login_view = "auth.login" # type: ignore
# Gets all the user data
@login_manager.user_loader # type: ignore
def load_user(user_id): # type: ignore
return User.query.get(int(user_id)) # type: ignore
# Keycloak
"""
oauth = OAuth(app=app)

View File

@@ -0,0 +1,9 @@
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField
from wtforms.validators import DataRequired
class login_form(FlaskForm):
username = StringField("Username", validators=[DataRequired()])
password = PasswordField("Password", validators=[DataRequired()])
submit = SubmitField(label="Sign in")

View File

@@ -0,0 +1,19 @@
from application import db
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
# User model
class User(db.Model, UserMixin):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(150), unique=True, nullable=False)
password = db.Column(db.String, nullable=False)
# Initialize user, prevents red stuff
def __init__(self, username: str, password: str, is_admin: bool = False):
self.username = username
self.password = generate_password_hash(password)
def check_password(self, password: str):
return check_password_hash(self.password, password=password)

View File

@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7" crossorigin="anonymous">
<title>Login</title>
<style>
.rounded-input {
border-radius: 20px;
}
.split-background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 50vh;
background-color: #424D66;
z-index: -1;
}
@keyframes moveLeftRight {
0% {
transform: translateX(0);
}
100% {
transform: translateX(1366px);
}
}
.logo {
margin-top: 70px;
width: 770px;
height: auto;
}
.car-image-container {
position: fixed;
bottom: 33px;
}
.animate {
animation: moveLeftRight 15s infinite alternate;
}
</style>
</head>
<body>
<div class="split-background">
<div class="container-fluid text-center">
<img src="{{ url_for('static', filename='images/logo-light.png') }}" alt="Logo" class="logo img-fluid mt-5">
</div>
</div>
<div class="container d-flex justify-content-center align-items-center" style="min-height: 100vh;">
<div class="col-md-6 col-lg-4">
<form method="POST" class="p-5 border rounded-input shadow-sm bg-white bg-opacity-75">
{{form.hidden_tag()}}
<div class=" p-4 border rounded-input shadow-sm bg-white">
<label for="username" class="form-label text-center w-100">Username</label>
{{form.username(class="form-control rounded-input")}}
<label for="password" class="form-label text-center w-100">Password</label>
{{form.password(class="form-control rounded-input")}}
<br>
<div class="d-grid">
{{form.submit(class="btn btn-dark rounded-input px-4 mx-auto w-50")}}
</div>
</div>
</form>
</div>
</div>
<div class="car-image-container">
<img src="{{ url_for('static', filename='images/car.png') }}" alt="Moving Image" class="animate">
</div>
<footer class="py-3 bg-dark text-white fixed-bottom"">
<div class=" container text-center">
<span class="text-muted"> </span>
</div>
</footer>
</body>
</html>

View File

@@ -1,23 +1,46 @@
from flask import Blueprint, session, redirect, url_for, render_template
from flask import Blueprint, redirect, url_for, render_template
from application.auth.forms import login_form
from application.auth.models import User
from flask_login import login_user, login_required, logout_user
# from application import keycloak
auth_blueprint = Blueprint("auth", __name__, template_folder="templates")
@auth_blueprint.route("/demo")
def demo():
return render_template("login.html")
# Login as user or admin
@auth_blueprint.route("/login", methods=["GET", "POST"])
def login():
loginForm = login_form()
if loginForm.validate_on_submit():
print("Test2")
username = loginForm.username.data
password = loginForm.password.data
user = User.query.filter_by(username=username).first()
if user and user.check_password(password=password):
login_user(user)
return redirect(url_for("dash.dashboard"))
return render_template("login.html", form=loginForm)
@auth_blueprint.route("/")
def home():
user = session.get("user")
if user:
return f'Hello, {user["name"]}'
return redirect(url_for("auth.login"))
@auth_blueprint.route("/logout")
@login_required
def logout():
logout_user()
return redirect("/")
"""
# Logout
@auth_blueprint.route("/logout")
@login_required
def logout():
logout_user()
flash("Logged out succesfully")
return redirect(url_for("index"))
"""
"""
@auth_blueprint.route("/login")
def login():

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -7,57 +8,65 @@
<title>Admin Dashboard</title>
<script defer src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/js/bootstrap.bundle.min.js"></script>
<style>
body.dark-mode {
background-color: #181a1b !important;
color: #e0e0e0 !important;
}
body.dark-mode {
background-color: #181a1b !important;
color: #e0e0e0 !important;
}
.dark-mode .card,
.dark-mode .table {
background-color: #23272b !important;
color: #e0e0e0 !important;
}
.dark-mode .table thead,
.dark-mode .table tbody,
.dark-mode .table th,
.dark-mode .table td,
.dark-mode .table tr {
background-color: #23272b !important;
color: #e0e0e0 !important;
border-color: #444 !important;
}
.dark-mode .card-title,
.dark-mode .display-4,
.dark-mode h5 {
color: #e0e0e0 !important;
}
.dark-mode input {
background-color: #23272b !important;
color: #e0e0e0 !important;
border: 1px solid #444 !important;
}
.dark-mode .btn {
background-color: #444 !important;
color: #e0e0e0 !important;
border-color: #666 !important;
}
.dark-mode .btn:hover {
background-color: #222 !important;
color: #fff !important;
}
.dark-mode .logout-btn {
background-color: #212529 !important;
color: #fff !important;
border-color: #212529 !important;
}
.dark-mode .logout-btn:hover {
background-color: #353738 !important;
color: #fff !important;
}
</style>
.dark-mode .card,
.dark-mode .table {
background-color: #23272b !important;
color: #e0e0e0 !important;
}
.dark-mode .table thead,
.dark-mode .table tbody,
.dark-mode .table th,
.dark-mode .table td,
.dark-mode .table tr {
background-color: #23272b !important;
color: #e0e0e0 !important;
border-color: #444 !important;
}
.dark-mode .card-title,
.dark-mode .display-4,
.dark-mode h5 {
color: #e0e0e0 !important;
}
.dark-mode input {
background-color: #23272b !important;
color: #e0e0e0 !important;
border: 1px solid #444 !important;
}
.dark-mode .btn {
background-color: #444 !important;
color: #e0e0e0 !important;
border-color: #666 !important;
}
.dark-mode .btn:hover {
background-color: #222 !important;
color: #fff !important;
}
.dark-mode .logout-btn {
background-color: #212529 !important;
color: #fff !important;
border-color: #212529 !important;
}
.dark-mode .logout-btn:hover {
background-color: #353738 !important;
color: #fff !important;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
@@ -65,7 +74,8 @@ body.dark-mode {
<div class="position-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<img src="{{ url_for('static', filename='images/warpnet-logo.png') }}" alt="Logo" class="img-fluid mt-3 mb-3">
<img src="{{ url_for('static', filename='images/warpnet-logo.png') }}" alt="Logo"
class="img-fluid mt-3 mb-3">
<hr style="border: 1px solid white; margin-top: 0;">
</li>
<li class="nav-item">
@@ -78,34 +88,37 @@ body.dark-mode {
</li>
</ul>
<div class="d-flex justify-content-center mt-3">
<a href="/auth/demo" class="btn btn-sm btn-dark logout-btn" style="width: 200px;">Logout</a>
<button id="darkModeToggle" class="btn btn-secondary btn-sm" style="position: fixed; top: 10px; right: 10px; z-index: 9999;">
<a href="{{ url_for('auth.logout') }}" class=" btn btn-sm btn-dark logout-btn"
style="width: 200px;">Logout</a>
<button id="darkModeToggle" class="btn btn-secondary btn-sm"
style="position: fixed; top: 10px; right: 10px; z-index: 9999;">
Toggle Theme
</button>
</div>
</div>
</div>
{%block content%}
{%block content%}
{% endblock %}
</div>
{% endblock %}
</div>
</div>
</div>
<script>
document.getElementById('darkModeToggle').onclick = function() {
document.body.classList.toggle('dark-mode');
if(document.body.classList.contains('dark-mode')) {
localStorage.setItem('darkMode', 'enabled');
} else {
localStorage.setItem('darkMode', 'disabled');
}
};
document.getElementById('darkModeToggle').onclick = function () {
document.body.classList.toggle('dark-mode');
if (document.body.classList.contains('dark-mode')) {
localStorage.setItem('darkMode', 'enabled');
} else {
localStorage.setItem('darkMode', 'disabled');
}
};
if(localStorage.getItem('darkMode') === 'enabled') {
document.body.classList.add('dark-mode');
}
</script>
if (localStorage.getItem('darkMode') === 'enabled') {
document.body.classList.add('dark-mode');
}
</script>
</body>
</html>
</html>

View File

@@ -2,12 +2,13 @@ from flask import Blueprint, render_template, request, jsonify
from application.dashboard.models import AllowedPlate, LoggedItem
from application import db
from application.dashboard.forms import npForm
from flask_login import login_required
dash_blueprint = Blueprint("dash", __name__, template_folder="templates")
@dash_blueprint.route("/dashboard")
# @login_required
@login_required
def dashboard():
Plates = AllowedPlate.query.all()
logs = (
@@ -17,7 +18,7 @@ def dashboard():
@dash_blueprint.route("/add", methods=["GET", "POST"])
# @login_required
@login_required
def add():
Plates = AllowedPlate.query.all()
form = npForm()

View File

@@ -3,6 +3,7 @@ blinker==1.9.0
click==8.2.1
colorama==0.4.6
Flask==3.1.1
Flask-Login==0.6.3
Flask-Migrate==4.1.0
Flask-SQLAlchemy==3.1.1
Flask-WTF==1.2.2