summaryrefslogtreecommitdiffstats
path: root/authentication/endpoints.py
diff options
context:
space:
mode:
authorGravatar Tom van der Lee <tom@vanderlee.io>2026-07-03 15:49:08 +0200
committerGravatar Tom van der Lee <tom@vanderlee.io>2026-07-03 15:49:14 +0200
commit7784c60c03ec277b456db6e2709383e302d9382b (patch)
treea008def666f04041fe77740693d42cfef89af558 /authentication/endpoints.py
parentfb28c648390464c75ba4903b0e1e0ff2642c8ffb (diff)
downloadserver-7784c60c03ec277b456db6e2709383e302d9382b.tar.gz
server-7784c60c03ec277b456db6e2709383e302d9382b.tar.bz2
server-7784c60c03ec277b456db6e2709383e302d9382b.zip
Added basic oauth flow
Diffstat (limited to 'authentication/endpoints.py')
-rw-r--r--authentication/endpoints.py87
1 files changed, 87 insertions, 0 deletions
diff --git a/authentication/endpoints.py b/authentication/endpoints.py
new file mode 100644
index 0000000..ebdc7a1
--- /dev/null
+++ b/authentication/endpoints.py
@@ -0,0 +1,87 @@
1from typing import Annotated
2from uuid import uuid7
3
4from fastapi import FastAPI, Request, Form, Query, HTTPException
5from pydantic import BaseModel, EmailStr, ValidationError
6from starlette.middleware.authentication import AuthenticationMiddleware
7from starlette.middleware.sessions import SessionMiddleware
8from starlette.responses import HTMLResponse, RedirectResponse
9from starlette.templating import Jinja2Templates
10
11import conf
12from authentication.backend import AuthBackend
13from authentication.utils import bcrypt_sha256_verify
14from db.models import User, Session, ClientApplication
15from db.session import get_session_context
16from proxy.utils import get_path_with_query_string
17
18authentication = FastAPI()
19authentication.add_middleware(SessionMiddleware, secret_key=conf.SECRET_KEY)
20
21oauth = FastAPI()
22oauth.add_middleware(AuthenticationMiddleware, backend=AuthBackend())
23oauth.add_middleware(SessionMiddleware, secret_key=conf.SECRET_KEY)
24
25templates = Jinja2Templates(directory='authentication/templates')
26
27class LoginForm(BaseModel):
28 username: EmailStr
29 password: str
30
31async def base_login(request: Request, form: LoginForm | None = None, errors: dict[str, str] | None = None):
32 return templates.TemplateResponse(request, 'login.html', context={
33 'form': form,
34 'errors': errors,
35 })
36
37@authentication.get('/login/', response_class=HTMLResponse)
38async def login(request: Request):
39 return await base_login(request)
40
41@authentication.post('/login/', response_class=HTMLResponse)
42async def login(request: Request, username: Annotated[str, Form()] = '', password: Annotated[str, Form()] = '', redirect: Annotated[str, Query()] = None):
43 form = None
44 errors = {}
45
46 try:
47 form = LoginForm.model_validate({
48 'username': username,
49 'password': password
50 })
51 with get_session_context() as db_session:
52 user = db_session.query(User).filter(User.username == form.username).first()
53
54 if not bcrypt_sha256_verify(password, user.password if user is not None else '') or user is None:
55 errors.update({'password': 'Invalid username or password'})
56
57 with get_session_context() as db_session:
58 session = Session(id=uuid7(), user=user)
59 db_session.add(session)
60
61 if redirect is not None:
62 return RedirectResponse(redirect)
63
64 except ValidationError as exc:
65 errors.update({ e['loc'][0]: e['msg'] for e in exc.errors() })
66
67 return await base_login(request, form, errors)
68
69
70async def base_authorize(request: Request, client_id: str, redirect_uri: str):
71 with get_session_context() as session:
72 app = session.query(ClientApplication).where(ClientApplication.client_id == client_id).first()
73
74 if app is None:
75 raise HTTPException(status_code=404, detail='Invalid client id')
76
77 if not request.user.is_authenticated:
78 return RedirectResponse(f'/auth/login/?redirect={get_path_with_query_string(request)}')
79
80
81@oauth.get('/authorize/')
82async def authorize(request: Request, client_id: Annotated[str, Query()], redirect_uri: Annotated[str, Query()]):
83 return await base_authorize(request, client_id, redirect_uri)
84
85@oauth.post('/authorize/')
86async def authorize(request: Request, client_id: Annotated[str, Query()], redirect_uri: Annotated[str, Query()]):
87 return await base_authorize(request, client_id, redirect_uri)