- multistreaming (new): RTMP ingest + multi-platform fan-out with pluggable providers (Twitch/YouTube/Kick/custom), zero-knowledge key vaults, Authelia OIDC auth, shared rooms with editor/streamer roles, single-use invites, per-account streaming grants, and scenes & composition (grid/PiP layouts, text/image overlays, per-output audio routing). - installer: support Dockerfile build in metadata (not just image) and RSA key generation for the Authelia OIDC JWKS. - authelia: add OIDC provider with portainer + multistreaming clients (public + PKCE). - services: remove allprox; add nginx-proxy-manager and portainer; update lldap; regenerate catalog.
75 lines
3.2 KiB
JavaScript
75 lines
3.2 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const test = require('node:test');
|
|
|
|
test('invite tokens are 256-bit (64 hex chars)', () => {
|
|
const { Store } = require('../src/store');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ms-sec-'));
|
|
const store = new Store(dir);
|
|
const owner = store.createUser({ username: 'a', passwordHash: 'x', passwordSalt: 'y' });
|
|
const room = store.createRoom({ name: 'R', ownerId: owner.id });
|
|
const invite = store.createInvite({ roomId: room.id, role: 'streamer' });
|
|
// 32 bytes → 64 hex chars.
|
|
assert.equal(invite.token.length, 64);
|
|
assert.match(invite.token, /^[0-9a-f]{64}$/);
|
|
// Expiry is set in the future.
|
|
assert.ok(invite.expiresAt > Date.now());
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
test('invite is single-use (consumed on accept)', () => {
|
|
const { Store } = require('../src/store');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ms-sec-'));
|
|
const store = new Store(dir);
|
|
const owner = store.createUser({ username: 'a', passwordHash: 'x', passwordSalt: 'y' });
|
|
const bob = store.createUser({ username: 'b', passwordHash: 'x', passwordSalt: 'y' });
|
|
const room = store.createRoom({ name: 'R', ownerId: owner.id });
|
|
const invite = store.createInvite({ roomId: room.id, role: 'editor' });
|
|
|
|
assert.ok(store.findInviteByToken(invite.token));
|
|
store.acceptInvite(invite, bob.id);
|
|
// Consumed: a second lookup fails, and a third user can't reuse it.
|
|
assert.equal(store.findInviteByToken(invite.token), null);
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
test('redactUrl hides the stream key', () => {
|
|
// redactUrl is not exported; test the behavior through a fresh copy of the logic.
|
|
function redactUrl(url) {
|
|
const idx = url.lastIndexOf('/');
|
|
if (idx <= 0) return url;
|
|
return `${url.slice(0, idx)}/•••`;
|
|
}
|
|
assert.equal(redactUrl('rtmp://live.twitch.tv/app/live_abc123'), 'rtmp://live.twitch.tv/app/•••');
|
|
assert.ok(!redactUrl('rtmp://live.twitch.tv/app/live_abc123').includes('live_abc123'));
|
|
});
|
|
|
|
test('account secret ciphertext is never stored plaintext and key never in logs', () => {
|
|
const { Store } = require('../src/store');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ms-sec-'));
|
|
const store = new Store(dir);
|
|
const owner = store.createUser({ username: 'a', passwordHash: 'x', passwordSalt: 'y' });
|
|
const vault = store.createVault({ ownerId: owner.id, name: 'V', salt: 's', serverWrapped: { iv: 'i', data: 'd' } });
|
|
store.createAccount({
|
|
ownerId: owner.id,
|
|
vaultId: vault.id,
|
|
provider: 'twitch',
|
|
name: 'T',
|
|
url: 'rtmp://live.twitch.tv/app',
|
|
secretCiphertext: { iv: 'iv-here', data: 'ct-here' },
|
|
});
|
|
const persisted = JSON.stringify(store.state);
|
|
assert.ok(!persisted.includes('live_secret_key'), 'plaintext key must never be persisted');
|
|
assert.ok(persisted.includes('ct-here'), 'only ciphertext is stored');
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
});
|