Skip to content
Source
Sign inSign up

desktopBootstrapToken.ts

36 lines1.4 KB

1import * as Mac from "effect/crypto/Mac";
2import * as Effect from "effect/Effect";
3import * as Hex from "effect/encoding/Hex";
4​
5/** Accept previous/current windows for token lifetime and the next window for WSL clock lag. */
6export const DESKTOP_BOOTSTRAP_TOKEN_WINDOW_MS = 12 * 60 * 60 * 1000;
7​
8const utf8 = (value: string) => new TextEncoder().encode(value);
9​
10function windowIndex(nowMs: number): number {
11 return Math.floor(nowMs / DESKTOP_BOOTSTRAP_TOKEN_WINDOW_MS);
13​
14// Untraced: an inner step of the named callers below, run up to three times per check.
15const deriveToken = Effect.fnUntraced(function* (secret: string, window: number) {
16 const mac = yield* Mac.Mac;
17 const key = yield* Mac.key(Mac.hmacSha256, utf8(secret));
18 return Hex.encode(yield* mac.sign(key, utf8(`t3-desktop-bootstrap:${window}`)));
19}, Effect.orDie);
20​
21export const currentDesktopBootstrapToken = Effect.fn("desktopBootstrapToken.current")(
22 (secret: string, nowMs: number) => deriveToken(secret, windowIndex(nowMs)),
23);
24​
25export const isValidDesktopBootstrapToken = Effect.fn("desktopBootstrapToken.isValid")(function* (
26 secret: string,
27 token: string,
28 nowMs: number,
29) {
30 const presented = utf8(token);
31 const current = windowIndex(nowMs);
32 for (const window of [current - 1, current, current + 1]) {
33 if (Mac.timingSafeEqual(utf8(yield* deriveToken(secret, window)), presented)) return true;
34 }
35 return false;
36});