aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2023-12-07 22:35:02 -0800
committerAdam Soltys <[email protected]>2023-12-07 22:35:05 -0800
commit602f509706493585e35ed0cdce3e47debd312445 (patch)
tree29926a1daef4c3280f4137b67e8dc0c269d8aced
parentef48a3815a5235e6838bf36b34b56354311824d7 (diff)
downloadcoinos-server-602f509706493585e35ed0cdce3e47debd312445.tar.gz
coinos-server-602f509706493585e35ed0cdce3e47debd312445.zip
password reset
-rw-r--r--index.js3
-rw-r--r--lib/mail.js4
-rw-r--r--routes/users.js71
-rw-r--r--templates/payments/reset.html25
4 files changed, 84 insertions, 19 deletions
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 @@
+<style>
+ @import url("https://fonts.googleapis.com/css2?family=Public+Sans:wght@400;700&display=swap");
+
+ body {
+ font-family: "Public Sans", sans-serif;
+ }
+</style>
+
+<html>
+ <body>
+ <div style="margin-bottom: 10px">Hello {{username}},</div>
+
+ <div style="margin-bottom: 10px">
+ 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:
+ </div>
+
+ <div style="margin-bottom: 10px">
+ <a href="{{link}}">{{link}}</a>
+ </div>
+
+ <div style="margin-bottom: 10px">Thanks!</div>
+
+ <div>The Coinos Team</div>
+ </body>
+</html>