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