fix docs
This commit is contained in:
File diff suppressed because one or more lines are too long
2
dist/index.html
vendored
2
dist/index.html
vendored
@@ -5,7 +5,7 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ЭКСА — Ваш мост в мир цифровых активов</title>
|
||||
<script type="module" crossorigin src="/assets/index-D1qd5k5N.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-D4qEPrTa.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CneFMUxK.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -2,6 +2,7 @@ import type {
|
||||
AdminLoginRequest,
|
||||
AdminLoginResponse,
|
||||
AdminMeResponse,
|
||||
AdminRefreshResponse,
|
||||
CreateOrganizationRequest,
|
||||
CreateWalletsResponse,
|
||||
WalletResponse,
|
||||
@@ -25,6 +26,22 @@ export const adminTokenStore = {
|
||||
clear: () => { adminToken = null },
|
||||
}
|
||||
|
||||
// The refresh token is body-based (sent in the `/v1/auth/refresh` request body),
|
||||
// so it must persist across reloads to restore a session. localStorage-backed.
|
||||
const ADMIN_REFRESH_KEY = 'admin_refresh_token'
|
||||
|
||||
export const adminRefreshStore = {
|
||||
get: (): string | null => {
|
||||
try { return localStorage.getItem(ADMIN_REFRESH_KEY) } catch { return null }
|
||||
},
|
||||
set: (token: string) => {
|
||||
try { localStorage.setItem(ADMIN_REFRESH_KEY, token) } catch { /* ignore */ }
|
||||
},
|
||||
clear: () => {
|
||||
try { localStorage.removeItem(ADMIN_REFRESH_KEY) } catch { /* ignore */ }
|
||||
},
|
||||
}
|
||||
|
||||
async function doAdminRequest<T>(
|
||||
path: string,
|
||||
options: RequestInit,
|
||||
@@ -60,17 +77,27 @@ async function doAdminRequest<T>(
|
||||
return data as T
|
||||
}
|
||||
|
||||
// Refresh by analogy with the main auth service: HttpOnly refresh cookie -> fresh access token.
|
||||
// Exact path is isolated here — adjust if the backend differs (e.g. /v1/jwt/refresh).
|
||||
// Body-based refresh: the API expects the refresh token in the request body and
|
||||
// returns a fresh access + refresh token pair (the refresh token rotates).
|
||||
export async function refreshAdminToken(): Promise<string> {
|
||||
const refreshToken = adminRefreshStore.get()
|
||||
if (!refreshToken) throw new Error('Unauthorized')
|
||||
|
||||
const res = await fetch(`${ADMIN_API_URL}/v1/auth/refresh`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ refresh_token: refreshToken }),
|
||||
})
|
||||
if (!res.ok) throw new Error('Unauthorized')
|
||||
const data = await res.json()
|
||||
if (!res.ok) {
|
||||
adminRefreshStore.clear()
|
||||
adminTokenStore.clear()
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
const data = (await res.json()) as AdminRefreshResponse
|
||||
if (data.access_token) adminTokenStore.set(data.access_token)
|
||||
return (data.access_token ?? true) as string
|
||||
if (data.refresh_token) adminRefreshStore.set(data.refresh_token)
|
||||
return data.access_token
|
||||
}
|
||||
|
||||
export async function adminLogin(payload: AdminLoginRequest): Promise<AdminLoginResponse> {
|
||||
@@ -80,6 +107,7 @@ export async function adminLogin(payload: AdminLoginRequest): Promise<AdminLogin
|
||||
false,
|
||||
)
|
||||
if (data.access_token) adminTokenStore.set(data.access_token)
|
||||
if (data.refresh_token) adminRefreshStore.set(data.refresh_token)
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -92,6 +120,7 @@ export async function adminLogout(): Promise<void> {
|
||||
await doAdminRequest<unknown>('/v1/auth/logout', { method: 'POST' }, false)
|
||||
} finally {
|
||||
adminTokenStore.clear()
|
||||
adminRefreshStore.clear()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { refreshAdminToken } from '../api/adminApi'
|
||||
import { getAdminMe } from '../api/adminApi'
|
||||
|
||||
export const ADMIN_AUTH_QUERY_KEY = ['admin-auth']
|
||||
|
||||
export function useAdminAuth(): { isAuthenticated: boolean; isLoading: boolean } {
|
||||
// `getAdminMe` is the real "is the session valid" check. It runs through
|
||||
// `doAdminRequest` with retry, so a 401 transparently triggers a body-based
|
||||
// refresh (using the persisted refresh token) and replays the request.
|
||||
const { data, isLoading, isError } = useQuery({
|
||||
queryKey: ADMIN_AUTH_QUERY_KEY,
|
||||
queryFn: refreshAdminToken,
|
||||
queryFn: getAdminMe,
|
||||
retry: false,
|
||||
staleTime: Infinity,
|
||||
gcTime: Infinity,
|
||||
|
||||
@@ -7,9 +7,16 @@ export function useAdminLogin() {
|
||||
return useMutation({
|
||||
mutationFn: adminLogin,
|
||||
onSuccess: (data) => {
|
||||
// The token is already stored by adminLogin; write it straight into the
|
||||
// gate's query cache so we flip to "authenticated" without re-hitting /refresh.
|
||||
queryClient.setQueryData(ADMIN_AUTH_QUERY_KEY, data.access_token)
|
||||
// Tokens are already stored by adminLogin. Seed the gate's cache with the
|
||||
// AdminMeResponse-shaped profile (login response carries all these fields)
|
||||
// so we flip to "authenticated" without an extra /auth/me round-trip.
|
||||
queryClient.setQueryData(ADMIN_AUTH_QUERY_KEY, {
|
||||
id: data.id,
|
||||
login: data.login,
|
||||
first_name: data.first_name,
|
||||
last_name: data.last_name,
|
||||
role: data.role,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface AdminLoginRequest {
|
||||
|
||||
export interface AdminLoginResponse {
|
||||
access_token: string
|
||||
refresh_token: string
|
||||
token_type: string
|
||||
id: string
|
||||
login: string
|
||||
@@ -13,6 +14,12 @@ export interface AdminLoginResponse {
|
||||
role: string
|
||||
}
|
||||
|
||||
export interface AdminRefreshResponse {
|
||||
access_token: string
|
||||
refresh_token: string
|
||||
token_type?: string
|
||||
}
|
||||
|
||||
export interface AdminMeResponse {
|
||||
id: string
|
||||
login: string
|
||||
|
||||
Reference in New Issue
Block a user