albion/web/src/apiKey.ts
Заид Омар Медхат 337dac78ba
All checks were successful
Deploy / deploy (push) Successful in 3m58s
Client/server split, Postgres, auth, Windows agent, Docker deploy
- 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>
2026-08-23 04:10:08 +05:00

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;
};
}