UniAuth/main.py

141 lines
4.2 KiB
Python
Raw Normal View History

2024-01-21 09:10:07 +00:00
#!/usr/bin/python3
"""Single sign-on application."""
import functools
import json
2024-01-21 09:10:07 +00:00
import typing
from datetime import datetime, timedelta
from typing import Any, Callable
import flask
import werkzeug
import UniAuth.auth
2024-01-21 09:10:07 +00:00
app = flask.Flask(__name__)
2024-02-19 09:45:17 +00:00
app.debug = False
2024-01-21 09:10:07 +00:00
app.config.from_object("config.default")
def login_required(f: Callable[..., Any]) -> Callable[..., Any]:
"""Route requires login decorator."""
@functools.wraps(f)
def decorated_function(*args, **kwargs) -> werkzeug.Response: # type: ignore
2024-02-19 09:45:17 +00:00
token = flask.request.cookies.get("uniauth_token")
if not token or UniAuth.auth.verify_auth_token(token) is None:
2024-01-21 09:10:07 +00:00
# Save the original URL in the session and redirect to login
flask.session["next"] = flask.request.url
return flask.redirect(flask.url_for("login_page"))
return typing.cast(werkzeug.Response, f(*args, **kwargs))
return decorated_function
@app.route("/")
@login_required
def root_page() -> str:
"""Root page."""
return flask.render_template("auth_good.html")
def check_user_auth() -> dict[str, Any] | None:
"""Load username and password and check if valid."""
# Extract username and password from POST request
username = flask.request.form.get("username", type=str)
password = flask.request.form.get("password", type=str)
# Retrieve users from app config
users = app.config["USERS"]
# Check if the username and password match any user in the list
return next(
(u for u in users if u["username"] == username and u["password"] == password),
None,
)
2024-02-19 09:45:17 +00:00
def read_callback_url_from_token() -> str | None:
"""Parse token and extract callback URL."""
token = flask.request.args.get("token")
if not token:
return None
json_data = UniAuth.auth.verify_secure_token(
token, salt="secure-redirect", max_age=600
)
if not json_data:
return None
assert isinstance(json_data, str)
token_payload = json.loads(json_data)
callback_url = token_payload["callback_url"]
assert isinstance(callback_url, str)
return callback_url
2024-01-21 09:10:07 +00:00
@app.route("/login", methods=["GET", "POST"])
def login_page() -> str | werkzeug.Response | tuple[str, int]:
2024-01-21 09:10:07 +00:00
"""Login page."""
2024-02-19 09:45:17 +00:00
app.logger.info("Login page.")
2024-01-21 09:10:07 +00:00
if flask.request.method == "GET":
2024-02-19 09:45:17 +00:00
uniauth_token = flask.request.cookies.get("uniauth_token")
if (
not uniauth_token
or not UniAuth.auth.verify_auth_token(uniauth_token)
or not (callback_url := read_callback_url_from_token())
):
return flask.render_template("login.html")
token = flask.request.args["token"]
redirect_to_callback = f"{callback_url}?auth_token={uniauth_token}&next={token}"
app.logger.info(f"Redirecting to: {redirect_to_callback}")
return flask.redirect(redirect_to_callback)
2024-01-21 09:10:07 +00:00
if not (user := check_user_auth()):
# Login failed: Show an error message on the login page
2024-02-19 09:45:17 +00:00
app.logger.info("User auth failed")
2024-01-21 09:10:07 +00:00
return flask.render_template("login.html", error="Invalid credentials")
2024-02-19 09:45:17 +00:00
callback_url = read_callback_url_from_token()
if not callback_url:
return "Invalid token", 400
auth_token = UniAuth.auth.generate_auth_token(user["username"])
2024-02-19 09:45:17 +00:00
token = flask.request.args["token"]
redirect_to_callback = f"{callback_url}?auth_token={auth_token}&next={token}"
2024-01-21 09:10:07 +00:00
2024-02-19 09:45:17 +00:00
app.logger.info(f"Redirecting to: {redirect_to_callback}")
# flask.flash("Welcome back! You have successfully logged in.")
expire_date = datetime.now() + timedelta(days=180)
response = flask.redirect(redirect_to_callback)
2024-01-21 09:10:07 +00:00
response.set_cookie(
2024-02-19 09:45:17 +00:00
"uniauth_token",
token,
2024-01-21 09:10:07 +00:00
expires=expire_date,
httponly=True,
secure=True,
samesite="Lax",
)
return response
2024-01-21 15:24:33 +00:00
@app.route("/logout")
def logout() -> werkzeug.Response:
"""Handle user logout by clearing the authentication cookie."""
2024-01-22 12:41:38 +00:00
after_login = flask.request.args.get("next")
response = flask.redirect(flask.url_for("login_page", next=after_login))
2024-01-21 15:24:33 +00:00
response.set_cookie("auth_token", "", expires=0)
flask.flash("You have been successfully logged out.", "info")
return response
2024-01-21 09:10:07 +00:00
if __name__ == "__main__":
app.run(host="0.0.0.0")