async def complete_github_integration(request: Request): if not github_integration_enabled(): return PlainTextResponse("GitHub integration is currently disabled.") if not request.user.is_authenticated: flash(request, "error", "You are not logged in!") return RedirectResponse("/") code = request.query_params.get("code") if not code: flash(request, "error", "Invalid OAuth code") return RedirectResponse("/") query = users.select().where(users.c.username == request.user.username) user_id = await database.fetch_val(query) async with httpx.AsyncClient() as http: res = await http.post( "https://github.com/login/oauth/access_token", params={ "client_id": GITHUB_CLIENT_ID, "client_secret": str(GITHUB_CLIENT_SECRET), "code": code, }, headers={"Accept": "application/json"}, ) if res.is_error or "error" in res.json(): # Why is this a 2xx, GitHub? print("Could not complete GitHub OAuth:") print(res.json()) flash( request, "error", "An error occurred while trying to authenticate with GitHub", ) return RedirectResponse("/") async with database.transaction(): query = user_integrations.insert().values( user_id=user_id, integration_type=GITHUB_INTEGRATION_TYPE, integration_domain="github.com", integration_data=res.json(), # { "access_token": ..., "token_type": ... } ) await database.execute(query) query = ( # Why does encode.io's databases module make it so hard to get back the created record? user_integrations.select() .where(user_integrations.c.user_id == user_id) .where(user_integrations.c.integration_type == GITHUB_INTEGRATION_TYPE) .where(user_integrations.c.integration_domain == "github.com") ) user_integration_row = await database.fetch_one(query) flash(request, "success", "Added GitHub integration for 'github.com'") async with get_integration_from_db(user_integration_row) as integration: return RedirectResponse( "/", background=BackgroundTask(integration.full_sync) )
async def delete_key_endpoint(request: Request): """Delete a key for the logged in user via its comment""" if not request.user.is_authenticated: flash(request, "error", "You are not logged in!") return RedirectResponse("/", 303) form_data = await request.form() key_comment = form_data.get("key_comment") if not key_comment: flash(request, "error", "key_comment is a required field.") return RedirectResponse("/", 303) query = users.select().where(users.c.username == request.user.username) user_id = await database.fetch_val(query) background_task = None async with database.transaction(): flash(request, "success", "Deleted key.") query = (keys.select().where(keys.c.user_id == user_id).where( keys.c.key_comment == key_comment)) key = await database.fetch_one(query) ssh_algo, ssh_contents, ssh_comment = key[2:] query = (keys.delete().where(keys.c.user_id == user_id).where( keys.c.key_comment == key_comment)) await database.execute(query) background_task = BackgroundTask(run_key_delete_integrations, user_id, ssh_algo, ssh_contents, ssh_comment) return RedirectResponse("/", 303, background=background_task)
async def login_valid(username: str, password: str) -> bool: query = users.select().where(users.c.username == username) matching_user = await database.fetch_one(query=query) if matching_user: user_id, user_username, user_password_hash = matching_user return bcrypt.checkpw(password.encode("utf-8"), user_password_hash.encode("ascii")) return False
async def main_page_endpoint(request: Request): """Render the main page. Shows either a login form or an SSH key submission form""" context = {"registration_enabled": REGISTRATION_ENABLED} if request.user.is_authenticated: query = users.select().where(users.c.username == request.user.username) user = await database.fetch_one(query) assert user is not None query = keys.select().where(keys.c.user_id == user[0]) user_ssh_keys = await database.fetch_all(query) user_ssh_keys = [(key[2], key[3], key[4]) for key in user_ssh_keys] context["user_ssh_keys"] = user_ssh_keys return render_template(request, "index.html.j2", **context)
async def deploy_key_endpoint(request: Request): if not request.user.is_authenticated: flash(request, "error", "Cannot deploy key: You are not logged in.") return RedirectResponse("/", 303) form_data = await request.form() plaintext_key = form_data.get("key") if not plaintext_key: flash(request, "error", "'key' is a required field.") return RedirectResponse("/", 303) plaintext_key = plaintext_key.strip() background_task = None try: ssh_algo, ssh_contents, ssh_comment = parse_ssh_key(plaintext_key) async with database.transaction(): query = users.select().where( users.c.username == request.user.username) user_id = await database.fetch_val(query) query = keys.insert().values( user_id=user_id, key_algorithm=ssh_algo, key_contents=ssh_contents, key_comment=ssh_comment, ) await database.execute(query) flash(request, "success", "The provided SSH key has been deployed.") background_task = BackgroundTask( run_key_deploy_integrations, user_id, ssh_algo, ssh_contents, ssh_comment, ) except: flash(request, "error", "Invalid key data.") return RedirectResponse("/", 303, background=background_task)
async def force_sync_github_integration(request): """Force sync any GitHub integrations for this user (via POST request only)""" if not request.user.is_authenticated: flash(request, "error", "You are not logged in!") return RedirectResponse("/", 303) username = request.user.username query = users.select().where(users.c.username == request.user.username) user_id = await database.fetch_val(query) query = ( user_integrations.select() .where(user_integrations.c.user_id == user_id) .where(user_integrations.c.integration_type == GITHUB_INTEGRATION_TYPE) ) integrations = await database.fetch_all(query) for integration_row in integrations: async with get_integration_from_db(integration_row) as integration: await integration.full_sync() return PlainTextResponse("Success.")
async def list_keys_endpoint(request: Request): """Serve a list of keys for a user. We include the comment fields if and only if authentication is provided. """ query = users.select().where( users.c.username == request.path_params["user"]) user = await database.fetch_one(query) if not user: return PlainTextResponse("User does not exist.", 404) query = keys.select().where(keys.c.user_id == user[0]) ssh_keys = await database.fetch_all(query) include_comments = (request.user.is_authenticated and request.user.username == user[1]) if (authorization_header := request.headers.get("Authorization")): auth_header_parts = authorization_header.split() if len(auth_header_parts) > 1 and auth_header_parts[0].casefold( ) == "bearer": if await access_key_matches(user[0], auth_header_parts[1]): include_comments = True
async def change_password_endpoint(request: Request): """Process a change password form submission (via POST request only)""" if not request.user.is_authenticated: flash(request, "error", "You are not logged in.") return RedirectResponse("/", 303) query = users.select().where(users.c.username == request.user.username) user = await database.fetch_one(query=query) user_id, user_username, user_password_hash = user form_data = await request.form() current_password = form_data.get("current_password") password = form_data.get("password") password_confirm = form_data.get("password_confirm") has_errors = False if not current_password: has_errors = True flash(request, "error", "'current_password' is a required field.") if not password: has_errors = True flash(request, "error", "'password' is a required field.") if not password_confirm: has_errors = True flash(request, "error", "'password_confirm' is a required field.") if not bcrypt.checkpw(current_password.encode("utf-8"), user_password_hash.encode("ascii")): has_errors = True flash(request, "error", "The provided password confirmation is incorrect.") if len(password) < 32: has_errors = True flash( request, "error", "The provided password is too short. It must be at least 32 characters in length.", ) if password != password_confirm: has_errors = True flash( request, "error", "The provided password does not match the password confirmation.", ) if has_errors: return RedirectResponse("/change_password/", 303) password_hash = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()) password_hash = password_hash.decode("ascii") async with database.transaction(): update_query = (users.update().where( users.c.username == request.user.username).values( password_hash=password_hash)) await database.execute(update_query) flash(request, "success", "Password changed.") return RedirectResponse("/", 303)