space ui and updates

main
jerl 2024-03-18 23:07:48 -07:00
parent f347e659e9
commit 26530690b9
15 changed files with 555 additions and 427 deletions

View File

@ -122,6 +122,11 @@ async function createThread(e) {
chooseThread({ chooseThread({
name: name.value, name: name.value,
id: msg.id, id: msg.id,
permissions: {
is_member: true,
view: true,
post: true
}
}); });
// since the form exists, this will perform cleanup // since the form exists, this will perform cleanup
newThread(); newThread();
@ -215,9 +220,9 @@ render(document.body, html`
</button><button id='spacetab' class='tab' onclick=${switchTab}>space</button> </button><button id='spacetab' class='tab' onclick=${switchTab}>space</button>
</div> </div>
<div id='message' class='tabcontent'> <div id='message' class='tabcontent'>
<button id="loadmore" class="hidden" onclick=${loadMessages}> <button id="loadmore" class="hidden" onclick=${loadMessages}>
load more messages load more messages
</button> </button>
<div id='messages'></div> <div id='messages'></div>
<form id="msginput" onsubmit=${sendMessage}> <form id="msginput" onsubmit=${sendMessage}>
<input type="text" placeholder="write a message..." id="msg" /> <input type="text" placeholder="write a message..." id="msg" />
@ -228,7 +233,7 @@ render(document.body, html`
</div> </div>
`); `);
window.socket.on("new_message", (msg) => { window.socket.on("new_message", msg => {
if (msg.thread !== window.currentThreadId) if (msg.thread !== window.currentThreadId)
return; return;
document.getElementById("messages").appendChild(html.node` document.getElementById("messages").appendChild(html.node`
@ -247,3 +252,5 @@ window.emit("list_threads", {}, msg => {
addThread(thread); addThread(thread);
chooseThread(msg.threads[0]); chooseThread(msg.threads[0]);
}); });
import('/space.js');

View File

@ -20,69 +20,64 @@ async function auth() {
"authenticate", "authenticate",
{ name: window.name, message: sig }, { name: window.name, message: sig },
msg => { msg => {
let register = document.getElementById('register');
if (!msg.success) { if (!msg.success) {
console.log('authenticate failed', msg); console.log('authenticate failed', msg);
register.classList.remove('hidden');
return; return;
} }
if (document.getElementById("register")) { if (register) {
document.getElementById("register").remove(); register.remove();
import('/app.js'); import('/app.js');
} }
} }
); );
} }
render( render(document.body, html`
document.body, <div id="register" class='hidden'>
html` <h1>welcome to vybe</h1>
<div id="register" class='hidden'> <h3>a communication network (beta)</h3>
<h1>welcome to vybe</h1> <p>
<h3>a communication network (beta)</h3> to get started, you'll need an account. we use public key cryptography
<p> for security, rather than passwords. your keys are stored in your
to get started, you'll need an account. we use public key cryptography browser storage only, so do this on a browser you can access again.
for security, rather than passwords. your keys are stored in your </p>
browser storage only, so do this on a browser you can access again. <form onsubmit=${async e => {
</p> e.preventDefault();
<form const name = document.getElementById("name").value;
onsubmit=${async e => { if (!name)
e.preventDefault(); return;
const name = document.getElementById("name").value; const keys = await openpgp.generateKey({
if (!name) 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('#registerform').insertAdjacentHTML(
'afterend',
`<p>${msg.message}</p>`
);
return; return;
const keys = await openpgp.generateKey({ }
userIDs: [{ name }], window.keys = { priv, pub };
}); localStorage.setItem("keys", JSON.stringify(keys));
const priv = await openpgp.readKey({ armoredKey: keys.privateKey }); localStorage.setItem("name", name);
const pub = await openpgp.readKey({ armoredKey: keys.publicKey }); window.name = name;
window.emit( auth();
"create_user", }
{ name, pubkey: keys.publicKey }, );
(msg) => { }} id="registerform">
if (!msg.success) { <label for="name">username: </label>
document.querySelector('#registerform').insertAdjacentHTML( <input id="name" type="text" />
'afterend', <button id="submit" type="submit">generate keys & register</button>
` </form>
<p>${msg.message}</p>` </div>
); `);
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>
<input id="name" type="text" />
<input id="submit" type="submit" value='generate keys & register' />
</form>
</div>
`
);
const gensession = async () => { const gensession = async () => {
window.session = rand(); window.session = rand();

View File

@ -14,6 +14,16 @@
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;
scrollbar-color: #505050 #111;
}
::-webkit-scrollbar-thumb {
background: #505050;
}
::-webkit-scrollbar {
background-color: #111;
}
::-webkit-scrollbar-corner {
background-color: #111;
} }
body, body,
button, button,
@ -33,6 +43,9 @@
margin: 0; margin: 0;
min-width: min-content; min-width: min-content;
} }
#register {
margin-inline: 14px;
}
button, button,
input, input,
.tab { .tab {
@ -42,9 +55,9 @@
background: #303030; background: #303030;
} }
input { input {
background: #171717; background: #1b1b1b;
border-bottom: 2px solid transparent;
padding-bottom: 3px; padding-bottom: 3px;
border-bottom: 2px solid transparent;
} }
input:focus { input:focus {
border-bottom: 2px solid #4f4f4f; border-bottom: 2px solid #4f4f4f;
@ -75,16 +88,14 @@
} }
.column { .column {
flex: 1; flex: 1;
margin: 5px;
overflow: hidden; overflow: hidden;
margin: 5px;
} }
#threads { #threads {
max-width: 250px; max-width: 250px;
} }
.thread { .thread {
display: block; padding: 2px;
width: 100%;
text-align: left;
} }
#newthread { #newthread {
margin-top: 5px; margin-top: 5px;
@ -98,30 +109,31 @@
#thread { #thread {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin: 0;
} }
#title { #title {
margin: 4px 2px; margin: 4px;
} }
#tabs { #tabs {
margin-block: 2px; margin: 4px 2px;
} }
.tab { .tab {
padding: 5px 7px; padding: 5px 7px;
background-color: #1f1f1f; background-color: #1f1f1f;
border: 0; border: 0;
margin-top: 2px;
color: #ddd; color: #ddd;
font-weight: 500; font-weight: 500;
} }
.tabcontent { .tabcontent {
flex-grow: 1; flex-grow: 1;
margin: 2px;
} }
#message { #message {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
#messages { #messages {
margin: 4px 2px; margin: 2px;
flex-grow: 1; flex-grow: 1;
} }
#msginput { #msginput {
@ -149,6 +161,14 @@
.member { .member {
margin: 5px 0; margin: 5px 0;
} }
#space {
margin: 0;
position: relative;
overflow: auto;
}
.span {
position: absolute;
}
</style> </style>
</head> </head>
<body></body> <body></body>

93
client/space.js Normal file
View File

@ -0,0 +1,93 @@
let space = document.getElementById('space');
let scale = 1;
let editing;
let dragging;
let moved;
let offset;
function mousemove(event) {
let left = (event.clientX - space.offsetLeft) * scale - offset.x;
let top = (event.clientY - space.offsetTop) * scale - offset.y;
dragging.style.left = `${left < 0 ? 0 : left}px`;
dragging.style.top = `${top < 0 ? 0 : top}px`;
moved = true;
//save
}
function remove(span) {
//remove
span.remove();
}
function add(x, y) {
let span = document.createElement('span');
span.classList.add('span');
span.contentEditable = true;
span.spellcheck = false;
span.scale = 1;
span.style.left = `${x}px`;
span.style.top = `${y}px`;
span.style.transform = 'translate(-50%, -50%)';
span.onkeydown = function(event) {
if (event.key === 'Enter' && !event.getModifierState('Shift')) {
event.preventDefault();
editing = null;
span.blur();
}
};
span.oninput = function(event) {
this.time = Date.now();
//save
};
span.onblur = function(event) {
if (!this.innerText)
remove(this);
};
span.onwheel = function(event) {
event.preventDefault();
this.scale *= 1 - event.deltaY * .001;
this.style.transform = `translate(-50%, -50%) scale(${this.scale})`;
//save
};
span.onmousedown = function(event) {
if (dragging || editing === this)
return;
dragging = this;
event.preventDefault();
offset = {
x: event.clientX - (space.offsetLeft + this.offsetLeft),
y: event.clientY - (space.offsetTop + this.offsetTop)
};
moved = false;
document.addEventListener('mousemove', mousemove);
};
span.onmouseup = function(event) {
event.stopPropagation();
document.removeEventListener('mousemove', mousemove);
dragging = null;
if (moved)
return;
this.focus();
editing = this;
};
space.append(span);
return span;
}
space.onmouseup = event => {
if (dragging) {
dragging.onmouseup(event);
return;
}
if (editing) {
if (!editing.innerText)
remove(editing);
if (event.target !== editing)
editing = null;
return;
}
editing = add(event.offsetX + space.scrollLeft, event.offsetY + space.scrollTop);
editing.focus();
};

View File

@ -1,60 +1,73 @@
CREATE TABLE users ( create table user (
id integer primary key asc, id integer primary key asc,
name text, name text,
pubkey text, pubkey text,
created timestamp default current_timestamp created timestamp default current_timestamp
); );
CREATE TABLE authentications ( create table authentication (
user integer, user integer,
salt text, salt text,
created timestamp default current_timestamp, created timestamp default current_timestamp,
foreign key(user) references users(id) foreign key(user) references user(id)
); );
CREATE TABLE threads ( create table thread (
id integer primary key asc, id integer primary key asc,
name text, name text,
creator integer, creator integer,
created timestamp default current_timestamp, created timestamp default current_timestamp,
foreign key(creator) references users(id) foreign key(creator) references user(id)
); );
CREATE TABLE permissions ( create table permission (
thread integer, thread integer,
user integer, user integer,
type text, type text,
mutable boolean, mutable boolean,
permission text, permission text,
value text, value text,
foreign key(user) references users(id), foreign key(user) references user(id),
foreign key(thread) references threads(id) foreign key(thread) references thread(id)
); );
CREATE TABLE members ( create table member (
thread integer, thread integer,
user integer, user integer,
key_delivery text, key_delivery text,
created timestamp default current_timestamp, created timestamp default current_timestamp,
foreign key(user) references users(id), foreign key(user) references user(id),
foreign key(thread) references threads(id) foreign key(thread) references thread(id)
); );
CREATE TABLE posts ( create table post (
id integer primary key asc, id integer primary key asc,
user integer, user integer,
thread integer, thread integer,
content text, content text,
encrypted bool, encrypted bool,
created timestamp default current_timestamp, created timestamp default current_timestamp,
foreign key(user) references users(id), foreign key(user) references user(id),
foreign key(thread) references threads(id) foreign key(thread) references thread(id)
); );
INSERT INTO threads (name) values ("meow"); create table span (
INSERT INTO permissions id integer primary key asc,
(thread, type, permission, value) values thread integer,
(1, "everyone", "view", "true"); creator integer,
INSERT INTO permissions created timestamp default current_timestamp,
(thread, type, permission, value) values content text,
(1, "everyone", "post", "true"); x integer,
y integer,
scale decimal,
foreign key(creator) references user(id),
foreign key(thread) references thread(id)
);
insert into thread (name) values ("meow");
insert into permission
(thread, type, permission, value) values
(1, "everyone", "view", "true");
insert into permission
(thread, type, permission, value) values
(1, "everyone", "post", "true");

View File

@ -2,68 +2,68 @@ 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, io) => {
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 users 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) {
return respond({ return respond({
success: false, success: false,
message: "User not found", message: "User not found",
}); });
} }
try { try {
const key = await openpgp.readKey({ armoredKey: result.rows[0].pubkey }); const key = await openpgp.readKey({ armoredKey: result.rows[0].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,
}); });
const data = verification.data.split(" "); const data = verification.data.split(" ");
if (data[0] !== "vybe_auth") { if (data[0] !== "vybe_auth") {
return respond({ return respond({
success: false, success: false,
message: "Bad auth message", message: "Bad auth message",
}); });
} }
const auths = await db.query( const auths = await db.query(
"select * from authentications where user = ? and salt = ?", "select * from authentication where user = ? and salt = ?",
[result.rows[0].id, data[1]] [result.rows[0].id, data[1]]
); );
if (auths.rows.length === 0) { if (auths.rows.length === 0) {
await db.query("insert into authentications (user, salt) values (?, ?)", [ await db.query("insert into authentication (user, salt) values (?, ?)", [
result.rows[0].id, result.rows[0].id,
data[1], data[1],
]); ]);
socket.username = msg.name; socket.username = msg.name;
if (io.cache[msg.name]) { if (io.cache[msg.name]) {
io.cache[msg.name].push(socket.id); io.cache[msg.name].push(socket.id);
} else { } else {
io.cache[msg.name] = [socket.id]; io.cache[msg.name] = [socket.id];
} }
return respond({ return respond({
success: true, success: true,
}); });
} else { } else {
return respond({ return respond({
success: false, success: false,
message: "Already authenticated with this message", message: "Already authenticated with this message",
}); });
} }
} catch (err) { } catch (err) {
console.error("error in authentication: " + err); console.error("error in authentication: " + err);
return respond({ return respond({
success: false, success: false,
message: "Message signature verification failed", message: "Message signature verification failed",
}); });
} }
}; };
module.exports = authenticate; module.exports = authenticate;

View File

@ -1,27 +1,27 @@
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 (!respond) if (!respond)
respond = () => {}; respond = () => {};
if (!msg || !msg.__session) { if (!msg || !msg.__session) {
return respond({ return respond({
success: false, success: false,
message: "Not authenticated", message: "Not authenticated",
}); });
} }
const result = await db.query( const result = await db.query(
`select users.* from users join authentications `select user.* from user join authentication
on authentications.user = users.id on authentication.user = user.id
where authentications.salt = ?`, where authentication.salt = ?`,
[msg.__session] [msg.__session]
); );
if (result.rows.length === 0) { if (result.rows.length === 0) {
return respond({ return respond({
success: false, success: false,
message: "User not found", message: "User not found",
}); });
} }
return await fn({ ...msg, auth_user: result.rows[0] }, respond, socket, io); return await fn({ ...msg, auth_user: result.rows[0] }, respond, socket, io);
}; };
module.exports = authwrap; module.exports = authwrap;

View File

@ -17,38 +17,38 @@ const create_thread = async (msg, respond, socket, io) => {
} }
// add to db // add to db
const insert = await db.query( const insert = await db.query(
"insert into threads (name, creator) values (?, ?) returning id", "insert into thread (name, creator) values (?, ?) returning id",
[msg.name, msg.auth_user.id] [msg.name, msg.auth_user.id]
); );
const thread_id = insert.rows[0].id; const thread_id = insert.rows[0].id;
// set up permissions // set up permissions
if (!msg.permissions || !msg.permissions.view_limited) { if (!msg.permissions || !msg.permissions.view_limited) {
await db.query( await db.query(
`insert into permissions (thread, type, mutable, permission, value) `insert into permission (thread, type, mutable, permission, value)
values (?, ?, ?, ?, ?)`, values (?, ?, ?, ?, ?)`,
[thread_id, "everyone", false, "view", "true"] [thread_id, "everyone", false, "view", "true"]
); );
if (!msg.permissions || !msg.permissions.post_limited) { if (!msg.permissions || !msg.permissions.post_limited) {
await db.query( await db.query(
`insert into permissions (thread, type, mutable, permission, value) `insert into permission (thread, type, mutable, permission, value)
values (?, ?, ?, ?, ?)`, values (?, ?, ?, ?, ?)`,
[thread_id, "everyone", false, "post", "true"] [thread_id, "everyone", false, "post", "true"]
); );
} else { } else {
await db.query( await db.query(
`insert into permissions (thread, type, mutable, permission, value) `insert into permission (thread, type, mutable, permission, value)
values (?, ?, ?, ?, ?)`, values (?, ?, ?, ?, ?)`,
[thread_id, "members", false, "post", "true"] [thread_id, "members", false, "post", "true"]
); );
} }
} else { } else {
await db.query( await db.query(
`insert into permissions (thread, type, mutable, permission, value) `insert into permission (thread, type, mutable, permission, value)
values (?, ?, ?, ?, ?)`, values (?, ?, ?, ?, ?)`,
[thread_id, "members", false, "view", "true"] [thread_id, "members", false, "view", "true"]
); );
await db.query( await db.query(
`insert into permissions (thread, type, mutable, permission, value) `insert into permission (thread, type, mutable, permission, value)
values (?, ?, ?, ?, ?)`, values (?, ?, ?, ?, ?)`,
[thread_id, "members", false, "post", "true"] [thread_id, "members", false, "post", "true"]
); );
@ -57,13 +57,13 @@ const create_thread = async (msg, respond, socket, io) => {
for (let user of msg.members) { for (let user of msg.members) {
if (!user) continue; if (!user) continue;
// get user id // get user id
const id = await db.query("select id from users where name = ?", [ const id = await db.query("select id from user where name = ?", [
user.name, user.name,
]); ]);
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 members (thread, user, key_delivery) values (?, ?, ?)", "insert into member (thread, user, key_delivery) values (?, ?, ?)",
[thread_id, user_id, user.key] [thread_id, user_id, user.key]
); );
} }

View File

@ -2,50 +2,50 @@ const db = require("../db");
const openpgp = require("openpgp"); const openpgp = require("openpgp");
const create_user = async (msg, respond) => { const create_user = async (msg, respond) => {
// validate inputs // validate inputs
if (!msg.name) { if (!msg.name) {
return respond({ return respond({
success: false, success: false,
message: "username required", message: "username required",
}); });
} }
// ensure username is not taken // ensure username is not taken
const result = await db.query("select * from users 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) { if (!msg.pubkey) {
return respond({ return respond({
success: false, success: false,
message: "public key required", 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 users (name, pubkey) values (?, ?) returning id", "insert into user (name, pubkey) values (?, ?) returning id",
[msg.name, msg.pubkey] [msg.name, msg.pubkey]
); );
// respond // respond
return respond({ return respond({
success: true, success: true,
id: insert.rows[0].id, id: insert.rows[0].id,
}); });
}; };
module.exports = create_user; module.exports = create_user;

View File

@ -1,42 +1,42 @@
const db = require("../db"); const db = require("../db");
const authwrap = require("./authwrap"); const authwrap = require("./authwrap");
const check_permissions = require("./helpers/check_permissions"); const check_permission = require("./helpers/check_permission");
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))) {
return respond({ return respond({
success: false, success: false,
message: "before must be a number", message: "before must be a number",
}); });
} }
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)).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 messages = await db.query( const messages = await db.query(
`select users.name, posts.id, content from posts `select user.name, post.id, content from post
join users on posts.user = users.id join user on post.user = user.id
${msg.before ? "where posts.id < ? and" : "where"} ${msg.before ? "where post.id < ? and" : "where"}
thread = ? thread = ?
order by posts.created desc order by post.created desc
limit 101`, limit 101`,
msg.before ? [msg.before, msg.thread] : [msg.thread] msg.before ? [msg.before, msg.thread] : [msg.thread]
); );
return respond({ return respond({
success: true, success: true,
messages: messages.rows messages: messages.rows
.slice(0, 100) .slice(0, 100)
.map((i) => ({ id: i.id, name: i.name, message: i.content })), .map((i) => ({ id: i.id, name: i.name, message: i.content })),
more: messages.rows.length > 100, more: messages.rows.length > 100,
}); });
}; };
module.exports = authwrap(get_history); module.exports = authwrap(get_history);

View File

@ -2,30 +2,30 @@ 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, socket, io) => {
// 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 users where name in (${msg.names `select name, pubkey from user where name in (${msg.names
.map((i) => "?") .map((i) => "?")
.join(",")})`, .join(",")})`,
msg.names msg.names
); );
// respond // respond
return respond({ return respond({
success: true, success: true,
keys: keys.rows, keys: keys.rows,
}); });
}; };
module.exports = authwrap(get_keys); module.exports = authwrap(get_keys);

View File

@ -0,0 +1,37 @@
const db = require("../../db");
const check_permission = async (user_id, thread_id) => {
// get all the permissions for the thread
const permissions = await db.query(
"select * from permission where thread = ?",
[thread_id]
);
// check if the user is a member
const is_member =
(
await db.query("select * from member 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_permission;

View File

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

View File

@ -1,64 +1,64 @@
const db = require("../db"); const db = require("../db");
const authwrap = require("./authwrap"); const authwrap = require("./authwrap");
const check_permissions = require("./helpers/check_permissions"); const check_permission = require("./helpers/check_permission");
const send_message = async (msg, respond, socket, io) => { 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) { 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
const id = await db.query( const id = await db.query(
"insert into posts (user, thread, content, encrypted) values (?, ?, ?, ?) returning id", "insert into post (user, thread, content, encrypted) values (?, ?, ?, ?) returning id",
[msg.auth_user.id, msg.thread, msg.message, msg.encrypted] [msg.auth_user.id, msg.thread, msg.message, msg.encrypted]
); );
// get thread members // get thread members
const members = ( const members = (
await db.query( await db.query(
"select name from users join members on members.user = users.id where members.thread = ?", "select name from user join member on member.user = user.id where member.thread = ?",
[msg.thread] [msg.thread]
) )
).rows.map((i) => i.name); ).rows.map((i) => i.name);
// get perms // get perms
const permissions = await db.query( const permissions = await db.query(
"select * from permissions 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 io.cache) {
if (members.includes(username)) { if (members.includes(username)) {
const sockets = io.cache[username]; const sockets = io.cache[username];
for (let s of sockets) { for (let s of sockets) {
io.to(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,
}); });
} }
} else if (permissions.rows.length > 0) { } else if (permissions.rows.length > 0) {
const sockets = io.cache[username]; const sockets = io.cache[username];
for (let s of sockets) { for (let s of sockets) {
io.to(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,
}); });
}; };
module.exports = authwrap(send_message); module.exports = authwrap(send_message);