async def delete_user_form(request: Request, user: User = Depends(get_current_active_user)): vm = VMBase(request, user=user) if user.deletion_protection: return templates.TemplateResponse( "dashboard/account_deletion_protection_warning.html", vm.to_dict(), headers={"HX-Trigger-After-Settle": "openModal"}, ) return templates.TemplateResponse( "dashboard/account_delete.html", vm.to_dict(), headers={"HX-Trigger-After-Settle": "openModal"}, )
async def request_login(request: Request, email: str = Form(...), flow: str = Form(...)): """ Request a login code or link. This will start the login process. :param request: request from the server :param email: Email address entered by user :param flow: Either "otp" or "magic" to indicate either one time passcode or magic link authentication flow :return: redirect to the appropriate page to continue the login process """ try: await user_crud.check_or_create_user_from_email(email) await auth_client.authenticate(email, flow) except pac.InvalidAuthFlow: raise HTTPException(status_code=400, detail="Invalid Auth Flow") except pac.AuthClientError: raise HTTPException(status_code=500, detail="Internal Error, try again later") if flow == "otp": vm = ConfirmCodeVM(request, email) return templates.TemplateResponse("login/confirm_code.html", vm.to_dict()) elif flow == "magic": return make_redirect_response( "/login/magic-message", status_code=status.HTTP_303_SEE_OTHER, ) # Currently, the only valid auth flows are "otp" and "magic." If something else # is provided, it should raise "InvalidAuthFlow," therefore this code should be # unreachable. raise HTTPException(status_code=500, detail="Something has gone wrong")
async def get_disable_deletion_protection_code( request: Request, user: User = Depends(get_current_active_user)): """ Initiate disabling deletion protection for the app. This will send an email. :param request: the request from the server :param user: the owner of the app. :return: HTML for the confirmation form """ vm = VMBase(request) await vm.check_for_user() code = deletion_protection.generate_dp_code(user, "account") try: await io_email.send( to=user.email, subject="Deletion Protection Code", text=f"Enter this code to disable deletion protection " f"for your user account: {code}. This code will expire in " f"{config.OTP_LIFETIME} minutes.", from_name="Purple Authentication", reply_to=config.WEBMASTER_EMAIL, ) except io_email.EmailError: raise HTTPException(status_code=500, detail="Failed to send email") return templates.TemplateResponse( "dashboard/account_deletion_protection_confirm.html", vm.to_dict())
async def update_me( request: Request, name: str = Form(...), current_user: User = Depends(get_current_active_user), ): """ Update information about a user. User emails can't be changed programmatically right now. :param request: request from the server :param name: The user's new name :param current_user: User from the database :return: the user """ updated_user = await user_crud.update_user(current_user, name) vm = VMBase(request=request, user=updated_user) res = templates.TemplateResponse("dashboard/user_account.html", vm.to_dict()) res.headers["HX-Trigger"] = htmx.make_show_notification_header( res.headers, "Account Updated", "Your account has been updated successfully.", "success", ) return res
async def confirm_otp_login( request: Request, email: str = Form(...), otp: str = Form(...), stay_logged_in: bool = Form(False), ): """ Process and confirm user login using one time passcode. :param request: request from the server :param email: Email address previously entered by the user :param otp: One time passcode entered by user :param stay_logged_in: Whether the user wants to stay logged in :return: redirect to the appropriate page to continue the login process """ try: tokens = await auth_client.submit_code(email, otp) return make_authenticated_response( "/dashboard", tokens["id_token"], tokens.get("refresh_token"), stay_logged_in, ) except pac.AuthClientError: vm = ConfirmCodeVM(request, email, error="Invalid code") return templates.TemplateResponse("login/confirm_code.html", vm.to_dict())
async def dashboard_form_create_app(request: Request): vm = DashboardVM(request) if not await vm.check_for_user(): return refresh_or_redirect_to_login("/dashboard") res = templates.TemplateResponse("dashboard/create_app_form.html", vm.to_dict()) res.headers["HX-Trigger-After-Swap"] = "openModal" return res
async def enable_deletion_protection( request: Request, app_id: str, user: User = Depends(get_current_active_user)): vm = SingleAppVM(request) await vm.check_for_user() vm.app = await clientapp_crud.enable_deletion_protection(app_id, user) res = templates.TemplateResponse( "dashboard/deletion_protection_display.html", vm.to_dict()) res.headers["HX-Trigger-After-Settle"] = ujson.dumps( {"flashAppSection": "deletion_protection"}) return res
async def delete_app_form(request: Request, app_id: str, _user: User = Depends(get_current_active_user)): """ Get HTML to either confirm deletion of the app or begin disabling deletion protection. :param request: the request from the server :param app_id: the unique id for the app to delete :param _user: the user who owns the app, this route requires authentication :return: HTML for the appropriate form """ vm = SingleAppVM(request) await vm.get_app(app_id) if vm.app.deletion_protection: return templates.TemplateResponse( "dashboard/app_deletion_protection_warning.html", vm.to_dict()) return templates.TemplateResponse( "dashboard/app_delete.html", vm.to_dict(), headers={"HX-Trigger-After-Settle": "openModal"}, )
async def update_app( request: Request, app_id: str, app_name: str = Form(...), redirect_url: str = Form(...), failure_redirect_url: str = Form(...), refresh_enabled: bool = Form(...), refresh_token_expire_hours: int = Form(24), low_quota_threshold: int = Form(10), user: User = Depends(get_current_active_user), ): """ Update an app. :param request: the request from the server :param app_id: the unique id for the app to updated :param app_name: the new name for the app :param redirect_url: updated redirect url (see create_app) :param failure_redirect_url: updated failure redirect url (see create_app) :param refresh_enabled: whether to enable refresh on the app :param refresh_token_expire_hours: how long refresh tokens are valid for. :param low_quota_threshold: how many authentications should be left before the app owner is notified that they are running out :param user: the owner of the app. This route requires authentication. :return: HTML to replace the app in the list and an event to display the app """ updated_app = await clientapp_crud.update_client_app( app_id, user, app_name, redirect_url, refresh_enabled, refresh_token_expire_hours, failure_redirect_url, low_quota_threshold, ) vm = SingleAppVM(request, updated_app) res = templates.TemplateResponse( "dashboard/single_app.html", vm.to_dict(), ) res.headers["HX-Trigger"] = htmx.make_event_header(res.headers, {"closeModal": {}}) res.headers["HX-Trigger"] = htmx.make_show_notification_header( res.headers, "App Updated", f"Your app {updated_app.name} has been updated!", "success", ) res.headers["HX-Trigger-After-Settle"] = ujson.dumps( {"showApp": updated_app.app_id}) return res
async def create_app( request: Request, app_name: str = Form(...), redirect_url: str = Form(...), failure_redirect_url: str = Form(...), refresh: bool = Form(False), refresh_token_expire_hours: int = Form(24), user: User = Depends(get_current_active_user), ): """ Create a new app to authenticate against. :param request: request from the server :param app_name: The name of the app :param redirect_url: The url to redirect to after a successful magic link authentication. :param failure_redirect_url: The url to redirect to after a failed magic link authentication attempt. :param refresh: Whether to enable refresh on the app :param refresh_token_expire_hours: How long refresh tokens are valid for. :param user: The user creating the app. This route requires authentication. :return: HTML for the app in the list and events to display the full app. """ new_app = await clientapp_crud.create_client_app( app_name=app_name, owner=user.email, redirect_url=redirect_url, refresh=refresh, refresh_token_expire_hours=refresh_token_expire_hours, failure_redirect_url=failure_redirect_url, ) vm = SingleAppVM(request, new_app) res = templates.TemplateResponse( "dashboard/single_app.html", vm.to_dict(), headers={"HX-Trigger": '{"closeModal": "{}"}'}, status_code=201, ) res.headers["HX-Trigger"] = htmx.make_show_notification_header( res.headers, "App Created", f"Your app {new_app.name} has been created!", "success", ) res.headers["HX-Trigger"] = htmx.make_event_header(res.headers, {"appCreated": "{}"}) res.headers["HX-Trigger-After-Settle"] = ujson.dumps( {"showApp": new_app.app_id}) return res
async def get_app_display(request: Request, app_id: str, _user: User = Depends(get_current_active_user)): """ Get the HTML for the app display. :param request: the request from the server :param app_id: the unique id for the app :param _user: the user who owns the app. This route requires authentication. :return: HTML for the app display modal with an event to open the modal. """ vm = SingleAppVM(request) await vm.get_app(app_id) return templates.TemplateResponse( "dashboard/app_display.html", vm.to_dict(), headers={"HX-Trigger-After-Settle": "openModal"}, )
async def rotate_app_keys_form(request: Request, app_id: str, _user: User = Depends(get_current_active_user)): """ Get the form to rotate app encryption keys. :param request: request from the server :param app_id: the unique id of the app to rotate keys for :param _user: the user who owns the app. this route requires authentication :return: HTML of the appropriate form """ vm = SingleAppVM(request) await vm.get_app(app_id) return templates.TemplateResponse( "dashboard/app_change_keys.html", vm.to_dict(), )
async def how_it_works(request: Request): vm = HowItWorksVM(request) await vm.check_for_user() return templates.TemplateResponse("how-it-works.html", vm.to_dict())
@portal_router.get("/login/magic-message") async def magic_message(request: Request): vm = LoginVM(request) if await vm.check_for_user(): return make_redirect_response("/dashboard") return templates.TemplateResponse("login/magic_message.html", vm.to_dict()) @portal_router.get("/login/magic-failed") async def magic_failed_message(request: Request): vm = LoginVM(request) if await vm.check_for_user(): return make_redirect_response("/dashboard") return templates.TemplateResponse("login/magic_failed.html", vm.to_dict()) @portal_router.get("/login") async def start_login( request: Request, next_url: str = Query("/dashboard", alias="next"), ): vm = LoginVM(request) if await vm.check_for_user(): return make_redirect_response("/dashboard") if id_token := await try_refresh(request): logging.debug("successfully refreshed") return make_authenticated_response(f"{next_url}?sessionRefreshed=true", id_token) return templates.TemplateResponse("login/login.html", vm.to_dict())
async def index(request: Request): vm = IndexVM(request) await vm.check_for_user() return templates.TemplateResponse("index.html", vm.to_dict())
async def magic_failed_message(request: Request): vm = LoginVM(request) if await vm.check_for_user(): return make_redirect_response("/dashboard") return templates.TemplateResponse("login/magic_failed.html", vm.to_dict())
async def confirm_login(request: Request, email: str = Query(None)): vm = LoginVM(request, user_email=email) if await vm.check_for_user(): return make_redirect_response("/dashboard") return templates.TemplateResponse("login/confirm_code.html", vm.to_dict())
async def tech_docs(request: Request): vm = TechDocsVM(request) return templates.TemplateResponse("tech-docs.html", vm.to_dict())
async def dashboard(request: Request): vm = DashboardVM(request) if not await vm.check_for_user(): return refresh_or_redirect_to_login("/dashboard") await vm.get_user_apps() return templates.TemplateResponse("dashboard/dashboard.html", vm.to_dict())