internaluploader: server + docs
One-way upload server: authentik SSO pairing (QR + app-initiated deep link), hashed device tokens, streamed sha256-verified uploads into the metube directory as <sha256>.<ext>. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
42
server/test/tokens.test.ts
Normal file
42
server/test/tokens.test.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtempSync } from "node:fs";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { test } from "node:test";
|
||||
|
||||
import { TokenStore } from "../src/tokens.ts";
|
||||
|
||||
test("pairing code round-trip mints a working device token", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "tokens-"));
|
||||
const store = new TokenStore(dir, 60_000);
|
||||
|
||||
const pairing = store.issuePairingCode("michal");
|
||||
assert.match(pairing.code, /^[0-9a-f]{64}$/);
|
||||
|
||||
const redeemed = store.redeemPairingCode(pairing.code);
|
||||
assert.ok(redeemed);
|
||||
assert.equal(redeemed.username, "michal");
|
||||
|
||||
// Single use.
|
||||
assert.equal(store.redeemPairingCode(pairing.code), undefined);
|
||||
|
||||
// The token authenticates, wrong tokens do not.
|
||||
assert.equal(store.authenticate(redeemed.token), "michal");
|
||||
assert.equal(store.authenticate("not-a-token"), undefined);
|
||||
|
||||
// Nothing on disk contains the plaintext token.
|
||||
const persisted = await readFile(join(dir, "devices.json"), "utf8");
|
||||
assert.ok(!persisted.includes(redeemed.token));
|
||||
|
||||
// A fresh store reads the same devices back.
|
||||
const reloaded = new TokenStore(dir, 60_000);
|
||||
assert.equal(reloaded.authenticate(redeemed.token), "michal");
|
||||
});
|
||||
|
||||
test("expired pairing codes are refused", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "tokens-"));
|
||||
const store = new TokenStore(dir, -1); // already expired at issue time
|
||||
const pairing = store.issuePairingCode("michal");
|
||||
assert.equal(store.redeemPairingCode(pairing.code), undefined);
|
||||
});
|
||||
72
server/test/upload.test.ts
Normal file
72
server/test/upload.test.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash, randomBytes } from "node:crypto";
|
||||
import { mkdtempSync } from "node:fs";
|
||||
import { readFile, readdir, stat } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { Readable } from "node:stream";
|
||||
import { test } from "node:test";
|
||||
|
||||
import { UploadDir, hashOf, isValidObjectName } from "../src/upload.ts";
|
||||
|
||||
test("object name validation", () => {
|
||||
const hash = "a".repeat(64);
|
||||
assert.ok(isValidObjectName(hash));
|
||||
assert.ok(isValidObjectName(`${hash}.mp4`));
|
||||
assert.ok(isValidObjectName(`${hash}.webm`));
|
||||
assert.ok(!isValidObjectName(`${hash}.`));
|
||||
assert.ok(!isValidObjectName(`${hash}.MP4`), "uppercase extension");
|
||||
assert.ok(!isValidObjectName(`${hash}.m p4`), "space in extension");
|
||||
assert.ok(!isValidObjectName(`../../etc/passwd`), "traversal");
|
||||
assert.ok(!isValidObjectName(`${"a".repeat(63)}.mp4`), "short hash");
|
||||
assert.ok(!isValidObjectName(`${hash}.${"x".repeat(17)}`), "long extension");
|
||||
assert.equal(hashOf(`${hash}.mp4`), hash);
|
||||
});
|
||||
|
||||
function payload(bytes: Buffer): { name: string; body: Readable } {
|
||||
const digest = createHash("sha256").update(bytes).digest("hex");
|
||||
return { name: `${digest}.bin`, body: Readable.from([bytes]) };
|
||||
}
|
||||
|
||||
test("receive stores a verified upload and is idempotent", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "uploads-"));
|
||||
const uploads = new UploadDir(dir, 1024 * 1024);
|
||||
await uploads.init();
|
||||
|
||||
const bytes = randomBytes(4096);
|
||||
const { name, body } = payload(bytes);
|
||||
|
||||
assert.deepEqual(await uploads.receive(name, body), { status: "created" });
|
||||
assert.deepEqual(await readFile(join(dir, name)), bytes);
|
||||
assert.ok(await uploads.exists(name));
|
||||
|
||||
const again = await uploads.receive(name, Readable.from([bytes]));
|
||||
assert.deepEqual(again, { status: "exists" });
|
||||
|
||||
// The temp dir holds no leftovers either way.
|
||||
assert.deepEqual(await readdir(join(dir, ".internaluploader-tmp")), []);
|
||||
});
|
||||
|
||||
test("receive rejects a body that does not match its name", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "uploads-"));
|
||||
const uploads = new UploadDir(dir, 1024 * 1024);
|
||||
await uploads.init();
|
||||
|
||||
const name = `${"0".repeat(64)}.bin`;
|
||||
const outcome = await uploads.receive(name, Readable.from([randomBytes(16)]));
|
||||
assert.equal(outcome.status, "hash-mismatch");
|
||||
await assert.rejects(stat(join(dir, name)));
|
||||
assert.deepEqual(await readdir(join(dir, ".internaluploader-tmp")), []);
|
||||
});
|
||||
|
||||
test("receive enforces the size ceiling", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "uploads-"));
|
||||
const uploads = new UploadDir(dir, 1000);
|
||||
await uploads.init();
|
||||
|
||||
const bytes = randomBytes(4096);
|
||||
const { name, body } = payload(bytes);
|
||||
const outcome = await uploads.receive(name, body);
|
||||
assert.equal(outcome.status, "too-large");
|
||||
assert.deepEqual(await readdir(join(dir, ".internaluploader-tmp")), []);
|
||||
});
|
||||
Reference in New Issue
Block a user