120 lines
3.3 KiB
TypeScript
120 lines
3.3 KiB
TypeScript
// useAuth.tsx
|
|
import { AuthContext } from '@/contexts/AuthContext';
|
|
import type { GenerateTokenResponse } from '@/features/authorization/api/identityAPI';
|
|
import axios from 'axios';
|
|
import { useEffect, useState, type ReactNode } from 'react';
|
|
|
|
export const ACCESS_TOKEN_KEY: 'access_token' = 'access_token' as const;
|
|
export const REFRESH_TOKEN_KEY: 'refresh_token' = 'refresh_token' as const;
|
|
export const EXPIRES_IN_KEY: 'expires_in' = 'expires_in' as const;
|
|
|
|
let inMemoryToken: string | null = null;
|
|
let expiresAt = 0;
|
|
|
|
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|
const [accessToken, setAccessToken] = useState<string | null>(
|
|
sessionStorage.getItem(ACCESS_TOKEN_KEY),
|
|
);
|
|
|
|
// Initialize from sessionStorage (page reload)
|
|
useEffect(() => {
|
|
const token = sessionStorage.getItem(ACCESS_TOKEN_KEY);
|
|
const exp = Number(sessionStorage.getItem(EXPIRES_IN_KEY) || 0);
|
|
|
|
if (token && Date.now() < exp) {
|
|
inMemoryToken = token;
|
|
expiresAt = exp;
|
|
setAccessToken(token);
|
|
}
|
|
}, []);
|
|
|
|
// Background refresh
|
|
useEffect(() => {
|
|
const handleRefreshTokenIfNeeded = async () => {
|
|
if (!inMemoryToken) return;
|
|
if (Date.now() < expiresAt - 60_000) return; // still valid (buffer)
|
|
|
|
await refreshAccessToken();
|
|
setAccessToken(inMemoryToken);
|
|
};
|
|
|
|
handleRefreshTokenIfNeeded();
|
|
|
|
const interval = setInterval(async () => {
|
|
await handleRefreshTokenIfNeeded();
|
|
}, 30_000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
function login(tokens: {
|
|
access_token: string;
|
|
refresh_token: string;
|
|
expires_in: number;
|
|
}) {
|
|
inMemoryToken = tokens.access_token;
|
|
expiresAt = Date.now() + tokens.expires_in * 1000;
|
|
|
|
sessionStorage.setItem(ACCESS_TOKEN_KEY, tokens.access_token);
|
|
sessionStorage.setItem(REFRESH_TOKEN_KEY, tokens.refresh_token);
|
|
sessionStorage.setItem(EXPIRES_IN_KEY, String(expiresAt));
|
|
|
|
setAccessToken(tokens.access_token);
|
|
}
|
|
|
|
function logout() {
|
|
inMemoryToken = null;
|
|
expiresAt = 0;
|
|
sessionStorage.clear();
|
|
setAccessToken(null);
|
|
}
|
|
|
|
function getToken() {
|
|
return inMemoryToken;
|
|
}
|
|
|
|
async function refreshAccessToken() {
|
|
const refreshToken = sessionStorage.getItem(REFRESH_TOKEN_KEY);
|
|
if (!refreshToken) {
|
|
logout();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const result = await axios.post<GenerateTokenResponse>(
|
|
import.meta.env.VITE_IDENTITY_URL,
|
|
new URLSearchParams({
|
|
grant_type: 'refresh_token',
|
|
refresh_token: refreshToken,
|
|
client_id: import.meta.env.VITE_IDENTITY_CLIENT_ID, // from your token payload
|
|
}),
|
|
{
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
},
|
|
);
|
|
|
|
if (result.data.access_token) {
|
|
inMemoryToken = result.data.access_token;
|
|
expiresAt = Date.now() + result.data.expires_in * 1000;
|
|
|
|
sessionStorage.setItem(ACCESS_TOKEN_KEY, inMemoryToken as string);
|
|
sessionStorage.setItem(EXPIRES_IN_KEY, String(expiresAt));
|
|
|
|
setAccessToken(inMemoryToken);
|
|
} else {
|
|
logout();
|
|
}
|
|
} catch {
|
|
logout();
|
|
}
|
|
}
|
|
|
|
return (
|
|
<AuthContext.Provider value={{ accessToken, getToken, login, logout }}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
};
|