71 lines
2.0 KiB
TypeScript
71 lines
2.0 KiB
TypeScript
/**
|
|||
|
|
* Default HTTP transport for the shared auth engine.
|
||
|
|
*
|
||
|
|
* The editor injects its own richer axios instance (with platform routing,
|
||
|
|
* error toasts, credit headers, ...) via {@link configureSpringAuth}. Apps that
|
||
|
|
* don't have one - notably the portal - fall back to this minimal client, which
|
||
|
|
* attaches the `stirling_jwt` bearer token so the portal and editor share a
|
||
|
|
* single same-origin session.
|
||
|
|
*/
|
||
|
|
import axios, { type AxiosInstance } from "axios";
|
||
|
|
|
||
|
|
/** localStorage key holding the Spring JWT. Shared so portal + editor agree. */
|
||
|
|
export const JWT_STORAGE_KEY = "stirling_jwt";
|
||
|
|
|
||
|
|
export function getStoredToken(): string | null {
|
||
|
|
try {
|
||
|
|
if (typeof localStorage === "undefined") return null;
|
||
|
|
return localStorage.getItem(JWT_STORAGE_KEY);
|
||
|
|
} catch {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export function setStoredToken(token: string): void {
|
||
|
|
try {
|
||
|
|
if (typeof localStorage !== "undefined") {
|
||
|
|
localStorage.setItem(JWT_STORAGE_KEY, token);
|
||
|
|
}
|
||
|
|
} catch {
|
||
|
|
// localStorage unavailable (private mode) - fail open
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export function clearStoredToken(): void {
|
||
|
|
try {
|
||
|
|
if (typeof localStorage !== "undefined") {
|
||
|
|
localStorage.removeItem(JWT_STORAGE_KEY);
|
||
|
|
}
|
||
|
|
} catch {
|
||
|
|
// ignore
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create the fallback transport. `baseURL` defaults to "/" so it targets the
|
||
|
|
* same origin that served the SPA - the backend serves both portal and editor,
|
||
|
|
* so the cookie/token domain is shared.
|
||
|
|
*/
|
||
|
|
export function createDefaultHttpClient(baseURL = "/"): AxiosInstance {
|
||
|
|
const client = axios.create({
|
||
|
|
baseURL,
|
||
|
|
responseType: "json",
|
||
|
|
withCredentials: true,
|
||
|
|
});
|
||
|
|
|
||
|
|
client.interceptors.request.use((config) => {
|
||
|
|
const token = getStoredToken();
|
||
|
|
if (token) {
|
||
|
|
config.headers = config.headers ?? {};
|
||
|
|
// Respect an explicit Authorization header (e.g. /auth/me passes the
|
||
|
|
// candidate token directly); only fill it in when absent.
|
||
|
|
if (!config.headers.Authorization) {
|
||
|
|
config.headers.Authorization = `Bearer ${token}`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return config;
|
||
|
|
});
|
||
|
|
|
||
|
|
return client;
|
||
|
|
}
|