Authentication flow changes

main
june moretz 2023-05-07 21:43:57 -04:00
parent 989ea77e84
commit 57c89a6b31
9 changed files with 148 additions and 52 deletions

13
DOCS.md
View File

@ -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"
} }
``` ```

View File

@ -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;

View File

@ -24,6 +24,9 @@
.message { .message {
margin-bottom: 5px; margin-bottom: 5px;
} }
#loadmore {
margin-bottom: 10px;
}
</style> </style>
</head> </head>
<body> <body>

View File

@ -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)

View File

@ -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"));

View File

@ -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,
}; };

63
src/authenticate.js Normal file
View File

@ -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;

22
src/authwrap.js Normal file
View File

@ -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;

View File

@ -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);