async def change_password_page_endpoint(request: Request):
    """Render the change password form"""

    if not request.user.is_authenticated:
        flash(request, "error", "You are not logged in.")
        return RedirectResponse("/", 303)

    return render_template(request, "change_password.html.j2")
Example #2
0
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)
            )
Example #3
0
async def initiate_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("/", 303)

    params = {
        "client_id": GITHUB_CLIENT_ID,
        "redirect_uri": GITHUB_REDIRECT_URL,
        "scope": "admin:public_key",
    }

    return RedirectResponse(
        "https://github.com/login/oauth/authorize?" + urlencode(params)
    )
Example #4
0
async def login_endpoint(request: Request):
    """Process a login form submission (via POST request only)"""
    form_data = await request.form()
    username = form_data.get("username")
    password = form_data.get("password")

    if (username and password) and await login_valid(username, password):
        request.session["username"] = username
        flash(request, "success", "Logged in.")
    else:
        flash(
            request,
            "error",
            "The username and password specified not match a user. Please try again.",
        )

    return RedirectResponse("/", 303)
Example #5
0
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)
Example #6
0
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 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 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)
Example #9
0
async def logout_endpoint(request: Request):
    """Process a logout request (via POST request only)"""
    del request.session["username"]
    flash(request, "success", "Logged out.")
    return RedirectResponse("/", 303)