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.
82 lines
4 KiB
JavaScript
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$/);
|
|
});
|