Commit 772aa27f authored by berumuron's avatar berumuron
Browse files

add: #9 Provide the authentication workflow

parents 8587220a b50e4b6a
__pycache__
/env
/instance
......@@ -4,6 +4,11 @@
dep: ## Install the dependencies
python3 -m pip install -r requirements.txt
.PHONY: db-setup
db-setup: ## Initialize the database and seed the database
FLASK_APP=src FLASK_ENV=development flask db-init
FLASK_APP=src FLASK_ENV=development flask user-create admin --password secret
.PHONY: run
run: ## Run the application binary
FLASK_APP=src FLASK_ENV=development flask run
......
......@@ -83,6 +83,21 @@ Si vous disposez de la commande `make` sur votre PC, vous pouvez aussi utiliser
reflets$ make dep
```
## Initialiser la base de données
Avant de lancer l'application, il est nécessaire d'initialiser la base de
données avec :
```console
reflets$ FLASK_APP=src FLASK_ENV=development flask db-init
reflets$ FLASK_APP=src FLASK_ENV=development flask create-user admin --password secret
reflets$ # ou avec la commande make
reflets$ make db-setup
```
Cela va créer les tables en base de données et créer un utilisateur avec les
identifiants `admin` / `secret`.
## Lancer l'application (développement)
Une fois les dépendances installées, pour lancer l'application, exécutez la
......@@ -98,3 +113,6 @@ Cela exécute Flask (le framework utilisé pour le backend) en lui précisant qu
l'application se trouve dans le répertoire `src` et de se lancer en mode
`development` pour qu'il détecte automatiquement les changements faits dans le
code.
Vous pouvez désormais vous connecter en allant sur [127.0.0.1:5000](http://127.0.0.1:5000/)
et en saisissant les identifiants donnés plus haut.
import os
from flask import Flask, render_template
from . import cli, db, auth
__version__ = "0.1.0"
app = Flask(__name__)
def create_app():
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
SECRET_KEY="a-dev-secret", DATABASE=os.path.join(app.instance_path, "db.sqlite")
)
try:
os.makedirs(app.instance_path)
except FileExistsError:
pass
# Register commands and teardowns to the app.
cli.init_app(app)
db.init_app(app)
# Register blueprints (collection of views)
app.register_blueprint(auth.bp)
# Define a default basic route (it will be improved later).
@app.route("/")
def index():
return render_template("index.html")
@app.route("/")
def index():
return render_template("index.html")
return app
from flask import (
Blueprint,
request,
session,
redirect,
url_for,
flash,
render_template,
g,
)
from .models import User
bp = Blueprint("auth", __name__, url_prefix="/auth")
@bp.before_app_request
def load_logged_in_user():
"""Set the global current_user if logged in."""
user_id = session.get("user_id")
g.current_user = User.find_by_id(user_id)
@bp.route("/login", methods=("GET", "POST"))
def login():
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
user = User.find_by_username(username)
if user is not None and user.check_password(password):
session.clear()
session["user_id"] = user.id
return redirect(url_for("index"))
else:
flash("Les identifiants sont incorrects.")
return render_template("auth/login.html")
@bp.route("/logout")
def logout():
session.clear()
return redirect(url_for("index"))
import click
from flask.cli import with_appcontext
from . import db
from .models import User
@click.command("db-init")
@with_appcontext
def db_init_command():
"""Clear the existing data and create new tables."""
db.init_db()
click.echo("Database initialized.")
@click.command("user-create")
@click.argument("username")
@click.password_option()
@with_appcontext
def user_create_command(username, password):
"""Create a user in database."""
user = User.create(username=username, password=password)
click.echo(f"User {user.username} initialized.")
def init_app(app):
"""Register CLI commands to the application."""
app.cli.add_command(db_init_command)
app.cli.add_command(user_create_command)
import sqlite3
from flask import current_app, g
def get_db():
"""Return the app database."""
if "db" not in g:
g.db = sqlite3.connect(
current_app.config["DATABASE"], detect_types=sqlite3.PARSE_DECLTYPES
)
g.db.row_factory = sqlite3.Row
return g.db
def init_db():
"""Initialize the database with the src/schema.sql file."""
db = get_db()
with current_app.open_resource("schema.sql") as f:
db.executescript(f.read().decode("utf8"))
def close_db(e=None):
"""Close the app database (it should not be called directly)."""
db = g.pop("db", None)
if db is not None:
db.close()
def init_app(app):
"""Register the db closing on app teardown."""
app.teardown_appcontext(close_db)
import werkzeug.security as security
from . import db
class User:
def __init__(self, id, username, password):
self.id = id
self.username = username
self.password = password
def check_password(self, password):
return security.check_password_hash(self.password, password)
@classmethod
def find_by_id(cls, user_id):
database = db.get_db()
user = database.execute(
"SELECT * FROM users WHERE id = ?", (user_id,)
).fetchone()
if user is None:
return None
return cls(user["id"], user["username"], user["password"])
@classmethod
def find_by_username(cls, username):
database = db.get_db()
user = database.execute(
"SELECT * FROM users WHERE username = ?", (username,)
).fetchone()
if user is None:
return None
return cls(user["id"], user["username"], user["password"])
@classmethod
def create(cls, username, password):
database = db.get_db()
cursor = database.execute(
"INSERT INTO users (username, password) VALUES (?, ?)",
(username, security.generate_password_hash(password)),
)
database.commit()
return cls.find_by_id(cursor.lastrowid)
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL
);
{% extends 'layout.html' %}
{% block content %}
<div class="container">
<h1>Connexion</h1>
<form method="post">
<div class="alert alert-error">
{% for message in get_flashed_messages() %}
<p>{{ message }}</p>
{% endfor %}
</div>
<div class="form-group">
<label for="username">Nom d’utilisateur</label>
<input type="text" name="username" id="username" required autofocus />
</div>
<div class="form-group">
<label for="password">Mot de passe</label>
<input type="password" name="password" id="password" required />
</div>
<div class="form-group form-group-actions">
<button type="submit" class="button-primary">
Se connecter
</button>
<a href="{{ url_for('index') }}">
Retour à l'accueil
</a>
</div>
</form>
</div>
{% endblock %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1.0" />
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='app.css') }}">
<title>Reflets</title>
</head>
{% extends 'layout.html' %}
<body>
<div class="container">
{% block content %}
<div class="container">
{% if g.current_user %}
<h1>Bonjour {{ g.current_user.username }}&nbsp;!</h1>
{% else %}
<h1>Bonjour Monde&nbsp;!</h1>
{% endif %}
<p>Bienvenue sur Reflets.</p>
</div>
</body>
</html>
<p>Bienvenue sur Reflets.</p>
</div>
{% endblock %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1.0" />
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='app.css') }}">
<title>Reflets</title>
</head>
<body class="app">
<header>
<nav class="app-navigation">
<ul class="container">
<li class="app-navigation-title">
<a href="{{ url_for('index') }}">Reflets</a>
</li>
<li class="app-navigation-auth">
{% if g.current_user %}
{{ g.current_user.username }}
(<a href="{{ url_for('auth.logout') }}">Se déconnecter</a>)
{% else %}
<a href="{{ url_for('auth.login') }}">Connexion</a>
{% endif %}
</li>
</ul>
</nav>
</header>
<main class="app-page">
{% block content %}{% endblock %}
</main>
</body>
</html>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment