multiple device authentication !

main
jerl 2024-04-29 00:55:54 -05:00
parent 51f4536424
commit 15acf67922
14 changed files with 258 additions and 177 deletions

View File

@ -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,11 +158,20 @@ function clickedTab(event) {
} }
render(document.body, html` render(document.body, html`
<div id='threads' class='column'> <div id='home' class='column'>
<h3>vybe</h3> <div id='threads'>
<h4>threads</h4> <h3>vybe</h3>
<div id='threadlist'>loading...</div> <h4>threads</h4>
<button id='newthread' onclick=${newThread}>create</button> <div id='threadlist'>loading...</div>
<button id='newthread' onclick=${newThread}>create</button>
</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> </div>
<hr class='separator' color='#666'> <hr class='separator' color='#666'>
<div id='thread' class='column'> <div id='thread' class='column'>
@ -171,7 +179,8 @@ render(document.body, html`
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);
});

View File

@ -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,14 +30,46 @@ 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);
import('/app.js'); register.classList.add('hidden');
} 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`
<div id='register' class='hidden'> <div id='register' class='hidden'>
<h1>welcome to vybe</h1> <h1>welcome to vybe</h1>
@ -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();
}); });
}; };

View File

@ -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; }
} &::placeholder {
input::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;

View File

@ -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)

View File

@ -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);
}); });

View File

@ -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) { }, respond, socket);
return respond({
success: false,
message: 'user not found',
});
}
return await fn({ ...msg, auth_user: result.rows[0] }, respond, socket, io);
}; };
module.exports = authwrap; module.exports = authwrap;

View File

@ -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
socket.username = msg.name; let id = key.getFingerprint().slice(0, 8);
if (io.cache[msg.name]) { let time = Date.now();
io.cache[msg.name].push(socket.id); if (!await new Promise(resolve => {
} else { user.authrequests[id] = { time, callback: resolve };
io.cache[msg.name] = [socket.id]; for (let s of user.sockets)
} s.emit('authrequest', { id, time }, resolve);
return respond({ setTimeout(() => {
success: true, delete user.authrequests[id];
}); resolve(false);
} else { }, 60000 * 5);
return respond({ }))
success: false, return;
message: 'already authenticated with this message' 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]);
} }
} catch (err) { // this socket is now authenticated
socket.auth = data[1];
socket.username = msg.name;
user.sockets.push(socket);
respond({
success: true
});
// 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) {
console.error('error in authentication: ' + err); console.error('error in authentication: ' + err);
return respond({ return respond({
success: false, success: false,

View File

@ -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
}); });
}; };

View File

@ -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
}); });
}; };

View File

@ -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
}); });
}; };

View File

@ -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({

View File

@ -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
}); });
}; };

View File

@ -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,

View File

@ -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
}); });
}; };