diff options
author | cyfraeviolae <cyfraeviolae> | 2024-04-03 13:54:40 -0400 |
---|---|---|
committer | cyfraeviolae <cyfraeviolae> | 2024-04-03 13:54:40 -0400 |
commit | 0466dca275d01b659f977e5471c8ded34cabf9ab (patch) | |
tree | 5edd89bf32f24ac1da66352323fc9bf78c04c594 | |
parent | aaf5ab84eb155a0f76719a2d5253272c5053d247 (diff) |
errors
-rw-r--r-- | app.py | 93 | ||||
-rw-r--r-- | templates/error.html | 27 |
2 files changed, 84 insertions, 36 deletions
@@ -20,7 +20,7 @@ from sqlalchemy.dialects.postgresql import ARRAY from litestar import Litestar, get, post, Request from litestar.contrib.jinja import JinjaTemplateEngine -from litestar.exceptions import HTTPException +from litestar.exceptions import HTTPException, ValidationException, NotFoundException, NotAuthorizedException from litestar.template.config import TemplateConfig from litestar.response import Template, Redirect from litestar.response.file import ASGIFileResponse @@ -33,9 +33,6 @@ from litestar.datastructures import State import ics -# use url_fors, timezones? -# error handling, auth errors, sql errors, input validation - class Base(DeclarativeBase): pass @@ -107,32 +104,49 @@ parts = [ def gen_iden(): return "-".join(random.choices(parts, k=8)) +def error(msg) -> Template: + return Template(template_name="error.html", context=dict(error=msg)) + +def auth_error() -> Template: + return error("Unauthorized.") + +def check_auth(password, event): + return password and hmac.compare_digest(event.password, password) + @get("/") async def index() -> Template: return Template(template_name="index.html") -@get("/event/{iden:str}") +@get("/event/{iden:str}", name="event") async def event(state: State, iden: str, password: str = "") -> Template: async with sessionmaker(bind=state.engine) as session: async with session.begin(): query = select(Event).where(Event.iden == iden) result = await session.execute(query) - event = result.scalar_one() + try: + event = result.scalar_one() + except NoResultFound: + return error("Not found.") manage = False - if password and hmac.compare_digest(event.password, password): + if password: + if not check_auth(password, event): + return auth_error() manage = True context = dict(event=event, manage=manage) return Template(template_name="event.html", context=context) -@get("/calendar/{iden:str}") +@get("/event/{iden:str}/calendar") async def calendar(state: State, iden: str) -> ASGIStreamingResponse: async with sessionmaker(bind=state.engine) as session: async with session.begin(): query = select(Event).where(Event.iden == iden) result = await session.execute(query) - event = result.scalar_one() + try: + event = result.scalar_one() + except NoResultFound: + return error("Not found.") ics = event.to_ics() f = io.StringIO(ics) return ASGIStreamingResponse(iterator=f, media_type='text/plain', headers={ @@ -147,84 +161,91 @@ class EditRequest: what: str @post("/event/create") -async def create(state: State, data: Annotated[EditRequest, Body(media_type=RequestEncodingType.URL_ENCODED)]) -> Redirect: +async def create(request: Request, state: State, data: Annotated[EditRequest, Body(media_type=RequestEncodingType.URL_ENCODED)]) -> Redirect: iden = gen_iden() password = secrets.token_bytes(16).hex() event = Event(iden, password, data.title, datetime.datetime.fromisoformat(data.when), data.where, data.what) async with sessionmaker(bind=state.engine) as session: - async with session.begin(): - session.add(event) + try: + async with session.begin(): + session.add(event) + except IntegrityError: + return error("Iden already exists.") - return Redirect(path="/symposium/event/" + iden + "?password=" + event.password) + return Redirect("/symposium" + request.app.route_reverse('event', iden=iden) + "?password=" + event.password) @post("/event/{iden:str}/edit") -async def edit(state: State, request: Request, iden: str, password: str, data: Annotated[EditRequest, Body(media_type=RequestEncodingType.URL_ENCODED)]) -> Redirect: #-> Template: +async def edit(state: State, request: Request, iden: str, password: str, data: Annotated[EditRequest, Body(media_type=RequestEncodingType.URL_ENCODED)]) -> Redirect: async with sessionmaker(bind=state.engine) as session: async with session.begin(): query = select(Event).where(Event.iden == iden) result = await session.execute(query) - event = result.scalar_one() + try: + event = result.scalar_one() + except NoResultFound: + return error("Not found.") - manage = False - if password and hmac.compare_digest(event.password, password): - manage = True - if not manage: - raise ValueError("no auth") + if not check_auth(password, event): + return auth_error() event.title = data.title event.time = datetime.datetime.fromisoformat(data.when) event.location = data.where event.description = data.what - return Redirect(path="/symposium/event/" + iden + "?password=" + event.password) + return Redirect("/symposium" + request.app.route_reverse('event', iden=iden) + "?password=" + event.password) @dataclass class RemoveRequest: name: str @post("/event/{iden:str}/remove") -async def remove(state: State, request: Request, iden: str, data: Annotated[RemoveRequest, Body(media_type=RequestEncodingType.URL_ENCODED)], password: str = "") -> Redirect: #-> Template: +async def remove(state: State, request: Request, iden: str, data: Annotated[RemoveRequest, Body(media_type=RequestEncodingType.URL_ENCODED)], password: str = "") -> Redirect: async with sessionmaker(bind=state.engine) as session: async with session.begin(): query = select(Event).where(Event.iden == iden) result = await session.execute(query) - event = result.scalar_one() + try: + event = result.scalar_one() + except NoResultFound: + return error("Not found.") - manage = False - if password and hmac.compare_digest(event.password, password): - manage = True - if not manage: - raise ValueError("no auth") + if not check_auth(password, event): + return auth_error() name = data.name invites = json.loads(event.invites) if name in invites: invites.remove(name) event.invites = json.dumps(invites) - url = "/symposium/event/" + iden - if password: - url += "?password=" + password - return Redirect(path=url) + return Redirect("/symposium" + request.app.route_reverse('event', iden=iden) + "?password=" + event.password) @dataclass class JoinRequest: name: str @post("/event/{iden:str}/join") -async def join(state: State, request: Request, iden: str, data: Annotated[JoinRequest, Body(media_type=RequestEncodingType.URL_ENCODED)], password: str = "") -> Redirect: #-> Template: +async def join(state: State, request: Request, iden: str, data: Annotated[JoinRequest, Body(media_type=RequestEncodingType.URL_ENCODED)], password: str = "") -> Redirect: async with sessionmaker(bind=state.engine) as session: async with session.begin(): query = select(Event).where(Event.iden == iden) result = await session.execute(query) - event = result.scalar_one() + try: + event = result.scalar_one() + except NoResultFound: + return error("Not found.") + name = data.name + if len(name) > 50: + return error("Name too long.") invites = json.loads(event.invites) if name in invites: - raise ValueError("already exists") + return error("Name already exists in event.") invites.append(name) event.invites = json.dumps(invites) - url = "/symposium/event/" + iden + + url = "/symposium" + request.app.route_reverse('event', iden=iden) if password: url += "?password=" + password return Redirect(path=url) diff --git a/templates/error.html b/templates/error.html new file mode 100644 index 0000000..0617a20 --- /dev/null +++ b/templates/error.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> + <head> + <title>Symposium</title> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <link rel="stylesheet" type="text/css" href="/static/styles.css"> + <link rel="stylesheet" type="text/css" href="/symposium/static/styles.css"> + <link rel="shortcut icon" type="image/x-icon" href="/symposium/static/favicon.ico"> + </head> + <body> + <div class="container"> + <div> + <div class="home"> + <a href="/symposium" class="home-title">Symposium</a> + <span> at </span><a class="site" href="/">cyfraeviolae.org</a> + <a class="source" href="/git/symposium">[src]</a> + </div> + </div> + + <br> + <h3>Error.</h3> + + {{ error }} + </div> + </body> +</html> |