85 lines
3.4 KiB
TypeScript
85 lines
3.4 KiB
TypeScript
// Unit tests for the OAuth credential blob codec used by the remote login helper.
|
|
//
|
|
// Context (operator report 2026-06-27): Google's `firstparty/nativeapp` consent
|
|
// for the embedded Antigravity desktop client only releases the authorization
|
|
// code when the loopback redirect (127.0.0.1:<port>) is reachable. On a remote
|
|
// VPS install the loopback is unreachable, so the consent hangs and never emits
|
|
// a code — the normal "paste the callback URL" fallback has nothing to paste.
|
|
//
|
|
// The fix is a local helper (`omniroute login antigravity`) that runs the OAuth
|
|
// on the user's own machine (loopback reachable → consent completes → tokens),
|
|
// then prints a single-line, paste-safe credential blob. The user pastes it into
|
|
// the remote dashboard, which decodes it and persists the connection.
|
|
//
|
|
// This codec is the contract between the helper (encoder) and the server/dashboard
|
|
// (decoder). These tests pin: roundtrip fidelity, the human-recognizable prefix,
|
|
// version + provider gating, and rejection of malformed/tampered input.
|
|
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
CREDENTIAL_BLOB_PREFIX,
|
|
encodeCredentialBlob,
|
|
decodeCredentialBlob,
|
|
} from "../../src/lib/oauth/credentialBlob.ts";
|
|
|
|
const SAMPLE = {
|
|
provider: "antigravity",
|
|
tokens: {
|
|
access_token: "ya29.access",
|
|
refresh_token: "1//refresh",
|
|
id_token: "eyJ.id.token",
|
|
expires_in: 3599,
|
|
scope: "https://www.googleapis.com/auth/cloud-platform",
|
|
},
|
|
};
|
|
|
|
test("encode → decode roundtrips the provider and tokens", () => {
|
|
const blob = encodeCredentialBlob(SAMPLE);
|
|
const decoded = decodeCredentialBlob(blob);
|
|
assert.equal(decoded.provider, "antigravity");
|
|
assert.deepEqual(decoded.tokens, SAMPLE.tokens);
|
|
});
|
|
|
|
test("blob is a single paste-safe line with the recognizable prefix", () => {
|
|
const blob = encodeCredentialBlob(SAMPLE);
|
|
assert.ok(blob.startsWith(CREDENTIAL_BLOB_PREFIX), "must carry the omniroute prefix");
|
|
assert.ok(!/\s/.test(blob), "must contain no whitespace (single line, copy-paste safe)");
|
|
// base64url only after the prefix — no +/= that break in URLs / shells.
|
|
const payload = blob.slice(CREDENTIAL_BLOB_PREFIX.length);
|
|
assert.match(payload, /^[A-Za-z0-9_-]+$/, "payload must be base64url");
|
|
});
|
|
|
|
test("decode rejects a blob without the prefix", () => {
|
|
const raw = Buffer.from(JSON.stringify({ v: 1, ...SAMPLE })).toString("base64url");
|
|
assert.throws(() => decodeCredentialBlob(raw), /prefix|invalid|format/i);
|
|
});
|
|
|
|
test("decode rejects an unsupported version", () => {
|
|
// Hand-craft a v999 blob with the right prefix.
|
|
const payload = Buffer.from(JSON.stringify({ v: 999, ...SAMPLE })).toString("base64url");
|
|
assert.throws(() => decodeCredentialBlob(`${CREDENTIAL_BLOB_PREFIX}${payload}`), /version/i);
|
|
});
|
|
|
|
test("decode rejects a blob missing an access_token", () => {
|
|
const bad = encodeCredentialBlob({
|
|
provider: "antigravity",
|
|
tokens: { refresh_token: "only-refresh" },
|
|
});
|
|
assert.throws(() => decodeCredentialBlob(bad), /access_token|token/i);
|
|
});
|
|
|
|
test("decode rejects tampered base64 (not valid JSON)", () => {
|
|
assert.throws(
|
|
() => decodeCredentialBlob(`${CREDENTIAL_BLOB_PREFIX}not-valid-base64-json!!!`),
|
|
/invalid|parse|format/i
|
|
);
|
|
});
|
|
|
|
test("encode requires a provider", () => {
|
|
assert.throws(
|
|
() => encodeCredentialBlob({ tokens: { access_token: "x" } } as never),
|
|
/provider/i
|
|
);
|
|
});
|