104 lines
2.9 KiB
TypeScript
104 lines
2.9 KiB
TypeScript
import type {
|
|
AdminLoginRequest,
|
|
AdminLoginResponse,
|
|
AdminMeResponse,
|
|
CreateOrganizationRequest,
|
|
Organization,
|
|
OrganizationListResponse,
|
|
} from '../model/types'
|
|
|
|
const ADMIN_API_URL = 'https://app.admin.elcsa.ru/api'
|
|
|
|
// In-memory admin access token — deliberately separate from the user `tokenStore`
|
|
// so the two independent auth systems never collide. No CSRF on the admin API.
|
|
let adminToken: string | null = null
|
|
|
|
export const adminTokenStore = {
|
|
get: () => adminToken,
|
|
set: (token: string) => { adminToken = token },
|
|
clear: () => { adminToken = null },
|
|
}
|
|
|
|
async function doAdminRequest<T>(
|
|
path: string,
|
|
options: RequestInit,
|
|
allowRetry: boolean,
|
|
): Promise<T> {
|
|
const bearer = adminTokenStore.get()
|
|
|
|
const res = await fetch(`${ADMIN_API_URL}${path}`, {
|
|
...options,
|
|
credentials: 'include',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...(bearer ? { Authorization: `Bearer ${bearer}` } : {}),
|
|
...options.headers,
|
|
},
|
|
})
|
|
|
|
if (res.status === 401 && allowRetry) {
|
|
try {
|
|
await refreshAdminToken()
|
|
return doAdminRequest<T>(path, options, false)
|
|
} catch {
|
|
adminTokenStore.clear()
|
|
throw new Error('Unauthorized')
|
|
}
|
|
}
|
|
|
|
const data = await res.json().catch(() => null)
|
|
if (!res.ok) throw data
|
|
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).
|
|
export async function refreshAdminToken(): Promise<string> {
|
|
const res = await fetch(`${ADMIN_API_URL}/v1/auth/refresh`, {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
})
|
|
if (!res.ok) throw new Error('Unauthorized')
|
|
const data = await res.json()
|
|
if (data.access_token) adminTokenStore.set(data.access_token)
|
|
return (data.access_token ?? true) as string
|
|
}
|
|
|
|
export async function adminLogin(payload: AdminLoginRequest): Promise<AdminLoginResponse> {
|
|
const data = await doAdminRequest<AdminLoginResponse>(
|
|
'/v1/auth/login',
|
|
{ method: 'POST', body: JSON.stringify(payload) },
|
|
false,
|
|
)
|
|
if (data.access_token) adminTokenStore.set(data.access_token)
|
|
return data
|
|
}
|
|
|
|
export function getAdminMe(): Promise<AdminMeResponse> {
|
|
return doAdminRequest<AdminMeResponse>('/v1/auth/me', {}, true)
|
|
}
|
|
|
|
export async function adminLogout(): Promise<void> {
|
|
try {
|
|
await doAdminRequest<unknown>('/v1/auth/logout', { method: 'POST' }, false)
|
|
} finally {
|
|
adminTokenStore.clear()
|
|
}
|
|
}
|
|
|
|
export function getOrganizations(limit = 50, offset = 0): Promise<OrganizationListResponse> {
|
|
return doAdminRequest<OrganizationListResponse>(
|
|
`/v1/organizations?limit=${limit}&offset=${offset}`,
|
|
{},
|
|
true,
|
|
)
|
|
}
|
|
|
|
export function createOrganization(payload: CreateOrganizationRequest): Promise<Organization> {
|
|
return doAdminRequest<Organization>(
|
|
'/v1/organizations',
|
|
{ method: 'POST', body: JSON.stringify(payload) },
|
|
true,
|
|
)
|
|
}
|