From 0777c6b0d997109499ac0474c1048399759498ed Mon Sep 17 00:00:00 2001 From: sd109 Date: Tue, 13 Aug 2024 13:28:43 +0100 Subject: [PATCH] Add x-remote header auth support --- backend/danswer/auth/users.py | 12 ++--- backend/danswer/db/auth.py | 10 ++-- web/src/app/auth/login/HeaderLogin.tsx | 69 ++++++++++++++++++++++++++ web/src/app/auth/login/page.tsx | 41 ++++++++------- web/src/lib/user.ts | 3 +- 5 files changed, 106 insertions(+), 29 deletions(-) create mode 100644 web/src/app/auth/login/HeaderLogin.tsx diff --git a/backend/danswer/auth/users.py b/backend/danswer/auth/users.py index dff6a60363c..eec1db412e0 100644 --- a/backend/danswer/auth/users.py +++ b/backend/danswer/auth/users.py @@ -204,12 +204,12 @@ async def create( ) -> models.UP: verify_email_is_invited(user_create.email) verify_email_domain(user_create.email) - if hasattr(user_create, "role"): - user_count = await get_user_count() - if user_count == 0 or user_create.email in get_default_admin_user_emails(): - user_create.role = UserRole.ADMIN - else: - user_create.role = UserRole.BASIC + # if hasattr(user_create, "role"): + # user_count = await get_user_count() + # if user_count == 0 or user_create.email in get_default_admin_user_emails(): + # user_create.role = UserRole.ADMIN + # else: + # user_create.role = UserRole.BASIC return await super().create(user_create, safe=safe, request=request) # type: ignore async def oauth_callback( diff --git a/backend/danswer/db/auth.py b/backend/danswer/db/auth.py index 161fdc8f10b..7710232d01f 100644 --- a/backend/danswer/db/auth.py +++ b/backend/danswer/db/auth.py @@ -46,11 +46,11 @@ async def get_user_count() -> int: # Need to override this because FastAPI Users doesn't give flexibility for backend field creation logic in OAuth flow class SQLAlchemyUserAdminDB(SQLAlchemyUserDatabase): async def create(self, create_dict: Dict[str, Any]) -> UP: - user_count = await get_user_count() - if user_count == 0 or create_dict["email"] in get_default_admin_user_emails(): - create_dict["role"] = UserRole.ADMIN - else: - create_dict["role"] = UserRole.BASIC + # user_count = await get_user_count() + # if user_count == 0 or create_dict["email"] in get_default_admin_user_emails(): + # create_dict["role"] = UserRole.ADMIN + # else: + # create_dict["role"] = UserRole.BASIC return await super().create(create_dict) diff --git a/web/src/app/auth/login/HeaderLogin.tsx b/web/src/app/auth/login/HeaderLogin.tsx new file mode 100644 index 00000000000..eb2d6b5c570 --- /dev/null +++ b/web/src/app/auth/login/HeaderLogin.tsx @@ -0,0 +1,69 @@ +"use client"; + +import { usePopup } from "@/components/admin/connectors/Popup"; +import { basicLogin, basicSignup } from "@/lib/user"; +import { useRouter } from "next/navigation"; +import { useEffect } from "react"; +import { Spinner } from "@/components/Spinner"; + +export function HeaderLoginLoading({ + user, groups +}: { + user: string; + groups: string[]; +}) { + console.log(user, groups); + + const router = useRouter(); + const { popup, setPopup } = usePopup(); + const email = `${user}@default.com`; + const password = `not-used-${window.crypto.randomUUID()}` + const role = groups.includes("/admins") ? "admin" : "basic" + + async function tryLogin() { + // TODO: Update user role here if groups have changed? + + // TODO: Use other API endpoints here to update user roles + // and check for existence instead of attempting sign up + // Endpoints: + // - /api/manage/users + // - /api/manage/promote-user-to-admin (auth required) + // - /api/manage/demote-admin-to-user (auth required) + + // signup every time. + // Ensure user exists + const response = await basicSignup(email, password, role); + if (!response.ok) { + const errorDetail = (await response.json()).detail; + + if (errorDetail !== "REGISTER_USER_ALREADY_EXISTS") { + setPopup({ + type: "error", + message: `Failed to sign up - ${errorDetail}`, + }); + } + } + // Login as user + const loginResponse = await basicLogin(email, password); + if (loginResponse.ok) { + router.push("/"); + } else { + const errorDetail = (await loginResponse.json()).detail; + setPopup({ + type: "error", + message: `Failed to login - ${errorDetail}`, + }); + } + } + + useEffect(() => { + tryLogin() + }, []); + + return ( + <> + {popup} + + + ); +} diff --git a/web/src/app/auth/login/page.tsx b/web/src/app/auth/login/page.tsx index 50f1d42d9b4..9b12b53bff0 100644 --- a/web/src/app/auth/login/page.tsx +++ b/web/src/app/auth/login/page.tsx @@ -14,6 +14,8 @@ import Link from "next/link"; import { Logo } from "@/components/Logo"; import { LoginText } from "./LoginText"; import { getSecondsUntilExpiration } from "@/lib/time"; +import { headers } from 'next/headers'; +import { HeaderLoginLoading } from "./HeaderLogin"; const Page = async ({ searchParams, @@ -69,6 +71,9 @@ const Page = async ({ return redirect(authUrl); } + const userHeader = headers().get('x-remote-user'); + const groupsHeader = headers().get('x-remote-group'); + return (
@@ -90,23 +95,25 @@ const Page = async ({ )} {authTypeMetadata?.authType === "basic" && ( - -
- - <LoginText /> - -
- -
- - Don't have an account?{" "} - - Create an account - - -
-
- )} + (userHeader && groupsHeader) ? + : ( + +
+ + <LoginText /> + +
+ +
+ + Don't have an account?{" "} + + Create an account + + +
+
+ ))}
diff --git a/web/src/lib/user.ts b/web/src/lib/user.ts index eb416c3c442..221cf3056c4 100644 --- a/web/src/lib/user.ts +++ b/web/src/lib/user.ts @@ -42,7 +42,7 @@ export const basicLogin = async ( return response; }; -export const basicSignup = async (email: string, password: string) => { +export const basicSignup = async (email: string, password: string, role = "basic") => { const response = await fetch("/api/auth/register", { method: "POST", credentials: "include", @@ -53,6 +53,7 @@ export const basicSignup = async (email: string, password: string) => { email, username: email, password, + role, }), }); return response;