decryptCollection now takes the full key material {masterKey,
publicKey, secretKey} and dispatches on keyDecryptionNonce: present
means an owned collection (secretbox under the master key), absent
means a shared collection (sealed box to our public key). Client
already held the keypair for unsealing the auth token, so it just
passes it through.
119 lines
4.2 KiB
TypeScript
119 lines
4.2 KiB
TypeScript
import { init } from "../../src/crypto/index.js";
|
|
import { ApiClient } from "../../src/api/client.js";
|
|
import { beginLogin } from "../../src/auth/login.js";
|
|
import { unwrapAuth } from "../../src/auth/unwrap.js";
|
|
import { decryptCollection, decryptFile } from "../../src/model/index.js";
|
|
import { downloadFile } from "../../src/download/index.js";
|
|
import type { RawCollection, RawEnteFile } from "../../src/model/index.js";
|
|
|
|
// Dev account — not a secret, throwaway account for integration testing.
|
|
const EMAIL = "entedev2026jp@acidhou.se";
|
|
const PASSWORD = "loldongs";
|
|
const RECOVERY_KEY =
|
|
"deliver have behave collect void chicken boring embrace coast reflect squeeze cotton dish resemble license remain quick dwarf plastic ensure amused cry nasty equip";
|
|
|
|
void RECOVERY_KEY;
|
|
|
|
const main = async () => {
|
|
await init();
|
|
const api = new ApiClient();
|
|
|
|
console.log(`Logging in as ${EMAIL}...`);
|
|
const challenge = await beginLogin(api, EMAIL, PASSWORD);
|
|
if (challenge.kind !== "complete") {
|
|
console.error("Expected complete login, got:", challenge.kind);
|
|
process.exit(1);
|
|
}
|
|
|
|
const { masterKey, secretKey, publicKey, token } = await unwrapAuth(
|
|
challenge.response,
|
|
PASSWORD,
|
|
);
|
|
api.setAuthToken(token);
|
|
console.log("Logged in, user ID:", challenge.response.id);
|
|
|
|
// Fetch and decrypt collections
|
|
console.log("\nFetching collections...");
|
|
const { collections: rawCollections } = await api.getJSON<{
|
|
collections: RawCollection[];
|
|
}>("/collections/v2", { sinceTime: 0 });
|
|
|
|
const userID = challenge.response.id;
|
|
const collections = rawCollections.map((raw) =>
|
|
decryptCollection(raw, { masterKey, publicKey, secretKey }, userID),
|
|
);
|
|
|
|
console.log(`${collections.length} collection(s):`);
|
|
for (const c of collections) {
|
|
console.log(
|
|
` [${c.type}] "${c.name}" (id=${c.id}, shared=${c.isShared})`,
|
|
);
|
|
}
|
|
|
|
// Fetch and decrypt files from the first non-empty collection
|
|
for (const col of collections) {
|
|
console.log(`\nFetching files from "${col.name}" (id=${col.id})...`);
|
|
const { diff: rawFiles } = await api.getJSON<{
|
|
diff: RawEnteFile[];
|
|
hasMore: boolean;
|
|
}>("/collections/v2/diff", {
|
|
collectionID: col.id,
|
|
sinceTime: 0,
|
|
});
|
|
|
|
if (rawFiles.length === 0) {
|
|
console.log(" (empty)");
|
|
continue;
|
|
}
|
|
|
|
const files = rawFiles
|
|
.filter((f) => !f.isDeleted)
|
|
.map((raw) => decryptFile(raw, col.key));
|
|
|
|
console.log(` ${files.length} file(s):`);
|
|
for (const f of files.slice(0, 10)) {
|
|
console.log(
|
|
` ${f.metadata.title} [${f.metadata.fileType}] id=${f.id}`,
|
|
);
|
|
if (f.metadata.latitude !== undefined) {
|
|
console.log(
|
|
` location: ${f.metadata.latitude}, ${f.metadata.longitude}`,
|
|
);
|
|
}
|
|
}
|
|
if (files.length > 10) {
|
|
console.log(` ... and ${files.length - 10} more`);
|
|
}
|
|
// Download the first file to a temp directory
|
|
if (files.length > 0) {
|
|
const first = files[0]!;
|
|
const { mkdtempSync, statSync } = await import("node:fs");
|
|
const { join } = await import("node:path");
|
|
const { tmpdir } = await import("node:os");
|
|
const outDir = mkdtempSync(join(tmpdir(), "quak-live-test-"));
|
|
const outPath = `${outDir}/${first.metadata.title}`;
|
|
|
|
console.log(`\n Downloading "${first.metadata.title}"...`);
|
|
const result = await downloadFile(api, first, outPath);
|
|
const stat = statSync(result.path);
|
|
console.log(
|
|
` Saved to ${result.path} (${result.bytesWritten} bytes decrypted, ${stat.size} on disk)`,
|
|
);
|
|
|
|
if (result.bytesWritten < 1000) {
|
|
console.error(
|
|
" WARNING: file seems too small, possible decryption issue",
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
console.log("\nLive integration test passed.");
|
|
};
|
|
|
|
main().catch((err) => {
|
|
console.error("FAILED:", err);
|
|
process.exit(1);
|
|
});
|