Files
cloudflare-domain-manager/apps/web/src/lib/api-client.ts
T
Denozordec 6a6cb34eeb
Build and Push CFDM Docker Image / build-and-push (push) Successful in 1m57s
Build and Push CFDM Docker Image / create-release (push) Skipped
Build and Push CFDM Docker Image / update-wiki (push) Successful in 6s
feat(auth): implement portal SSO and local admin authentication
Added support for portal SSO with JWT authentication and local admin login. Updated environment configuration to include AUTH_REQUIRED, AUTH_JWT_SECRET, AUTH_ISSUER, and AUTH_PORTAL_URL. Enhanced the auth plugin to handle JWT verification based on the new configuration. Introduced new routes for authentication and updated the API client to manage token handling and redirects. Improved user experience by integrating authentication checks across various routes and components.
2026-07-18 18:25:29 +07:00

74 lines
2.0 KiB
TypeScript

import {
clearToken,
ensureAuthConfig,
getToken,
hasPortalHandoffFlag,
isAuthEnabled,
isPortalHandoffCoolingDown,
redirectToPortalLogin,
} from '@/lib/auth'
export class ApiError extends Error {
constructor(
public status: number,
public code: string,
message: string,
) {
super(message)
this.name = 'ApiError'
}
}
async function handoffOnUnauthorized(): Promise<void> {
clearToken()
const cfg = await ensureAuthConfig()
if (
(cfg.required || isAuthEnabled()) &&
!hasPortalHandoffFlag() &&
!isPortalHandoffCoolingDown()
) {
redirectToPortalLogin(`${window.location.origin}/auth/callback`)
return
}
if (!cfg.required && !isAuthEnabled()) {
window.location.href = '/login'
}
}
async function request<T>(path: string, init?: RequestInit): Promise<T> {
const token = getToken()
const headers = new Headers(init?.headers)
if (init?.body != null && !headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json')
}
if (token) headers.set('Authorization', `Bearer ${token}`)
const res = await fetch(path, { ...init, headers })
if (res.status === 401 && !path.includes('/auth/login')) {
await handoffOnUnauthorized()
throw new ApiError(401, 'UNAUTHORIZED', 'Unauthorized')
}
if (!res.ok) {
const body = await res.json().catch(() => ({}))
const err = body?.error
throw new ApiError(
res.status,
err?.code ?? 'UNKNOWN',
err?.message ?? res.statusText,
)
}
if (res.status === 204) return undefined as T
return res.json() as Promise<T>
}
export const api = {
get: <T>(path: string) => request<T>(path),
post: <T>(path: string, body?: unknown) =>
request<T>(path, { method: 'POST', body: body ? JSON.stringify(body) : undefined }),
patch: <T>(path: string, body: unknown) =>
request<T>(path, { method: 'PATCH', body: JSON.stringify(body) }),
put: <T>(path: string, body: unknown) =>
request<T>(path, { method: 'PUT', body: JSON.stringify(body) }),
delete: <T>(path: string) => request<T>(path, { method: 'DELETE' }),
}