mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-11-17 22:43:42 +08:00
cap nhat auth he thong de nhung voi chebichat
This commit is contained in:
227
app/hooks/useAuth.ts
Normal file
227
app/hooks/useAuth.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
"use client";
|
||||
|
||||
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 Record<string, any>>(
|
||||
Component: React.ComponentType<T>,
|
||||
) {
|
||||
const AuthenticatedComponent = (props: T) => {
|
||||
const { authenticated, loading } = 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 React.createElement("div", null, "Loading...");
|
||||
}
|
||||
|
||||
if (!authenticated) {
|
||||
return React.createElement("div", null, "Redirecting to login...");
|
||||
}
|
||||
|
||||
return React.createElement(Component, props);
|
||||
};
|
||||
|
||||
AuthenticatedComponent.displayName = `withAuth(${
|
||||
Component.displayName || Component.name
|
||||
})`;
|
||||
|
||||
return AuthenticatedComponent;
|
||||
}
|
||||
217
app/hooks/useAuth.tsx
Normal file
217
app/hooks/useAuth.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
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} />;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user