summaryrefslogtreecommitdiffstats
path: root/authentication/endpoints.py
diff options
context:
space:
mode:
Diffstat (limited to 'authentication/endpoints.py')
-rw-r--r--authentication/endpoints.py88
1 files changed, 64 insertions, 24 deletions
diff --git a/authentication/endpoints.py b/authentication/endpoints.py
index ebdc7a1..f94d55b 100644
--- a/authentication/endpoints.py
+++ b/authentication/endpoints.py
@@ -1,31 +1,40 @@
1import json
2from base64 import b64encode
3from functools import partial
1from typing import Annotated 4from typing import Annotated
2from uuid import uuid7 5from uuid import uuid7
3 6
4from fastapi import FastAPI, Request, Form, Query, HTTPException 7from alembic.testing import requirements
5from pydantic import BaseModel, EmailStr, ValidationError 8from fastapi import FastAPI, Request, Form, Query
9from pydantic import BaseModel, ValidationError
10from sqlmodel import select
11from starlette.authentication import requires
6from starlette.middleware.authentication import AuthenticationMiddleware 12from starlette.middleware.authentication import AuthenticationMiddleware
7from starlette.middleware.sessions import SessionMiddleware 13from starlette.middleware.sessions import SessionMiddleware
8from starlette.responses import HTMLResponse, RedirectResponse 14from starlette.responses import HTMLResponse, RedirectResponse
9from starlette.templating import Jinja2Templates 15from starlette.templating import Jinja2Templates
10 16
11import conf 17import conf
12from authentication.backend import AuthBackend 18from authentication.authlib import AuthorizationServer, AuthorizationCodeGrant, prepare_oauth_request
13from authentication.utils import bcrypt_sha256_verify 19from authentication.backend import SessionAuthBackend
20from authentication.utils import bcrypt_sha256_verify, get_url_path_with_query
14from db.models import User, Session, ClientApplication 21from db.models import User, Session, ClientApplication
15from db.session import get_session_context 22from db.session import get_session_context
16from proxy.utils import get_path_with_query_string
17 23
18authentication = FastAPI() 24authentication = FastAPI()
19authentication.add_middleware(SessionMiddleware, secret_key=conf.SECRET_KEY) 25authentication.add_middleware(SessionMiddleware, secret_key=conf.SECRET_KEY)
20 26
21oauth = FastAPI() 27oauth = FastAPI()
22oauth.add_middleware(AuthenticationMiddleware, backend=AuthBackend()) 28oauth.add_middleware(AuthenticationMiddleware, backend=SessionAuthBackend())
23oauth.add_middleware(SessionMiddleware, secret_key=conf.SECRET_KEY) 29oauth.add_middleware(SessionMiddleware, secret_key=conf.SECRET_KEY)
24 30
31auth_server = AuthorizationServer()
32code_grant = partial(AuthorizationCodeGrant, server=auth_server)
33
25templates = Jinja2Templates(directory='authentication/templates') 34templates = Jinja2Templates(directory='authentication/templates')
26 35
27class LoginForm(BaseModel): 36class LoginForm(BaseModel):
28 username: EmailStr 37 username: str
29 password: str 38 password: str
30 39
31async def base_login(request: Request, form: LoginForm | None = None, errors: dict[str, str] | None = None): 40async def base_login(request: Request, form: LoginForm | None = None, errors: dict[str, str] | None = None):
@@ -35,11 +44,11 @@ async def base_login(request: Request, form: LoginForm | None = None, errors: di
35 }) 44 })
36 45
37@authentication.get('/login/', response_class=HTMLResponse) 46@authentication.get('/login/', response_class=HTMLResponse)
38async def login(request: Request): 47async def get_login(request: Request, next: Annotated[str, Query()] = None):
39 return await base_login(request) 48 return await base_login(request)
40 49
41@authentication.post('/login/', response_class=HTMLResponse) 50@authentication.post('/login/', response_class=HTMLResponse)
42async def login(request: Request, username: Annotated[str, Form()] = '', password: Annotated[str, Form()] = '', redirect: Annotated[str, Query()] = None): 51async def post_login(request: Request, username: Annotated[str, Form()] = '', password: Annotated[str, Form()] = '', next: Annotated[str, Query()] = None):
43 form = None 52 form = None
44 errors = {} 53 errors = {}
45 54
@@ -58,30 +67,61 @@ async def login(request: Request, username: Annotated[str, Form()] = '', passwor
58 session = Session(id=uuid7(), user=user) 67 session = Session(id=uuid7(), user=user)
59 db_session.add(session) 68 db_session.add(session)
60 69
61 if redirect is not None: 70 request.session.update({'id': str(session.id)})
62 return RedirectResponse(redirect) 71
72 if next is not None:
73 return RedirectResponse(next)
63 74
64 except ValidationError as exc: 75 except ValidationError as exc:
65 errors.update({ e['loc'][0]: e['msg'] for e in exc.errors() }) 76 errors.update({ e['loc'][0]: e['msg'] for e in exc.errors() })
66 77
67 return await base_login(request, form, errors) 78 return await base_login(request, form, errors)
68 79
80async def base_authorize(request: Request, user: User | None = None):
81 if not conf.IS_LOCAL:
82 with get_session_context() as db_session:
83 db_session.query(Session).filter(Session.id == request.session.get('id')).delete()
69 84
70async def base_authorize(request: Request, client_id: str, redirect_uri: str): 85 return auth_server.create_authorization_response(request, request.user, grant=None)
71 with get_session_context() as session:
72 app = session.query(ClientApplication).where(ClientApplication.client_id == client_id).first()
73 86
74 if app is None: 87@oauth.get('/authorize/')
75 raise HTTPException(status_code=404, detail='Invalid client id') 88@requires(['authenticated', 'session'], redirect='get_login')
89async def get_authorize(request: Request):
90 return await base_authorize(request, request.user)
76 91
77 if not request.user.is_authenticated: 92@oauth.post('/authorize/')
78 return RedirectResponse(f'/auth/login/?redirect={get_path_with_query_string(request)}') 93@requires(['authenticated', 'session'], redirect='get_login')
94async def post_authorize(request: Request):
95 return await base_authorize(request, request.user)
79 96
97@oauth.post('/token/')
98async def post_token(request: Request):
99 await prepare_oauth_request(request)
100 return auth_server.create_token_response(request)
80 101
81@oauth.get('/authorize/') 102@authentication.get('/connect/')
82async def authorize(request: Request, client_id: Annotated[str, Query()], redirect_uri: Annotated[str, Query()]): 103async def connect(request: Request):
83 return await base_authorize(request, client_id, redirect_uri) 104 origin = request.url.scheme + '://' + request.url.netloc
84 105
85@oauth.post('/authorize/') 106 with get_session_context() as session:
86async def authorize(request: Request, client_id: Annotated[str, Query()], redirect_uri: Annotated[str, Query()]): 107 app = session.exec(select(ClientApplication)).first()
87 return await base_authorize(request, client_id, redirect_uri) 108
109 return RedirectResponse(
110 '/oauth' + get_url_path_with_query(oauth, 'get_authorize', {
111 'client_id': app.client_id,
112 'redirect_uri': f'{origin}/oauth' + get_url_path_with_query(oauth, 'callback'),
113 'response_type': 'code',
114 'state': b64encode(json.dumps({
115 'client_id': app.client_id,
116 }).encode()).decode(),
117 })
118 )
119
120@oauth.get('/config/')
121async def get_config(request: Request):
122 with get_session_context() as session:
123 app = session.exec(select(ClientApplication)).first()
124
125 return {
126 'client_id': app.client_id,
127 }