session-open-group-server-l.../test/test.js

591 lines
20 KiB
JavaScript
Raw Normal View History

const fs = require('fs');
const path = require('path');
const nconf = require('nconf');
const assert = require('assert');
const lokinet = require('loki-launcher/lokinet');
const crypto = require('crypto');
const bb = require('bytebuffer');
const libsignal = require('libsignal');
const adnServerAPI = require('../fetchWrapper');
const config = require('../config');
const ADN_SCOPES = 'basic stream write_post follow messages update_profile files export';
2019-09-09 07:42:49 +02:00
// Look for a config file
const disk_config = config.getDiskConfig();
2019-09-09 07:42:49 +02:00
//console.log('disk_config', disk_config)
const overlay_port = parseInt(disk_config.api.port) || 8080;
// has to have the trailing slash
2019-09-09 07:42:49 +02:00
const overlay_url = 'http://localhost:' + overlay_port + '/';
const config_path = path.join(__dirname, '/../server/config.json');
nconf.argv().env('__').file({file: config_path});
console.log('config_path', config_path);
const platform_api_url = disk_config.api.api_url;
const platform_admin_url = disk_config.api.admin_url.replace(/\/$/, '');
// configure the admin interface for use
// can be easily swapped out later
const proxyAdmin = require('../server/dataaccess.proxy-admin');
// fake dispatcher that only implements what we need
proxyAdmin.dispatcher = {
// ignore local user updates
updateUser: (user, ts, cb) => { cb(user); },
// ignore local message updates
setMessage: (message, cb) => { if (cb) cb(message); },
}
// backward compatible
if (proxyAdmin.start) {
proxyAdmin.start(nconf);
}
proxyAdmin.apiroot = disk_config.api.api_url;
if (proxyAdmin.apiroot.replace) {
proxyAdmin.apiroot = proxyAdmin.apiroot.replace(/\/$/, '');
}
proxyAdmin.adminroot = disk_config.api.admin_url;
if (proxyAdmin.adminroot.replace) {
proxyAdmin.adminroot = proxyAdmin.adminroot.replace(/\/$/, '');
}
const cache = proxyAdmin
2019-09-10 10:16:57 +02:00
const ensurePlatformServer = () => {
return new Promise((resolve, rej) => {
const platformURL = new URL(platform_api_url);
console.log('platform port', platformURL.port);
lokinet.portIsFree(platformURL.hostname, platformURL.port, function(free) {
if (free) {
// ini overrides server/config.json in unit testing (if platform isn't running where it should)
// override any config to make sure it runs the way we request
process.env.web__port = platformURL.port;
const platformAdminURL = new URL(platform_admin_url);
process.env.admin__port = platformAdminURL.port;
process.env.admin__modKey = disk_config.api.modKey?disk_config.api.modKey:'123abc';
2019-09-10 10:16:57 +02:00
const startPlatform = require('../server/app');
// probably don't need this wait
function portNowClaimed() {
lokinet.portIsFree(platformURL.hostname, platformURL.port, function(free) {
if (!free) {
console.log(platformURL.port, 'now claimed')
resolve();
} else {
setTimeout(portNowClaimed, 100)
}
})
}
portNowClaimed()
2019-09-10 10:16:57 +02:00
} else {
console.log('detected running platform server using that');
resolve();
2019-09-10 10:16:57 +02:00
}
})
});
};
let weStartedOverlayServer = false;
2019-09-10 10:16:57 +02:00
const ensureOverlayServer = () => {
return new Promise((resolve, rej) => {
console.log('overlay port', overlay_port);
lokinet.portIsFree('localhost', overlay_port, function(free) {
if (free) {
const startPlatform = require('../overlay_server');
weStartedOverlayServer = true;
2019-09-10 10:16:57 +02:00
} else {
console.log('detected running overlay server testing that');
}
resolve();
});
});
};
2019-09-10 10:16:57 +02:00
const overlayApi = new adnServerAPI(overlay_url);
const platformApi = new adnServerAPI(platform_api_url);
const adminApi = new adnServerAPI(platform_admin_url, disk_config.api.modKey);
let modPubKey = '';
// grab a mod from ini
const selectModToken = async () => {
if (!disk_config.globals && !weStartedOverlayServer) {
console.warn('no moderators configured, skipping moderation tests');
2019-09-10 10:16:57 +02:00
return;
}
let modKeys = []
if (disk_config.globals) {
modKeys = Object.keys(disk_config.globals);
}
let modToken = '';
if (!modKeys.length) {
// we started platform?
if (weStartedOverlayServer) {
console.warn('test.js - no moderators configured and we control overlayServer, creating temporary moderator');
const ourModKey = libsignal.curve.generateKeyPair();
// encode server's pubKey in base64
const ourModPubKey64 = bb.wrap(ourModKey.pubKey).toString('base64');
const modPubKey = bb.wrap(ourModKey.pubKey).toString('hex');
modToken = await get_challenge(ourModKey, modPubKey);
await submit_challenge(modToken, modPubKey);
// now elevate to a moderator
var promise = new Promise( (resolve,rej) => {
cache.getUserID(modPubKey, async (user, err) => {
await config.addTempModerator(user.id)
resolve()
});
});
await promise;
return modToken;
} else {
console.warn('no moderators configured, skipping moderation tests');
return;
}
}
2019-09-10 10:16:57 +02:00
const selectedMod = Math.floor(Math.random() * modKeys.length);
modPubKey = modKeys[selectedMod];
if (!modPubKey) {
console.warn('selectedMod', selectedMod, 'not in', modKeys.length);
return;
}
const res = await adminApi.serverRequest('tokens/@'+modPubKey, {});
if (res.response && res.response.data) {
modToken = res.response.data.token;
}
// it's async
/*
if (res.response && res.response.data === null) {
console.log('need to create a token for this moderator')
cache.getUserID(modPubKey, (user, err) => {
if (err) console.error('getUserID err', err)
if (!user || !user.id) {
console.warn('No such moderator user object for', modPubKey);
// create user...
process.exit();
}
cache.createOrFindUserToken(user.id, 'messenger', ADN_SCOPES, (tokenObj, err) => {
if (err) console.error('createOrFindUserToken err', err)
console.log('tokenObj', tokenObj);
})
})
}
*/
if (!modToken) console.warn('modToken failure! res', res);
return modToken;
}
2019-09-09 07:42:49 +02:00
// make our local keypair
const ourKey = libsignal.curve.generateKeyPair();
// encode server's pubKey in base64
const ourPubKey64 = bb.wrap(ourKey.pubKey).toString('base64');
const ourPubKeyHex = bb.wrap(ourKey.pubKey).toString('hex');
console.log('running as', ourPubKeyHex);
2019-09-09 07:42:49 +02:00
const IV_LENGTH = 16;
const DHDecrypt = async (symmetricKey, ivAndCiphertext) => {
2019-09-09 07:42:49 +02:00
const iv = ivAndCiphertext.slice(0, IV_LENGTH);
const ciphertext = ivAndCiphertext.slice(IV_LENGTH);
return libsignal.crypto.decrypt(symmetricKey, ciphertext, iv);
}
2019-09-10 10:16:57 +02:00
// globally passing overlayApi
function get_challenge(ourKey, ourPubKeyHex) {
return new Promise((resolve, rej) => {
describe(`get challenge for ${ourPubKeyHex} /loki/v1/get_challenge`, async function() {
2019-09-10 10:16:57 +02:00
// this can be broken into more it() if desired
2019-09-10 10:49:18 +02:00
//it("returns status code 200", async () => {
let tokenString
try {
2019-09-10 10:16:57 +02:00
const result = await overlayApi.serverRequest('loki/v1/get_challenge', {
params: {
pubKey: ourPubKeyHex
}
});
assert.equal(200, result.statusCode);
const body = result.response;
//console.log('get challenge body', body);
// body.cipherText64
// body.serverPubKey64 // base64 encoded pubkey
// console.log('serverPubKey64', body.serverPubKey64);
const serverPubKeyBuff = Buffer.from(body.serverPubKey64, 'base64')
const serverPubKeyHex = serverPubKeyBuff.toString('hex');
//console.log('serverPubKeyHex', serverPubKeyHex)
const ivAndCiphertext = Buffer.from(body.cipherText64, 'base64');
const symmetricKey = libsignal.curve.calculateAgreement(
serverPubKeyBuff,
ourKey.privKey
);
const token = await DHDecrypt(symmetricKey, ivAndCiphertext);
tokenString = token.toString('utf8');
} catch (e) {
console.error('platformApi.serverRequest err', e, result)
tokenString = '';
}
//console.log('tokenString', tokenString);
resolve(tokenString);
2019-09-10 10:49:18 +02:00
//});
2019-09-10 10:16:57 +02:00
});
});
}
const submit_challenge = (tokenString, pubKey) => {
// we use this promise to delay resolution
2019-09-10 10:16:57 +02:00
return new Promise((resolve, rej) => {
// I don't think we need or want this describe at all...
describe(`submit challenge for ${tokenString} /loki/v1/submit_challenge`, async function() {
2019-09-10 10:49:18 +02:00
//it("returns status code 200", async () => {
2019-09-10 10:16:57 +02:00
const result = await overlayApi.serverRequest('loki/v1/submit_challenge', {
method: 'POST',
objBody: {
pubKey: pubKey,
2019-09-10 10:16:57 +02:00
token: tokenString,
},
noJson: true
});
assert.equal(200, result.statusCode);
// body should be ''
//console.log('submit challenge body', body);
2019-09-10 10:16:57 +02:00
resolve();
2019-09-10 10:49:18 +02:00
//});
2019-09-10 10:16:57 +02:00
});
});
}
// requires overlayApi to be configured with a token
function getUserID(pubKey) {
2019-09-10 10:16:57 +02:00
return new Promise((resolve, rej) => {
cache.getUserID(pubKey, function(user, err, meta) {
//assert.equal(200, result.statusCode);
resolve(user.id);
2019-09-10 10:16:57 +02:00
});
});
}
2019-09-10 10:16:57 +02:00
function get_deletes(channelId) {
return new Promise((resolve, rej) => {
describe("get deletes /loki/v1/channels/1/deletes", async function() {
2019-09-10 10:49:18 +02:00
//it("returns status code 200", async () => {
2019-09-13 08:09:07 +02:00
const result = await overlayApi.serverRequest('loki/v1/channels/1/deletes');
2019-09-10 10:16:57 +02:00
assert.equal(200, result.statusCode);
resolve();
2019-09-10 10:49:18 +02:00
//});
2019-09-10 10:16:57 +02:00
});
});
}
2019-09-13 08:03:01 +02:00
function get_moderators(channelId) {
return new Promise((resolve, rej) => {
describe("get moderators /loki/v1/channels/1/moderators", async function() {
2019-09-13 08:03:01 +02:00
//it("returns status code 200", async () => {
let result
try {
result = await overlayApi.serverRequest('loki/v1/channels/1/moderators');
2019-09-13 08:03:01 +02:00
assert.equal(200, result.statusCode);
} catch (e) {
console.error('platformApi.serverRequest err', e, result)
rej();
}
resolve();
2019-09-13 08:03:01 +02:00
//});
});
});
}
2019-09-10 10:16:57 +02:00
function create_message(channelId) {
return new Promise((resolve, rej) => {
describe("create message /channels/1/messages", async function() {
2019-09-10 10:49:18 +02:00
//it("returns status code 200", async () => {
2019-09-10 10:16:57 +02:00
// create a dummy message
let result;
try {
result = await platformApi.serverRequest('channels/1/messages', {
method: 'POST',
objBody: {
text: 'testing message',
},
});
//console.log('create message result', result, 'token', platformApi.token);
assert.equal(200, result.statusCode);
} catch (e) {
console.error('platformApi.serverRequest err', e, result)
}
2019-09-10 10:16:57 +02:00
resolve(result.response.data.id);
2019-09-10 10:49:18 +02:00
//});
2019-09-10 10:16:57 +02:00
});
});
}
function get_message(messageId) {
2019-09-10 10:16:57 +02:00
return new Promise(async (resolve, rej) => {
// not really a test
//describe(`get channel /channels/${channelId}`, function() {
2019-09-10 10:16:57 +02:00
//it("returns status code 200", async () => {
// get a channel
const result = await platformApi.serverRequest(`channels/messages`, {
2019-10-30 08:51:48 +01:00
params: {
ids: messageId
2019-10-30 08:51:48 +01:00
}
});
//assert.equal(200, result.statusCode);
resolve(result.response.data);
//});
2019-10-30 08:51:48 +01:00
//});
});
}
2019-09-10 10:16:57 +02:00
const runIntegrationTests = async (ourKey, ourPubKeyHex) => {
let channelId = 3; // default channel to try to test first
2019-09-10 10:16:57 +02:00
// get our token
let tokenString, userid, mod_userid;
describe('get our token', function() {
it('get token', async function() {
2019-09-10 10:49:18 +02:00
tokenString = await get_challenge(ourKey, ourPubKeyHex);
});
it('activate token', async function() {
2019-09-10 10:49:18 +02:00
// activate token
await submit_challenge(tokenString, ourPubKeyHex);
2019-09-10 10:49:18 +02:00
});
it('set token', async function() {
2019-09-10 10:49:18 +02:00
// set token
overlayApi.token = tokenString;
platformApi.token = tokenString;
//userid = await getUserID(ourPubKeyHex);
2019-09-10 10:49:18 +02:00
});
2019-09-10 10:16:57 +02:00
it('user info (non-mod)', async function() {
2019-09-10 10:49:18 +02:00
// test token endpoints
const result = await overlayApi.serverRequest('loki/v1/user_info');
//console.log('user user_info result', result)
assert.equal(200, result.statusCode);
assert.ok(result.response);
assert.ok(result.response.data);
// we're a freshly created user (hopefully)
assert.ok(!result.response.data.moderator_status);
assert.ok(result.response.data.user_id);
userid = result.response.data.user_id;
});
// test moderator security...
describe('moderator security tests', function() {
it('cant promote to moderator', async function() {
const result = await overlayApi.serverRequest(`loki/v1/moderators/${userid}`, {
method: 'POST',
});
assert.equal(401, result.statusCode);
});
it('cant blacklist', async function() {
const result = await overlayApi.serverRequest(`loki/v1/moderation/blacklist/${userid}`, {
method: 'POST',
});
/*
{
err: 'statusCode',
statusCode: 401,
response: {
meta: {
code: 401,
error_message: 'Call requires authentication: Authentication required to fetch token.'
}
}
}
*/
assert.equal(401, result.statusCode);
});
2019-09-10 10:49:18 +02:00
});
2019-09-10 10:16:57 +02:00
2019-09-10 10:49:18 +02:00
// make sure we have a channel to test with
describe('channel testing', function() {
it('make sure we have a channel to test', async function() {
const result = await platformApi.serverRequest(`channels/${channelId}`, {
params: {
include_recent_message: 1
}
});
const chnlCheck = result.response.data;
2019-09-10 10:49:18 +02:00
if (Array.isArray(chnlCheck)) {
// make a channel for testing
const result = await platformApi.serverRequest('channels', {
method: 'POST',
objBody: {
type: 'moe.sapphire.test',
},
});
assert.equal(200, result.statusCode);
channelId = result.response.data.id;
2019-09-10 11:17:19 +02:00
console.log('created channel', channelId);
2019-09-10 10:49:18 +02:00
}
});
2019-10-30 08:51:48 +01:00
let messageId, messageId1, messageId2, messageId3, messageId4
it('create message to test with', async function() {
2019-09-10 10:49:18 +02:00
// well we need to create a new message for moderation test
messageId = await create_message(channelId);
2019-10-30 08:51:48 +01:00
messageId1 = await create_message(channelId);
messageId2 = await create_message(channelId);
messageId3 = await create_message(channelId);
messageId4 = await create_message(channelId);
2019-09-10 10:49:18 +02:00
});
it('user cant mod delete message', async function() {
const result = await overlayApi.serverRequest(`loki/v1/moderation/message/${messageId}`, {
method: 'DELETE',
});
assert.equal(401, result.statusCode);
});
it('user multi delete test', async function () {
//let message = await get_message(messageId);
2019-10-30 08:51:48 +01:00
if (messageId3 && messageId4) {
const result = await overlayApi.serverRequest('loki/v1/messages', {
method: 'DELETE',
params: {
ids: [messageId3, messageId4].join(',')
}
});
2019-10-30 08:51:48 +01:00
} else {
console.log('skipping')
}
//message = await get_message(messageId);
//console.log('message after', message);
2019-10-30 08:51:48 +01:00
});
it('can get deletes for channel', function() {
2019-09-10 10:49:18 +02:00
get_deletes(channelId);
});
it('can get moderators for channel', function() {
2019-09-13 08:03:01 +02:00
get_moderators(channelId);
});
// Moderator only functions
let modToken
describe('channel moderator testing', function() {
it('we have moderator to test with', async function() {
// now do moderation tests
modToken = await selectModToken();
if (!modToken) {
console.error('No modToken, skipping moderation tests');
// all tests should be complete
//process.exit(0);
return;
}
console.log('Setting modToken to', modToken);
overlayApi.token = modToken;
});
it('mod user info', async function() {
// test token endpoints
const result = await overlayApi.serverRequest('loki/v1/user_info');
//console.log('mod user_info result', result)
assert.equal(200, result.statusCode);
assert.ok(result.response);
assert.ok(result.response.data);
assert.ok(result.response.data.moderator_status);
// || result.response.moderator_status.match(',')
// I think we only should be global here for now...
assert.equal(true, result.response.data.moderator_status === true);
assert.ok(result.response.data.user_id);
mod_userid=result.response.data.user_id;
});
it('user cant demote moderators', async function() {
overlayApi.token = tokenString; // switch to user
const result = await overlayApi.serverRequest(`loki/v1/moderators/${mod_userid}`, {
method: 'DELETE',
});
assert.equal(401, result.statusCode);
});
it('mod delete test', async function() {
overlayApi.token = modToken; // switch back to mod
if (modToken && messageId) {
//let message = await get_message(messageId);
//console.log('message1', message);
const result = await overlayApi.serverRequest(`loki/v1/moderation/message/${messageId}`, {
method: 'DELETE',
});
assert.equal(200, result.statusCode);
} else {
console.log('skipping modSingleDelete');
}
});
it('mod multi delete test', async function() {
if (modToken && messageId1 && messageId2) {
const result = await overlayApi.serverRequest('loki/v1/moderation/messages', {
method: 'DELETE',
params: {
ids: [messageId1, messageId2].join(',')
}
});
assert.equal(200, result.statusCode);
} else {
console.log('skipping modMutliDelete');
}
});
});
describe('blacklist testing', function() {
it('make sure token is still valid', async function() {
// test token endpoints
const result = await overlayApi.serverRequest('loki/v1/user_info');
//console.log('user user_info result', result)
assert.equal(200, result.statusCode);
assert.ok(result.response);
assert.ok(result.response.data);
});
it('blacklist', async function() {
//console.log('key', ourPubKeyHex);
const userid = await getUserID(ourPubKeyHex);
assert.ok(userid);
//console.log('userid', userid);
const result = await overlayApi.serverRequest(`loki/v1/moderation/blacklist/${userid}`, {
method: 'POST',
});
//console.log('after blacklist', result);
assert.equal(200, result.statusCode);
assert.ok(result.response);
assert.ok(result.response.data);
});
it('switch back to banned user', async function() {
//console.log('changing back to', tokenString);
overlayApi.token = tokenString;
});
it('banned token vs platform', async function() {
//user_info();
try {
const result = await platformApi.serverRequest('token');
assert.equal(401, result.statusCode);
} catch(e) {
console.error('e', e);
}
});
it('banned token vs overlay', async function() {
//user_info();
try {
const result = await overlayApi.serverRequest('loki/v1/user_info');
assert.equal(401, result.statusCode);
} catch(e) {
console.error('e', e);
}
});
it('try to reregister with banned token', async function() {
const result = await overlayApi.serverRequest('loki/v1/get_challenge', {
params: {
pubKey: ourPubKeyHex
}
});
assert.equal(401, result.statusCode);
//console.log('tokenString', result)
});
});
2019-09-10 10:49:18 +02:00
});
});
2019-09-10 10:16:57 +02:00
}
// you can't use => with mocha, you'll loose this context
before(async function() {
//this.timeout(60 * 1000); // can't be in an arrow function
await ensurePlatformServer();
console.log('platform ready');
await ensureOverlayServer();
console.log('overlay ready');
})
2019-09-10 10:16:57 +02:00
runIntegrationTests(ourKey, ourPubKeyHex);