mirror of
https://github.com/saltbo/agent-kanban.git
synced 2026-09-19 09:01:47 +08:00
a2fa54a729
Replace the v1 Agent/Machine runtime and CLI architecture with the v2 Resource Server, AMA-backed projections, task lifecycle, session observation, BDD specifications, and a single-package layered codebase. BREAKING CHANGE: remove the v1 CLI, internal Agent/Machine runtime entities, workspace layout, and legacy runtime coordination APIs.
48 lines
2.0 KiB
TypeScript
48 lines
2.0 KiB
TypeScript
export async function signTokenPayload(payload: string, encodedKey: string): Promise<string> {
|
|
const key = await crypto.subtle.importKey("raw", signingKeyBytes(encodedKey), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
return base64UrlEncode(new Uint8Array(await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(payload))));
|
|
}
|
|
|
|
export function assertSigningKey(encodedKey: string): void {
|
|
signingKeyBytes(encodedKey);
|
|
}
|
|
|
|
function signingKeyBytes(encodedKey: string): ArrayBuffer {
|
|
if (typeof encodedKey !== "string" || !/^[A-Za-z0-9+/]{43}=$/.test(encodedKey)) throw invalidSigningKey();
|
|
let binary: string;
|
|
try {
|
|
binary = atob(encodedKey);
|
|
} catch (error) {
|
|
throw invalidSigningKey(error);
|
|
}
|
|
if (binary.length !== 32 || btoa(binary) !== encodedKey) throw invalidSigningKey();
|
|
const bytes = new Uint8Array(32);
|
|
for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
|
|
return bytes.buffer;
|
|
}
|
|
|
|
function invalidSigningKey(cause?: unknown): Error {
|
|
return new Error("AK_SIGNING_KEY must be a canonical Base64-encoded 32-byte key", cause === undefined ? undefined : { cause });
|
|
}
|
|
|
|
export function base64UrlEncode(bytes: Uint8Array): string {
|
|
let binary = "";
|
|
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
}
|
|
|
|
export function base64UrlDecode(value: string): Uint8Array {
|
|
const normalized = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
const binary = atob(normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "="));
|
|
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
}
|
|
|
|
export function constantTimeEqual(left: string, right: string): boolean {
|
|
const length = Math.max(left.length, right.length);
|
|
let difference = left.length ^ right.length;
|
|
for (let index = 0; index < length; index++) {
|
|
difference |= (left.charCodeAt(index) || 0) ^ (right.charCodeAt(index) || 0);
|
|
}
|
|
return difference === 0;
|
|
}
|