Exemplo n.º 1
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)
            )
Exemplo n.º 2
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)
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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)
Exemplo n.º 5
0
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)
Exemplo n.º 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.")
Exemplo n.º 7
0
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)