103 lines
2.9 KiB
React
103 lines
2.9 KiB
React
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
|
import { apiGet, apiPost, ApiError } from "../api/client";
|
|
|
|
const STORAGE_ACCESS = "auth_access";
|
|
const STORAGE_REFRESH = "auth_refresh";
|
|
|
|
const AuthContext = createContext(null);
|
|
|
|
export function AuthProvider({ children }) {
|
|
const [user, setUser] = useState(null);
|
|
const [accessToken, setAccessToken] = useState(() => {
|
|
if (typeof window === "undefined") return null;
|
|
return localStorage.getItem(STORAGE_ACCESS);
|
|
});
|
|
const [refreshToken, setRefreshToken] = useState(() => {
|
|
if (typeof window === "undefined") return null;
|
|
return localStorage.getItem(STORAGE_REFRESH);
|
|
});
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
const persistTokens = useCallback((access, refresh) => {
|
|
setAccessToken(access);
|
|
setRefreshToken(refresh);
|
|
if (typeof window !== "undefined") {
|
|
if (access) localStorage.setItem(STORAGE_ACCESS, access);
|
|
else localStorage.removeItem(STORAGE_ACCESS);
|
|
if (refresh) localStorage.setItem(STORAGE_REFRESH, refresh);
|
|
else localStorage.removeItem(STORAGE_REFRESH);
|
|
}
|
|
}, []);
|
|
|
|
const logout = useCallback(() => {
|
|
setUser(null);
|
|
persistTokens(null, null);
|
|
}, [persistTokens]);
|
|
|
|
const login = useCallback((access, refresh, userData) => {
|
|
persistTokens(access, refresh);
|
|
setUser(userData);
|
|
}, [persistTokens]);
|
|
|
|
// Restore user from token on mount
|
|
useEffect(() => {
|
|
if (!accessToken) {
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
apiGet("/auth/me/", accessToken)
|
|
.then((data) => {
|
|
setUser(data);
|
|
setLoading(false);
|
|
})
|
|
.catch((err) => {
|
|
const status = ApiError && err instanceof ApiError ? err.status : err?.status;
|
|
const message = typeof err?.message === "string" ? err.message : "";
|
|
const isUnauthorized = status === 401 || message.includes("401");
|
|
if (!isUnauthorized) {
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
// Token invalid, try refresh
|
|
if (!refreshToken) {
|
|
logout();
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
apiPost("/auth/token/refresh/", { refresh: refreshToken })
|
|
.then(({ access }) => {
|
|
persistTokens(access, refreshToken);
|
|
return apiGet("/auth/me/", access);
|
|
})
|
|
.then((data) => {
|
|
setUser(data);
|
|
})
|
|
.catch(() => {
|
|
logout();
|
|
})
|
|
.finally(() => {
|
|
setLoading(false);
|
|
});
|
|
});
|
|
}, [accessToken, refreshToken, logout, persistTokens]);
|
|
|
|
const value = {
|
|
user,
|
|
accessToken,
|
|
loading,
|
|
login,
|
|
logout,
|
|
isAuthenticated: !!user,
|
|
};
|
|
|
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
}
|
|
|
|
export function useAuth() {
|
|
const ctx = useContext(AuthContext);
|
|
if (!ctx) {
|
|
throw new Error("useAuth must be used within AuthProvider");
|
|
}
|
|
return ctx;
|
|
}
|