login flow through ui
This commit is contained in:
parent
15770f2879
commit
07fe8f6ffc
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -5,3 +5,4 @@ build/
|
||||||
**/__pycache__
|
**/__pycache__
|
||||||
mounts/
|
mounts/
|
||||||
**/*.pem
|
**/*.pem
|
||||||
|
**/*.secret
|
||||||
|
|
|
||||||
|
|
@ -28,3 +28,4 @@ packages = ["roc_fnb"]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
# Put scripts here
|
# Put scripts here
|
||||||
|
bootstrap-first-admin = "roc_fnb.scripts.bootstrap_first_admin:bootstrap_first_admin"
|
||||||
|
|
|
||||||
0
roc_fnb/scripts/__init__.py
Normal file
0
roc_fnb/scripts/__init__.py
Normal file
24
roc_fnb/scripts/bootstrap_first_admin.py
Normal file
24
roc_fnb/scripts/bootstrap_first_admin.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
from click import command, option, prompt, confirm
|
||||||
|
from roc_fnb.website.database import Database
|
||||||
|
from roc_fnb.website.models.user import User
|
||||||
|
|
||||||
|
|
||||||
|
@command
|
||||||
|
@option('--name', '-n', type=str, required=True)
|
||||||
|
@option('--email', '-e', type=str, required=True)
|
||||||
|
def bootstrap_first_admin(name: str, email: str):
|
||||||
|
password = prompt('Enter the account password',
|
||||||
|
hide_input=True, prompt_suffix=': ')
|
||||||
|
confirmation = prompt('Confirm the account password',
|
||||||
|
hide_input=True, prompt_suffix=': ')
|
||||||
|
if password != confirmation:
|
||||||
|
raise ValueError('passwords did not match')
|
||||||
|
admin = User.create(email, name, password, moderator=True, admin=True)
|
||||||
|
db = Database.from_env()
|
||||||
|
db.store_user(admin)
|
||||||
|
if confirm('Display an auth token for testing?', default=False):
|
||||||
|
print(admin.jwt)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
bootstrap_first_admin()
|
||||||
|
|
@ -1,19 +1,59 @@
|
||||||
from flask import Flask, redirect, url_for, request, send_file
|
from pathlib import Path
|
||||||
|
from sys import stderr
|
||||||
|
|
||||||
|
from flask import (Flask, redirect, url_for, request, send_file, make_response,
|
||||||
|
abort, render_template, g)
|
||||||
|
|
||||||
|
from roc_fnb.website.database import Database
|
||||||
|
from roc_fnb.website.models.user import User
|
||||||
|
|
||||||
|
db = Database.from_env()
|
||||||
|
|
||||||
app = Flask(
|
app = Flask(
|
||||||
import_name=__name__.split('.')[0],
|
import_name=__name__.split('.')[0],
|
||||||
static_url_path='/'
|
static_url_path='/',
|
||||||
|
template_folder=Path(__file__).absolute().parent / 'templates',
|
||||||
|
static_folder=Path(__file__).absolute().parent / 'static',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def decode_user():
|
||||||
|
if token := request.cookies.get('auth-token'):
|
||||||
|
g.user = User.verify_jwt(token)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/ig')
|
@app.route('/ig')
|
||||||
def ig_redir():
|
def ig_redir():
|
||||||
return redirect('https://instagram.com/RocFNB')
|
return redirect('https://instagram.com/RocFNB')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/donate')
|
@app.route('/donate')
|
||||||
def donate_redir():
|
def donate_redir():
|
||||||
return redirect('https://venmo.com/RocFoodNotBombs')
|
return redirect('https://venmo.com/RocFoodNotBombs')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
return redirect('/index.html')
|
return redirect('/index.html')
|
||||||
|
|
||||||
|
|
||||||
|
@app.post('/login')
|
||||||
|
def submit_login():
|
||||||
|
form = request.json
|
||||||
|
user = db.get_user_by_name(form['name'])
|
||||||
|
if not user.check_password(form['password']):
|
||||||
|
abort(401) # unauthorized
|
||||||
|
response = make_response(redirect('/me'))
|
||||||
|
response.set_cookie('auth-token', user.jwt)
|
||||||
|
return response
|
||||||
|
|
||||||
|
@app.get('/login')
|
||||||
|
def render_login_page():
|
||||||
|
return render_template('login.html')
|
||||||
|
|
||||||
|
@app.get('/me')
|
||||||
|
def get_profile():
|
||||||
|
if g.user is not None:
|
||||||
|
return render_template('profile.html', user=g.user)
|
||||||
|
abort(401)
|
||||||
|
|
|
||||||
27
roc_fnb/website/templates/base.html
Normal file
27
roc_fnb/website/templates/base.html
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<title>
|
||||||
|
Rochester Food Not Bombs
|
||||||
|
</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Teko:wght@300..700&display=swap" rel="stylesheet">
|
||||||
|
<script>
|
||||||
|
function start_animation() {
|
||||||
|
var element = document.getElementById("biglogo")
|
||||||
|
element.style.animation = 'none';
|
||||||
|
element.offsetHeight;
|
||||||
|
element.style.animation = null;
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
60
roc_fnb/website/templates/login.html
Normal file
60
roc_fnb/website/templates/login.html
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
{% extends "base.html" %} {% block content %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("readystatechange", () => {
|
||||||
|
if (document.readyState === "complete") {
|
||||||
|
/**
|
||||||
|
* @type {HTMLInputElement}
|
||||||
|
*/
|
||||||
|
const nameInput = document.getElementById("input-name");
|
||||||
|
/**
|
||||||
|
* @type {HTMLInputElement}
|
||||||
|
*/
|
||||||
|
const passwordInput = document.getElementById("input-password");
|
||||||
|
/**
|
||||||
|
* @type {HTMLButtonElement}
|
||||||
|
*/
|
||||||
|
const button = document.getElementById("submit-button");
|
||||||
|
button.addEventListener("click", async (event) => {
|
||||||
|
const name = nameInput.value;
|
||||||
|
const password = passwordInput.value;
|
||||||
|
const result = await fetch("/login", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ name, password }),
|
||||||
|
headers: {'Content-Type': 'application/json'}
|
||||||
|
});
|
||||||
|
if (result.ok) {
|
||||||
|
window.location = '/me'
|
||||||
|
} else {
|
||||||
|
console.dir(result)
|
||||||
|
// TODO handle error!
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<img
|
||||||
|
id="biglogo"
|
||||||
|
class="spinny"
|
||||||
|
onclick="start_animation()"
|
||||||
|
src="logo.png"
|
||||||
|
alt="logo"
|
||||||
|
/>
|
||||||
|
<h1>Rochester Food Not Bombs!</h1>
|
||||||
|
|
||||||
|
<h3>Login:</h3>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="name">Your name</label>
|
||||||
|
<input type="text" id="input-name" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="password">Your password</label>
|
||||||
|
<input type="password" id="input-password" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button id="submit-button" type="submit">Log in</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
7
roc_fnb/website/templates/profile.html
Normal file
7
roc_fnb/website/templates/profile.html
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<img id="biglogo" class="spinny" onclick="start_animation()" src="logo.png" alt="logo">
|
||||||
|
<h1>Rochester Food Not Bombs!</h1>
|
||||||
|
|
||||||
|
<p>This will be the profile/settings page for {{user.name}}</p>
|
||||||
|
{% endblock %}
|
||||||
Loading…
Reference in a new issue