All checks were successful
Deploy / deploy (push) Successful in 3m58s
- Extract world-parse + packet capture into @albion/shared (reused by server and a new standalone agent workspace). - Migrate the data layer from SQLite/better-sqlite3 to shared Postgres (async pg with a ?->$n helper; economy engine preloads the item/recipe graph so its sync recursion still works). - API-key auth (ALBION_API_KEY) on all /api routes except health; web UI sends it via a fetch wrapper + key-entry gate; server serves the built UI with a path-traversal-safe static handler (drops @fastify/static). - Windows companion agent: pushes world map + captured market orders to /api/ingest/*; runs on the gaming PC where the game traffic lives. - Docker + docker-compose (external proxy + backend networks for Nginx Proxy Manager and shared Postgres) and a Forgejo deploy workflow. - Validated end-to-end against Postgres 16 and a full compose run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
41 lines
1.6 KiB
TypeScript
41 lines
1.6 KiB
TypeScript
/**
|
|
* When the server runs behind ALBION_API_KEY (any public deployment), the web
|
|
* UI must send it too. We store the key in localStorage and transparently add
|
|
* `Authorization: Bearer <key>` to same-origin /api requests via a fetch wrapper,
|
|
* so individual pages keep calling fetch('/api/...') unchanged.
|
|
*
|
|
* On localhost with no key configured, everything works with an empty key.
|
|
*/
|
|
const STORAGE_KEY = 'albion_api_key';
|
|
|
|
export function getApiKey(): string {
|
|
return localStorage.getItem(STORAGE_KEY) ?? '';
|
|
}
|
|
|
|
export function setApiKey(key: string): void {
|
|
if (key) localStorage.setItem(STORAGE_KEY, key);
|
|
else localStorage.removeItem(STORAGE_KEY);
|
|
}
|
|
|
|
let unauthorizedHandler: (() => void) | null = null;
|
|
export function onUnauthorized(handler: () => void): void {
|
|
unauthorizedHandler = handler;
|
|
}
|
|
|
|
export function installFetchAuth(): void {
|
|
const original = window.fetch.bind(window);
|
|
window.fetch = async (input, init) => {
|
|
const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : (input as Request).url;
|
|
const isApi = url.startsWith('/api/') || url.includes(`${location.origin}/api/`);
|
|
const key = getApiKey();
|
|
let nextInit = init;
|
|
if (isApi && key) {
|
|
const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined));
|
|
if (!headers.has('Authorization')) headers.set('Authorization', `Bearer ${key}`);
|
|
nextInit = { ...init, headers };
|
|
}
|
|
const res = await original(input as any, nextInit);
|
|
if (isApi && res.status === 401) unauthorizedHandler?.();
|
|
return res;
|
|
};
|
|
}
|