Files
backend/accounts/manage.js
2025-06-22 11:41:21 -04:00

252 lines
8.5 KiB
JavaScript
Executable File

import crypto from "node:crypto";
import { accs } from "../database.js";
import { rword } from "rword";
import axios from "axios";
import { fileURLToPath } from "url";
import path, { dirname } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
async function createAccount(name, pass, captcha) {
try {
if (!(name.length < 17 && name.length > 2 && !/[^a-zA-Z0-9._-]/.test(name))) {
return { success: false, reason: "Bad username." };
}
if (!captcha) {
return { success: false, reason: "No captcha response." };
}
if (!/^((?=\S*?[A-Z])(?=\S*?[a-z])(?=\S*?[0-9]).{5,})\S$/.test(pass)) {
return { success: false, reason: "Bad password." };
}
const response = await axios.post("https://api.hcaptcha.com/siteverify", `response=${captcha}&secret=${process.env.HCAPTCHA_SECRET}`);
const data = response.data;
if (!data.success) {
return { success: false, reason: "Captcha failed." };
}
const existingAccount = accs.query(`SELECT * FROM accounts WHERE username LIKE $1`)
let userData = existingAccount.get({ $1: name });
if (userData !== null) {
return { success: false, reason: "The account already exists." };
}
const id = `${Date.now()}${Math.round(Math.random() * 1000000)
.toString()
.padStart(6, 0)}`;
const salt = crypto.randomBytes(128).toString("base64");
const salted_pass = pass + salt;
const new_pass = crypto.createHash("sha256").update(salted_pass).digest("hex");
const hash_pass = JSON.stringify({ pass: new_pass, salt: salt });
let secret_key = rword.generate(6, { length: "3-7" }).join(" ").toUpperCase();
const createAccount = accs.query(`INSERT INTO accounts (id, username, name, hashed_pass, secret_key, createdAt, updatedAt) VALUES ($id, $username, $name, $hashed_pass, $secret_key, $date, $date)`)
createAccount.get({ $id: Number(id), $username: name.toLowerCase(), $name: name, $hashed_pass: hash_pass, $secret_key: secret_key, $date: new Date().toISOString().replace(/T/, ' ').replace(/\..+/g, '') });
const updateAccount = accs.query(`UPDATE accounts SET last_login = $login WHERE username = $user`)
updateAccount.get({ $login: new Date().toUTCString(), $user: name.toLowerCase() });
return { success: true, key: secret_key };
} catch (e) {
shitHitTheFan("Account failed to create, probably something bad happened");
shitHitTheFan("User info: " + name + ", " + pass);
console.error("Error:", e);
return { success: false, reason: e.message };
}
}
async function resetPassword(name, key, pass, captcha) {
const response = await axios.post("https://api.hcaptcha.com/siteverify", `response=${captcha}&secret=${process.env.HCAPTCHA_SECRET}`);
const data = response.data;
if (!data.success) {
return { success: false, reason: "Captcha failed." };
}
key = key.toUpperCase();
const existingAccount = accs.query(`SELECT * FROM accounts WHERE username LIKE $1`)
let userData = existingAccount.get({ $1: name });
if (userData == null) {
return { success: false, reason: "The account does not exist." };
}
if (userData.secret_key == key) {
const salt = crypto.randomBytes(128).toString("base64");
const salted_pass = pass + salt;
const new_pass = crypto.createHash("sha256").update(salted_pass).digest("hex");
const hash_pass = JSON.stringify({ pass: new_pass, salt: salt });
const updateAccount = accs.query(`UPDATE accounts SET hashed_pass = $pass WHERE username = $user`)
updateAccount.get({ $pass: hash_pass, $user: name.toLowerCase() });
return { success: true };
} else {
return { success: false, reason: "Wrong key" };
}
}
async function loginAccount(name, pass, captcha) {
const existingAccount = accs.query(`SELECT * FROM accounts WHERE username LIKE $1`)
let userData = existingAccount.get({ $1: name });
if (userData == null) {
return { success: false, reason: "The account doesn't exists." };
}
const response = await axios.post("https://api.hcaptcha.com/siteverify", `response=${captcha}&secret=${process.env.HCAPTCHA_SECRET}`);
const data = response.data;
if (!data.success) {
return { success: false, reason: "Captcha failed." };
}
let account_pass = JSON.parse(userData.hashed_pass);
const salted_pass = pass + account_pass.salt;
const new_pass = crypto.createHash("sha256").update(salted_pass).digest("hex");
if (account_pass.pass == new_pass) {
const updateAccount = accs.query(`UPDATE accounts SET last_login = $login WHERE username = $user`)
updateAccount.get({ $login: new Date().toUTCString(), $user: name.toLowerCase() });
return { success: true, token: await generateCookie(name, pass) };
} else {
return { success: false, reason: "Incorrect password." };
}
}
async function generateCookie(name, pass) {
let date = new Date();
date.setMonth(date.getMonth() + 6);
let unencryptedCookie = JSON.stringify({ n: name, p: pass, e: date });
const secretKey = process.env.AUTH_KEY;
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv("aes-256-cbc", secretKey, iv);
let encrypted = cipher.update(unencryptedCookie, "utf8", "base64");
encrypted += cipher.final("base64");
const encryptedCookie = (iv.toString("base64") + "." + encrypted).replaceAll("=", "");
return encryptedCookie;
}
async function verifyCookie(cookie) {
if(cookie){
let decrypted = JSON.parse(await decryptCookie(cookie));
if (decrypted) {
return (await isLoginValid(decrypted["n"], decrypted["p"])) && new Date(decrypted["e"]) > new Date();
}
}
return false;
}
async function getUserFromCookie(cookie) {
return await JSON.parse(await decryptCookie(cookie))["n"];
}
async function decryptCookie(cookie) {
if (cookie) {
try {
const secretKey = process.env.AUTH_KEY;
if (secretKey.length !== 32) {
shitHitTheFan("Encryption key isn't valid");
return false;
}
let tokenSplit = cookie.split(".");
if (tokenSplit.length !== 2) {
return false;
}
const iv = Buffer.from(tokenSplit[0], "base64");
let encryptedData = Buffer.from(tokenSplit[1], "base64");
let cipher = crypto.createDecipheriv("aes-256-cbc", secretKey, iv);
let decrypted = cipher.update(encryptedData, "base64", "utf8");
decrypted += cipher.final("utf8");
if (decrypted) {
return decrypted;
} else {
return false;
}
} catch (e) {
console.error(e);
return {};
}
}
}
async function isLoginValid(name, pass) {
const existingAccount = accs.query(`SELECT * FROM accounts WHERE username LIKE $1`)
let userData = existingAccount.get({ $1: name });
if (userData == null) {
return false;
}
let account_pass = JSON.parse(userData.hashed_pass);
const salted_pass = pass + account_pass.salt;
const new_pass = crypto.createHash("sha256").update(salted_pass).digest("hex");
return userData && account_pass.pass == new_pass;
}
async function addBadge(user, badge, cookie) {
if (await isAdmin(cookie)) {
const existingAccount = accs.query(`SELECT * FROM accounts WHERE username LIKE $1`)
let userData = existingAccount.get({ $1: user });
if (userData == null) {
return { success: false, reason: "The account doesn't exists." };
}
let badges;
if (userData.badges !== null) {
badges = JSON.parse(userData.badges);
} else {
badges = [];
}
if (badges.includes(badge)) {
badges.splice(badges.indexOf(badge), 1);
} else {
badges.push(badge);
}
const updateAccount = accs.query(`UPDATE accounts SET badges = $badge WHERE username = $user`)
updateAccount.get({ $badges: JSON.stringify(badges), $user: user.toLowerCase() });
return { success: true };
}
return { success: false };
}
async function removeAccount(user, cookie) {
if (await isAdmin(cookie)) {
const updateAccount = accs.query(`DELETE FROM accounts WHERE username = $user`)
updateAccount.get({ $user: user.toLowerCase() });
return true;
}
}
async function banUser(name, reason, token) {
if (await isAdmin(token)) {
const existingAccount = accs.query(`SELECT * FROM accounts WHERE username LIKE $1`)
let userData = existingAccount.get({ $1: name });
if (userData == null) {
return { success: false, reason: "Does not exist" };
}
const updateAccount = accs.query(`UPDATE accounts SET banned = $reason WHERE username = $user`)
updateAccount.get({ $reason: reason, $user: name.toLowerCase() });
return true;
}
}
async function isBanned(user) {
const existingAccount = accs.query(`SELECT * FROM accounts WHERE username LIKE ?1`)
if (existingAccount.get({ $1: user }) == null) {
return false;
}
if (existingAccount.banned) {
return true;
}
return false;
}
export { banUser, removeAccount, verifyCookie, getUserFromCookie, createAccount, resetPassword, loginAccount, addBadge, isBanned, decryptCookie };