New UniAuth.auth to be used by other services

This commit is contained in:
Edward Betts 2024-01-23 10:44:16 +00:00
parent 33b7d7c5c1
commit 5d30314531
3 changed files with 59 additions and 24 deletions

0
UniAuth/__init__.py Normal file
View file

53
UniAuth/auth.py Normal file
View file

@ -0,0 +1,53 @@
"""Authentication via UniAuth."""
import typing
import flask
import werkzeug
from itsdangerous.url_safe import URLSafeTimedSerializer
max_age = 60 * 60 * 24 * 90
def is_authentication() -> bool:
"""User is authenticated."""
return not flask.current_app.config.get("REQUIRE_AUTH") or bool(
(token := flask.request.cookies.get("auth_token")) and verify_auth_token(token)
)
def verify_auth_token(token: str) -> str | None:
"""Verify the authentication token."""
serializer = URLSafeTimedSerializer(flask.current_app.config["SECRET_KEY"])
try:
username = serializer.loads(token, salt="auth", max_age=max_age)
except Exception:
return None
assert isinstance(username, str)
return username
def generate_auth_token(username: str) -> str:
"""Generate a secure authentication token."""
serializer = URLSafeTimedSerializer(flask.current_app.config["SECRET_KEY"])
token = typing.cast(str, serializer.dumps(username, salt="auth"))
assert isinstance(token, str)
return token
def require_authentication() -> werkzeug.Response | None:
"""Require authentication and redirect with return URL."""
if not flask.current_app.config.get("REQUIRE_AUTH"):
return None
token = flask.request.cookies.get("auth_token")
if token and verify_auth_token(token):
return None
# Construct the redirect URL with the original URL as a parameter
return flask.redirect(
flask.current_app.config["UNIAUTH_URL"]
+ "/login?next="
+ werkzeug.urls.url_quote(flask.request.url)
)

30
main.py
View file

@ -8,33 +8,13 @@ from typing import Any, Callable
import flask
import werkzeug
from itsdangerous.url_safe import URLSafeTimedSerializer
import UniAuth.auth
app = flask.Flask(__name__)
app.debug = True
app.config.from_object("config.default")
serializer = URLSafeTimedSerializer(app.config["SECRET_KEY"])
max_age = 60 * 60 * 24 * 90
def generate_auth_token(username: str) -> str:
"""Generate a secure authentication token."""
token = typing.cast(str, serializer.dumps(username, salt="auth"))
assert isinstance(token, str)
return token
def verify_auth_token(token: str) -> str | None:
"""Verify the authentication token."""
try:
username = serializer.loads(token, salt="auth", max_age=max_age)
except Exception:
return None
assert isinstance(username, str)
return username
def login_required(f: Callable[..., Any]) -> Callable[..., Any]:
"""Route requires login decorator."""
@ -42,7 +22,7 @@ def login_required(f: Callable[..., Any]) -> Callable[..., Any]:
@functools.wraps(f)
def decorated_function(*args, **kwargs) -> werkzeug.Response: # type: ignore
token = flask.request.cookies.get("auth_token")
if not token or verify_auth_token(token) is None:
if not token or UniAuth.auth.verify_auth_token(token) is None:
# 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"))
@ -93,10 +73,12 @@ def login_page() -> str | werkzeug.Response:
expire_date = datetime.now() + timedelta(days=180)
flask.flash("Welcome back! You have successfully logged in.")
token = UniAuth.auth.generate_auth_token(user["username"])
response = flask.redirect(redirect_to)
response.set_cookie(
"auth_token",
generate_auth_token(user["username"]),
token,
expires=expire_date,
httponly=True,
secure=True,