mirror of
https://github.com/StefBuwalda/ProjectIOT.git
synced 2025-10-29 18:59:57 +00:00
Added login functionality and login required
This commit is contained in:
2
app.py
2
app.py
@@ -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__":
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
9
application/auth/forms.py
Normal file
9
application/auth/forms.py
Normal 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")
|
||||
19
application/auth/models.py
Normal file
19
application/auth/models.py
Normal 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)
|
||||
85
application/auth/templates/login.html
Normal file
85
application/auth/templates/login.html
Normal 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>
|
||||
@@ -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():
|
||||
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user