diff --git a/aurweb/asgi.py b/aurweb/asgi.py index 4d21ad03..b15e5874 100644 --- a/aurweb/asgi.py +++ b/aurweb/asgi.py @@ -11,7 +11,7 @@ import aurweb.config from aurweb.auth import BasicAuthBackend from aurweb.db import get_engine -from aurweb.routers import html, sso, errors +from aurweb.routers import auth, html, sso, errors routes = set() @@ -42,6 +42,7 @@ async def app_startup(): # Add application routes. app.include_router(sso.router) app.include_router(html.router) + app.include_router(auth.router) # Initialize the database engine and ORM. get_engine() diff --git a/aurweb/routers/auth.py b/aurweb/routers/auth.py new file mode 100644 index 00000000..24f5d4e3 --- /dev/null +++ b/aurweb/routers/auth.py @@ -0,0 +1,85 @@ +from datetime import datetime +from http import HTTPStatus + +from fastapi import APIRouter, Form, Request +from fastapi.responses import HTMLResponse, RedirectResponse + +import aurweb.config + +from aurweb.models.user import User +from aurweb.templates import make_context, render_template + +router = APIRouter() + + +def login_template(request: Request, next: str, errors: list = None): + """ Provide login-specific template context to render_template. """ + context = make_context(request, "Login", next) + context["errors"] = errors + context["url_base"] = f"{request.url.scheme}://{request.url.netloc}" + return render_template("login.html", context) + + +@router.get("/login", response_class=HTMLResponse) +async def login_get(request: Request, next: str = "/"): + """ Homepage route. """ + return login_template(request, next) + + +@router.post("/login", response_class=HTMLResponse) +async def login_post(request: Request, + next: str = Form(...), + user: str = Form(default=str()), + passwd: str = Form(default=str()), + remember_me: bool = Form(default=False)): + from aurweb.db import session + + user = session.query(User).filter(User.Username == user).first() + if not user: + return login_template(request, next, + errors=["Bad username or password."]) + + cookie_timeout = 0 + + if remember_me: + cookie_timeout = aurweb.config.getint( + "options", "persistent_cookie_timeout") + + _, sid = user.login(request, passwd, cookie_timeout) + if not _: + return login_template(request, next, + errors=["Bad username or password."]) + + login_timeout = aurweb.config.getint("options", "login_timeout") + + expires_at = int(datetime.utcnow().timestamp() + + max(cookie_timeout, login_timeout)) + + response = RedirectResponse(url=next, + status_code=int(HTTPStatus.SEE_OTHER)) + response.set_cookie("AURSID", sid, expires=expires_at) + return response + + +@router.get("/logout") +async def logout(request: Request, next: str = "/"): + """ A GET and POST route for logging out. + + @param request FastAPI request + @param next Route to redirect to + """ + if request.user.is_authenticated(): + request.user.logout(request) + + # Use 303 since we may be handling a post request, that'll get it + # to redirect to a get request. + response = RedirectResponse(url=next, + status_code=int(HTTPStatus.SEE_OTHER)) + response.delete_cookie("AURSID") + response.delete_cookie("AURTZ") + return response + + +@router.post("/logout") +async def logout_post(request: Request, next: str = "/"): + return await logout(request=request, next=next) diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 00000000..da7bd722 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,84 @@ +{% extends 'partials/layout.html' %} + +{% block pageContent %} + +
+ {{ "HTTP login is disabled. Please %sswitch to HTTPs%s if you want to login." + | tr + | format( + '' | format(https_login), + "") + | safe + }} +
+ {% else %} + {% if request.user.is_authenticated() %} ++ {{ "Logged-in as: %s" + | tr + | format("%s" | format(request.user.Username)) + | safe + }} + [{% trans %}Logout{% endtrans %}] +
+ {% else %} + + {% endif %} + {% endif %} + +