permissions (basic)

main
june moretz 2023-05-28 17:56:08 -04:00
parent 85d5297c94
commit 89f8032034
13 changed files with 529 additions and 248 deletions

View File

@ -90,7 +90,12 @@ Message format:
```json ```json
{ {
"name": "thread name" "name": "thread name",
"permissions": {
"view_limited": true,
"post_limited": true
},
"members": ["username1", "username2"]
} }
``` ```

View File

@ -1,161 +1,218 @@
function rand() { function rand() {
let str = ""; let str = "";
const lookups = const lookups =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split(""); "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split("");
while (str.length < 16) { while (str.length < 16) {
const n = Math.random() * lookups.length; const n = Math.random() * lookups.length;
str += lookups[Math.floor(n)]; str += lookups[Math.floor(n)];
} }
return str; return str;
} }
async function auth() { async function auth() {
let session = rand(); let session = rand();
const sig = await openpgp.sign({ const sig = await openpgp.sign({
message: new openpgp.CleartextMessage("vybe_auth " + session, ""), message: new openpgp.CleartextMessage("vybe_auth " + session, ""),
signingKeys: window.keys.priv, signingKeys: window.keys.priv,
}); });
window.session = session; window.session = session;
window.socket.emit("authenticate", { name: window.name, message: sig }); window.socket.emit("authenticate", { name: window.name, message: sig });
} }
async function loadKeys(keys) { async function loadKeys(keys) {
const priv = await openpgp.readKey({ armoredKey: keys.privateKey }); const priv = await openpgp.readKey({ armoredKey: keys.privateKey });
const pub = await openpgp.readKey({ armoredKey: keys.publicKey }); const pub = await openpgp.readKey({ armoredKey: keys.publicKey });
window.keys = { priv, pub }; window.keys = { priv, pub };
await auth(); await auth();
} }
function chooseThread(thread) { function chooseThread(thread) {
window.currentThreadId = thread.id; window.currentThreadId = thread.id;
window.earliestMessage = null; window.earliestMessage = null;
document.getElementById("messages").innerHTML = ""; document.getElementById("messages").innerHTML = "";
document.getElementById("threadname").innerHTML = thread.name; document.getElementById("threadname").innerHTML = thread.name;
} }
function loadMessages() { function loadMessages() {
window.socket.emit( window.socket.emit("get_history", {
"get_history", before: window.earliestMessage,
{ before: window.earliestMessage, thread: window.currentThreadId } thread: window.currentThreadId,
); });
} }
function addThread(thread) { function addThread(thread) {
const el = document.createElement("div"); const el = document.createElement("div");
el.classList.add("thread"); el.classList.add("thread");
el.innerHTML = thread.name; el.innerHTML = thread.name;
const btn = document.createElement("button"); const btn = document.createElement("button");
btn.innerHTML = "choose"; btn.innerHTML = "choose";
btn.onclick = () => { btn.onclick = () => {
chooseThread(thread); chooseThread(thread);
loadMessages(); loadMessages();
document.getElementById("loadmore").classList.remove("hidden"); document.getElementById("loadmore").classList.remove("hidden");
}; if (!thread.permissions.post) {
el.appendChild(btn); document.getElementById("msginput").classList.add("hidden");
document.getElementById("threadlist").appendChild(el); } else {
document.getElementById("msginput").classList.remove("hidden");
}
};
el.appendChild(btn);
document.getElementById("threadlist").appendChild(el);
}
function addMember() {
const name = document.getElementById("membername").value;
if (!window.threadmembers) {
window.threadmembers = [window.name, name];
} else {
window.threadmembers.push(name);
}
const member = document.createElement("p");
member.textContent = name;
member.classList.add("member");
document.getElementById("memberlist").appendChild(member);
document.getElementById("membername").value = "";
} }
window.onload = () => { window.onload = () => {
window.currentThreadId = 1; window.currentThreadId = 1;
window.socket = io(); window.socket = io();
window.socket.on("create_user", auth); window.socket.on("create_user", auth);
window.socket.on("new_message", (msg) => { window.socket.on("new_message", (msg) => {
if (msg.thread !== window.currentThreadId) return; if (msg.thread !== window.currentThreadId) return;
const el = document.createElement("div"); const el = document.createElement("div");
el.classList.add("message"); el.classList.add("message");
const strong = document.createElement('strong'); const strong = document.createElement("strong");
strong.textContent = msg.name + ': '; strong.textContent = msg.name + ": ";
el.append(strong, msg.message); el.append(strong, msg.message);
document.getElementById("messages").appendChild(el); document.getElementById("messages").appendChild(el);
if (!window.earliestMessage) if (!window.earliestMessage) window.earliestMessage = msg.id;
window.earliestMessage = msg.id; });
}); window.socket.on("get_history", (msg) => {
window.socket.on("get_history", (msg) => { if (msg.messages.length > 0) {
if (msg.messages.length > 0) { window.earliestMessage = msg.messages[msg.messages.length - 1].id;
window.earliestMessage = msg.messages[msg.messages.length - 1].id; for (let message of msg.messages) {
for (let message of msg.messages) { const el = document.createElement("div");
const el = document.createElement("div"); el.classList.add("message");
el.classList.add("message"); const strong = document.createElement("strong");
const strong = document.createElement('strong'); strong.textContent = message.name + ": ";
strong.textContent = message.name + ': '; el.append(strong, message.message);
el.append(strong, message.message); document.getElementById("messages").prepend(el);
document.getElementById("messages").prepend(el); }
} }
} if (!msg.more) document.getElementById("loadmore").classList.add("hidden");
if (!msg.more) });
document.getElementById("loadmore").classList.add("hidden"); window.socket.on("authenticate", (msg) => {
}); if (msg.success) {
window.socket.on("authenticate", (msg) => { document.getElementById("register").classList.add("hidden");
if (msg.success) { document.getElementById("threads").classList.remove("hidden");
document.getElementById("register").classList.add("hidden"); document.getElementById("chat").classList.remove("hidden");
document.getElementById("threads").classList.remove("hidden"); const member = document.createElement("p");
document.getElementById("chat").classList.remove("hidden"); member.textContent = window.name;
window.socket.emit("list_threads"); member.classList.add("member");
} document.getElementById("memberlist").appendChild(member);
let emitter = window.socket.emit; let emitter = window.socket.emit;
window.socket.emit = (type, data) => { window.socket.emit = (type, data) => {
if (data) if (data)
return emitter.call(window.socket, type, { return emitter.call(window.socket, type, {
...data, ...data,
__session: window.session, __session: window.session,
}); });
else return emitter.call(window.socket, type); else return emitter.call(window.socket, type);
}; };
}); window.socket.emit("list_threads", {});
window.socket.on("list_threads", (msg) => { } else {
document.getElementById("threadlist").innerHTML = ""; document.getElementById("register").classList.remove("hidden");
for (let thread of msg.threads) }
addThread(thread); });
}); window.socket.on("list_threads", (msg) => {
window.socket.on('new_thread', addThread); document.getElementById("threadlist").innerHTML = "";
window.socket.on("create_thread", (msg) => { for (let thread of msg.threads) addThread(thread);
chooseThread({ });
name: document.getElementById("newthreadname").value, window.socket.on("new_thread", addThread);
id: msg.id, window.socket.on("create_thread", (msg) => {
}); chooseThread({
document.getElementById("newthreadname").value = ""; name: document.getElementById("newthreadname").value,
}); id: msg.id,
});
document.getElementById("newthreadname").value = "";
document.getElementById("loadmore").classList.add("hidden");
document.getElementById("msginput").classList.remove("hidden");
});
document.getElementById("registerform").onsubmit = async e => { document.getElementById("registerform").onsubmit = async (e) => {
e.preventDefault(); e.preventDefault();
const name = document.getElementById("name").value; const name = document.getElementById("name").value;
if (!name) return; if (!name) return;
const keys = await openpgp.generateKey({ const keys = await openpgp.generateKey({
userIDs: [{ name }], userIDs: [{ name }],
}); });
const priv = await openpgp.readKey({ armoredKey: keys.privateKey }); const priv = await openpgp.readKey({ armoredKey: keys.privateKey });
const pub = await openpgp.readKey({ armoredKey: keys.publicKey }); const pub = await openpgp.readKey({ armoredKey: keys.publicKey });
window.keys = { priv, pub }; window.keys = { priv, pub };
localStorage.setItem("keys", JSON.stringify(keys)); localStorage.setItem("keys", JSON.stringify(keys));
localStorage.setItem("name", name); localStorage.setItem("name", name);
window.name = name; window.name = name;
window.socket.emit("create_user", { name, pubkey: keys.publicKey }); window.socket.emit("create_user", { name, pubkey: keys.publicKey });
}; };
document.getElementById("msginput").onsubmit = e => { document.getElementById("msginput").onsubmit = (e) => {
e.preventDefault(); e.preventDefault();
const msg = document.getElementById("msg").value; const msg = document.getElementById("msg").value;
if (!msg) return; if (!msg) return;
window.socket.emit("send_message", { window.socket.emit("send_message", {
message: msg, message: msg,
thread: window.currentThreadId, thread: window.currentThreadId,
}); });
document.getElementById("msg").value = ""; document.getElementById("msg").value = "";
}; };
document.getElementById("loadmore").onclick = e => { document.getElementById("loadmore").onclick = (e) => {
loadMessages(); loadMessages();
}; };
document.getElementById("createthread").onsubmit = e => { document.getElementById("createthread").onsubmit = (e) => {
e.preventDefault(); e.preventDefault();
window.socket.emit("create_thread", { const perms = document.querySelector(
name: document.getElementById("newthreadname").value, 'input[name="permissions"]:checked'
}); ).value;
}; let permissions;
if (perms === "public") {
permissions = {
view_limited: false,
post_limited: false,
};
} else if (perms === "private_post") {
permissions = {
view_limited: false,
post_limited: true,
};
} else if (perms === "private_view") {
permissions = {
view_limited: true,
post_limited: true,
};
}
window.socket.emit("create_thread", {
name: document.getElementById("newthreadname").value,
permissions,
members: window.threadmembers || [window.name],
});
document.getElementById(perms).checked = false;
window.threadmembers = null;
document.getElementById("memberlist").innerHTML = "";
const member = document.createElement("p");
member.textContent = window.name;
member.classList.add("member");
document.getElementById("memberlist").appendChild(member);
};
document.getElementById("membername").onkeydown = (e) => {
if (e.key == "Enter") {
addMember();
}
};
document.getElementById("addmember").onclick = addMember;
const keys = localStorage.getItem("keys"); const keys = localStorage.getItem("keys");
if (keys) { if (keys) {
window.name = localStorage.getItem("name"); window.name = localStorage.getItem("name");
loadKeys(JSON.parse(keys)).then(() => {}); loadKeys(JSON.parse(keys)).then(() => {});
} } else document.getElementById("register").classList.remove("hidden");
else
document.getElementById("register").classList.remove("hidden");
}; };

View File

@ -1,89 +1,125 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<script src="/openpgp.min.js"></script> <script src="/openpgp.min.js"></script>
<script src="/socket.io.min.v4.6.1.js"></script> <script src="/socket.io.min.v4.6.1.js"></script>
<script src="/chat.js"></script> <script src="/chat.js"></script>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vybe</title> <title>vybe</title>
<style> <style>
* { * {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue",
sans-serif; sans-serif;
} }
body, button, input { body,
background: #020202; button,
color: #eaeaea; input {
} background: #020202;
body { color: #eaeaea;
display: flex; }
align-items: stretch; body {
margin: 0; display: flex;
min-width: min-content; align-items: stretch;
} margin: 0;
.column { min-width: min-content;
flex-grow: 1; padding: 0 20px;
} }
button { .column {
border-color: #767676; flex: 1;
} max-width: 50vw;
.hidden { overflow: hidden;
display: none; }
} button {
#msginput { border-color: #767676;
margin-top: 15px; }
} .hidden {
.message { display: none;
margin-bottom: 5px; }
} #msginput {
#loadmore { margin-top: 15px;
margin-bottom: 10px; }
} .message {
.channel { margin-bottom: 5px;
font-weight: normal; overflow-wrap: break-word;
} }
</style> #loadmore {
</head> margin-bottom: 10px;
<body> }
<div id="register" class="hidden"> .channel {
<h1>welcome to vybe</h1> font-weight: normal;
<h3>a communication network (beta)</h3> }
<p> .member {
to get started, you'll need an account. we use public key margin: 5px 0;
cryptography for security, rather than passwords. for now }
your keys are stored in your browser storage only. </style>
</p> </head>
<form id="registerform"> <body>
<label for="name">name/username: </label> <div id="register" class="hidden">
<input type="text" id="name" /> <h1>welcome to vybe</h1>
<button id="submit" type="submit">generate keys & register</button> <h3>a communication network (beta)</h3>
</form> <p>
</div> to get started, you'll need an account. we use public key cryptography
<div id="threads" class="column hidden"> for security, rather than passwords. for now your keys are stored in
<h1>vybe</h1> your browser storage only.
<h3>threads</h3> </p>
<div id="threadlist">loading...</div> <form id="registerform">
<h3>create thread</h3> <label for="name">name/username: </label>
<form id="createthread"> <input type="text" id="name" />
<label for="newthreadname">thread name</label> <button id="submit" type="submit">generate keys & register</button>
<input type="text" id="newthreadname" /> </form>
<button id="submitthread" type="submit">create</button> </div>
</form> <div id="threads" class="column hidden">
</div> <h1>vybe</h1>
<div id="chat" class="column hidden"> <h3>threads</h3>
<h3 class="thread"> <div id="threadlist">loading...</div>
current thread: <strong id="threadname">meow</strong> <h3>create thread</h3>
</h3> <form id="createthread">
<h3>messages will appear below as they are sent</h3> <label for="newthreadname">thread name</label>
<button id="loadmore">load more messages</button> <input type="text" id="newthreadname" /><br /><br />
<div id="messages"></div> <span>thread permissions</span><br />
<form id="msginput"> <input type="radio" id="public" name="permissions" value="public" />
<input type="text" placeholder="write a message..." id="msg" /> <label for="public">anyone can view and post</label><br />
<button type="submit" class="hidden" id="sendmsg"></button> <input
</form> type="radio"
</div> id="private_post"
</body> name="permissions"
value="private_post"
/>
<label for="private_post">only members can post, anyone can view</label
><br />
<input
type="radio"
id="private_view"
name="permissions"
value="private_view"
/>
<label for="private_view">only members can view and post</label
><br /><br />
<span>members</span><br />
<input type="text" id="membername" placeholder="username" /><button
id="addmember"
>
add
</button>
<div id="memberlist"></div>
<br />
<button id="submitthread" type="submit">create</button>
</form>
</div>
<div id="chat" class="column hidden">
<h3 class="thread">
current thread: <strong id="threadname">meow</strong>
</h3>
<h3>messages will appear below as they are sent</h3>
<button id="loadmore" class="hidden">load more messages</button>
<div id="messages"></div>
<form id="msginput">
<input type="text" placeholder="write a message..." id="msg" />
<button type="submit" class="hidden" id="sendmsg"></button>
</form>
</div>
</body>
</html> </html>

View File

@ -28,14 +28,14 @@ CREATE TABLE permissions (
value text, value text,
foreign key(user) references users(id), foreign key(user) references users(id),
foreign key(thread) references threads(id) foreign key(thread) references threads(id)
) );
CREATE TABLE members ( CREATE TABLE members (
thread integer, thread integer,
user integer, user integer,
foreign key(user) references users(id), foreign key(user) references users(id),
foreign key(thread) references threads(id) foreign key(thread) references threads(id)
) );
CREATE TABLE posts ( CREATE TABLE posts (
id integer primary key asc, id integer primary key asc,

View File

@ -9,8 +9,8 @@
## permissions -> permission ## permissions -> permission
- manage_permissions - manage_permissions
- add_users - add_members
- remove_users - remove_members
- view - view
- post - post

View File

@ -14,10 +14,17 @@ const PORT = process.env.PORT || 3435;
const actions = require("./src/actions"); const actions = require("./src/actions");
io.cache = {};
io.on("connection", (socket) => { io.on("connection", (socket) => {
for (let action in actions) { for (let action in actions) {
socket.on(action, (msg) => socket.on(action, (msg) =>
actions[action](msg, (response) => socket.emit(action, response), socket, io) actions[action](
msg,
(response) => socket.emit(action, response),
socket,
io
)
); );
} }
}); });

View File

@ -1,7 +1,7 @@
const db = require("../db"); const db = require("../db");
const openpgp = require("openpgp"); const openpgp = require("openpgp");
const authenticate = async (msg, respond, socket) => { const authenticate = async (msg, respond, socket, io) => {
if (!msg.name || !msg.message) { if (!msg.name || !msg.message) {
return respond({ return respond({
success: false, success: false,
@ -43,6 +43,11 @@ const authenticate = async (msg, respond, socket) => {
data[1], data[1],
]); ]);
socket.userid = result.rows[0].id; socket.userid = result.rows[0].id;
if (io.cache[msg.name]) {
io.cache[msg.name].push(socket.id);
} else {
io.cache[msg.name] = [socket.id];
}
return respond({ return respond({
success: true, success: true,
}); });

View File

@ -1,7 +1,7 @@
const db = require("../db"); const db = require("../db");
const authwrap = (fn) => async (msg, respond, socket, io) => { const authwrap = (fn) => async (msg, respond, socket, io) => {
if (!msg.__session) { if (!msg || !msg.__session) {
return respond({ return respond({
success: false, success: false,
message: "Not authenticated", message: "Not authenticated",

View File

@ -20,10 +20,82 @@ const create_thread = async (msg, respond, socket, io) => {
"insert into threads (name, creator) values (?, ?) returning id", "insert into threads (name, creator) values (?, ?) returning id",
[msg.name, msg.auth_user.id] [msg.name, msg.auth_user.id]
); );
io.emit('new_thread', { const thread_id = insert.rows[0].id;
name: msg.name, // set up permissions
id: insert.rows[0].id if (!msg.permissions || !msg.permissions.view_limited) {
}); await db.query(
`insert into permissions (thread, type, flexible, permission, value)
values (?, ?, ?, ?, ?)`,
[thread_id, "everyone", false, "view", "true"]
);
if (!msg.permissions || !msg.permissions.post_limited) {
await db.query(
`insert into permissions (thread, type, flexible, permission, value)
values (?, ?, ?, ?, ?)`,
[thread_id, "everyone", false, "post", "true"]
);
} else {
await db.query(
`insert into permissions (thread, type, flexible, permission, value)
values (?, ?, ?, ?, ?)`,
[thread_id, "members", false, "post", "true"]
);
}
} else {
await db.query(
`insert into permissions (thread, type, flexible, permission, value)
values (?, ?, ?, ?, ?)`,
[thread_id, "members", false, "view", "true"]
);
await db.query(
`insert into permissions (thread, type, flexible, permission, value)
values (?, ?, ?, ?, ?)`,
[thread_id, "members", false, "post", "true"]
);
}
// add members
for (let user of msg.members) {
// get user id
const id = await db.query("select id from users where name = ?", [user]);
if (id.rows.length > 0) {
const user_id = id.rows[0].id;
await db.query("insert into members (thread, user) values (?, ?)", [
thread_id,
user_id,
]);
}
}
const member_perms = {
is_member: true,
view: true,
post: true,
};
const general_perms = {
is_member: false,
view: !msg.permissions || !msg.permissions.view_limited,
post: !msg.permissions || !msg.permissions.post_limited,
};
for (let username in io.cache) {
if (msg.members.includes(username)) {
const sockets = io.cache[username];
for (let s of sockets) {
io.to(s).emit("new_thread", {
name: msg.name,
id: insert.rows[0].id,
permissions: member_perms,
});
}
} else if (general_perms.view) {
const sockets = io.cache[username];
for (let s of sockets) {
io.to(s).emit("new_thread", {
name: msg.name,
id: insert.rows[0].id,
permissions: general_perms,
});
}
}
}
// respond // respond
return respond({ return respond({
success: true, success: true,

View File

@ -1,4 +1,6 @@
const db = require("../db"); const db = require("../db");
const authwrap = require("./authwrap");
const check_permissions = require("./helpers/check_permissions");
const get_history = async (msg, respond) => { const get_history = async (msg, respond) => {
if (msg.before && isNaN(Number(msg.before))) { if (msg.before && isNaN(Number(msg.before))) {
@ -13,6 +15,12 @@ const get_history = async (msg, respond) => {
message: "thread ID required", message: "thread ID required",
}); });
} }
if (!(await check_permissions(msg.auth_user.id, msg.thread)).view) {
return respond({
success: false,
message: "you can't view this thread",
});
}
const messages = await db.query( const messages = await db.query(
`select users.name, posts.id, content from posts `select users.name, posts.id, content from posts
join users on posts.user = users.id join users on posts.user = users.id
@ -31,4 +39,4 @@ const get_history = async (msg, respond) => {
}); });
}; };
module.exports = get_history; module.exports = authwrap(get_history);

View File

@ -0,0 +1,37 @@
const db = require("../../db");
const check_permissions = async (user_id, thread_id) => {
// get all the permissions for the thread
const permissions = await db.query(
"select * from permissions where thread = ?",
[thread_id]
);
// check if the user is a member
const is_member =
(
await db.query("select * from members where thread = ? and user = ?", [
thread_id,
user_id,
])
).rows.length > 0;
const get_permission = (permission) => {
const relevant = permissions.rows.filter(
(i) => i.permission === permission
);
for (let i of relevant) {
if (i.type === "everyone" && i.value === "true") {
return true;
}
if (i.type === "members" && i.value === "true" && is_member) {
return true;
}
}
};
return {
is_member,
view: get_permission("view"),
post: get_permission("post"),
};
};
module.exports = check_permissions;

View File

@ -1,14 +1,32 @@
const db = require("../db"); const db = require("../db");
const authwrap = require("./authwrap");
const check_permissions = require("./helpers/check_permissions");
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 from threads order by created desc" `select name, id from threads
join permissions on threads.id = permissions.thread
join members on threads.id = members.thread
where permissions.permission = 'view'
and permissions.value = 'true'
and ((permissions.type = 'everyone') or
permissions.type = 'members' and members.user = ?)
group by threads.id
order by created desc`,
[msg.auth_user.id]
); );
// respond // respond
const rows = [];
for (let i of threads.rows) {
rows.push({
...i,
permissions: await check_permissions(msg.auth_user.id, i.id),
});
}
return respond({ return respond({
success: true, success: true,
threads: threads.rows, threads: rows,
}); });
}; };
module.exports = list_threads; module.exports = authwrap(list_threads);

View File

@ -1,24 +1,60 @@
const db = require("../db"); const db = require("../db");
const authwrap = require("./authwrap"); const authwrap = require("./authwrap");
const check_permissions = require("./helpers/check_permissions");
const send_message = async (msg, respond, socket) => { const send_message = async (msg, respond, socket, io) => {
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_permissions(msg.auth_user.id, msg.thread)).post) {
return respond({
success: false,
message: "you can't post to this thread",
});
}
// add message and send it to everyone // add message and send it to everyone
const id = await db.query( const id = await db.query(
"insert into posts (user, thread, content) values (?, ?, ?) returning id", "insert into posts (user, thread, content) values (?, ?, ?) returning id",
[msg.auth_user.id, msg.thread, msg.message] [msg.auth_user.id, msg.thread, msg.message]
); );
socket.broadcast.emit("new_message", { // get thread members
id: id.rows[0].id, const members = (
name: msg.auth_user.name, await db.query(
message: msg.message, "select name from users join members on members.user = users.id where members.thread = ?",
thread: msg.thread, [msg.thread]
}); )
).rows.map((i) => i.name);
// get perms
const permissions = await db.query(
"select * from permissions where thread = ? and type = 'everyone' and value = 'true' and permission = 'view'",
[msg.thread]
);
for (let username in io.cache) {
if (members.includes(username)) {
const sockets = io.cache[username];
for (let s of sockets) {
io.to(s).emit("new_message", {
id: id.rows[0].id,
name: msg.auth_user.name,
message: msg.message,
thread: msg.thread,
});
}
} else if (permissions.rows.length > 0) {
const sockets = io.cache[username];
for (let s of sockets) {
io.to(s).emit("new_message", {
id: id.rows[0].id,
name: msg.auth_user.name,
message: msg.message,
thread: msg.thread,
});
}
}
}
return respond({ return respond({
success: true, success: true,
id: id.rows[0].id, id: id.rows[0].id,