mirror of
https://github.com/StefBuwalda/ProjectIOT.git
synced 2025-10-30 11:19: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
|
# Default app route
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def home():
|
def home():
|
||||||
return redirect(url_for("auth.demo"))
|
return redirect(url_for("auth.login"))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from application import db, app
|
from application import db, app
|
||||||
from application.dashboard.models import AllowedPlate, LoggedItem, datetime
|
from application.dashboard.models import AllowedPlate, LoggedItem, datetime
|
||||||
|
from application.auth.models import User
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
AllowedPlate.query.delete()
|
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(), True))
|
||||||
db.session.add(LoggedItem("MUN389", datetime.now(), False))
|
db.session.add(LoggedItem("MUN389", datetime.now(), False))
|
||||||
db.session.commit()
|
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 import Flask
|
||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_login import LoginManager
|
||||||
|
|
||||||
# from authlib.integrations.flask_client import OAuth
|
# from authlib.integrations.flask_client import OAuth
|
||||||
|
|
||||||
@@ -16,6 +17,20 @@ db = SQLAlchemy(app)
|
|||||||
|
|
||||||
migrate = Migrate(app, db)
|
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
|
# Keycloak
|
||||||
"""
|
"""
|
||||||
oauth = OAuth(app=app)
|
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
|
# from application import keycloak
|
||||||
|
|
||||||
auth_blueprint = Blueprint("auth", __name__, template_folder="templates")
|
auth_blueprint = Blueprint("auth", __name__, template_folder="templates")
|
||||||
|
|
||||||
|
|
||||||
@auth_blueprint.route("/demo")
|
# Login as user or admin
|
||||||
def demo():
|
@auth_blueprint.route("/login", methods=["GET", "POST"])
|
||||||
return render_template("login.html")
|
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("/")
|
@auth_blueprint.route("/logout")
|
||||||
def home():
|
@login_required
|
||||||
user = session.get("user")
|
def logout():
|
||||||
if user:
|
logout_user()
|
||||||
return f'Hello, {user["name"]}'
|
return redirect("/")
|
||||||
return redirect(url_for("auth.login"))
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Logout
|
||||||
|
@auth_blueprint.route("/logout")
|
||||||
|
@login_required
|
||||||
|
def logout():
|
||||||
|
logout_user()
|
||||||
|
flash("Logged out succesfully")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@auth_blueprint.route("/login")
|
@auth_blueprint.route("/login")
|
||||||
def login():
|
def login():
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
@@ -7,16 +8,17 @@
|
|||||||
<title>Admin Dashboard</title>
|
<title>Admin Dashboard</title>
|
||||||
<script defer src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/js/bootstrap.bundle.min.js"></script>
|
<script defer src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
body.dark-mode {
|
body.dark-mode {
|
||||||
background-color: #181a1b !important;
|
background-color: #181a1b !important;
|
||||||
color: #e0e0e0 !important;
|
color: #e0e0e0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-mode .card,
|
.dark-mode .card,
|
||||||
.dark-mode .table {
|
.dark-mode .table {
|
||||||
background-color: #23272b !important;
|
background-color: #23272b !important;
|
||||||
color: #e0e0e0 !important;
|
color: #e0e0e0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-mode .table thead,
|
.dark-mode .table thead,
|
||||||
.dark-mode .table tbody,
|
.dark-mode .table tbody,
|
||||||
.dark-mode .table th,
|
.dark-mode .table th,
|
||||||
@@ -26,30 +28,36 @@ body.dark-mode {
|
|||||||
color: #e0e0e0 !important;
|
color: #e0e0e0 !important;
|
||||||
border-color: #444 !important;
|
border-color: #444 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-mode .card-title,
|
.dark-mode .card-title,
|
||||||
.dark-mode .display-4,
|
.dark-mode .display-4,
|
||||||
.dark-mode h5 {
|
.dark-mode h5 {
|
||||||
color: #e0e0e0 !important;
|
color: #e0e0e0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-mode input {
|
.dark-mode input {
|
||||||
background-color: #23272b !important;
|
background-color: #23272b !important;
|
||||||
color: #e0e0e0 !important;
|
color: #e0e0e0 !important;
|
||||||
border: 1px solid #444 !important;
|
border: 1px solid #444 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-mode .btn {
|
.dark-mode .btn {
|
||||||
background-color: #444 !important;
|
background-color: #444 !important;
|
||||||
color: #e0e0e0 !important;
|
color: #e0e0e0 !important;
|
||||||
border-color: #666 !important;
|
border-color: #666 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-mode .btn:hover {
|
.dark-mode .btn:hover {
|
||||||
background-color: #222 !important;
|
background-color: #222 !important;
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-mode .logout-btn {
|
.dark-mode .logout-btn {
|
||||||
background-color: #212529 !important;
|
background-color: #212529 !important;
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
border-color: #212529 !important;
|
border-color: #212529 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-mode .logout-btn:hover {
|
.dark-mode .logout-btn:hover {
|
||||||
background-color: #353738 !important;
|
background-color: #353738 !important;
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
@@ -58,6 +66,7 @@ body.dark-mode {
|
|||||||
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -65,7 +74,8 @@ body.dark-mode {
|
|||||||
<div class="position-sticky pt-3">
|
<div class="position-sticky pt-3">
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
<li class="nav-item">
|
<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;">
|
<hr style="border: 1px solid white; margin-top: 0;">
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
@@ -78,8 +88,10 @@ body.dark-mode {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="d-flex justify-content-center mt-3">
|
<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>
|
<a href="{{ url_for('auth.logout') }}" class=" btn btn-sm btn-dark logout-btn"
|
||||||
<button id="darkModeToggle" class="btn btn-secondary btn-sm" style="position: fixed; top: 10px; right: 10px; z-index: 9999;">
|
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
|
Toggle Theme
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -108,4 +120,5 @@ if(localStorage.getItem('darkMode') === 'enabled') {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -2,12 +2,13 @@ from flask import Blueprint, render_template, request, jsonify
|
|||||||
from application.dashboard.models import AllowedPlate, LoggedItem
|
from application.dashboard.models import AllowedPlate, LoggedItem
|
||||||
from application import db
|
from application import db
|
||||||
from application.dashboard.forms import npForm
|
from application.dashboard.forms import npForm
|
||||||
|
from flask_login import login_required
|
||||||
|
|
||||||
dash_blueprint = Blueprint("dash", __name__, template_folder="templates")
|
dash_blueprint = Blueprint("dash", __name__, template_folder="templates")
|
||||||
|
|
||||||
|
|
||||||
@dash_blueprint.route("/dashboard")
|
@dash_blueprint.route("/dashboard")
|
||||||
# @login_required
|
@login_required
|
||||||
def dashboard():
|
def dashboard():
|
||||||
Plates = AllowedPlate.query.all()
|
Plates = AllowedPlate.query.all()
|
||||||
logs = (
|
logs = (
|
||||||
@@ -17,7 +18,7 @@ def dashboard():
|
|||||||
|
|
||||||
|
|
||||||
@dash_blueprint.route("/add", methods=["GET", "POST"])
|
@dash_blueprint.route("/add", methods=["GET", "POST"])
|
||||||
# @login_required
|
@login_required
|
||||||
def add():
|
def add():
|
||||||
Plates = AllowedPlate.query.all()
|
Plates = AllowedPlate.query.all()
|
||||||
form = npForm()
|
form = npForm()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ blinker==1.9.0
|
|||||||
click==8.2.1
|
click==8.2.1
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
Flask==3.1.1
|
Flask==3.1.1
|
||||||
|
Flask-Login==0.6.3
|
||||||
Flask-Migrate==4.1.0
|
Flask-Migrate==4.1.0
|
||||||
Flask-SQLAlchemy==3.1.1
|
Flask-SQLAlchemy==3.1.1
|
||||||
Flask-WTF==1.2.2
|
Flask-WTF==1.2.2
|
||||||
|
|||||||
Reference in New Issue
Block a user