mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-01-09 23:08:04 -05:00
Update login (#8743)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
@@ -129,7 +129,7 @@ export function FeedbackForm({ onClose, polarity }: FeedbackFormProps) {
|
||||
isDisabled={isPending}
|
||||
>
|
||||
{isPending
|
||||
? t(I18nKey.FEEDBACK$SUBMITTING_LABEL) || "Submitting..."
|
||||
? t(I18nKey.FEEDBACK$SUBMITTING_LABEL)
|
||||
: t(I18nKey.FEEDBACK$SHARE_LABEL)}
|
||||
</BrandButton>
|
||||
<BrandButton
|
||||
@@ -144,8 +144,7 @@ export function FeedbackForm({ onClose, polarity }: FeedbackFormProps) {
|
||||
</div>
|
||||
{isPending && (
|
||||
<p className="text-sm text-center text-neutral-400">
|
||||
{t(I18nKey.FEEDBACK$SUBMITTING_MESSAGE) ||
|
||||
"Submitting your feedback, please wait..."}
|
||||
{t(I18nKey.FEEDBACK$SUBMITTING_MESSAGE)}
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
|
||||
@@ -9,7 +9,6 @@ import GitHubLogo from "#/assets/branding/github-logo.svg?react";
|
||||
import GitLabLogo from "#/assets/branding/gitlab-logo.svg?react";
|
||||
import { useAuthUrl } from "#/hooks/use-auth-url";
|
||||
import { GetConfigResponse } from "#/api/open-hands.types";
|
||||
import { LoginMethod, setLoginMethod } from "#/utils/local-storage";
|
||||
|
||||
interface AuthModalProps {
|
||||
githubAuthUrl: string | null;
|
||||
@@ -26,10 +25,6 @@ export function AuthModal({ githubAuthUrl, appMode }: AuthModalProps) {
|
||||
|
||||
const handleGitHubAuth = () => {
|
||||
if (githubAuthUrl) {
|
||||
// Store the login method in local storage (only in SAAS mode)
|
||||
if (appMode === "saas") {
|
||||
setLoginMethod(LoginMethod.GITHUB);
|
||||
}
|
||||
// Always start the OIDC flow, let the backend handle TOS check
|
||||
window.location.href = githubAuthUrl;
|
||||
}
|
||||
@@ -37,10 +32,6 @@ export function AuthModal({ githubAuthUrl, appMode }: AuthModalProps) {
|
||||
|
||||
const handleGitLabAuth = () => {
|
||||
if (gitlabAuthUrl) {
|
||||
// Store the login method in local storage (only in SAAS mode)
|
||||
if (appMode === "saas") {
|
||||
setLoginMethod(LoginMethod.GITLAB);
|
||||
}
|
||||
// Always start the OIDC flow, let the backend handle TOS check
|
||||
window.location.href = gitlabAuthUrl;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useNavigate } from "react-router";
|
||||
import posthog from "posthog-js";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { useConfig } from "../query/use-config";
|
||||
@@ -8,7 +7,6 @@ import { clearLoginData } from "#/utils/local-storage";
|
||||
export const useLogout = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { data: config } = useConfig();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: () => OpenHands.logout(config?.APP_MODE ?? "oss"),
|
||||
@@ -24,7 +22,6 @@ export const useLogout = () => {
|
||||
}
|
||||
|
||||
posthog.reset();
|
||||
await navigate("/");
|
||||
|
||||
// Refresh the page after all logout logic is completed
|
||||
window.location.reload();
|
||||
|
||||
49
frontend/src/hooks/use-auth-callback.ts
Normal file
49
frontend/src/hooks/use-auth-callback.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { useEffect } from "react";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { useIsAuthed } from "./query/use-is-authed";
|
||||
import { LoginMethod, setLoginMethod } from "#/utils/local-storage";
|
||||
import { useConfig } from "./query/use-config";
|
||||
|
||||
/**
|
||||
* Hook to handle authentication callback and set login method after successful authentication
|
||||
*/
|
||||
export const useAuthCallback = () => {
|
||||
const location = useLocation();
|
||||
const { data: isAuthed, isLoading: isAuthLoading } = useIsAuthed();
|
||||
const { data: config } = useConfig();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
// Only run in SAAS mode
|
||||
if (config?.APP_MODE !== "saas") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for auth to load
|
||||
if (isAuthLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only set login method if authentication was successful
|
||||
if (!isAuthed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we have a login_method query parameter
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
const loginMethod = searchParams.get("login_method");
|
||||
|
||||
// Set the login method if it's valid
|
||||
if (
|
||||
loginMethod === LoginMethod.GITHUB ||
|
||||
loginMethod === LoginMethod.GITLAB
|
||||
) {
|
||||
setLoginMethod(loginMethod as LoginMethod);
|
||||
|
||||
// Clean up the URL by removing the login_method parameter
|
||||
searchParams.delete("login_method");
|
||||
const newUrl = `${location.pathname}${searchParams.toString() ? `?${searchParams.toString()}` : ""}`;
|
||||
navigate(newUrl, { replace: true });
|
||||
}
|
||||
}, [isAuthed, isAuthLoading, location.search, config?.APP_MODE]);
|
||||
};
|
||||
@@ -53,8 +53,12 @@ export const useAutoLogin = () => {
|
||||
|
||||
// If we have an auth URL, redirect to it
|
||||
if (authUrl) {
|
||||
// Add the login method as a query parameter
|
||||
const url = new URL(authUrl);
|
||||
url.searchParams.append("login_method", loginMethod);
|
||||
|
||||
// After successful login, the user will be redirected back and can navigate to the last page
|
||||
window.location.href = authUrl;
|
||||
window.location.href = url.toString();
|
||||
}
|
||||
}, [
|
||||
config?.APP_MODE,
|
||||
|
||||
@@ -36,6 +36,7 @@ import { useDocumentTitleFromState } from "#/hooks/use-document-title-from-state
|
||||
import { transformVSCodeUrl } from "#/utils/vscode-url-helper";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { TabContent } from "#/components/layout/tab-content";
|
||||
import { useIsAuthed } from "#/hooks/query/use-is-authed";
|
||||
|
||||
function AppContent() {
|
||||
useConversationConfig();
|
||||
@@ -43,6 +44,7 @@ function AppContent() {
|
||||
const { data: settings } = useSettings();
|
||||
const { conversationId } = useConversationId();
|
||||
const { data: conversation, isFetched } = useActiveConversation();
|
||||
const { data: isAuthed } = useIsAuthed();
|
||||
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const dispatch = useDispatch();
|
||||
@@ -54,13 +56,13 @@ function AppContent() {
|
||||
const [width, setWidth] = React.useState(window.innerWidth);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isFetched && !conversation) {
|
||||
if (isFetched && !conversation && isAuthed) {
|
||||
displayErrorToast(
|
||||
"This conversation does not exist, or you do not have permission to access it.",
|
||||
);
|
||||
navigate("/");
|
||||
}
|
||||
}, [conversation, isFetched]);
|
||||
}, [conversation, isFetched, isAuthed]);
|
||||
|
||||
React.useEffect(() => {
|
||||
dispatch(clearTerminal());
|
||||
|
||||
@@ -23,6 +23,7 @@ import { SetupPaymentModal } from "#/components/features/payment/setup-payment-m
|
||||
import { displaySuccessToast } from "#/utils/custom-toast-handlers";
|
||||
import { useIsOnTosPage } from "#/hooks/use-is-on-tos-page";
|
||||
import { useAutoLogin } from "#/hooks/use-auto-login";
|
||||
import { useAuthCallback } from "#/hooks/use-auth-callback";
|
||||
import { LOCAL_STORAGE_KEYS } from "#/utils/local-storage";
|
||||
|
||||
export function ErrorBoundary() {
|
||||
@@ -88,6 +89,9 @@ export default function MainApp() {
|
||||
// Auto-login if login method is stored in local storage
|
||||
useAutoLogin();
|
||||
|
||||
// Handle authentication callback and set login method after successful authentication
|
||||
useAuthCallback();
|
||||
|
||||
React.useEffect(() => {
|
||||
// Don't change language when on TOS page
|
||||
if (!isOnTosPage && settings?.LANGUAGE) {
|
||||
@@ -131,8 +135,8 @@ export default function MainApp() {
|
||||
}
|
||||
}, [error?.status, pathname, isOnTosPage]);
|
||||
|
||||
// Check if login method exists in local storage
|
||||
const loginMethodExists = React.useMemo(() => {
|
||||
// Function to check if login method exists in local storage
|
||||
const checkLoginMethodExists = React.useCallback(() => {
|
||||
// Only check localStorage if we're in a browser environment
|
||||
if (typeof window !== "undefined" && window.localStorage) {
|
||||
return localStorage.getItem(LOCAL_STORAGE_KEYS.LOGIN_METHOD) !== null;
|
||||
@@ -140,6 +144,39 @@ export default function MainApp() {
|
||||
return false;
|
||||
}, []);
|
||||
|
||||
// State to track if login method exists
|
||||
const [loginMethodExists, setLoginMethodExists] = React.useState(
|
||||
checkLoginMethodExists(),
|
||||
);
|
||||
|
||||
// Listen for storage events to update loginMethodExists when logout happens
|
||||
React.useEffect(() => {
|
||||
const handleStorageChange = (event: StorageEvent) => {
|
||||
if (event.key === LOCAL_STORAGE_KEYS.LOGIN_METHOD) {
|
||||
setLoginMethodExists(checkLoginMethodExists());
|
||||
}
|
||||
};
|
||||
|
||||
// Also check on window focus, as logout might happen in another tab
|
||||
const handleWindowFocus = () => {
|
||||
setLoginMethodExists(checkLoginMethodExists());
|
||||
};
|
||||
|
||||
window.addEventListener("storage", handleStorageChange);
|
||||
window.addEventListener("focus", handleWindowFocus);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("storage", handleStorageChange);
|
||||
window.removeEventListener("focus", handleWindowFocus);
|
||||
};
|
||||
}, [checkLoginMethodExists]);
|
||||
|
||||
// Check login method status when auth status changes
|
||||
React.useEffect(() => {
|
||||
// When auth status changes (especially on logout), recheck login method
|
||||
setLoginMethodExists(checkLoginMethodExists());
|
||||
}, [isAuthed, checkLoginMethodExists]);
|
||||
|
||||
const renderAuthModal =
|
||||
!isAuthed &&
|
||||
!isAuthError &&
|
||||
|
||||
@@ -16,5 +16,8 @@ export const generateAuthUrl = (identityProvider: string, requestUrl: URL) => {
|
||||
authUrl = `auth.${requestUrl.hostname}`;
|
||||
}
|
||||
const scope = "openid email profile"; // OAuth scope - not user-facing
|
||||
return `https://${authUrl}/realms/allhands/protocol/openid-connect/auth?client_id=allhands&kc_idp_hint=${identityProvider}&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${encodeURIComponent(scope)}&state=${encodeURIComponent(requestUrl.href)}`;
|
||||
const separator = requestUrl.search ? "&" : "?";
|
||||
const cleanHref = requestUrl.href.replace(/\/$/, "");
|
||||
const state = `${cleanHref}${separator}login_method=${identityProvider}`;
|
||||
return `https://${authUrl}/realms/allhands/protocol/openid-connect/auth?client_id=allhands&kc_idp_hint=${identityProvider}&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${encodeURIComponent(scope)}&state=${encodeURIComponent(state)}`;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user