1
0
Fork 0
mirror of https://github.com/TryGhost/Ghost-Admin.git synced 2023-12-14 02:33:04 +01:00

Improved Password Reset Tool

Closes #1471

- add api and User model methods for generating and validating tokens
- add routes and handlers for reset password pages
- add client styles and views for reset password form
- some basic integration tests for User model methods
This commit is contained in:
Jacob Gable 2013-11-21 21:17:38 -06:00
parent f510ab8310
commit 41eff2c4db
4 changed files with 118 additions and 17 deletions

View file

@ -15,7 +15,8 @@
.ghost-login,
.ghost-signup,
.ghost-forgotten {
.ghost-forgotten,
.ghost-reset {
color: $midgrey;
background: $darkgrey;
@ -35,7 +36,8 @@
.login-box,
.signup-box,
.forgotten-box {
.forgotten-box,
.reset-box {
max-width: 530px;
height: 90%;
margin: 0 auto;
@ -177,10 +179,10 @@
/* =============================================================================
2. Signup
2. Signup and Reset
============================================================================= */
#signup {
#signup, #reset {
@include box-sizing(border-box);
max-width: 280px;
color: lighten($midgrey, 15%);

View file

@ -13,7 +13,8 @@
'register/' : 'register',
'signup/' : 'signup',
'signin/' : 'login',
'forgotten/' : 'forgotten'
'forgotten/' : 'forgotten',
'reset/:token/' : 'reset'
},
signup: function () {
@ -28,6 +29,10 @@
Ghost.currentView = new Ghost.Views.Forgotten({ el: '.js-forgotten-box' });
},
reset: function (token) {
Ghost.currentView = new Ghost.Views.ResetPassword({ el: '.js-reset-box', token: token });
},
blog: function () {
var posts = new Ghost.Collections.Posts();
NProgress.start();

9
tpl/reset.hbs Normal file
View file

@ -0,0 +1,9 @@
<form id="reset" method="post" novalidate="novalidate">
<div class="password-wrap">
<input class="password" type="password" placeholder="Password" name="newpassword" />
</div>
<div class="password-wrap">
<input class="password" type="password" placeholder="Confirm Password" name="ne2password" />
</div>
<button class="button-save" type="submit">Reset Password</button>
</form>

View file

@ -6,9 +6,6 @@
initialize: function () {
this.render();
$(".js-login-box").css({"opacity": 0}).animate({"opacity": 1}, 500, function () {
$("[name='email']").focus();
});
},
templateName: "login",
@ -17,6 +14,13 @@
'submit #login': 'submitHandler'
},
afterRender: function () {
var self = this;
this.$el.css({"opacity": 0}).animate({"opacity": 1}, 500, function () {
self.$("[name='email']").focus();
});
},
submitHandler: function (event) {
event.preventDefault();
var email = this.$el.find('.email').val(),
@ -61,9 +65,6 @@
initialize: function () {
this.render();
$(".js-signup-box").css({"opacity": 0}).animate({"opacity": 1}, 500, function () {
$("[name='name']").focus();
});
},
templateName: "signup",
@ -72,11 +73,21 @@
'submit #signup': 'submitHandler'
},
afterRender: function () {
var self = this;
this.$el
.css({"opacity": 0})
.animate({"opacity": 1}, 500, function () {
self.$("[name='name']").focus();
});
},
submitHandler: function (event) {
event.preventDefault();
var name = this.$el.find('.name').val(),
email = this.$el.find('.email').val(),
password = this.$el.find('.password').val();
var name = this.$('.name').val(),
email = this.$('.email').val(),
password = this.$('.password').val();
// This is needed due to how error handling is done. If this is not here, there will not be a time
// when there is no error.
@ -119,9 +130,6 @@
initialize: function () {
this.render();
$(".js-forgotten-box").css({"opacity": 0}).animate({"opacity": 1}, 500, function () {
$("[name='email']").focus();
});
},
templateName: "forgotten",
@ -130,6 +138,13 @@
'submit #forgotten': 'submitHandler'
},
afterRender: function () {
var self = this;
this.$el.css({"opacity": 0}).animate({"opacity": 1}, 500, function () {
self.$("[name='email']").focus();
});
},
submitHandler: function (event) {
event.preventDefault();
@ -166,4 +181,74 @@
}
}
});
Ghost.Views.ResetPassword = Ghost.View.extend({
templateName: 'reset',
events: {
'submit #reset': 'submitHandler'
},
initialize: function (attrs) {
attrs = attrs || {};
this.token = attrs.token;
this.render();
},
afterRender: function () {
var self = this;
this.$el.css({"opacity": 0}).animate({"opacity": 1}, 500, function () {
self.$("[name='newpassword']").focus();
});
},
submitHandler: function (ev) {
ev.preventDefault();
var self = this,
newPassword = this.$('input[name="newpassword"]').val(),
ne2Password = this.$('input[name="ne2password"]').val();
if (newPassword !== ne2Password) {
Ghost.notifications.addItem({
type: 'error',
message: "Your passwords do not match.",
status: 'passive'
});
return;
}
this.$('input, button').prop('disabled', true);
$.ajax({
url: '/ghost/reset/' + this.token + '/',
type: 'POST',
headers: {
'X-CSRF-Token': $("meta[name='csrf-param']").attr('content')
},
data: {
newpassword: newPassword,
ne2password: ne2Password
},
success: function (msg) {
window.location.href = msg.redirect;
},
error: function (xhr) {
self.$('input, button').prop('disabled', false);
Ghost.notifications.clearEverything();
Ghost.notifications.addItem({
type: 'error',
message: Ghost.Views.Utils.getRequestErrorMessage(xhr),
status: 'passive'
});
}
});
return false;
}
});
}());