multiple device authentication !
parent
51f4536424
commit
15acf67922
|
@ -115,7 +115,7 @@ function newThread() {
|
||||||
document.getElementById('newthread').textContent = 'create';
|
document.getElementById('newthread').textContent = 'create';
|
||||||
} else {
|
} else {
|
||||||
window.threadmembers = [window.name];
|
window.threadmembers = [window.name];
|
||||||
document.getElementById('threads').insertAdjacentElement('afterend', html.node`
|
document.getElementById('home').insertAdjacentElement('afterend', html.node`
|
||||||
<form id='createthread' class='column' onsubmit=${createThread}>
|
<form id='createthread' class='column' onsubmit=${createThread}>
|
||||||
<h3>create thread</h3>
|
<h3>create thread</h3>
|
||||||
<label for='newthreadname' class='heading'>thread name</label>
|
<label for='newthreadname' class='heading'>thread name</label>
|
||||||
|
@ -147,8 +147,7 @@ function newThread() {
|
||||||
<br />
|
<br />
|
||||||
<button id='submitthread' type='submit'>create</button>
|
<button id='submitthread' type='submit'>create</button>
|
||||||
</form>
|
</form>
|
||||||
`
|
`);
|
||||||
);
|
|
||||||
document.getElementById('newthread').textContent = 'cancel';
|
document.getElementById('newthread').textContent = 'cancel';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,19 +158,29 @@ function clickedTab(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
render(document.body, html`
|
render(document.body, html`
|
||||||
<div id='threads' class='column'>
|
<div id='home' class='column'>
|
||||||
|
<div id='threads'>
|
||||||
<h3>vybe</h3>
|
<h3>vybe</h3>
|
||||||
<h4>threads</h4>
|
<h4>threads</h4>
|
||||||
<div id='threadlist'>loading...</div>
|
<div id='threadlist'>loading...</div>
|
||||||
<button id='newthread' onclick=${newThread}>create</button>
|
<button id='newthread' onclick=${newThread}>create</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div id='user' onclick=${() =>
|
||||||
|
document.getElementById('profile').classList.toggle('hidden')
|
||||||
|
}>${window.name}</div>
|
||||||
|
</div>
|
||||||
|
<div id='profile' class='column hidden'>
|
||||||
|
<strong>authentication requests</strong>
|
||||||
|
<div id='authrequests'></div>
|
||||||
|
</div>
|
||||||
<hr class='separator' color='#666'>
|
<hr class='separator' color='#666'>
|
||||||
<div id='thread' class='column'>
|
<div id='thread' class='column'>
|
||||||
<div id='title'>
|
<div id='title'>
|
||||||
thread: <strong id='threadname'>meow</strong>
|
thread: <strong id='threadname'>meow</strong>
|
||||||
</div>
|
</div>
|
||||||
<div id='tabs'>
|
<div id='tabs'>
|
||||||
<button id='messagetab' class='tab active' onclick=${clickedTab}>messages</button><button id='spacetab' class='tab' onclick=${clickedTab}>space</button>
|
<button id='messagetab' class='tab active' onclick=${clickedTab}>messages
|
||||||
|
</button><button id='spacetab' class='tab' onclick=${clickedTab}>space</button>
|
||||||
</div>
|
</div>
|
||||||
<div id='message' class='tabcontent'></div>
|
<div id='message' class='tabcontent'></div>
|
||||||
<div id='space' class='tabcontent hidden'></div>
|
<div id='space' class='tabcontent hidden'></div>
|
||||||
|
@ -200,3 +209,16 @@ window.emit('list_threads', {}, msg => {
|
||||||
threadlist.appendChild(makeThread(thread));
|
threadlist.appendChild(makeThread(thread));
|
||||||
chooseThread.call(threadlist.firstChild);
|
chooseThread.call(threadlist.firstChild);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.socket.on('authrequest', (msg, respond) => {
|
||||||
|
const div = html.node`
|
||||||
|
<div class='authrequest'>
|
||||||
|
<button onclick=${() => {
|
||||||
|
respond(true);
|
||||||
|
div.remove();
|
||||||
|
}}>approve</button>
|
||||||
|
session <strong>${msg.id}</strong>
|
||||||
|
</div>`;
|
||||||
|
document.getElementById('authrequests').append(div);
|
||||||
|
setTimeout(() => div.remove(), Date.now() - msg.time + 60000 * 5);
|
||||||
|
});
|
||||||
|
|
|
@ -18,7 +18,11 @@ async function auth() {
|
||||||
});
|
});
|
||||||
window.socket.emit(
|
window.socket.emit(
|
||||||
'authenticate',
|
'authenticate',
|
||||||
{ name: window.name, message: sig },
|
{
|
||||||
|
name: window.name,
|
||||||
|
message: sig,
|
||||||
|
pubkey: window.keys.armored.publicKey
|
||||||
|
},
|
||||||
msg => {
|
msg => {
|
||||||
let register = document.getElementById('register');
|
let register = document.getElementById('register');
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
|
@ -26,12 +30,44 @@ async function auth() {
|
||||||
register.classList.remove('hidden');
|
register.classList.remove('hidden');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (register) {
|
localStorage.setItem('keys', JSON.stringify(window.keys.armored));
|
||||||
register.remove();
|
localStorage.setItem('name', window.name);
|
||||||
|
register.classList.add('hidden');
|
||||||
import('/app.js');
|
import('/app.js');
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submit(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const name = document.getElementById('name').value;
|
||||||
|
if (!name)
|
||||||
|
return;
|
||||||
|
const keys = await openpgp.generateKey({
|
||||||
|
userIDs: [{ name }]
|
||||||
|
});
|
||||||
|
const priv = await openpgp.readKey({ armoredKey: keys.privateKey });
|
||||||
|
const pub = await openpgp.readKey({ armoredKey: keys.publicKey });
|
||||||
|
window.keys = { priv, pub, armored: keys };
|
||||||
|
window.name = name;
|
||||||
|
if (this.id === 'registerform') {
|
||||||
|
window.emit('create_user',
|
||||||
|
{ name, pubkey: keys.publicKey },
|
||||||
|
(msg) => {
|
||||||
|
if (!msg.success) {
|
||||||
|
document.getElementById('result').innerText = msg.message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auth();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await auth();
|
||||||
|
document.getElementById('result').innerHTML =
|
||||||
|
`now open your profile on your other device to approve this authentication.
|
||||||
|
this session's ID is <strong>${pub.getFingerprint().slice(0,8)}</strong>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render(document.body, html`
|
render(document.body, html`
|
||||||
|
@ -43,41 +79,21 @@ render(document.body, html`
|
||||||
for security, rather than passwords. your keys are stored in your
|
for security, rather than passwords. your keys are stored in your
|
||||||
browser storage only, so do this on a browser you can access again.
|
browser storage only, so do this on a browser you can access again.
|
||||||
</p>
|
</p>
|
||||||
<form onsubmit=${async e => {
|
<p>
|
||||||
e.preventDefault();
|
if you already have an account, enter your username here,
|
||||||
const name = document.getElementById('name').value;
|
and you can authenticate using your other device.
|
||||||
if (!name)
|
</p>
|
||||||
return;
|
<form onsubmit=${submit} id='registerform'>
|
||||||
const keys = await openpgp.generateKey({
|
|
||||||
userIDs: [{ name }],
|
|
||||||
});
|
|
||||||
const priv = await openpgp.readKey({ armoredKey: keys.privateKey });
|
|
||||||
const pub = await openpgp.readKey({ armoredKey: keys.publicKey });
|
|
||||||
window.emit(
|
|
||||||
'create_user',
|
|
||||||
{ name, pubkey: keys.publicKey },
|
|
||||||
(msg) => {
|
|
||||||
if (!msg.success) {
|
|
||||||
document.querySelector('#message').innerText = msg.message;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.keys = { priv, pub };
|
|
||||||
localStorage.setItem('keys', JSON.stringify(keys));
|
|
||||||
localStorage.setItem('name', name);
|
|
||||||
window.name = name;
|
|
||||||
auth();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}} id='registerform'>
|
|
||||||
<label for='name'>username: </label>
|
<label for='name'>username: </label>
|
||||||
<input id='name' type='text' />
|
<input id='name' type='text' />
|
||||||
<button id='submit' type='submit'>generate keys & register</button>
|
<button id='submit' type='submit'>register</button>
|
||||||
|
<button onclick=${submit}>authenticate</button>
|
||||||
</form>
|
</form>
|
||||||
<p id='message'></p>
|
<p id='result'></p>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
async function gensession() {
|
function gensession() {
|
||||||
window.session = rand();
|
window.session = rand();
|
||||||
|
|
||||||
window.emit = (type, data, callback) =>
|
window.emit = (type, data, callback) =>
|
||||||
|
@ -94,7 +110,7 @@ async function gensession() {
|
||||||
window.onload = async () => {
|
window.onload = async () => {
|
||||||
window.socket = io();
|
window.socket = io();
|
||||||
|
|
||||||
await gensession();
|
gensession();
|
||||||
|
|
||||||
let keys = localStorage.getItem('keys');
|
let keys = localStorage.getItem('keys');
|
||||||
if (keys) {
|
if (keys) {
|
||||||
|
@ -102,7 +118,8 @@ window.onload = async () => {
|
||||||
keys = JSON.parse(keys);
|
keys = JSON.parse(keys);
|
||||||
window.keys = {
|
window.keys = {
|
||||||
priv: await openpgp.readKey({ armoredKey: keys.privateKey }),
|
priv: await openpgp.readKey({ armoredKey: keys.privateKey }),
|
||||||
pub: await openpgp.readKey({ armoredKey: keys.publicKey })
|
pub: await openpgp.readKey({ armoredKey: keys.publicKey }),
|
||||||
|
armored: keys
|
||||||
};
|
};
|
||||||
await auth();
|
await auth();
|
||||||
}
|
}
|
||||||
|
@ -110,8 +127,8 @@ window.onload = async () => {
|
||||||
document.getElementById('register').classList.remove('hidden');
|
document.getElementById('register').classList.remove('hidden');
|
||||||
|
|
||||||
window.socket.io.on('reconnect', async attempt => {
|
window.socket.io.on('reconnect', async attempt => {
|
||||||
await gensession();
|
gensession();
|
||||||
if (localStorage.getItem('keys'))
|
if (window.keys)
|
||||||
await auth();
|
await auth();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -54,20 +54,21 @@
|
||||||
background: #1b1b1b;
|
background: #1b1b1b;
|
||||||
outline: none;
|
outline: none;
|
||||||
border: 1px solid #444;
|
border: 1px solid #444;
|
||||||
}
|
&:focus {
|
||||||
input:focus {
|
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
border-bottom: 3px solid #777;
|
border-bottom: 3px solid #777;
|
||||||
}
|
}
|
||||||
input::placeholder {
|
&::placeholder {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#register {
|
#register {
|
||||||
margin-inline: 14px;
|
margin-inline: 14px;
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
}
|
}
|
||||||
.thread:hover,
|
.thread:hover,
|
||||||
.tab:hover {
|
.tab:hover,
|
||||||
|
#user:hover {
|
||||||
background-color: #303030;
|
background-color: #303030;
|
||||||
}
|
}
|
||||||
.tab.active,
|
.tab.active,
|
||||||
|
@ -90,13 +91,32 @@
|
||||||
.column {
|
.column {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 5px;
|
|
||||||
}
|
}
|
||||||
.separator {
|
.separator {
|
||||||
margin: 8px 2px;
|
margin: 8px 2px;
|
||||||
}
|
}
|
||||||
#threads {
|
#home {
|
||||||
max-width: 250px;
|
max-width: 250px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
#threads {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
#user {
|
||||||
|
margin: 3px;
|
||||||
|
padding: 6px;
|
||||||
|
background-color: #191919;
|
||||||
|
}
|
||||||
|
#profile {
|
||||||
|
max-width: 250px;
|
||||||
|
> * {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.authrequest {
|
||||||
|
margin-block: 3px;
|
||||||
}
|
}
|
||||||
.thread {
|
.thread {
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
|
@ -113,7 +133,6 @@
|
||||||
#thread {
|
#thread {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
#title {
|
#title {
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
|
@ -143,14 +162,12 @@
|
||||||
#msginput {
|
#msginput {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin: 2px;
|
> * {
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#msg {
|
#msg {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
margin: 2px;
|
|
||||||
}
|
|
||||||
#sendmsg {
|
|
||||||
margin: 2px 3px;
|
|
||||||
}
|
}
|
||||||
.message {
|
.message {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
create table user (
|
create table user (
|
||||||
id integer primary key asc,
|
id integer primary key asc,
|
||||||
name text,
|
name text,
|
||||||
pubkey text,
|
|
||||||
created timestamp default current_timestamp
|
created timestamp default current_timestamp
|
||||||
);
|
);
|
||||||
|
|
||||||
create table authentication (
|
create table key (
|
||||||
user integer,
|
user integer,
|
||||||
salt text,
|
pubkey text,
|
||||||
created timestamp default current_timestamp,
|
created timestamp default current_timestamp,
|
||||||
|
active boolean,
|
||||||
foreign key(user) references user(id)
|
foreign key(user) references user(id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -34,7 +34,6 @@ create table permission (
|
||||||
create table member (
|
create table member (
|
||||||
thread integer,
|
thread integer,
|
||||||
user integer,
|
user integer,
|
||||||
key_delivery text,
|
|
||||||
created timestamp default current_timestamp,
|
created timestamp default current_timestamp,
|
||||||
foreign key(user) references user(id),
|
foreign key(user) references user(id),
|
||||||
foreign key(thread) references thread(id)
|
foreign key(thread) references thread(id)
|
||||||
|
|
22
index.js
22
index.js
|
@ -20,21 +20,27 @@ const io = new Server(server, {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
io.cache = {};
|
|
||||||
|
|
||||||
io.on('connection', (socket) => {
|
io.on('connection', (socket) => {
|
||||||
for (let event in events) {
|
for (let event in events) {
|
||||||
socket.on(event, (msg, callback) =>
|
socket.on(event, (msg, callback) => {
|
||||||
events[event](msg, callback, socket, io)
|
if (!events[event]) {
|
||||||
);
|
callback('no such event ' + event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
events[event](msg, callback, socket);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
socket.on('disconnect', reason => {
|
socket.on('disconnect', reason => {
|
||||||
let sockets = io.cache[socket.username];
|
let user = vybe.users[socket.username];
|
||||||
if (sockets)
|
if (user)
|
||||||
sockets.splice(sockets.indexOf(socket.id), 1);
|
user.sockets.splice(user.sockets.indexOf(socket), 1);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
global.vybe = {
|
||||||
|
users: {}
|
||||||
|
};
|
||||||
|
|
||||||
server.listen(PORT, () => {
|
server.listen(PORT, () => {
|
||||||
console.log('server running on port ' + PORT);
|
console.log('server running on port ' + PORT);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,27 +1,21 @@
|
||||||
const db = require('./db');
|
const db = require('./db');
|
||||||
|
|
||||||
const authwrap = (fn) => async (msg, respond, socket, io) => {
|
const authwrap = (fn) => async (msg, respond, socket) => {
|
||||||
if (!respond)
|
if (!respond)
|
||||||
respond = () => {};
|
respond = () => {};
|
||||||
if (!msg || !msg.__session) {
|
if (!msg || !msg.__session || socket.auth !== msg.__session) {
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'not authenticated',
|
message: 'not authenticated'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const result = await db.query(
|
return await fn({
|
||||||
`select user.* from user join authentication
|
...msg,
|
||||||
on authentication.user = user.id
|
auth_user: {
|
||||||
where authentication.salt = ?`,
|
id: vybe.users[socket.username].id,
|
||||||
[msg.__session]
|
name: socket.username
|
||||||
);
|
|
||||||
if (result.rows.length === 0) {
|
|
||||||
return respond({
|
|
||||||
success: false,
|
|
||||||
message: 'user not found',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return await fn({ ...msg, auth_user: result.rows[0] }, respond, socket, io);
|
}, respond, socket);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = authwrap;
|
module.exports = authwrap;
|
||||||
|
|
|
@ -1,27 +1,32 @@
|
||||||
const db = require('../db');
|
const db = require('../db');
|
||||||
const openpgp = require('openpgp');
|
const openpgp = require('openpgp');
|
||||||
|
|
||||||
const authenticate = async (msg, respond, socket, io) => {
|
const authenticate = async (msg, respond, socket) => {
|
||||||
if (!msg.name || !msg.message) {
|
if (!msg.name || !msg.message) {
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'invalid message'
|
message: 'invalid message'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const result = await db.query('select * from user where name = ?', [
|
let userid = await db.query(
|
||||||
msg.name
|
`select user.id from user where name = ?`, [msg.name]);
|
||||||
]);
|
if (userid.rows.length === 0) {
|
||||||
if (result.rows.length === 0) {
|
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'user not found'
|
message: 'user not found'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let result = await db.query(
|
||||||
|
`select user.id from user
|
||||||
|
join key on key.user = user.id
|
||||||
|
where name = ? and pubkey = ? and active = true`,
|
||||||
|
[msg.name, msg.pubkey]
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
const key = await openpgp.readKey({ armoredKey: result.rows[0].pubkey });
|
const key = await openpgp.readKey({ armoredKey: msg.pubkey });
|
||||||
const verification = await openpgp.verify({
|
const verification = await openpgp.verify({
|
||||||
message: await openpgp.readCleartextMessage({
|
message: await openpgp.readCleartextMessage({
|
||||||
cleartextMessage: msg.message,
|
cleartextMessage: msg.message
|
||||||
}),
|
}),
|
||||||
verificationKeys: key,
|
verificationKeys: key,
|
||||||
expectSigned: true
|
expectSigned: true
|
||||||
|
@ -33,31 +38,52 @@ const authenticate = async (msg, respond, socket, io) => {
|
||||||
message: 'bad auth message'
|
message: 'bad auth message'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const auths = await db.query(
|
let user = vybe.users[msg.name];
|
||||||
'select * from authentication where user = ? and salt = ?',
|
if (!user)
|
||||||
[result.rows[0].id, data[1]]
|
user = vybe.users[msg.name] = {
|
||||||
);
|
id: userid.rows[0].id,
|
||||||
if (auths.rows.length === 0) {
|
sockets: [],
|
||||||
await db.query('insert into authentication (user, salt) values (?, ?)', [
|
authrequests: {}
|
||||||
result.rows[0].id,
|
};
|
||||||
data[1]
|
if (result.rows.length === 0) {
|
||||||
]);
|
// request auth from logged in sessions
|
||||||
|
let id = key.getFingerprint().slice(0, 8);
|
||||||
|
let time = Date.now();
|
||||||
|
if (!await new Promise(resolve => {
|
||||||
|
user.authrequests[id] = { time, callback: resolve };
|
||||||
|
for (let s of user.sockets)
|
||||||
|
s.emit('authrequest', { id, time }, resolve);
|
||||||
|
setTimeout(() => {
|
||||||
|
delete user.authrequests[id];
|
||||||
|
resolve(false);
|
||||||
|
}, 60000 * 5);
|
||||||
|
}))
|
||||||
|
return;
|
||||||
|
delete user.authrequests[id];
|
||||||
|
if (Date.now() - time > 60000 * 5)
|
||||||
|
return;
|
||||||
|
await db.query(
|
||||||
|
'insert into key (user, pubkey, active) values (?, ?, true)',
|
||||||
|
[user.id, msg.pubkey]);
|
||||||
|
}
|
||||||
|
// this socket is now authenticated
|
||||||
|
socket.auth = data[1];
|
||||||
socket.username = msg.name;
|
socket.username = msg.name;
|
||||||
if (io.cache[msg.name]) {
|
user.sockets.push(socket);
|
||||||
io.cache[msg.name].push(socket.id);
|
respond({
|
||||||
} else {
|
success: true
|
||||||
io.cache[msg.name] = [socket.id];
|
|
||||||
}
|
|
||||||
return respond({
|
|
||||||
success: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return respond({
|
|
||||||
success: false,
|
|
||||||
message: 'already authenticated with this message'
|
|
||||||
});
|
});
|
||||||
|
// send authenticated session any auth requests
|
||||||
|
if (result.rows.length)
|
||||||
|
setTimeout(() => {
|
||||||
|
for (let id in user.authrequests)
|
||||||
|
socket.emit('authrequest', {
|
||||||
|
id,
|
||||||
|
time: user.authrequests[id].time
|
||||||
|
}, user.authrequests[id].callback);
|
||||||
|
}, 1000); // todo: do this better
|
||||||
}
|
}
|
||||||
} catch (err) {
|
catch (err) {
|
||||||
console.error('error in authentication: ' + err);
|
console.error('error in authentication: ' + err);
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
const db = require('../db');
|
const db = require('../db');
|
||||||
const authwrap = require('../authwrap');
|
const authwrap = require('../authwrap');
|
||||||
|
|
||||||
const create_thread = async (msg, respond, socket, io) => {
|
const create_thread = async (msg, respond, socket) => {
|
||||||
// validate inputs
|
// validate inputs
|
||||||
if (typeof msg.name !== 'string') {
|
if (typeof msg.name !== 'string') {
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'thread name required',
|
message: 'thread name required'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (msg.name.length > 200) {
|
if (msg.name.length > 200) {
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'thread name 200 chars max',
|
message: 'thread name 200 chars max'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// add to db
|
// add to db
|
||||||
|
@ -63,38 +63,37 @@ const create_thread = async (msg, respond, socket, io) => {
|
||||||
if (id.rows.length > 0) {
|
if (id.rows.length > 0) {
|
||||||
const user_id = id.rows[0].id;
|
const user_id = id.rows[0].id;
|
||||||
await db.query(
|
await db.query(
|
||||||
'insert into member (thread, user, key_delivery) values (?, ?, ?)',
|
'insert into member (thread, user) values (?, ?)',
|
||||||
[thread_id, user_id, user.key]
|
[thread_id, user_id]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!msg.permissions || !msg.permissions.view_limited) {
|
if (!msg.permissions || !msg.permissions.view_limited) {
|
||||||
for (let username in io.cache) {
|
for (let username in vybe.users) {
|
||||||
for (let socket of io.cache[username]) {
|
for (let socket of vybe.users[username].sockets) {
|
||||||
io.to(socket).emit('new_thread', {
|
socket.emit('new_thread', {
|
||||||
name: msg.name,
|
name: msg.name,
|
||||||
id: insert.rows[0].id,
|
id: insert.rows[0].id,
|
||||||
permissions: {
|
permissions: {
|
||||||
is_member: false,
|
is_member: false,
|
||||||
view: true,
|
view: true,
|
||||||
post: !msg.permissions || !msg.permissions.post_limited
|
post: !msg.permissions || !msg.permissions.post_limited
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (let member of msg.members) {
|
for (let member of msg.members) {
|
||||||
for (let socket of io.cache[member.name]) {
|
for (let socket of vybe.users[member.name].sockets) {
|
||||||
io.to(socket).emit('new_thread', {
|
socket.emit('new_thread', {
|
||||||
name: msg.name,
|
name: msg.name,
|
||||||
id: insert.rows[0].id,
|
id: insert.rows[0].id,
|
||||||
permissions: {
|
permissions: {
|
||||||
is_member: true,
|
is_member: true,
|
||||||
view: true,
|
view: true,
|
||||||
post: true
|
post: true
|
||||||
},
|
}
|
||||||
key: member.key
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +101,7 @@ const create_thread = async (msg, respond, socket, io) => {
|
||||||
// respond
|
// respond
|
||||||
return respond({
|
return respond({
|
||||||
success: true,
|
success: true,
|
||||||
id: insert.rows[0].id,
|
id: insert.rows[0].id
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,45 +6,49 @@ const create_user = async (msg, respond) => {
|
||||||
if (!msg.name) {
|
if (!msg.name) {
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'username required',
|
message: 'username required'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!msg.pubkey) {
|
||||||
|
return respond({
|
||||||
|
success: false,
|
||||||
|
message: 'public key required'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// ensure username is not taken
|
// ensure username is not taken
|
||||||
const result = await db.query('select * from user where name = ?', [
|
const result = await db.query('select * from user where name = ?', [
|
||||||
msg.name,
|
msg.name
|
||||||
]);
|
]);
|
||||||
if (result.rows.length > 0) {
|
if (result.rows.length > 0) {
|
||||||
console.log(`username already exists: ${result}`);
|
console.log(`username already exists: ${result}`);
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'a user with this name already exists on this server',
|
message: 'a user with this name already exists on this server'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// validate public key
|
// validate public key
|
||||||
if (!msg.pubkey) {
|
|
||||||
return respond({
|
|
||||||
success: false,
|
|
||||||
message: 'public key required',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
await openpgp.readKey({ armoredKey: msg.pubkey });
|
await openpgp.readKey({ armoredKey: msg.pubkey });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.err('error in create_user readkey: ' + err);
|
console.err('error in create_user readkey: ' + err);
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'public key invalid',
|
message: 'public key invalid'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// add to db
|
// add to db
|
||||||
const insert = await db.query(
|
const insert = await db.query(
|
||||||
'insert into user (name, pubkey) values (?, ?) returning id',
|
'insert into user (name) values (?) returning id',
|
||||||
[msg.name, msg.pubkey]
|
[msg.name]
|
||||||
);
|
);
|
||||||
|
await db.query(
|
||||||
|
'insert into key (user, pubkey, active) values (?, ?, true)',
|
||||||
|
[insert.rows[0].id, msg.pubkey]
|
||||||
|
)
|
||||||
// respond
|
// respond
|
||||||
return respond({
|
return respond({
|
||||||
success: true,
|
success: true,
|
||||||
id: insert.rows[0].id,
|
id: insert.rows[0].id
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,30 +1,29 @@
|
||||||
const db = require('../db');
|
const db = require('../db');
|
||||||
const authwrap = require('../authwrap');
|
const authwrap = require('../authwrap');
|
||||||
|
|
||||||
const get_keys = async (msg, respond, socket, io) => {
|
const get_keys = async (msg, respond) => {
|
||||||
// validate inputs
|
// validate inputs
|
||||||
if (!msg.names) {
|
if (!msg.names) {
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'user names required',
|
message: 'user names required'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (typeof msg.names !== 'object') {
|
if (typeof msg.names !== 'object') {
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
message: "can't iterate user names",
|
message: "can't iterate user names"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const keys = await db.query(
|
const keys = await db.query(
|
||||||
`select name, pubkey from user where name in (${msg.names
|
`select name, pubkey from user where name in
|
||||||
.map((i) => '?')
|
(${msg.names.map((i) => '?').join(',')})`,
|
||||||
.join(',')})`,
|
|
||||||
msg.names
|
msg.names
|
||||||
);
|
);
|
||||||
// respond
|
// respond
|
||||||
return respond({
|
return respond({
|
||||||
success: true,
|
success: true,
|
||||||
keys: keys.rows,
|
keys: keys.rows
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,17 +6,17 @@ const get_space = async (msg, respond) => {
|
||||||
if (!msg.thread) {
|
if (!msg.thread) {
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'thread ID required',
|
message: 'thread ID required'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!(await check_permission(msg.auth_user.id, msg.thread)).view) {
|
if (!(await check_permission(msg.auth_user.id, msg.thread)).view) {
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
message: "you can't view this thread",
|
message: "you can't view this thread"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const spans = await db.query(
|
const spans = await db.query(
|
||||||
'select id, content, x, y, scale from span where thread=? and deleted=false',
|
'select id, content, x, y, scale from span where thread = ? and deleted = false',
|
||||||
[msg.thread]
|
[msg.thread]
|
||||||
);
|
);
|
||||||
return respond({
|
return respond({
|
||||||
|
|
|
@ -4,7 +4,7 @@ const check_permission = require('../check_permission');
|
||||||
|
|
||||||
const list_threads = async (msg, respond) => {
|
const list_threads = async (msg, respond) => {
|
||||||
const threads = await db.query(
|
const threads = await db.query(
|
||||||
`select name, id, member.key_delivery as key from thread
|
`select name, id from thread
|
||||||
join permission on thread.id = permission.thread
|
join permission on thread.id = permission.thread
|
||||||
left join member on thread.id = member.thread
|
left join member on thread.id = member.thread
|
||||||
where permission.permission = 'view'
|
where permission.permission = 'view'
|
||||||
|
@ -20,12 +20,12 @@ const list_threads = async (msg, respond) => {
|
||||||
for (let thread of threads.rows) {
|
for (let thread of threads.rows) {
|
||||||
rows.push({
|
rows.push({
|
||||||
...thread,
|
...thread,
|
||||||
permissions: await check_permission(msg.auth_user.id, thread.id),
|
permissions: await check_permission(msg.auth_user.id, thread.id)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return respond({
|
return respond({
|
||||||
success: true,
|
success: true,
|
||||||
threads: rows,
|
threads: rows
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ const db = require('../db');
|
||||||
const authwrap = require('../authwrap');
|
const authwrap = require('../authwrap');
|
||||||
const check_permission = require('../check_permission');
|
const check_permission = require('../check_permission');
|
||||||
|
|
||||||
const save_span = async (msg, respond, socket, io) => {
|
const save_span = async (msg, respond, socket) => {
|
||||||
if (!msg.thread) {
|
if (!msg.thread) {
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
|
@ -43,12 +43,11 @@ const save_span = async (msg, respond, socket, io) => {
|
||||||
"select * from permission where thread = ? and type = 'everyone' and value = 'true' and permission = 'view'",
|
"select * from permission where thread = ? and type = 'everyone' and value = 'true' and permission = 'view'",
|
||||||
[msg.thread]
|
[msg.thread]
|
||||||
);
|
);
|
||||||
for (let username in io.cache) {
|
for (let username in vybe.users) {
|
||||||
if (permissions.rows.length > 0 || members.includes(username)) {
|
if (permissions.rows.length > 0 || members.includes(username)) {
|
||||||
const sockets = io.cache[username];
|
for (let s of vybe.users[username].sockets) {
|
||||||
for (let s of sockets) {
|
if (s !== socket)
|
||||||
if (s !== socket.id)
|
s.emit('span', {
|
||||||
io.to(s).emit('span', {
|
|
||||||
id,
|
id,
|
||||||
thread: msg.thread,
|
thread: msg.thread,
|
||||||
content: msg.content,
|
content: msg.content,
|
||||||
|
|
|
@ -2,17 +2,17 @@ const db = require('../db');
|
||||||
const authwrap = require('../authwrap');
|
const authwrap = require('../authwrap');
|
||||||
const check_permission = require('../check_permission');
|
const check_permission = require('../check_permission');
|
||||||
|
|
||||||
const send_message = async (msg, respond, socket, io) => {
|
const send_message = async (msg, respond) => {
|
||||||
if (!msg.thread) {
|
if (!msg.thread) {
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'thread ID required',
|
message: 'thread ID required'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!(await check_permission(msg.auth_user.id, msg.thread)).post) {
|
if (!(await check_permission(msg.auth_user.id, msg.thread)).post) {
|
||||||
return respond({
|
return respond({
|
||||||
success: false,
|
success: false,
|
||||||
message: "you can't post to this thread",
|
message: "you can't post to this thread"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// add message and send it to everyone
|
// add message and send it to everyone
|
||||||
|
@ -32,22 +32,21 @@ const send_message = async (msg, respond, socket, io) => {
|
||||||
"select * from permission where thread = ? and type = 'everyone' and value = 'true' and permission = 'view'",
|
"select * from permission where thread = ? and type = 'everyone' and value = 'true' and permission = 'view'",
|
||||||
[msg.thread]
|
[msg.thread]
|
||||||
);
|
);
|
||||||
for (let username in io.cache) {
|
for (let username in vybe.users) {
|
||||||
if (permissions.rows.length > 0 || members.includes(username)) {
|
if (permissions.rows.length > 0 || members.includes(username)) {
|
||||||
const sockets = io.cache[username];
|
for (let s of vybe.users[username].sockets) {
|
||||||
for (let s of sockets) {
|
s.emit('new_message', {
|
||||||
io.to(s).emit('new_message', {
|
|
||||||
id: id.rows[0].id,
|
id: id.rows[0].id,
|
||||||
name: msg.auth_user.name,
|
name: msg.auth_user.name,
|
||||||
message: msg.message,
|
message: msg.message,
|
||||||
thread: msg.thread,
|
thread: msg.thread
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return respond({
|
return respond({
|
||||||
success: true,
|
success: true,
|
||||||
id: id.rows[0].id,
|
id: id.rows[0].id
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue