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.
74 lines
2.0 KiB
TypeScript
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' }),
|
|
}
|