From 602f509706493585e35ed0cdce3e47debd312445 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 7 Dec 2023 22:35:02 -0800 Subject: password reset --- index.js | 3 +- lib/mail.js | 4 ++- routes/users.js | 71 ++++++++++++++++++++++++++++++++----------- templates/payments/reset.html | 25 +++++++++++++++ 4 files changed, 84 insertions(+), 19 deletions(-) create mode 100644 templates/payments/reset.html diff --git a/index.js b/index.js index 112982ec..a6881cc5 100644 --- a/index.js +++ b/index.js @@ -81,13 +81,14 @@ app.post("/register", users.create); app.post("/disable2fa", auth, users.disable2fa); app.post("/2fa", auth, users.enable2fa); app.post("/user", auth, users.update); -app.post("/reset", auth, users.reset); +app.post("/reset", optional, users.reset); app.post("/upload/:type", users.upload); app.get("/users/delete/:username", users.del); app.post("/acl", users.acl); app.post("/superuser", users.superuser); app.get("/verify/:code", users.verify); app.post("/request", auth, users.request); +app.post("/forgot", users.forgot); app.post("/login", users.login); app.post("/subscribe", auth, users.subscribe); diff --git a/lib/mail.js b/lib/mail.js index d5586aa5..bda50bec 100644 --- a/lib/mail.js +++ b/lib/mail.js @@ -10,7 +10,8 @@ let Charset = "UTF-8"; export let templates = { verifyEmail: "templates/payments/verify.html", - paymentReceived: "templates/payments/received.html" + paymentReceived: "templates/payments/received.html", + passwordReset: "templates/payments/reset.html" }; export let mail = async (user, subject, template, params) => { @@ -38,6 +39,7 @@ export let mail = async (user, subject, template, params) => { }) ); } catch (e) { + console.log(e); err(e.message); } }; diff --git a/routes/users.js b/routes/users.js index 909cf186..68c8804a 100644 --- a/routes/users.js +++ b/routes/users.js @@ -9,7 +9,7 @@ import { wait, bail, fail, - getUser, + getUser } from "$lib/utils"; import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; @@ -99,7 +99,7 @@ export default { username: key, display: key.substr(0, 6), pubkey: key, - anon: true, + anon: true }; } @@ -117,7 +117,7 @@ export default { "pubkey", "display", "prompt", - "id", + "id" ]; if (user.pubkey) @@ -142,7 +142,7 @@ export default { pubkey, password, username, - salt, + salt }; user = await register(user, ip, false); @@ -195,7 +195,7 @@ export default { newpin, username, shopifyToken, - shopifyStore, + shopifyStore } = body; if (user.pin && !(pin === user.pin)) throw new Error("Pin required"); @@ -238,7 +238,7 @@ export default { "tokens", "twofa", "shopifyToken", - "shopifyStore", + "shopifyStore" ]; for (let a of attributes) { @@ -402,20 +402,31 @@ export default { res.send(req.headers); }, - async reset({ body: { username, password }, user: { admin } }, res) { - if (!admin) fail("unauthorized"); - try { - let id = await g(`user:${username.toLowerCase().replace(/\s/g, "")}`); - let user = await g(`user:${id}`); + async reset({ body: { code, username, password }, user: u }, res) { + let admin = u && u.admin; + let id, user; + if (admin) { + id = await g(`user:${username.toLowerCase().replace(/\s/g, "")}`); + user = await g(`user:${id}`); + } else { + id = await g(`reset:${code}`); + user = await g(`user:${id}`); + } + + if (!user) fail("user not found"); + + try { user.pin = null; user.pubkey = null; user.cipher = null; user.salt = null; user.password = await bcrypt.hash(password, 1); + await s(`user:${id}`, user); + await db.del(`reset:${code}`); - res.send({}); + res.send(pick(user, whitelist)); } catch (e) { bail(res, e.message); } @@ -436,11 +447,13 @@ export default { else bail(res, "unauthorized"); }, - async request({ body: { id, email }}, res) { + async request({ body: { id, email } }, res) { try { - let code = v4(); let user = await g(`user:${id}`); + if (!user) fail("user not found"); + if (await g(`email:${email.toLowerCase()}`)) fail("email already in use"); + let { username } = user; if (email !== user.email) { @@ -449,8 +462,8 @@ export default { user.email = email; } - - await s(`verify:${code}`, id); + let code = v4(); + await s(`verify:${code}`, { id, email }); let link = `${process.env.URL}/${username}/verify/${code}`; let subject = "Email Verification"; @@ -467,15 +480,39 @@ export default { async verify({ params: { code } }, res) { try { - let id = await g(`verify:${code}`); + let { id, email } = await g(`verify:${code}`); if (!id) fail(res, "verification failed"); let user = await g(`user:${id}`); + user.email = email; user.verified = true; await s(`user:${id}`, user); + await s(`email:${email.toLowerCase()}`, id); res.send(pick(user, whitelist)); } catch (e) { bail(res, e.message); } + }, + + async forgot({ body: { email } }, res) { + try { + let uid = await g(`email:${email.toLowerCase()}`); + let user = await g(`user:${uid}`); + + if (user) { + let code = v4(); + let link = `${process.env.URL}/reset/${code}`; + await s(`reset:${code}`, uid); + + await mail(user, "Password reset", templates.passwordReset, { + ...user, + link + }); + } + + res.send({}); + } catch (e) { + bail(res, e.message); + } } }; diff --git a/templates/payments/reset.html b/templates/payments/reset.html new file mode 100644 index 00000000..2cef6a41 --- /dev/null +++ b/templates/payments/reset.html @@ -0,0 +1,25 @@ + + + + +
Hello {{username}},
+ +
+ Somebody requested to reset the password for your account. If it wasn't you, please ignore this email. Otherwise, please visit this link to reset your password: +
+ +
+ {{link}} +
+ +
Thanks!
+ +
The Coinos Team
+ + -- cgit v1.2.3