← back to Govarbitrage
apps/mobile/lib/auth-headers.ts
35 lines
/**
* Pure auth-header policy — zero native dependencies so it is unit-testable.
*
* The GovArbitrage app never sniffs the client type and never carries a secret
* machine-token path. A request carries an Authorization header ONLY when the
* user has taken an explicit, visible action, with this precedence:
* 1. Signed in with Apple → Bearer <app session JWT>
* 2. Self-hosting creds set → Basic base64(user:pass)
* 3. otherwise → no header (anonymous public read)
*/
export function buildBasicAuthHeader(username: string, password: string): string {
// UTF-8-safe base64. Plain btoa() throws InvalidCharacterError on any code
// point > 255 (emoji, CJK, accented letters, smart quotes); since this runs
// BEFORE apiFetch's try/timeout block, that would propagate a raw Error out of
// every request for a self-hosting user with a non-Latin1 password. Encode to
// UTF-8 bytes first so btoa() only ever sees a Latin1 string.
const bytes = new TextEncoder().encode(`${username}:${password}`);
let binary = "";
for (const b of bytes) binary += String.fromCharCode(b);
return `Basic ${btoa(binary)}`;
}
export function authHeadersFor(input: {
appleToken?: string | null;
username?: string;
password?: string;
}): Record<string, string> {
if (input.appleToken) return { Authorization: `Bearer ${input.appleToken}` };
if (input.password) {
return { Authorization: buildBasicAuthHeader(input.username ?? "", input.password) };
}
return {};
}