Self-hosted dashboard and admin auth (#4837)
Co-authored-by: kartik-mem0 <kartik.labhshetwar@mem0.ai>
This commit is contained in:
co-authored by
kartik-mem0
parent
15feaa8ac4
commit
db8ac61713
@@ -0,0 +1,34 @@
|
||||
export const AUTH_ENDPOINTS = {
|
||||
SETUP_STATUS: "/auth/setup-status",
|
||||
REGISTER: "/auth/register",
|
||||
LOGIN: "/auth/login",
|
||||
REFRESH: "/auth/refresh",
|
||||
ME: "/auth/me",
|
||||
CHANGE_PASSWORD: "/auth/change-password",
|
||||
ONBOARDING_COMPLETE: "/auth/onboarding-complete",
|
||||
} as const;
|
||||
|
||||
export const MEMORY_ENDPOINTS = {
|
||||
BASE: "/memories",
|
||||
BY_ID: (memoryId: string) => `/memories/${memoryId}`,
|
||||
HISTORY: (memoryId: string) => `/memories/${memoryId}/history`,
|
||||
CONFIGURE: "/configure",
|
||||
CONFIGURE_PROVIDERS: "/configure/providers",
|
||||
RESET: "/reset",
|
||||
GENERATE_INSTRUCTIONS: "/generate-instructions",
|
||||
} as const;
|
||||
|
||||
export const API_KEY_ENDPOINTS = {
|
||||
BASE: "/api-keys",
|
||||
BY_ID: (keyId: string) => `/api-keys/${keyId}`,
|
||||
} as const;
|
||||
|
||||
export const REQUEST_ENDPOINTS = {
|
||||
BASE: "/requests",
|
||||
} as const;
|
||||
|
||||
export const ENTITY_ENDPOINTS = {
|
||||
BASE: "/entities",
|
||||
BY_ID: (type: string, id: string) =>
|
||||
`/entities/${type}/${encodeURIComponent(id)}`,
|
||||
} as const;
|
||||
@@ -0,0 +1,113 @@
|
||||
import axios, { AxiosError, AxiosInstance } from "axios";
|
||||
|
||||
let cachedToken: string | null = null;
|
||||
const LOGIN_PATH = "/login";
|
||||
|
||||
export const setAccessToken = (token: string | null) => {
|
||||
cachedToken = token;
|
||||
};
|
||||
|
||||
export const getAccessToken = (): string | null => {
|
||||
return cachedToken;
|
||||
};
|
||||
|
||||
const handleTokenError = () => {
|
||||
cachedToken = null;
|
||||
};
|
||||
|
||||
const redirectToLogin = () => {
|
||||
if (typeof window !== "undefined") {
|
||||
window.location.href = LOGIN_PATH;
|
||||
}
|
||||
};
|
||||
|
||||
const refreshAccessToken = async () => {
|
||||
const refreshResponse = await fetch("/api/auth/refresh", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!refreshResponse.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await refreshResponse.json();
|
||||
setAccessToken(data.access_token);
|
||||
return data.access_token as string;
|
||||
};
|
||||
|
||||
const createApi = (): AxiosInstance & {
|
||||
postStream: (url: string, data: unknown) => Promise<Response>;
|
||||
} => {
|
||||
const api = axios.create({
|
||||
baseURL: process.env.NEXT_PUBLIC_API_URL,
|
||||
});
|
||||
|
||||
api.interceptors.request.use(
|
||||
async (config) => {
|
||||
if (cachedToken) {
|
||||
config.headers = config.headers ?? {};
|
||||
config.headers.Authorization = `Bearer ${cachedToken}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error: AxiosError<{ error?: string }>) => {
|
||||
if (error.response?.status === 401) {
|
||||
handleTokenError();
|
||||
|
||||
try {
|
||||
const nextToken = await refreshAccessToken();
|
||||
if (nextToken && error.config) {
|
||||
error.config.headers = error.config.headers ?? {};
|
||||
error.config.headers.Authorization = `Bearer ${nextToken}`;
|
||||
return api.request(error.config);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
handleTokenError();
|
||||
redirectToLogin();
|
||||
}
|
||||
|
||||
if (error.response?.data?.error) {
|
||||
return Promise.reject(error.response.data.error);
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
const postStream = async (url: string, data: unknown): Promise<Response> => {
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}${url}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: cachedToken ? `Bearer ${cachedToken}` : "",
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
handleTokenError();
|
||||
redirectToLogin();
|
||||
throw new Error("Unauthorized");
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.error || "Request failed");
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
return Object.assign(api, { postStream });
|
||||
};
|
||||
|
||||
export const api = createApi();
|
||||
@@ -0,0 +1,5 @@
|
||||
export const toTitleCase = (str: string) => {
|
||||
if (!str) return "";
|
||||
str = str.toLowerCase();
|
||||
return str.replace(/\b\w/g, (char) => char.toUpperCase());
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
type ProviderConfig = {
|
||||
provider?: string;
|
||||
config?: {
|
||||
model?: string;
|
||||
api_key?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type EffectiveConfig = {
|
||||
llm?: ProviderConfig;
|
||||
embedder?: ProviderConfig;
|
||||
};
|
||||
|
||||
export const getEffectiveConfig = (data: unknown): EffectiveConfig | null => {
|
||||
if (!data || typeof data !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const record = data as Record<string, unknown>;
|
||||
return (
|
||||
(record.effective_config as EffectiveConfig) ||
|
||||
(record.config as EffectiveConfig) ||
|
||||
(record as EffectiveConfig)
|
||||
);
|
||||
};
|
||||
|
||||
export const buildProviderConfig = ({
|
||||
provider,
|
||||
model,
|
||||
apiKey,
|
||||
}: {
|
||||
provider: string;
|
||||
model: string;
|
||||
apiKey?: string;
|
||||
}) => {
|
||||
if (!provider) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
provider,
|
||||
config: {
|
||||
model: model || undefined,
|
||||
api_key: apiKey || undefined,
|
||||
},
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user