198 lines
5.0 KiB
TypeScript
198 lines
5.0 KiB
TypeScript
import { IS_ENTERPRISE } from "@/lib/constants/config";
|
|
import { BifrostErrorResponse } from "@/lib/types/config";
|
|
import { getApiBaseUrl } from "@/lib/utils/port";
|
|
import { createBaseQueryWithRefresh } from "@enterprise/lib/store/utils/baseQueryWithRefresh";
|
|
import { clearOAuthStorage } from "@enterprise/lib/store/utils/tokenManager";
|
|
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
|
|
|
// Auth tokens are now stored in HTTP-only cookies (set by server)
|
|
// No client-side token needed — handled by credentials: "include"
|
|
export const getTokenFromStorage = (): Promise<string | null> => {
|
|
return Promise.resolve(null);
|
|
};
|
|
|
|
// Helper function to set auth token
|
|
// Non-enterprise: no-op — auth relies on HTTPOnly cookies set by the server
|
|
// Enterprise: handled separately via tokenManager
|
|
export const setAuthToken = (_token: string | null) => {
|
|
// Non-enterprise auth is cookie-based; no client-side token storage needed.
|
|
// Enterprise token management is handled by the tokenManager module.
|
|
};
|
|
|
|
// Helper function to clear all auth-related storage
|
|
export const clearAuthStorage = () => {
|
|
if (typeof window === "undefined") {
|
|
return;
|
|
}
|
|
try {
|
|
// Clear traditional auth token
|
|
localStorage.removeItem("bifrost-auth-token");
|
|
|
|
// Clear enterprise OAuth tokens using tokenManager
|
|
if (IS_ENTERPRISE) {
|
|
clearOAuthStorage();
|
|
}
|
|
} catch (error) {
|
|
console.error("Error clearing auth storage:", error);
|
|
}
|
|
};
|
|
|
|
// Define the base query with authentication headers
|
|
const baseQuery = fetchBaseQuery({
|
|
baseUrl: getApiBaseUrl(),
|
|
credentials: "include",
|
|
prepareHeaders: async (headers) => {
|
|
headers.set("Content-Type", "application/json");
|
|
// Automatically include token from localStorage in Authorization header
|
|
const token = await getTokenFromStorage();
|
|
if (token) {
|
|
headers.set("Authorization", `Bearer ${token}`);
|
|
}
|
|
return headers;
|
|
},
|
|
});
|
|
|
|
// Wrap base query with enterprise refresh logic (or passthrough for non-enterprise)
|
|
const baseQueryWithRefresh = createBaseQueryWithRefresh(baseQuery);
|
|
|
|
// Enhanced base query with error handling
|
|
const baseQueryWithErrorHandling: typeof baseQueryWithRefresh = async (args: any, api: any, extraOptions: any) => {
|
|
// First apply refresh logic (enterprise-specific, handles 401)
|
|
const result = await baseQueryWithRefresh(args, api, extraOptions);
|
|
|
|
// Then handle other error types
|
|
if (result.error) {
|
|
const error = result.error as any;
|
|
|
|
// Handle 401 for non-enterprise (no refresh available)
|
|
if (error?.status === 401 && !IS_ENTERPRISE) {
|
|
clearAuthStorage();
|
|
if (typeof window !== "undefined" && !window.location.pathname.includes("/login")) {
|
|
window.location.href = "/login";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Handle specific error types
|
|
if (error?.status === "FETCH_ERROR") {
|
|
// Network error
|
|
return {
|
|
...result,
|
|
error: {
|
|
...error,
|
|
data: {
|
|
error: {
|
|
message: "Network error: Unable to connect to the server",
|
|
},
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
// Handle other errors with proper BifrostErrorResponse format
|
|
if (error?.data) {
|
|
const errorData = error.data as BifrostErrorResponse;
|
|
if (errorData.error?.message) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// Fallback error message
|
|
return {
|
|
...result,
|
|
error: {
|
|
...error,
|
|
data: {
|
|
error: {
|
|
message: "An unexpected error occurred",
|
|
},
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
// Create the base API
|
|
export const baseApi = createApi({
|
|
reducerPath: "api",
|
|
baseQuery: baseQueryWithErrorHandling,
|
|
tagTypes: [
|
|
"Logs",
|
|
"MCPLogs",
|
|
"Providers",
|
|
"MCPClients",
|
|
"Config",
|
|
"CacheConfig",
|
|
"VirtualKeys",
|
|
"Teams",
|
|
"Customers",
|
|
"Budgets",
|
|
"RateLimits",
|
|
"UsageStats",
|
|
"DebugStats",
|
|
"HealthCheck",
|
|
"DBKeys",
|
|
"ProviderKeys",
|
|
"Models",
|
|
"BaseModels",
|
|
"ModelConfigs",
|
|
"ProviderGovernance",
|
|
"Plugins",
|
|
"SCIMProviders",
|
|
"User",
|
|
"Guardrails",
|
|
"ClusterNodes",
|
|
"Users",
|
|
"GuardrailRules",
|
|
"Roles",
|
|
"Resources",
|
|
"Operations",
|
|
"Permissions",
|
|
"APIKeys",
|
|
"OAuth2Config",
|
|
"RoutingRules",
|
|
"PricingOverrides",
|
|
"MCPToolGroups",
|
|
"AuditLogs",
|
|
"UserGovernance",
|
|
"LargePayloadConfig",
|
|
"Folders",
|
|
"Prompts",
|
|
"Versions",
|
|
"Sessions",
|
|
"AccessProfiles",
|
|
"BusinessUnits",
|
|
"PromptDeployments",
|
|
],
|
|
endpoints: () => ({}),
|
|
});
|
|
|
|
// Helper function to extract error message from RTK Query error
|
|
export const getErrorMessage = (error: unknown): string => {
|
|
if (error === undefined || error === null) {
|
|
return "An unexpected error occurred";
|
|
}
|
|
if (error instanceof Error) {
|
|
return error.message;
|
|
}
|
|
if (
|
|
typeof error === "object" &&
|
|
error &&
|
|
"data" in error &&
|
|
error.data &&
|
|
typeof error.data === "object" &&
|
|
"error" in error.data &&
|
|
error.data.error &&
|
|
typeof error.data.error === "object" &&
|
|
"message" in error.data.error &&
|
|
typeof error.data.error.message === "string"
|
|
) {
|
|
return error.data.error.message.charAt(0).toUpperCase() + error.data.error.message.slice(1);
|
|
}
|
|
if (typeof error === "object" && error && "message" in error && typeof error.message === "string") {
|
|
return error.message;
|
|
}
|
|
return "An unexpected error occurred";
|
|
}; |