space ui and updates
parent
f347e659e9
commit
26530690b9
|
@ -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');
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
|
};
|
109
db/1-init.sql
109
db/1-init.sql
|
@ -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");
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue