mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-09-27 13:46:37 +08:00
218 lines
4.9 KiB
TypeScript
218 lines
4.9 KiB
TypeScript
import React, { useState, useEffect } from "react";
|
|
|
|
export interface User {
|
|
id: string;
|
|
email?: string;
|
|
user_metadata?: any;
|
|
}
|
|
|
|
export interface AuthState {
|
|
user: User | null;
|
|
loading: boolean;
|
|
authenticated: boolean;
|
|
}
|
|
|
|
export function useAuth() {
|
|
const [authState, setAuthState] = useState<AuthState>({
|
|
user: null,
|
|
loading: true,
|
|
authenticated: false,
|
|
});
|
|
|
|
const checkAuth = async () => {
|
|
try {
|
|
const response = await fetch("/api/auth/check", {
|
|
method: "GET",
|
|
credentials: "include",
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
if (data.authenticated) {
|
|
setAuthState({
|
|
user: data.user,
|
|
loading: false,
|
|
authenticated: true,
|
|
});
|
|
return data.user;
|
|
}
|
|
}
|
|
|
|
setAuthState({
|
|
user: null,
|
|
loading: false,
|
|
authenticated: false,
|
|
});
|
|
return null;
|
|
} catch (error) {
|
|
console.error("Auth check failed:", error);
|
|
setAuthState({
|
|
user: null,
|
|
loading: false,
|
|
authenticated: false,
|
|
});
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const login = async (accessToken: string, refreshToken?: string) => {
|
|
try {
|
|
const response = await fetch("/api/auth/callback", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
credentials: "include",
|
|
body: JSON.stringify({
|
|
access_token: accessToken,
|
|
refresh_token: refreshToken,
|
|
}),
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
setAuthState({
|
|
user: data.user,
|
|
loading: false,
|
|
authenticated: true,
|
|
});
|
|
return data.user;
|
|
} else {
|
|
throw new Error("Login failed");
|
|
}
|
|
} catch (error) {
|
|
console.error("Login failed:", error);
|
|
setAuthState({
|
|
user: null,
|
|
loading: false,
|
|
authenticated: false,
|
|
});
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const logout = async () => {
|
|
try {
|
|
await fetch("/api/auth/logout", {
|
|
method: "POST",
|
|
credentials: "include",
|
|
});
|
|
} catch (error) {
|
|
console.error("Logout request failed:", error);
|
|
} finally {
|
|
setAuthState({
|
|
user: null,
|
|
loading: false,
|
|
authenticated: false,
|
|
});
|
|
// Redirect to login page
|
|
window.location.href = "/login";
|
|
}
|
|
};
|
|
|
|
const refreshToken = async () => {
|
|
try {
|
|
const response = await fetch("/api/auth/refresh", {
|
|
method: "POST",
|
|
credentials: "include",
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
setAuthState({
|
|
user: data.user,
|
|
loading: false,
|
|
authenticated: true,
|
|
});
|
|
return data.user;
|
|
} else {
|
|
// Refresh failed, user needs to login again
|
|
setAuthState({
|
|
user: null,
|
|
loading: false,
|
|
authenticated: false,
|
|
});
|
|
return null;
|
|
}
|
|
} catch (error) {
|
|
console.error("Token refresh failed:", error);
|
|
setAuthState({
|
|
user: null,
|
|
loading: false,
|
|
authenticated: false,
|
|
});
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const getUserFromCookie = () => {
|
|
if (typeof window === "undefined") return null;
|
|
|
|
try {
|
|
const userInfoCookie = document.cookie
|
|
.split("; ")
|
|
.find((row) => row.startsWith("sb-user-info="));
|
|
|
|
if (userInfoCookie) {
|
|
const userInfo = decodeURIComponent(userInfoCookie.split("=")[1]);
|
|
return JSON.parse(userInfo);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error reading user cookie:", error);
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
useEffect(() => {
|
|
// Try to get user from cookie first for faster initial load
|
|
const cachedUser = getUserFromCookie();
|
|
if (cachedUser) {
|
|
setAuthState({
|
|
user: cachedUser,
|
|
loading: true, // Still check with server
|
|
authenticated: true,
|
|
});
|
|
}
|
|
|
|
// Then verify with server
|
|
checkAuth();
|
|
}, []);
|
|
|
|
return {
|
|
...authState,
|
|
checkAuth,
|
|
login,
|
|
logout,
|
|
refreshToken,
|
|
getUserFromCookie,
|
|
};
|
|
}
|
|
|
|
// Higher-order component for protecting routes
|
|
export function withAuth<T extends object>(Component: React.ComponentType<T>) {
|
|
return function AuthenticatedComponent(props: T) {
|
|
const { authenticated, loading, user } = useAuth();
|
|
|
|
useEffect(() => {
|
|
if (!loading && !authenticated) {
|
|
// Redirect to login if not authenticated
|
|
const currentPath = window.location.pathname;
|
|
window.location.href = `/login?redirect_to=${encodeURIComponent(
|
|
currentPath,
|
|
)}`;
|
|
}
|
|
}, [authenticated, loading]);
|
|
|
|
if (loading) {
|
|
return <div>Loading...</div>;
|
|
}
|
|
|
|
if (!authenticated) {
|
|
return <div>Redirecting to login...</div>;
|
|
}
|
|
|
|
return <Component {...props} user={user} />;
|
|
};
|
|
}
|