mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-01-09 14:57:59 -05:00
refactor(frontend): move auth APIs to a dedicated service handler (#10932)
This commit is contained in:
@@ -7,6 +7,7 @@ import i18next from "i18next";
|
|||||||
import { I18nextProvider } from "react-i18next";
|
import { I18nextProvider } from "react-i18next";
|
||||||
import GitSettingsScreen from "#/routes/git-settings";
|
import GitSettingsScreen from "#/routes/git-settings";
|
||||||
import OpenHands from "#/api/open-hands";
|
import OpenHands from "#/api/open-hands";
|
||||||
|
import AuthService from "#/api/auth-service/auth-service.api";
|
||||||
import { MOCK_DEFAULT_USER_SETTINGS } from "#/mocks/handlers";
|
import { MOCK_DEFAULT_USER_SETTINGS } from "#/mocks/handlers";
|
||||||
import { GetConfigResponse } from "#/api/open-hands.types";
|
import { GetConfigResponse } from "#/api/open-hands.types";
|
||||||
import * as ToastHandlers from "#/utils/custom-toast-handlers";
|
import * as ToastHandlers from "#/utils/custom-toast-handlers";
|
||||||
@@ -392,7 +393,7 @@ describe("Form submission", () => {
|
|||||||
|
|
||||||
it("should call logout when pressing the disconnect tokens button", async () => {
|
it("should call logout when pressing the disconnect tokens button", async () => {
|
||||||
const getConfigSpy = vi.spyOn(OpenHands, "getConfig");
|
const getConfigSpy = vi.spyOn(OpenHands, "getConfig");
|
||||||
const logoutSpy = vi.spyOn(OpenHands, "logout");
|
const logoutSpy = vi.spyOn(AuthService, "logout");
|
||||||
const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
|
const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
|
||||||
|
|
||||||
getConfigSpy.mockResolvedValue(VALID_OSS_CONFIG);
|
getConfigSpy.mockResolvedValue(VALID_OSS_CONFIG);
|
||||||
|
|||||||
52
frontend/src/api/auth-service/auth-service.api.ts
Normal file
52
frontend/src/api/auth-service/auth-service.api.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { openHands } from "../open-hands-axios";
|
||||||
|
import { AuthenticateResponse, GitHubAccessTokenResponse } from "./auth.types";
|
||||||
|
import { GetConfigResponse } from "../open-hands.types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication service for handling all authentication-related API calls
|
||||||
|
*/
|
||||||
|
class AuthService {
|
||||||
|
/**
|
||||||
|
* Authenticate with GitHub token
|
||||||
|
* @param appMode The application mode (saas or oss)
|
||||||
|
* @returns Response with authentication status and user info if successful
|
||||||
|
*/
|
||||||
|
static async authenticate(
|
||||||
|
appMode: GetConfigResponse["APP_MODE"],
|
||||||
|
): Promise<boolean> {
|
||||||
|
if (appMode === "oss") return true;
|
||||||
|
|
||||||
|
// Just make the request, if it succeeds (no exception thrown), return true
|
||||||
|
await openHands.post<AuthenticateResponse>("/api/authenticate");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get GitHub access token from Keycloak callback
|
||||||
|
* @param code Code provided by GitHub
|
||||||
|
* @returns GitHub access token
|
||||||
|
*/
|
||||||
|
static async getGitHubAccessToken(
|
||||||
|
code: string,
|
||||||
|
): Promise<GitHubAccessTokenResponse> {
|
||||||
|
const { data } = await openHands.post<GitHubAccessTokenResponse>(
|
||||||
|
"/api/keycloak/callback",
|
||||||
|
{
|
||||||
|
code,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout user from the application
|
||||||
|
* @param appMode The application mode (saas or oss)
|
||||||
|
*/
|
||||||
|
static async logout(appMode: GetConfigResponse["APP_MODE"]): Promise<void> {
|
||||||
|
const endpoint =
|
||||||
|
appMode === "saas" ? "/api/logout" : "/api/unset-provider-tokens";
|
||||||
|
await openHands.post(endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthService;
|
||||||
8
frontend/src/api/auth-service/auth.types.ts
Normal file
8
frontend/src/api/auth-service/auth.types.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export interface AuthenticateResponse {
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GitHubAccessTokenResponse {
|
||||||
|
access_token: string;
|
||||||
|
}
|
||||||
@@ -2,10 +2,8 @@ import { AxiosHeaders } from "axios";
|
|||||||
import {
|
import {
|
||||||
Feedback,
|
Feedback,
|
||||||
FeedbackResponse,
|
FeedbackResponse,
|
||||||
GitHubAccessTokenResponse,
|
|
||||||
GetConfigResponse,
|
GetConfigResponse,
|
||||||
GetVSCodeUrlResponse,
|
GetVSCodeUrlResponse,
|
||||||
AuthenticateResponse,
|
|
||||||
Conversation,
|
Conversation,
|
||||||
ResultSet,
|
ResultSet,
|
||||||
GetTrajectoryResponse,
|
GetTrajectoryResponse,
|
||||||
@@ -210,20 +208,6 @@ class OpenHands {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Authenticate with GitHub token
|
|
||||||
* @returns Response with authentication status and user info if successful
|
|
||||||
*/
|
|
||||||
static async authenticate(
|
|
||||||
appMode: GetConfigResponse["APP_MODE"],
|
|
||||||
): Promise<boolean> {
|
|
||||||
if (appMode === "oss") return true;
|
|
||||||
|
|
||||||
// Just make the request, if it succeeds (no exception thrown), return true
|
|
||||||
await openHands.post<AuthenticateResponse>("/api/authenticate");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the blob of the workspace zip
|
* Get the blob of the workspace zip
|
||||||
* @returns Blob of the workspace zip
|
* @returns Blob of the workspace zip
|
||||||
@@ -249,22 +233,6 @@ class OpenHands {
|
|||||||
return Object.keys(response.data.hosts);
|
return Object.keys(response.data.hosts);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param code Code provided by GitHub
|
|
||||||
* @returns GitHub access token
|
|
||||||
*/
|
|
||||||
static async getGitHubAccessToken(
|
|
||||||
code: string,
|
|
||||||
): Promise<GitHubAccessTokenResponse> {
|
|
||||||
const { data } = await openHands.post<GitHubAccessTokenResponse>(
|
|
||||||
"/api/keycloak/callback",
|
|
||||||
{
|
|
||||||
code,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the VSCode URL
|
* Get the VSCode URL
|
||||||
* @returns VSCode URL
|
* @returns VSCode URL
|
||||||
@@ -487,12 +455,6 @@ class OpenHands {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async logout(appMode: GetConfigResponse["APP_MODE"]): Promise<void> {
|
|
||||||
const endpoint =
|
|
||||||
appMode === "saas" ? "/api/logout" : "/api/unset-provider-tokens";
|
|
||||||
await openHands.post(endpoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async getGitChanges(conversationId: string): Promise<GitChange[]> {
|
static async getGitChanges(conversationId: string): Promise<GitChange[]> {
|
||||||
const url = `${this.getConversationUrl(conversationId)}/git/changes`;
|
const url = `${this.getConversationUrl(conversationId)}/git/changes`;
|
||||||
const { data } = await openHands.get<GitChange[]>(url, {
|
const { data } = await openHands.get<GitChange[]>(url, {
|
||||||
|
|||||||
@@ -26,10 +26,6 @@ export interface FeedbackResponse {
|
|||||||
body: FeedbackBodyResponse;
|
body: FeedbackBodyResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GitHubAccessTokenResponse {
|
|
||||||
access_token: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AuthenticationResponse {
|
export interface AuthenticationResponse {
|
||||||
message: string;
|
message: string;
|
||||||
login?: string; // Only present when allow list is enabled
|
login?: string; // Only present when allow list is enabled
|
||||||
@@ -44,6 +40,16 @@ export interface Feedback {
|
|||||||
trajectory: unknown[];
|
trajectory: unknown[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GetVSCodeUrlResponse {
|
||||||
|
vscode_url: string | null;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetTrajectoryResponse {
|
||||||
|
trajectory: unknown[] | null;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GetConfigResponse {
|
export interface GetConfigResponse {
|
||||||
APP_MODE: "saas" | "oss";
|
APP_MODE: "saas" | "oss";
|
||||||
APP_SLUG?: string;
|
APP_SLUG?: string;
|
||||||
@@ -63,21 +69,6 @@ export interface GetConfigResponse {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetVSCodeUrlResponse {
|
|
||||||
vscode_url: string | null;
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetTrajectoryResponse {
|
|
||||||
trajectory: unknown[] | null;
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AuthenticateResponse {
|
|
||||||
message?: string;
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RepositorySelection {
|
export interface RepositorySelection {
|
||||||
selected_repository: string | null;
|
selected_repository: string | null;
|
||||||
selected_branch: string | null;
|
selected_branch: string | null;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import posthog from "posthog-js";
|
import posthog from "posthog-js";
|
||||||
import OpenHands from "#/api/open-hands";
|
import AuthService from "#/api/auth-service/auth-service.api";
|
||||||
import { useConfig } from "../query/use-config";
|
import { useConfig } from "../query/use-config";
|
||||||
import { clearLoginData } from "#/utils/local-storage";
|
import { clearLoginData } from "#/utils/local-storage";
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ export const useLogout = () => {
|
|||||||
const { data: config } = useConfig();
|
const { data: config } = useConfig();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: () => OpenHands.logout(config?.APP_MODE ?? "oss"),
|
mutationFn: () => AuthService.logout(config?.APP_MODE ?? "oss"),
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
queryClient.removeQueries({ queryKey: ["tasks"] });
|
queryClient.removeQueries({ queryKey: ["tasks"] });
|
||||||
queryClient.removeQueries({ queryKey: ["settings"] });
|
queryClient.removeQueries({ queryKey: ["settings"] });
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import axios, { AxiosError } from "axios";
|
import axios, { AxiosError } from "axios";
|
||||||
import OpenHands from "#/api/open-hands";
|
import AuthService from "#/api/auth-service/auth-service.api";
|
||||||
import { useConfig } from "./use-config";
|
import { useConfig } from "./use-config";
|
||||||
import { useIsOnTosPage } from "#/hooks/use-is-on-tos-page";
|
import { useIsOnTosPage } from "#/hooks/use-is-on-tos-page";
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ export const useIsAuthed = () => {
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
try {
|
try {
|
||||||
// If in OSS mode or authentication succeeds, return true
|
// If in OSS mode or authentication succeeds, return true
|
||||||
await OpenHands.authenticate(appMode!);
|
await AuthService.authenticate(appMode!);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If it's a 401 error, return false (not authenticated)
|
// If it's a 401 error, return false (not authenticated)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { delay, http, HttpResponse } from "msw";
|
import { delay, http, HttpResponse } from "msw";
|
||||||
import {
|
import {
|
||||||
GetConfigResponse,
|
|
||||||
Conversation,
|
Conversation,
|
||||||
ResultSet,
|
ResultSet,
|
||||||
|
GetConfigResponse,
|
||||||
} from "#/api/open-hands.types";
|
} from "#/api/open-hands.types";
|
||||||
import { DEFAULT_SETTINGS } from "#/services/settings";
|
import { DEFAULT_SETTINGS } from "#/services/settings";
|
||||||
import { STRIPE_BILLING_HANDLERS } from "./billing-handlers";
|
import { STRIPE_BILLING_HANDLERS } from "./billing-handlers";
|
||||||
|
|||||||
Reference in New Issue
Block a user