Spaces:
Running
Running
| // Derives the auto-rotating wire token from the root rotation secret, mirroring the | |
| // backend's authtoken.py exactly: base64url(hmac_sha256(secret, str(floor(now/window)))[:18]). | |
| // The secret stays on-device; only the short-lived derived token travels, and a leaked | |
| // one self-expires when the window rolls. The server also accepts the previous window's | |
| // token, so pass windowsBack=1 to retry across a boundary / small clock skew. | |
| // 6-hour window (was 1 hour). With the server's grace=2, a token stays valid for | |
| // up to 18 hours — so a user who closes the tab and comes back the next morning | |
| // isn't greeted with "missing or invalid bearer token" just because the window | |
| // rolled. The client + server MUST use the same window or token derivation | |
| // diverges; this matches authtoken.py's TOKEN_WINDOW_S default. | |
| const WINDOW_S = 21600; // 6 hours — must match the server's TOKEN_WINDOW_S | |
| export async function deriveToken(secret: string, windowsBack = 0): Promise<string> { | |
| if (!crypto?.subtle) { | |
| // WebCrypto needs a secure context; hf.space is always https, dev is localhost. | |
| throw new Error("rotating tokens need a secure (https) context"); | |
| } | |
| const enc = new TextEncoder(); | |
| const key = await crypto.subtle.importKey( | |
| "raw", | |
| enc.encode(secret), | |
| { name: "HMAC", hash: "SHA-256" }, | |
| false, | |
| ["sign"], | |
| ); | |
| const win = Math.floor(Date.now() / 1000 / WINDOW_S) - windowsBack; | |
| const mac = new Uint8Array(await crypto.subtle.sign("HMAC", key, enc.encode(String(win)))); | |
| let bin = ""; | |
| for (const b of mac.slice(0, 18)) bin += String.fromCharCode(b); | |
| // 18 bytes -> exactly 24 url-safe base64 chars, no padding (matches urlsafe_b64encode) | |
| return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_"); | |
| } | |