The Big Refactor
All checks were successful
continuous-integration/drone/push Build is passing

Closes #3.
Closes #5.
Closes #7.
Closes #4.
Closes #2.
This commit is contained in:
decentral1se
2021-06-13 11:43:30 +02:00
parent 195c6626c0
commit d22abd2bc5
31 changed files with 588 additions and 328 deletions

View File

@ -0,0 +1,10 @@
"""Healthcheck routes."""
from fastapi import APIRouter, Request
router = APIRouter()
@router.get("/healthz")
async def healthz(request: Request):
return {"detail": "ALL ENGINES FIRING"}

View File

@ -0,0 +1,49 @@
"""Routes for invite logic."""
from datetime import datetime as dt
from uuid import uuid4
from fastapi import APIRouter, Depends, Request
from fastapi.responses import RedirectResponse
from keycloak_collective_portal.dependencies import (
get_invites,
get_user,
logged_in,
)
router = APIRouter()
@router.get("/invite/keycloak/create", dependencies=[Depends(logged_in)])
async def invite_keycloak_create(
request: Request, user=Depends(get_user), invites=Depends(get_invites)
):
username = user["preferred_username"]
new_invite = {"link": str(uuid4()), "time": str(dt.now())}
invites = await request.app.state.redis.get(username)
if invites:
invites.append(new_invite)
else:
invites = [new_invite]
await request.app.state.redis.set(username, invites)
return RedirectResponse(request.url_for("home"))
@router.get("/invite/keycloak/delete", dependencies=[Depends(logged_in)])
async def invite_keycloak_delete(
request: Request, user=Depends(get_user), invites=Depends(get_invites)
):
username = user["preferred_username"]
invite_to_delete = request.query_params.get("invite")
invites = await request.app.state.redis.get(username)
purged = [i for i in invites if i["link"] != invite_to_delete]
await request.app.state.redis.set(user["preferred_username"], purged)
return RedirectResponse(request.url_for("home"))

View File

@ -0,0 +1,53 @@
"""OpenID Connect routes."""
import httpx
from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from keycloak_collective_portal.dependencies import logged_in
router = APIRouter()
@router.get("/login")
async def login(request: Request):
return request.app.state.templates.TemplateResponse(
"login.html", context={"request": request}
)
@router.get("/login/keycloak")
async def login_keycloak(request: Request):
redirect_uri = request.url_for("auth_keycloak")
return await request.app.state.oidc.keycloak.authorize_redirect(
request, redirect_uri
)
@router.get("/auth/keycloak")
async def auth_keycloak(request: Request):
try:
token = await request.app.state.oidc.keycloak.authorize_access_token(
request
)
except Exception as exc:
return HTMLResponse(f"<p>{exc} (<a href='/'>home</a>)</p>")
user = await request.app.state.oidc.keycloak.parse_id_token(request, token)
request.session["user"] = dict(user)
return RedirectResponse(request.url_for("home"))
@router.get("/logout", dependencies=[Depends(logged_in)])
async def logout(request: Request):
from keycloak_collective_portal.config import KEYCLOAK_BASE_URL
try:
httpx.get(f"{KEYCLOAK_BASE_URL}/logout")
except Exception as exc:
return HTMLResponse(f"<p>{exc} (<a href='/'>home</a>)</p>")
request.session.pop("user", None)
return RedirectResponse(request.url_for("login"))

View File

@ -0,0 +1,96 @@
"""Registration routes."""
import json
from datetime import datetime as dt
from datetime import timedelta
from fastapi import APIRouter, Depends, Form, Request
from keycloak_collective_portal.dependencies import get_invites
router = APIRouter()
@router.get("/register/{invite}")
async def register_invite(
request: Request, invite: str, invites=Depends(get_invites)
):
from keycloak_collective_portal.config import INVITE_TIME_LIMIT
matching, username, matching_invite = False, None, None
for username in invites:
for _invite in invites[username]:
if invite == _invite["link"]:
matching = True
username = username
matching_invite = _invite
if not matching:
message = "This invite does not exist, sorry."
context = {"request": request, "message": message}
return request.app.state.templates.TemplateResponse(
"invalid.html", context=context
)
expired = (
dt.fromisoformat(matching_invite["time"])
+ timedelta(days=INVITE_TIME_LIMIT)
).day > dt.now().day
if expired:
message = "This invite has expired, sorry."
context = {"request": request, "message": message}
return request.app.state.templates.TemplateResponse(
"invalid.html", context=context
)
context = {"request": request, "username": username}
return request.app.state.templates.TemplateResponse(
"register.html", context=context
)
@router.post("/form/keycloak/register")
def form_keycloak_register(
request: Request,
first_name: str = Form(...),
last_name: str = Form(...),
username: str = Form(...),
email: str = Form(...),
password: str = Form(...),
):
payload = {
"email": email,
"username": username,
"enabled": True,
"firstName": first_name,
"lastName": last_name,
"credentials": [
{
"value": password,
"type": "password",
}
],
"realmRoles": [
"user_default",
],
}
try:
user_id = request.app.state.keycloak.create_user(
payload, exist_ok=False
)
request.app.state.keycloak.send_verify_email(user_id=user_id)
except Exception as exception:
message = json.loads(exception.error_message).get(
"errorMessage", "Unknown reason!"
)
context = {"request": request, "exception": message}
return request.app.state.templates.TemplateResponse(
"submit.html", context=context
)
context = {"request": request, "email": email}
return request.app.state.templates.TemplateResponse(
"submit.html", context=context
)

View File

@ -0,0 +1,21 @@
"""Home routes."""
from fastapi import APIRouter, Depends, Request
from keycloak_collective_portal.dependencies import (
get_invites,
get_user,
logged_in,
)
router = APIRouter()
@router.get("/", dependencies=[Depends(logged_in)])
async def home(
request: Request, user=Depends(get_user), invites=Depends(get_invites)
):
context = {"request": request, "user": user, "invites": invites}
return request.app.state.templates.TemplateResponse(
"admin.html", context=context
)