Closes #3. Closes #5. Closes #7. Closes #4. Closes #2.
This commit is contained in:
10
keycloak_collective_portal/routes/health.py
Normal file
10
keycloak_collective_portal/routes/health.py
Normal 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"}
|
49
keycloak_collective_portal/routes/invite.py
Normal file
49
keycloak_collective_portal/routes/invite.py
Normal 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"))
|
53
keycloak_collective_portal/routes/oidc.py
Normal file
53
keycloak_collective_portal/routes/oidc.py
Normal 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"))
|
96
keycloak_collective_portal/routes/register.py
Normal file
96
keycloak_collective_portal/routes/register.py
Normal 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
|
||||
)
|
21
keycloak_collective_portal/routes/root.py
Normal file
21
keycloak_collective_portal/routes/root.py
Normal 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
|
||||
)
|
Reference in New Issue
Block a user