pithos/test/handoff.test.js
Local Dev 251f95cdee Pithos: pithos.sia/app is the app's front door
pithos.sia itself is the website, so people had no obvious address to
type for the app. /app (and /app/<page>) now opens Pithos in Theseus and
through the website's Open my Pithos; it is no longer read as an S3 user
named app.
2026-10-05 02:04:06 +02:00

82 lines
4 KiB
JavaScript

// "Open my Pithos" from the pithos.sia website: nobody is signed in until the
// host says yes, and the sign-in link it hands out works once.
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import http from 'node:http';
import { createPithos, handoffRoute } from '../core/server.js';
const get = (url, headers = {}) => new Promise((resolve, reject) => {
http.get(url, { headers }, (res) => { let body = ''; res.on('data', (d) => { body += d; }); res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body })); }).on('error', reject);
});
test('website paths become app routes, and odd ones go to Overview', () => {
assert.equal(handoffRoute('/'), '#/overview');
assert.equal(handoffRoute('/backup-agent/nightly-backups/2026'), '#/u/backup-agent/nightly-backups/2026');
assert.equal(handoffRoute('/settings'), '#/settings');
assert.equal(handoffRoute('/drives'), '#/buckets');
assert.equal(handoffRoute('/app'), '#/overview');
assert.equal(handoffRoute('/app/settings'), '#/settings');
assert.equal(handoffRoute('/a/<script>'), '#/overview');
assert.equal(handoffRoute('/a/b"onload'), '#/overview');
});
test('the handoff asks first, signs in once, and marks the browser as external', async (t) => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'pithos-handoff-'));
fs.writeFileSync(path.join(dir, 's3d.yml'), `apiAddress: 127.0.0.1:18400\nadminAddress: 127.0.0.1:18401\ndirectory: ${dir.replace(/\\/g, '/')}/data\n`);
const port = 47700 + Math.floor(Math.random() * 200);
let answer = false;
const asked = [];
const opened = [];
const p = await createPithos({
configFile: path.join(dir, 's3d.yml'), binary: path.join(dir, 'no-s3d'), hostName: 'desktop', handoffPort: port,
confirmBrowserOpen: async (q) => { asked.push(q.path); return answer; },
openBrowser: (url) => opened.push(url),
});
t.after(async () => { await p.close(); fs.rmSync(dir, { recursive: true, force: true }); });
await new Promise((r) => setTimeout(r, 150));
// a hostname that merely resolves to 127.0.0.1 (DNS rebinding) is refused before asking
const rebind = await get(`http://127.0.0.1:${port}/open?path=/`, { host: `evil.example:${port}` });
assert.equal(rebind.status, 403);
assert.equal(asked.length, 0);
// declined: no session, nothing to follow
const no = await get(`http://127.0.0.1:${port}/open?path=/u1/d1`);
assert.equal(no.status, 403);
assert.deepEqual(asked, ['/u1/d1']);
// allowed: a one-time link at the right page
answer = true;
const yes = await get(`http://127.0.0.1:${port}/open?path=/u1/d1`);
assert.equal(yes.status, 302);
const link = new URL(yes.headers.location);
assert.equal(link.origin, new URL(p.url).origin);
assert.equal(link.hash, '#/u/u1/d1');
assert.ok(link.searchParams.get('s'));
const first = await get(link.href.replace(link.hash, ''));
assert.equal(first.status, 302);
const cookie = String(first.headers['set-cookie'] || '').split(';')[0];
assert.match(cookie, /^pithos=/);
const session = JSON.parse((await get(`${p.url}api/session`, { cookie })).body);
assert.equal(session.authenticated, true);
assert.equal(session.external, true);
// the same link again signs nobody in
const again = await get(link.href.replace(link.hash, ''));
assert.equal(again.headers['set-cookie'], undefined);
// "Open in browser" from the app hands the browser the same kind of link
const own = await p.internalSession();
const res = await new Promise((resolve, reject) => {
const req = http.request(`${p.url}api/open-in-browser`, { method: 'POST', headers: { cookie: own, 'x-pithos': '1', 'content-type': 'application/json' } }, (r) => { r.resume(); r.on('end', () => resolve(r.statusCode)); });
req.on('error', reject);
req.end(JSON.stringify({ route: '#/u/u1/d1' }));
});
assert.equal(res, 200);
assert.equal(opened.length, 1);
assert.match(opened[0], /\?s=[\w-]+#\/u\/u1\/d1$/);
});