mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-06-26 16:01:09 +02:00
Fix logout redirect
This commit is contained in:
parent
ae72cd56f8
commit
37c5f24d91
@ -129,7 +129,6 @@ services:
|
||||
environment:
|
||||
- INTERNAL_URL=http://api_server:8080
|
||||
- WEB_DOMAIN=${WEB_DOMAIN:-}
|
||||
- OAUTH_NAME=${OAUTH_NAME:-}
|
||||
relational_db:
|
||||
image: postgres:15.2-alpine
|
||||
restart: always
|
||||
|
51
web/src/app/auth/login/SignInButton.tsx
Normal file
51
web/src/app/auth/login/SignInButton.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import { AuthType } from "@/lib/constants";
|
||||
import { FaGoogle } from "react-icons/fa";
|
||||
|
||||
export function SignInButton({
|
||||
authorizeUrl,
|
||||
authType,
|
||||
}: {
|
||||
authorizeUrl: string;
|
||||
authType: AuthType;
|
||||
}) {
|
||||
let button;
|
||||
if (authType === "google_oauth") {
|
||||
button = (
|
||||
<div className="mx-auto flex">
|
||||
<div className="my-auto mr-2">
|
||||
<FaGoogle />
|
||||
</div>
|
||||
<p className="text-sm font-medium select-none">Continue with Google</p>
|
||||
</div>
|
||||
);
|
||||
} else if (authType === "oidc") {
|
||||
button = (
|
||||
<div className="mx-auto flex">
|
||||
<p className="text-sm font-medium select-none">
|
||||
Continue with OIDC SSO
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else if (authType === "saml") {
|
||||
button = (
|
||||
<div className="mx-auto flex">
|
||||
<p className="text-sm font-medium select-none">
|
||||
Continue with SAML SSO
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!button) {
|
||||
throw new Error(`Unhandled authType: ${authType}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
className="mt-6 py-3 w-72 bg-blue-900 flex rounded cursor-pointer hover:bg-blue-950"
|
||||
href={authorizeUrl}
|
||||
>
|
||||
{button}
|
||||
</a>
|
||||
);
|
||||
}
|
@ -1,25 +1,31 @@
|
||||
import { HealthCheckBanner } from "@/components/health/healthcheck";
|
||||
import { AuthType, OAUTH_NAME } from "@/lib/constants";
|
||||
import { User } from "@/lib/types";
|
||||
import { getCurrentUserSS, getAuthUrlSS, getAuthTypeSS } from "@/lib/userSS";
|
||||
import {
|
||||
getCurrentUserSS,
|
||||
getAuthUrlSS,
|
||||
getAuthTypeMetadataSS,
|
||||
AuthTypeMetadata,
|
||||
} from "@/lib/userSS";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getWebVersion, getBackendVersion } from "@/lib/version";
|
||||
import Image from "next/image";
|
||||
import { SignInButton } from "./SignInButton";
|
||||
|
||||
const BUTTON_STYLE =
|
||||
"group relative w-64 flex justify-center " +
|
||||
"py-2 px-4 border border-transparent text-sm " +
|
||||
"font-medium rounded-md text-white bg-red-600 " +
|
||||
" mx-auto";
|
||||
const Page = async ({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams?: { [key: string]: string | string[] | undefined };
|
||||
}) => {
|
||||
const autoRedirectDisabled = searchParams?.disableAutoRedirect === "true";
|
||||
|
||||
const Page = async () => {
|
||||
// catch cases where the backend is completely unreachable here
|
||||
// without try / catch, will just raise an exception and the page
|
||||
// will not render
|
||||
let authType: AuthType | null = null;
|
||||
let authTypeMetadata: AuthTypeMetadata | null = null;
|
||||
let currentUser: User | null = null;
|
||||
try {
|
||||
[authType, currentUser] = await Promise.all([
|
||||
getAuthTypeSS(),
|
||||
[authTypeMetadata, currentUser] = await Promise.all([
|
||||
getAuthTypeMetadataSS(),
|
||||
getCurrentUserSS(),
|
||||
]);
|
||||
} catch (e) {
|
||||
@ -38,7 +44,7 @@ const Page = async () => {
|
||||
}
|
||||
|
||||
// simply take the user to the home page if Auth is disabled
|
||||
if (authType === "disabled") {
|
||||
if (authTypeMetadata?.authType === "disabled") {
|
||||
return redirect("/");
|
||||
}
|
||||
|
||||
@ -49,16 +55,15 @@ const Page = async () => {
|
||||
|
||||
// get where to send the user to authenticate
|
||||
let authUrl: string | null = null;
|
||||
let autoRedirect: boolean = false;
|
||||
if (authType) {
|
||||
if (authTypeMetadata) {
|
||||
try {
|
||||
[authUrl, autoRedirect] = await getAuthUrlSS(authType);
|
||||
authUrl = await getAuthUrlSS(authTypeMetadata.authType);
|
||||
} catch (e) {
|
||||
console.log(`Some fetch failed for the login page - ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (autoRedirect && authUrl) {
|
||||
if (authTypeMetadata?.autoRedirect && authUrl && !autoRedirectDisabled) {
|
||||
return redirect(authUrl);
|
||||
}
|
||||
|
||||
@ -68,33 +73,23 @@ const Page = async () => {
|
||||
<HealthCheckBanner />
|
||||
</div>
|
||||
<div className="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-md w-full space-y-8">
|
||||
<div>
|
||||
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-200">
|
||||
danswer 💃
|
||||
</h2>
|
||||
</div>
|
||||
<div className="flex">
|
||||
{authUrl ? (
|
||||
<a
|
||||
href={authUrl || ""}
|
||||
className={
|
||||
BUTTON_STYLE +
|
||||
" focus:outline-none focus:ring-2 hover:bg-red-700 focus:ring-offset-2 focus:ring-red-500"
|
||||
}
|
||||
>
|
||||
Sign in with {OAUTH_NAME}
|
||||
</a>
|
||||
) : (
|
||||
<button className={BUTTON_STYLE + " cursor-default"}>
|
||||
Sign in with {OAUTH_NAME}
|
||||
</button>
|
||||
)}
|
||||
<div>
|
||||
<div className="h-16 w-16 mx-auto">
|
||||
<Image src="/logo.png" alt="Logo" width="1419" height="1520" />
|
||||
</div>
|
||||
<h2 className="text-center text-xl font-bold mt-4">
|
||||
Log In to Danswer
|
||||
</h2>
|
||||
{authUrl && authTypeMetadata && (
|
||||
<SignInButton
|
||||
authorizeUrl={authUrl}
|
||||
authType={authTypeMetadata?.authType}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="fixed bottom-4 right-4 z-50 text-slate-400 p-2">
|
||||
VERSION w{web_version} b{backend_version}
|
||||
</div>
|
||||
</div>
|
||||
<div className="fixed bottom-4 right-4 z-50 text-slate-400 p-2">
|
||||
VERSION w{web_version} b{backend_version}
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
@ -1,14 +1,13 @@
|
||||
import { getDomain } from "@/lib/redirectSS";
|
||||
import { getAuthTypeSS, logoutSS } from "@/lib/userSS";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getAuthTypeMetadataSS, logoutSS } from "@/lib/userSS";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export const POST = async (request: NextRequest) => {
|
||||
// Directs the logout request to the appropriate FastAPI endpoint.
|
||||
// Needed since env variables don't work well on the client-side
|
||||
const authType = await getAuthTypeSS();
|
||||
const response = await logoutSS(authType, request.headers);
|
||||
if (response && response.ok) {
|
||||
return NextResponse.redirect(new URL("/auth/login", getDomain(request)));
|
||||
const authTypeMetadata = await getAuthTypeMetadataSS();
|
||||
const response = await logoutSS(authTypeMetadata.authType, request.headers);
|
||||
if (!response || response.ok) {
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
return new Response(null, { status: 204 });
|
||||
return new Response(response.body, { status: response?.status });
|
||||
};
|
||||
|
@ -17,13 +17,14 @@ export const Header: React.FC<HeaderProps> = ({ user }) => {
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleLogout = () => {
|
||||
logout().then((isSuccess) => {
|
||||
if (!isSuccess) {
|
||||
alert("Failed to logout");
|
||||
}
|
||||
router.push("/auth/login");
|
||||
});
|
||||
const handleLogout = async () => {
|
||||
const response = await logout();
|
||||
if (!response.ok) {
|
||||
alert("Failed to logout");
|
||||
}
|
||||
// disable auto-redirect immediately after logging out so the user
|
||||
// is not immediately re-logged in
|
||||
router.push("/auth/login?disableAutoRedirect=true");
|
||||
};
|
||||
|
||||
// When dropdownOpen state changes, it attaches/removes the click listener
|
||||
|
@ -1,7 +1,5 @@
|
||||
export type AuthType = "disabled" | "google_oauth" | "oidc" | "saml";
|
||||
|
||||
export const OAUTH_NAME = process.env.OAUTH_NAME || "Google";
|
||||
|
||||
export const INTERNAL_URL = process.env.INTERNAL_URL || "http://127.0.0.1:8080";
|
||||
export const NEXT_PUBLIC_DISABLE_STREAMING =
|
||||
process.env.NEXT_PUBLIC_DISABLE_STREAMING?.toLowerCase() === "true";
|
||||
|
@ -12,10 +12,10 @@ export const getCurrentUser = async (): Promise<User | null> => {
|
||||
return user;
|
||||
};
|
||||
|
||||
export const logout = async (): Promise<boolean> => {
|
||||
export const logout = async (): Promise<Response> => {
|
||||
const response = await fetch("/auth/logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
return response.ok;
|
||||
return response;
|
||||
};
|
||||
|
@ -4,18 +4,30 @@ import { buildUrl } from "./utilsSS";
|
||||
import { ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies";
|
||||
import { AuthType } from "./constants";
|
||||
|
||||
export const getAuthTypeSS = async (): Promise<AuthType> => {
|
||||
export interface AuthTypeMetadata {
|
||||
authType: AuthType;
|
||||
autoRedirect: boolean;
|
||||
}
|
||||
|
||||
export const getAuthTypeMetadataSS = async (): Promise<AuthTypeMetadata> => {
|
||||
const res = await fetch(buildUrl("/auth/type"));
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to fetch data");
|
||||
}
|
||||
|
||||
const data: { auth_type: string } = await res.json();
|
||||
return data.auth_type as AuthType;
|
||||
const authType = data.auth_type as AuthType;
|
||||
|
||||
// for SAML / OIDC, we auto-redirect the user to the IdP when the user visits
|
||||
// Danswer in an un-authenticated state
|
||||
if (authType === "oidc" || authType === "saml") {
|
||||
return { authType, autoRedirect: true };
|
||||
}
|
||||
return { authType, autoRedirect: false };
|
||||
};
|
||||
|
||||
export const getAuthDisabledSS = async (): Promise<boolean> => {
|
||||
return (await getAuthTypeSS()) === "disabled";
|
||||
return (await getAuthTypeMetadataSS()).authType === "disabled";
|
||||
};
|
||||
|
||||
const geOIDCAuthUrlSS = async (): Promise<string> => {
|
||||
@ -48,21 +60,19 @@ const getSAMLAuthUrlSS = async (): Promise<string> => {
|
||||
return data.authorization_url;
|
||||
};
|
||||
|
||||
export const getAuthUrlSS = async (
|
||||
authType: AuthType
|
||||
): Promise<[string, boolean]> => {
|
||||
// Returns the auth url and whether or not we should auto-redirect
|
||||
export const getAuthUrlSS = async (authType: AuthType): Promise<string> => {
|
||||
// Returns the auth url for the given auth type
|
||||
switch (authType) {
|
||||
case "disabled":
|
||||
return ["", true];
|
||||
return "";
|
||||
case "google_oauth": {
|
||||
return [await getGoogleOAuthUrlSS(), false];
|
||||
return await getGoogleOAuthUrlSS();
|
||||
}
|
||||
case "saml": {
|
||||
return [await getSAMLAuthUrlSS(), true];
|
||||
return await getSAMLAuthUrlSS();
|
||||
}
|
||||
case "oidc": {
|
||||
return [await geOIDCAuthUrlSS(), true];
|
||||
return await geOIDCAuthUrlSS();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user