Authentication flow changes
							parent
							
								
									989ea77e84
								
							
						
					
					
						commit
						57c89a6b31
					
				
							
								
								
									
										13
									
								
								DOCS.md
								
								
								
								
							
							
						
						
									
										13
									
								
								DOCS.md
								
								
								
								
							|  | @ -13,6 +13,17 @@ if a message fails, you get a response with the following format: | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | ## authenticate | ||||||
|  | 
 | ||||||
|  | you must generate a random salt, sign a message `vybe_auth [salt]`, and then send | ||||||
|  | 
 | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "name": "username", | ||||||
|  |   "message": "that message you signed" | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| ## create_user | ## create_user | ||||||
| 
 | 
 | ||||||
| Message format: | Message format: | ||||||
|  | @ -58,7 +69,7 @@ Message format: | ||||||
| ```json | ```json | ||||||
| { | { | ||||||
|   "name": "unique_username", |   "name": "unique_username", | ||||||
|   "message": "signed message" |   "message": "message" | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,3 +1,21 @@ | ||||||
|  | function rand() { | ||||||
|  |   let str = ""; | ||||||
|  |   const lookups = | ||||||
|  |     "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split(""); | ||||||
|  |   while (str.length < 16) { | ||||||
|  |     const n = Math.random() * lookups.length; | ||||||
|  |     str += lookups[Math.floor(n)]; | ||||||
|  |   } | ||||||
|  |   return str; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function auth() { | ||||||
|  |   const sig = await openpgp.sign({ | ||||||
|  |     message: new openpgp.CleartextMessage("vybe_auth " + rand(), ""), | ||||||
|  |     signingKeys: window.keys.priv, | ||||||
|  |   }); | ||||||
|  |   window.socket.emit("authenticate", { name: window.name, message: sig }); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| async function register(e) { | async function register(e) { | ||||||
|   e.preventDefault(); |   e.preventDefault(); | ||||||
|  | @ -19,11 +37,7 @@ async function message(e) { | ||||||
|   e.preventDefault(); |   e.preventDefault(); | ||||||
|   const msg = document.getElementById("msg").value; |   const msg = document.getElementById("msg").value; | ||||||
|   if (!msg) return; |   if (!msg) return; | ||||||
|   const sig = await openpgp.sign({ |   window.socket.emit("send_message", { name: window.name, message: msg }); | ||||||
|     message: new openpgp.CleartextMessage(msg, ""), |  | ||||||
|     signingKeys: window.keys.priv, |  | ||||||
|   }); |  | ||||||
|   window.socket.emit("send_message", { name: window.name, message: sig }); |  | ||||||
|   document.getElementById("msg").value = ""; |   document.getElementById("msg").value = ""; | ||||||
|   const el = document.createElement("div"); |   const el = document.createElement("div"); | ||||||
|   el.classList.add("message"); |   el.classList.add("message"); | ||||||
|  | @ -40,6 +54,7 @@ async function loadKeys(keys) { | ||||||
|   const priv = await openpgp.readKey({ armoredKey: keys.privateKey }); |   const priv = await openpgp.readKey({ armoredKey: keys.privateKey }); | ||||||
|   const pub = await openpgp.readKey({ armoredKey: keys.publicKey }); |   const pub = await openpgp.readKey({ armoredKey: keys.publicKey }); | ||||||
|   window.keys = { priv, pub }; |   window.keys = { priv, pub }; | ||||||
|  |   await auth(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function loadMessages() { | async function loadMessages() { | ||||||
|  | @ -51,7 +66,7 @@ async function loadMessages() { | ||||||
| 
 | 
 | ||||||
| window.onload = () => { | window.onload = () => { | ||||||
|   window.socket = io(); |   window.socket = io(); | ||||||
|   window.socket.on("create_user", swap); |   window.socket.on("create_user", auth); | ||||||
|   window.socket.on("new_message", (msg) => { |   window.socket.on("new_message", (msg) => { | ||||||
|     const el = document.createElement("div"); |     const el = document.createElement("div"); | ||||||
|     el.classList.add("message"); |     el.classList.add("message"); | ||||||
|  | @ -72,10 +87,11 @@ window.onload = () => { | ||||||
|     } |     } | ||||||
|     if (!msg.more) document.getElementById("loadmore").classList.add("hidden"); |     if (!msg.more) document.getElementById("loadmore").classList.add("hidden"); | ||||||
|   }); |   }); | ||||||
|  |   window.socket.on("authenticate", swap); | ||||||
|   const keys = localStorage.getItem("keys"); |   const keys = localStorage.getItem("keys"); | ||||||
|   if (keys) { |   if (keys) { | ||||||
|     window.name = localStorage.getItem("name"); |     window.name = localStorage.getItem("name"); | ||||||
|     loadKeys(JSON.parse(keys)).then(swap); |     loadKeys(JSON.parse(keys)).then(() => {}); | ||||||
|   } |   } | ||||||
|   document.getElementById("registerform").onsubmit = register; |   document.getElementById("registerform").onsubmit = register; | ||||||
|   document.getElementById("msginput").onsubmit = message; |   document.getElementById("msginput").onsubmit = message; | ||||||
|  |  | ||||||
|  | @ -24,6 +24,9 @@ | ||||||
|       .message { |       .message { | ||||||
|         margin-bottom: 5px; |         margin-bottom: 5px; | ||||||
|       } |       } | ||||||
|  |       #loadmore { | ||||||
|  |         margin-bottom: 10px; | ||||||
|  |       } | ||||||
|     </style> |     </style> | ||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|  |  | ||||||
|  | @ -5,6 +5,12 @@ CREATE TABLE users ( | ||||||
|   created timestamp default current_timestamp |   created timestamp default current_timestamp | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
|  | CREATE TABLE authentications ( | ||||||
|  |   user integer, | ||||||
|  |   salt text, | ||||||
|  |   foreign key(user) references users(id) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
| CREATE TABLE threads ( | CREATE TABLE threads ( | ||||||
|   id integer primary key asc, |   id integer primary key asc, | ||||||
|   created timestamp default current_timestamp |   created timestamp default current_timestamp | ||||||
|  | @ -15,7 +21,6 @@ CREATE TABLE posts ( | ||||||
|   user integer, |   user integer, | ||||||
|   thread integer, |   thread integer, | ||||||
|   content text, |   content text, | ||||||
|   sig text, |  | ||||||
|   created timestamp default current_timestamp, |   created timestamp default current_timestamp, | ||||||
|   foreign key(user) references users(id), |   foreign key(user) references users(id), | ||||||
|   foreign key(thread) references threads(id) |   foreign key(thread) references threads(id) | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								index.js
								
								
								
								
							
							
						
						
									
										2
									
								
								index.js
								
								
								
								
							|  | @ -25,4 +25,4 @@ server.listen(PORT, () => { | ||||||
|   console.log("server running on port " + PORT); |   console.log("server running on port " + PORT); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| app.use(express.static('client')); | app.use(express.static("client")); | ||||||
|  |  | ||||||
|  | @ -1,9 +1,11 @@ | ||||||
| const create_user = require("./create_user"); | const create_user = require("./create_user"); | ||||||
| const get_history = require("./get_history"); | const get_history = require("./get_history"); | ||||||
| const send_message = require("./send_message"); | const send_message = require("./send_message"); | ||||||
|  | const authenticate = require("./authenticate"); | ||||||
| 
 | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|   create_user, |   create_user, | ||||||
|   get_history, |   get_history, | ||||||
|   send_message, |   send_message, | ||||||
|  |   authenticate, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -0,0 +1,63 @@ | ||||||
|  | const db = require("../db"); | ||||||
|  | const openpgp = require("openpgp"); | ||||||
|  | 
 | ||||||
|  | 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 users where name = ?", [ | ||||||
|  |     msg.name, | ||||||
|  |   ]); | ||||||
|  |   if (result.rows.length === 0) { | ||||||
|  |     return respond({ | ||||||
|  |       success: false, | ||||||
|  |       message: "User not found", | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |   try { | ||||||
|  |     const key = await openpgp.readKey({ armoredKey: result.rows[0].pubkey }); | ||||||
|  |     const verification = await openpgp.verify({ | ||||||
|  |       message: await openpgp.readCleartextMessage({ | ||||||
|  |         cleartextMessage: msg.message, | ||||||
|  |       }), | ||||||
|  |       verificationKeys: key, | ||||||
|  |       expectSigned: true, | ||||||
|  |     }); | ||||||
|  |     const data = verification.data.split(" "); | ||||||
|  |     if (data[0] !== "vybe_auth") { | ||||||
|  |       return respond({ | ||||||
|  |         success: false, | ||||||
|  |         message: "Bad auth message", | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |     const auths = await db.query( | ||||||
|  |       "select * from authentications where user = ? and salt = ?", | ||||||
|  |       [result.rows[0].id, data[1]] | ||||||
|  |     ); | ||||||
|  |     if (auths.rows.length === 0) { | ||||||
|  |       await db.query("insert into authentications (user, salt) values (?, ?)", [ | ||||||
|  |         result.rows[0].id, | ||||||
|  |         data[1], | ||||||
|  |       ]); | ||||||
|  |       socket.userid = result.rows[0].id; | ||||||
|  |       return respond({ | ||||||
|  |         success: true, | ||||||
|  |       }); | ||||||
|  |     } else { | ||||||
|  |       return respond({ | ||||||
|  |         success: false, | ||||||
|  |         message: "Already authenticated with this message", | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   } catch (err) { | ||||||
|  |     return respond({ | ||||||
|  |       success: false, | ||||||
|  |       message: "Message signature verification failed", | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | module.exports = authenticate; | ||||||
|  | @ -0,0 +1,22 @@ | ||||||
|  | const db = require("../db"); | ||||||
|  | 
 | ||||||
|  | const authwrap = (fn) => async (msg, respond, socket) => { | ||||||
|  |   if (!socket.userid) { | ||||||
|  |     return respond({ | ||||||
|  |       success: false, | ||||||
|  |       message: "Not authenticated", | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |   const result = await db.query("select * from users where id = ?", [ | ||||||
|  |     socket.userid, | ||||||
|  |   ]); | ||||||
|  |   if (result.rows.length === 0) { | ||||||
|  |     return respond({ | ||||||
|  |       success: false, | ||||||
|  |       message: "User not found", | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |   return await fn({ ...msg, auth_user: result.rows[0] }, respond, socket); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | module.exports = authwrap; | ||||||
|  | @ -1,47 +1,21 @@ | ||||||
| const db = require("../db"); | const db = require("../db"); | ||||||
| const openpgp = require("openpgp"); | const authwrap = require("./authwrap"); | ||||||
| 
 | 
 | ||||||
| const send_message = async (msg, respond, socket) => { | const send_message = async (msg, respond, socket) => { | ||||||
|   // find user
 |   // add message and send it to everyone
 | ||||||
|   const result = await db.query("select * from users where name = ?", [ |   const id = await db.query( | ||||||
|     msg.name, |     "insert into posts (user, thread, content) values (?, 1, ?) returning id", | ||||||
|   ]); |     [msg.auth_user.id, msg.message] | ||||||
|   if (result.rows.length === 0) { |   ); | ||||||
|     return respond({ |   socket.broadcast.emit("new_message", { | ||||||
|       success: false, |     id: id.rows[0].id, | ||||||
|       message: "User not found", |     name: msg.name, | ||||||
|     }); |     message: msg.message, | ||||||
|   } |   }); | ||||||
|   // validate signature
 |   return respond({ | ||||||
|   try { |     success: true, | ||||||
|     const key = await openpgp.readKey({ armoredKey: result.rows[0].pubkey }); |     id: id.rows[0].id, | ||||||
|     const verification = await openpgp.verify({ |   }); | ||||||
|       message: await openpgp.readCleartextMessage({ |  | ||||||
|         cleartextMessage: msg.message, |  | ||||||
|       }), |  | ||||||
|       verificationKeys: key, |  | ||||||
|       expectSigned: true, |  | ||||||
|     }); |  | ||||||
|     // add message and send it to everyone
 |  | ||||||
|     const id = await db.query( |  | ||||||
|       "insert into posts (user, thread, content, sig) values (?, 1, ?, ?) returning id", |  | ||||||
|       [result.rows[0].id, verification.data, msg.message] |  | ||||||
|     ); |  | ||||||
|     socket.broadcast.emit("new_message", { |  | ||||||
|       id: id.rows[0].id, |  | ||||||
|       name: msg.name, |  | ||||||
|       message: verification.data, |  | ||||||
|     }); |  | ||||||
|     return respond({ |  | ||||||
|       success: true, |  | ||||||
|       id: id.rows[0].id, |  | ||||||
|     }); |  | ||||||
|   } catch (err) { |  | ||||||
|     return respond({ |  | ||||||
|       success: false, |  | ||||||
|       message: "Message signature verification failed", |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| module.exports = send_message; | module.exports = authwrap(send_message); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue