Working auth with dummy user

main
Micke Nordin 2 months ago
parent 109a11de30
commit c084b2c820
Signed by: micke
GPG Key ID: F53C4CC83EDAB3BE

@ -0,0 +1,13 @@
FROM debian:12-slim
RUN DEBIAN_FRONTEND=noninteractive apt update && apt install -y pipx
RUN useradd --add-subids-for-system --system --create-home --home-dir /app appuser
USER appuser
WORKDIR /app
ENV PATH /app/.local/bin:$PATH
COPY ./requirements.txt /app/requirements.txt
RUN pipx install gunicorn==21.2.0 && cat /app/requirements.txt | xargs pipx inject gunicorn
COPY ./ /app/
EXPOSE 8080
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8080", "app:app"]

@ -0,0 +1,60 @@
import os
from typing import Union
from flask import (Flask, flash, redirect, render_template, request,
send_from_directory, url_for)
from flask_login import LoginManager, login_required, login_user, logout_user
from forms import LoginForm
from lotosa import LoToSa
from user import User
app = Flask(__name__)
app.config.update(
SECRET_KEY=os.urandom(32),
SESSION_COOKIE_HTTPONLY=True,
REMEMBER_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE="Strict",
)
login_manager = LoginManager()
login_manager.init_app(app)
lotosa = LoToSa(app)
@login_manager.user_loader
def load_user(user_id) -> Union[User, None]:
for user in lotosa.get_users():
if user.uid == user_id:
return user
return None
@app.route('/', methods=['GET', 'POST'])
def index():
i18n = lotosa.get_i18n(request)
form = LoginForm()
if request.method == 'POST':
username = form.username.data
password = form.password.data
user = lotosa.login_user(username, password)
if user:
login_user(user)
flash('Logged in successfully.')
return redirect(url_for('admin'))
flash('Logged in faled, please try again.')
return render_template('index.html', i18n=i18n, form=form)
@app.route('/admin', methods=['GET'])
@login_required
def admin():
i18n = lotosa.get_i18n(request)
return render_template('admin.html', i18n=i18n)
@app.route('/favicon.ico')
def favicon():
return send_from_directory(os.path.join(app.root_path, 'static'),
'favicon.ico',
mimetype='image/vnd.microsoft.icon')

@ -0,0 +1,8 @@
from flask_wtf import FlaskForm
from wtforms import PasswordField, StringField, SubmitField
from wtforms.validators import DataRequired
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
submit = SubmitField('Submit')

@ -0,0 +1,5 @@
head:
title: LoToSA
body:
h1: LoToSA

@ -0,0 +1,42 @@
import glob
import sys
import yaml
from flask import Flask, request
from user import User
class LoToSa:
def __init__(self, app: Flask):
self.users = [
User(app, 'micke', 'Micke Nordin', 'hej@mic.ke', 'S3cr3t!')
]
def get_users(self):
return self.users
def login_user(self, username, password):
for user in self.users:
if user.get_id() == username and user.check_password(password):
user.set_authenticated(True)
user.set_active(True)
print(f'Logged in {user.get_id()}', file=sys.stderr)
return user
print(f'Login failed for {username}', file=sys.stderr)
return None
def get_i18n(self, request: request):
language_files = glob.glob("i18n/*.yaml")
languages = {}
for lang in language_files:
filename = lang.split('/')
lang_code = filename[1].split('.')[0]
with open(lang, 'r', encoding='utf8') as file:
languages[lang_code] = yaml.safe_load(file.read())
supported_languages = list(languages.keys())
user_language = request.accept_languages.best_match(
supported_languages)
return languages[user_language]

@ -0,0 +1,5 @@
flask==3.0.2
flask-login==0.6.3
flask-bcrypt==1.0.1
flask-wtf==1.2.1
pyyaml==6.0.1

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="{{url_for('static', filename='simple.min.css')}}">
<link rel="stylesheet" href="{{url_for('static', filename='main.css')}}">
<script src="{{url_for('static', filename='main.js')}}"></script>
<title>{{i18n.head.title}}</title>
<link rel="icon" href="{{url_for('static', filename='favicon.png')}}" />
</head>
<body>
<h1>{{i18n.body.h1}}</h1>
</body>
</html>

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="{{url_for('static', filename='simple.min.css')}}">
<link rel="stylesheet" href="{{url_for('static', filename='main.css')}}">
<script src="{{url_for('static', filename='main.js')}}"></script>
<title>{{i18n.head.title}}</title>
<link rel="icon" href="/favicon.ico" />
</head>
<body>
{% block content %}
<h1>{{i18n.body.h1}}</h1>
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}
</p>
<p>
{{ form.password.label }}<br>
{{ form.password(size=32) }}
</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}
</body>
</html>

@ -0,0 +1,48 @@
from flask import Flask
from flask_bcrypt import Bcrypt
class User:
def __init__(self, app: Flask, uid: str, display_name: str, email:str, password: str, admin: bool = False):
self.uid = uid
self.display_name = display_name
self.email = email
self.is_admin = admin
self.is_authenticated = False
self.is_active = False
self.is_anonymous = False
self.bcrypt = Bcrypt(app)
self.salt = self.get_salt()
self.password_hash = self.bcrypt.generate_password_hash(password + self.salt).decode('utf-8')
def check_password(self, password: str):
return self.bcrypt.check_password_hash(self.password_hash, password + self.salt)
def get_id(self):
return self.uid
def get_display_name(self):
return self.display_name
def get_email(self):
return self.email
def get_salt(self):
return "salt"
def set_active(self, active: bool):
self.is_active = active
def set_authenticated(self, authenticated: bool):
self.is_authenticated = authenticated
def set_anonymous(self, anonymous: bool):
self.is_anonymous = anonymous
def set_admin(self, admin: bool):
self.is_admin = admin
def set_email(self, email: str):
self.email = email
def set_password(self, password: str):
self.password_hash = self.bcrypt.generate_password_hash(password + self.salt).decode('utf-8')
Loading…
Cancel
Save