Initial Commit
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* DTP-19 — CdnUploadToken issuance and verification.
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import type { CdnUploadToken } from "../src/index.js";
|
||||
import {
|
||||
canonicalSerializeUploadToken,
|
||||
type DriveTemplateError,
|
||||
DTP_CODES,
|
||||
signUploadToken,
|
||||
UPLOAD_TOKEN_TTL_DEFAULT_MS,
|
||||
UPLOAD_TOKEN_TTL_MAX_MS,
|
||||
verifyUploadToken,
|
||||
} from "../src/index.js";
|
||||
import { makeKeys } from "./_helpers.js";
|
||||
|
||||
const NOW = 2_000_000_000;
|
||||
|
||||
function baseToken(): Omit<CdnUploadToken, "serverSignature" | "expiresAt"> {
|
||||
return {
|
||||
fileId: "f".repeat(64),
|
||||
chunkId: 12,
|
||||
chunkHash: "blake3:abcdef",
|
||||
uploaderPubkey: "pubkey-hex",
|
||||
};
|
||||
}
|
||||
|
||||
describe("DTP-19 — CdnUploadToken", () => {
|
||||
it("default TTL pinned at 60s; max at 5m", { timeout: 5000 }, () => {
|
||||
expect(UPLOAD_TOKEN_TTL_DEFAULT_MS).toBe(60_000);
|
||||
expect(UPLOAD_TOKEN_TTL_MAX_MS).toBe(300_000);
|
||||
});
|
||||
|
||||
it("valid token accepted", { timeout: 5000 }, () => {
|
||||
const keys = makeKeys();
|
||||
const tok = signUploadToken(
|
||||
{ ...baseToken(), expiresAt: NOW + 30_000 },
|
||||
keys.secret,
|
||||
{ clock: () => NOW }
|
||||
);
|
||||
expect(verifyUploadToken(tok, keys.publicHex, () => NOW + 1_000)).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it("expired token rejected", { timeout: 5000 }, () => {
|
||||
const keys = makeKeys();
|
||||
const tok = signUploadToken(
|
||||
{ ...baseToken(), expiresAt: NOW + 30_000 },
|
||||
keys.secret,
|
||||
{ clock: () => NOW }
|
||||
);
|
||||
try {
|
||||
verifyUploadToken(tok, keys.publicHex, () => NOW + 100_000);
|
||||
throw new Error("expected expired");
|
||||
} catch (err) {
|
||||
expect((err as DriveTemplateError).code).toBe(
|
||||
DTP_CODES.CDN_BAD_SIGNATURE
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("tampered token rejected", { timeout: 5000 }, () => {
|
||||
const keys = makeKeys();
|
||||
const tok = signUploadToken(
|
||||
{ ...baseToken(), expiresAt: NOW + 30_000 },
|
||||
keys.secret,
|
||||
{ clock: () => NOW }
|
||||
);
|
||||
const tampered = { ...tok, chunkId: tok.chunkId + 1 };
|
||||
try {
|
||||
verifyUploadToken(tampered, keys.publicHex, () => NOW);
|
||||
throw new Error("expected sig failure");
|
||||
} catch (err) {
|
||||
expect((err as DriveTemplateError).code).toBe(
|
||||
DTP_CODES.CDN_BAD_SIGNATURE
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("token signed by wrong key rejected", { timeout: 5000 }, () => {
|
||||
const k1 = makeKeys();
|
||||
const k2 = makeKeys();
|
||||
const tok = signUploadToken(
|
||||
{ ...baseToken(), expiresAt: NOW + 30_000 },
|
||||
k1.secret,
|
||||
{ clock: () => NOW }
|
||||
);
|
||||
try {
|
||||
verifyUploadToken(tok, k2.publicHex, () => NOW);
|
||||
throw new Error("expected sig failure");
|
||||
} catch (err) {
|
||||
expect((err as DriveTemplateError).code).toBe(
|
||||
DTP_CODES.CDN_BAD_SIGNATURE
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("invalid server pubkey hex rejected", { timeout: 5000 }, () => {
|
||||
const keys = makeKeys();
|
||||
const tok = signUploadToken(
|
||||
{ ...baseToken(), expiresAt: NOW + 30_000 },
|
||||
keys.secret,
|
||||
{ clock: () => NOW }
|
||||
);
|
||||
try {
|
||||
verifyUploadToken(tok, "not-hex", () => NOW);
|
||||
throw new Error("expected pubkey rejection");
|
||||
} catch (err) {
|
||||
expect((err as DriveTemplateError).code).toBe(
|
||||
DTP_CODES.CDN_BAD_SIGNATURE
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("ttlMs out of range rejected at sign", { timeout: 5000 }, () => {
|
||||
const keys = makeKeys();
|
||||
expect(() =>
|
||||
signUploadToken({ ...baseToken(), expiresAt: 0 }, keys.secret, {
|
||||
clock: () => NOW,
|
||||
ttlMs: 0,
|
||||
})
|
||||
).toThrow();
|
||||
expect(() =>
|
||||
signUploadToken({ ...baseToken(), expiresAt: 0 }, keys.secret, {
|
||||
clock: () => NOW,
|
||||
ttlMs: UPLOAD_TOKEN_TTL_MAX_MS + 1,
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("canonicalSerializeUploadToken is deterministic", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const tok = { ...baseToken(), expiresAt: NOW + 30_000 };
|
||||
const a = canonicalSerializeUploadToken(tok);
|
||||
const b = canonicalSerializeUploadToken(tok);
|
||||
expect(a).toEqual(b);
|
||||
});
|
||||
|
||||
it("default options use Date.now() as clock", { timeout: 5000 }, () => {
|
||||
const keys = makeKeys();
|
||||
const tok = signUploadToken(
|
||||
{ ...baseToken(), expiresAt: 0 },
|
||||
keys.secret
|
||||
);
|
||||
expect(tok.expiresAt).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("malformed signature buffer triggers verify catch arm", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const keys = makeKeys();
|
||||
const tok = signUploadToken(
|
||||
{ ...baseToken(), expiresAt: NOW + 30_000 },
|
||||
keys.secret,
|
||||
{ clock: () => NOW }
|
||||
);
|
||||
const broken = { ...tok, serverSignature: new Uint8Array(8) };
|
||||
try {
|
||||
verifyUploadToken(broken, keys.publicHex, () => NOW);
|
||||
throw new Error("expected rejection");
|
||||
} catch (err) {
|
||||
expect((err as DriveTemplateError).code).toBe(
|
||||
DTP_CODES.CDN_BAD_SIGNATURE
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user