implement password reset

This commit is contained in:
Puyodead1
2023-02-24 01:54:10 -05:00
parent dc48a74373
commit 05453ec148
18 changed files with 333 additions and 36 deletions
+2
View File
@@ -29,6 +29,8 @@ export const NO_AUTHORIZATION_ROUTES = [
"/auth/mfa/totp",
"/auth/mfa/webauthn",
"/auth/verify",
"/auth/forgot",
"/auth/reset",
// Routes with a seperate auth system
"/webhooks/",
// Public information endpoints
+92
View File
@@ -0,0 +1,92 @@
import { getIpAdress, route, verifyCaptcha } from "@fosscord/api";
import {
Config,
Email,
FieldErrors,
ForgotPasswordSchema,
User,
} from "@fosscord/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
const router = Router();
router.post(
"/",
route({ body: "ForgotPasswordSchema" }),
async (req: Request, res: Response) => {
const { login, captcha_key } = req.body as ForgotPasswordSchema;
const config = Config.get();
if (
config.password_reset.requireCaptcha &&
config.security.captcha.enabled
) {
const { sitekey, service } = config.security.captcha;
if (!captcha_key) {
return res.status(400).json({
captcha_key: ["captcha-required"],
captcha_sitekey: sitekey,
captcha_service: service,
});
}
const ip = getIpAdress(req);
const verify = await verifyCaptcha(captcha_key, ip);
if (!verify.success) {
return res.status(400).json({
captcha_key: verify["error-codes"],
captcha_sitekey: sitekey,
captcha_service: service,
});
}
}
const user = await User.findOneOrFail({
where: [{ phone: login }, { email: login }],
select: ["username", "id", "disabled", "deleted", "email"],
relations: ["security_keys"],
}).catch(() => {
throw FieldErrors({
login: {
message: req.t("auth:password_reset.EMAIL_DOES_NOT_EXIST"),
code: "EMAIL_DOES_NOT_EXIST",
},
});
});
if (!user.email)
throw FieldErrors({
login: {
message:
"This account does not have an email address associated with it.",
code: "NO_EMAIL",
},
});
if (user.deleted)
return res.status(400).json({
message: "This account is scheduled for deletion.",
code: 20011,
});
if (user.disabled)
return res.status(400).json({
message: req.t("auth:login.ACCOUNT_DISABLED"),
code: 20013,
});
return await Email.sendResetPassword(user, user.email)
.then(() => {
return res.sendStatus(204);
})
.catch((e) => {
console.error(
`Failed to send password reset email to ${user.username}#${user.discriminator}: ${e}`,
);
throw new HTTPError("Failed to send password reset email", 500);
});
},
);
export default router;
+57
View File
@@ -0,0 +1,57 @@
import { route } from "@fosscord/api";
import {
checkToken,
Config,
Email,
FieldErrors,
generateToken,
PasswordResetSchema,
User,
} from "@fosscord/util";
import bcrypt from "bcrypt";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
const router = Router();
router.post(
"/",
route({ body: "PasswordResetSchema" }),
async (req: Request, res: Response) => {
const { password, token } = req.body as PasswordResetSchema;
try {
const { jwtSecret } = Config.get().security;
const { user } = await checkToken(token, jwtSecret, true);
// the salt is saved in the password refer to bcrypt docs
const hash = await bcrypt.hash(password, 12);
const data = {
data: {
hash,
valid_tokens_since: new Date(),
},
};
await User.update({ id: user.id }, data);
// come on, the user has to have an email to reset their password in the first place
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await Email.sendPasswordChanged(user, user.email!);
res.json({ token: await generateToken(user.id) });
} catch (e) {
if ((e as Error).toString() === "Invalid Token")
throw FieldErrors({
password: {
message: req.t("auth:password_reset.INVALID_TOKEN"),
code: "INVALID_TOKEN",
},
});
throw new HTTPError((e as Error).toString(), 400);
}
},
);
export default router;
+1 -1
View File
@@ -36,7 +36,7 @@ router.post(
throw new HTTPError("User does not have an email address", 400);
}
await Email.sendVerificationEmail(user, user.email)
await Email.sendVerifyEmail(user, user.email)
.then(() => {
return res.sendStatus(204);
})