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
Message format:
@ -58,7 +69,7 @@ Message format:
```json
{
"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) {
e.preventDefault();
@ -19,11 +37,7 @@ async function message(e) {
e.preventDefault();
const msg = document.getElementById("msg").value;
if (!msg) return;
const sig = await openpgp.sign({
message: new openpgp.CleartextMessage(msg, ""),
signingKeys: window.keys.priv,
});
window.socket.emit("send_message", { name: window.name, message: sig });
window.socket.emit("send_message", { name: window.name, message: msg });
document.getElementById("msg").value = "";
const el = document.createElement("div");
el.classList.add("message");
@ -40,6 +54,7 @@ async function loadKeys(keys) {
const priv = await openpgp.readKey({ armoredKey: keys.privateKey });
const pub = await openpgp.readKey({ armoredKey: keys.publicKey });
window.keys = { priv, pub };
await auth();
}
async function loadMessages() {
@ -51,7 +66,7 @@ async function loadMessages() {
window.onload = () => {
window.socket = io();
window.socket.on("create_user", swap);
window.socket.on("create_user", auth);
window.socket.on("new_message", (msg) => {
const el = document.createElement("div");
el.classList.add("message");
@ -72,10 +87,11 @@ window.onload = () => {
}
if (!msg.more) document.getElementById("loadmore").classList.add("hidden");
});
window.socket.on("authenticate", swap);
const keys = localStorage.getItem("keys");
if (keys) {
window.name = localStorage.getItem("name");
loadKeys(JSON.parse(keys)).then(swap);
loadKeys(JSON.parse(keys)).then(() => {});
}
document.getElementById("registerform").onsubmit = register;
document.getElementById("msginput").onsubmit = message;

View File

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

View File

@ -5,6 +5,12 @@ CREATE TABLE users (
created timestamp default current_timestamp
);
CREATE TABLE authentications (
user integer,
salt text,
foreign key(user) references users(id)
);
CREATE TABLE threads (
id integer primary key asc,
created timestamp default current_timestamp
@ -15,7 +21,6 @@ CREATE TABLE posts (
user integer,
thread integer,
content text,
sig text,
created timestamp default current_timestamp,
foreign key(user) references users(id),
foreign key(thread) references threads(id)

View File

@ -25,4 +25,4 @@ server.listen(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 get_history = require("./get_history");
const send_message = require("./send_message");
const authenticate = require("./authenticate");
module.exports = {
create_user,
get_history,
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 openpgp = require("openpgp");
const authwrap = require("./authwrap");
const send_message = async (msg, respond, socket) => {
// find user
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",
});
}
// validate signature
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,
});
// 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]
"insert into posts (user, thread, content) values (?, 1, ?) returning id",
[msg.auth_user.id, msg.message]
);
socket.broadcast.emit("new_message", {
id: id.rows[0].id,
name: msg.name,
message: verification.data,
message: msg.message,
});
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);