add set new password form
This commit is contained in:
parent
2c83f8e2d8
commit
4379e6aea5
@ -223,7 +223,7 @@ async function generateApiKey(req, res) {
|
||||
return res.status(201).send({ apikey });
|
||||
}
|
||||
|
||||
async function resetPasswordRequest(req, res) {
|
||||
async function resetPassword(req, res) {
|
||||
const user = await query.user.update(
|
||||
{ email: req.body.email },
|
||||
{
|
||||
@ -239,7 +239,7 @@ async function resetPasswordRequest(req, res) {
|
||||
}
|
||||
|
||||
if (req.isHTML) {
|
||||
res.render("partials/reset_password/form", {
|
||||
res.render("partials/reset_password/request_form", {
|
||||
message: "If the email address exists, a reset password email will be sent to it."
|
||||
});
|
||||
return;
|
||||
@ -250,28 +250,29 @@ async function resetPasswordRequest(req, res) {
|
||||
});
|
||||
}
|
||||
|
||||
async function resetPassword(req, res, next) {
|
||||
const resetPasswordToken = req.params.resetPasswordToken;
|
||||
async function newPassword(req, res) {
|
||||
const { new_password, reset_password_token } = req.body;
|
||||
|
||||
if (resetPasswordToken) {
|
||||
const user = await query.user.update(
|
||||
{
|
||||
reset_password_token: resetPasswordToken,
|
||||
reset_password_expires: [">", utils.dateToUTC(new Date())]
|
||||
},
|
||||
{ reset_password_expires: null, reset_password_token: null }
|
||||
);
|
||||
|
||||
if (user) {
|
||||
const token = utils.signToken(user);
|
||||
utils.deleteCurrentToken(res);
|
||||
utils.setToken(res, token);
|
||||
res.locals.token_verified = true;
|
||||
req.cookies.token = token;
|
||||
const salt = await bcrypt.genSalt(12);
|
||||
const password = await bcrypt.hash(req.body.new_password, salt);
|
||||
|
||||
const user = await query.user.update(
|
||||
{
|
||||
reset_password_token,
|
||||
reset_password_expires: [">", utils.dateToUTC(new Date())]
|
||||
},
|
||||
{
|
||||
reset_password_expires: null,
|
||||
reset_password_token: null,
|
||||
password,
|
||||
}
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
throw new CustomError("Could not set the password. Please try again later.");
|
||||
}
|
||||
|
||||
next();
|
||||
res.render("partials/reset_password/new_password_success");
|
||||
}
|
||||
|
||||
async function changeEmailRequest(req, res) {
|
||||
@ -386,8 +387,8 @@ module.exports = {
|
||||
jwtPage,
|
||||
local,
|
||||
login,
|
||||
newPassword,
|
||||
resetPassword,
|
||||
resetPasswordRequest,
|
||||
signup,
|
||||
verify,
|
||||
}
|
||||
|
@ -37,6 +37,11 @@ async function user(req, res, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
function newPassword(req, res, next) {
|
||||
res.locals.reset_password_token = req.body.reset_password_token;
|
||||
next();
|
||||
}
|
||||
|
||||
function createLink(req, res, next) {
|
||||
res.locals.show_advanced = !!req.body.show_advanced;
|
||||
next();
|
||||
@ -73,6 +78,7 @@ module.exports = {
|
||||
createLink,
|
||||
editLink,
|
||||
isHTML,
|
||||
newPassword,
|
||||
noLayout,
|
||||
protected,
|
||||
user,
|
||||
|
@ -2,6 +2,12 @@ const query = require("../queries");
|
||||
const utils = require("../utils");
|
||||
const env = require("../env");
|
||||
|
||||
/**
|
||||
*
|
||||
* PAGES
|
||||
*
|
||||
**/
|
||||
|
||||
async function homepage(req, res) {
|
||||
// redirect to custom domain homepage if it is set by user
|
||||
const host = utils.removeWww(req.headers.host);
|
||||
@ -100,9 +106,25 @@ async function resetPassword(req, res) {
|
||||
});
|
||||
}
|
||||
|
||||
async function resetPasswordResult(req, res) {
|
||||
res.render("reset_password_result", {
|
||||
async function resetPasswordSetNewPassword(req, res) {
|
||||
const reset_password_token = req.params.resetPasswordToken;
|
||||
|
||||
if (reset_password_token) {
|
||||
const user = await query.user.find(
|
||||
{
|
||||
reset_password_token,
|
||||
reset_password_expires: [">", utils.dateToUTC(new Date())]
|
||||
}
|
||||
);
|
||||
if (user) {
|
||||
res.locals.token_verified = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
res.render("reset_password_set_new_password", {
|
||||
title: "Reset password",
|
||||
...(res.locals.token_verified && { reset_password_token }),
|
||||
});
|
||||
}
|
||||
|
||||
@ -124,6 +146,12 @@ async function terms(req, res) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* PARTIALS
|
||||
*
|
||||
**/
|
||||
|
||||
async function confirmLinkDelete(req, res) {
|
||||
const link = await query.link.find({
|
||||
uuid: req.query.id,
|
||||
@ -311,7 +339,7 @@ module.exports = {
|
||||
notFound,
|
||||
report,
|
||||
resetPassword,
|
||||
resetPasswordResult,
|
||||
resetPasswordSetNewPassword,
|
||||
settings,
|
||||
stats,
|
||||
terms,
|
||||
|
@ -469,6 +469,21 @@ const resetPassword = [
|
||||
.isEmail()
|
||||
];
|
||||
|
||||
const newPassword = [
|
||||
body("reset_password_token", "Reset password token is invalid.")
|
||||
.exists({ checkFalsy: true, checkNull: true })
|
||||
.isLength({ min: 36, max: 36 }),
|
||||
body("new_password", "Password is not valid.")
|
||||
.exists({ checkFalsy: true, checkNull: true })
|
||||
.isLength({ min: 8, max: 64 })
|
||||
.withMessage("Password length must be between 8 and 64."),
|
||||
body("repeat_password", "Password is not valid.")
|
||||
.custom((repeat_password, { req }) => {
|
||||
return repeat_password === req.body.new_password;
|
||||
})
|
||||
.withMessage("Passwords don't match."),
|
||||
];
|
||||
|
||||
const deleteUser = [
|
||||
body("password", "Password is not valid.")
|
||||
.exists({ checkFalsy: true, checkNull: true })
|
||||
@ -607,6 +622,7 @@ module.exports = {
|
||||
getStats,
|
||||
login,
|
||||
malware,
|
||||
newPassword,
|
||||
redirectProtected,
|
||||
removeDomain,
|
||||
removeDomainAdmin,
|
||||
|
@ -72,12 +72,22 @@ router.post(
|
||||
|
||||
router.post(
|
||||
"/reset-password",
|
||||
locals.viewTemplate("partials/reset_password/form"),
|
||||
locals.viewTemplate("partials/reset_password/request_form"),
|
||||
auth.featureAccess([env.MAIL_ENABLED]),
|
||||
validators.resetPassword,
|
||||
asyncHandler(helpers.verify),
|
||||
helpers.rateLimit({ window: 60, limit: 3 }),
|
||||
asyncHandler(auth.resetPasswordRequest)
|
||||
asyncHandler(auth.resetPassword)
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/new-password",
|
||||
locals.viewTemplate("partials/reset_password/new_password_form"),
|
||||
locals.newPassword,
|
||||
validators.newPassword,
|
||||
asyncHandler(helpers.verify),
|
||||
helpers.rateLimit({ window: 60, limit: 5 }),
|
||||
asyncHandler(auth.newPassword)
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
|
@ -86,10 +86,9 @@ router.get(
|
||||
|
||||
router.get(
|
||||
"/reset-password/:resetPasswordToken",
|
||||
asyncHandler(auth.resetPassword),
|
||||
asyncHandler(auth.jwtLoosePage),
|
||||
asyncHandler(locals.user),
|
||||
asyncHandler(renders.resetPasswordResult)
|
||||
asyncHandler(renders.resetPasswordSetNewPassword)
|
||||
);
|
||||
|
||||
router.get(
|
||||
|
42
server/views/partials/reset_password/new_password_form.hbs
Normal file
42
server/views/partials/reset_password/new_password_form.hbs
Normal file
@ -0,0 +1,42 @@
|
||||
<form
|
||||
id="new-password-form"
|
||||
class="htmx-spinner"
|
||||
hx-post="/api/auth/new-password"
|
||||
hx-vals='{"reset_password_token":"{{reset_password_token}}"}'
|
||||
hx-sync="this:abort"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<label class="{{#if errors.new_password}}error{{/if}}">
|
||||
New password:
|
||||
<input
|
||||
id="new_password"
|
||||
name="new_password"
|
||||
type="password"
|
||||
placeholder="New password..."
|
||||
hx-preserve="true"
|
||||
required
|
||||
/>
|
||||
{{#if errors.new_password}}<p class="error">{{errors.new_password}}</p>{{/if}}
|
||||
</label>
|
||||
<label class="{{#if errors.repeat_password}}error{{/if}}">
|
||||
Repeat password:
|
||||
<input
|
||||
id="repeat_password"
|
||||
name="repeat_password"
|
||||
type="password"
|
||||
placeholder="Repeat password..."
|
||||
hx-preserve="true"
|
||||
required
|
||||
/>
|
||||
{{#if errors.repeat_password}}<p class="error">{{errors.repeat_password}}</p>{{/if}}
|
||||
</label>
|
||||
<button type="submit" class="primary">
|
||||
<span>{{> icons/spinner}}</span>
|
||||
Set password
|
||||
</button>
|
||||
{{#unless errors}}
|
||||
{{#if error}}
|
||||
<p class="error">{{error}}</p>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
</form>
|
@ -0,0 +1,5 @@
|
||||
<p class="success">
|
||||
Your password is updated successfully.
|
||||
You can now log in with your new password.
|
||||
</p>
|
||||
<a href="/login" title="Log in">Log in →</a>
|
@ -1,5 +1,6 @@
|
||||
<form
|
||||
id="reset-password-form"
|
||||
class="htmx-spinner"
|
||||
hx-post="/api/auth/reset-password"
|
||||
hx-sync="this:abort"
|
||||
hx-swap="outerHTML"
|
@ -7,6 +7,6 @@
|
||||
If you forgot you password you can use the form below to get a reset
|
||||
password link.
|
||||
</p>
|
||||
{{> reset_password/form}}
|
||||
{{> reset_password/request_form}}
|
||||
</section>
|
||||
{{> footer}}
|
@ -1,9 +1,14 @@
|
||||
{{> header}}
|
||||
<section id="reset-password-token" class="section-container verify-page">
|
||||
<section
|
||||
id="new-password"
|
||||
class="section-container {{#unless token_verified}}verify-page{{/unless}}"
|
||||
>
|
||||
{{#if token_verified}}
|
||||
<h2 hx-get="/settings" hx-trigger="load delay:1s" hx-target="body" hx-push-url="/settings">
|
||||
Welcome back. Change your password from the settings page. Redirecting...
|
||||
<h2>
|
||||
Reset password.
|
||||
</h2>
|
||||
<p>Set your new password.</p>
|
||||
{{> reset_password/new_password_form}}
|
||||
{{else}}
|
||||
<h2>
|
||||
{{> icons/x}}
|
@ -985,6 +985,10 @@ table .tab a:not(.active):hover {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.htmx-spinner .spinner { display: none; }
|
||||
.htmx-spinner.htmx-request button svg { display: none; }
|
||||
.htmx-spinner.htmx-request .spinner { display: block; }
|
||||
|
||||
/* LOGIN & SIGNUP */
|
||||
|
||||
form#login-signup {
|
||||
@ -2192,15 +2196,42 @@ svg.map path.active { stroke: hsl(261, 46%, 50%); stroke-width: 1.5; }
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
margin-top: 2rem;
|
||||
|
||||
}
|
||||
|
||||
#reset-password form label { flex: 0 0 280px; }
|
||||
#reset-password form label input { width: 100%; }
|
||||
#reset-password form button { margin: 0 0 0.2rem 1rem; }
|
||||
#reset-password .spinner { display: none; }
|
||||
#reset-password .htmx-request svg { display: none; }
|
||||
#reset-password .htmx-request .spinner { display: block; }
|
||||
|
||||
#new-password h2 { margin-bottom: 0.5rem; }
|
||||
#new-password p { margin-bottom: 1.5rem; }
|
||||
|
||||
#new-password-form label { margin-bottom: 1.5rem; }
|
||||
#new-password-form label input { width: 280px; }
|
||||
|
||||
#new-password form {
|
||||
width: 420px;
|
||||
max-width: 100%;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
padding: 0 16px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#new-password form label { margin-bottom: 2rem; }
|
||||
|
||||
#new-password form input {
|
||||
width: 100%;
|
||||
height: 72px;
|
||||
margin-top: 1rem;
|
||||
padding: 0 3rem;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#new-password form button {
|
||||
height: 56px;
|
||||
padding: 0 1rem 2px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* VERIFY USER */
|
||||
/* VERIFY CHANGE EMAIL */
|
||||
@ -2425,6 +2456,15 @@ svg.map path.active { stroke: hsl(261, 46%, 50%); stroke-width: 1.5; }
|
||||
#reset-password form label { flex-basis: 0; width: 280px; }
|
||||
#reset-password form button { margin: 0.75rem 0 0.2rem 0; }
|
||||
|
||||
#new-password form label { margin-bottom: 1.5rem; }
|
||||
#new-password form input {
|
||||
height: 58px;
|
||||
margin-top: 0.75rem;
|
||||
padding: 0 2rem;
|
||||
font-size: 15px;
|
||||
}
|
||||
#new-password form button { height: 44px; }
|
||||
|
||||
.verify-page h2,
|
||||
.verify-page h3 { display: flex; flex-direction: column; }
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user