mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 11:15:05 +08:00
FEATURE: redesign the change password page to use javascript and validations
This commit is contained in:
parent
b251d11518
commit
c4e10f2a9d
|
@ -33,6 +33,7 @@
|
|||
//= require ./discourse/models/permission-type
|
||||
//= require ./discourse/models/user-action-group
|
||||
//= require ./discourse/models/category
|
||||
//= require ./discourse/models/input-validation
|
||||
//= require ./discourse/lib/ajax-error
|
||||
//= require ./discourse/lib/search
|
||||
//= require ./discourse/lib/user-search
|
||||
|
|
|
@ -5,8 +5,9 @@ import { setting } from 'discourse/lib/computed';
|
|||
import { on } from 'ember-addons/ember-computed-decorators';
|
||||
import { emailValid } from 'discourse/lib/utilities';
|
||||
import InputValidation from 'discourse/models/input-validation';
|
||||
import PasswordValidation from "discourse/mixins/password-validation";
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
export default Ember.Controller.extend(ModalFunctionality, PasswordValidation, {
|
||||
login: Ember.inject.controller(),
|
||||
|
||||
uniqueUsernameValidation: null,
|
||||
|
@ -16,7 +17,6 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
accountChallenge: 0,
|
||||
formSubmitted: false,
|
||||
rejectedEmails: Em.A([]),
|
||||
rejectedPasswords: Em.A([]),
|
||||
prefilledUsername: null,
|
||||
userFields: null,
|
||||
isDeveloper: false,
|
||||
|
@ -85,10 +85,6 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
});
|
||||
}.property(),
|
||||
|
||||
passwordInstructions: function() {
|
||||
return this.get('isDeveloper') ? I18n.t('user.password.instructions', {count: Discourse.SiteSettings.min_admin_password_length}) : I18n.t('user.password.instructions', {count: Discourse.SiteSettings.min_password_length});
|
||||
}.property('isDeveloper'),
|
||||
|
||||
nameInstructions: function() {
|
||||
return I18n.t(Discourse.SiteSettings.full_name_required ? 'user.name.instructions_required' : 'user.name.instructions');
|
||||
}.property(),
|
||||
|
@ -293,55 +289,6 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
return( this.get('globalNicknameExists') || false );
|
||||
},
|
||||
|
||||
// Validate the password
|
||||
passwordValidation: function() {
|
||||
if (!this.get('passwordRequired')) {
|
||||
return InputValidation.create({ ok: true });
|
||||
}
|
||||
|
||||
// If blank, fail without a reason
|
||||
const password = this.get("accountPassword");
|
||||
if (Ember.isEmpty(this.get('accountPassword'))) {
|
||||
return InputValidation.create({ failed: true });
|
||||
}
|
||||
|
||||
// If too short
|
||||
const passwordLength = this.get('isDeveloper') ? Discourse.SiteSettings.min_admin_password_length : Discourse.SiteSettings.min_password_length;
|
||||
if (password.length < passwordLength) {
|
||||
return InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.password.too_short')
|
||||
});
|
||||
}
|
||||
|
||||
if (this.get('rejectedPasswords').includes(password)) {
|
||||
return InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.password.common')
|
||||
});
|
||||
}
|
||||
|
||||
if (!Ember.isEmpty(this.get('accountUsername')) && this.get('accountPassword') === this.get('accountUsername')) {
|
||||
return InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.password.same_as_username')
|
||||
});
|
||||
}
|
||||
|
||||
if (!Ember.isEmpty(this.get('accountEmail')) && this.get('accountPassword') === this.get('accountEmail')) {
|
||||
return InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.password.same_as_email')
|
||||
});
|
||||
}
|
||||
|
||||
// Looks good!
|
||||
return InputValidation.create({
|
||||
ok: true,
|
||||
reason: I18n.t('user.password.ok')
|
||||
});
|
||||
}.property('accountPassword', 'rejectedPasswords.[]', 'accountUsername', 'accountEmail', 'isDeveloper'),
|
||||
|
||||
@on('init')
|
||||
fetchConfirmationValue() {
|
||||
return ajax('/users/hp.json').then(json => {
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import { default as computed } from 'ember-addons/ember-computed-decorators';
|
||||
import getUrl from 'discourse-common/lib/get-url';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import PasswordValidation from "discourse/mixins/password-validation";
|
||||
|
||||
export default Ember.Controller.extend(PasswordValidation, {
|
||||
isDeveloper: Ember.computed.alias('model.is_developer'),
|
||||
passwordRequired: true,
|
||||
errorMessage: null,
|
||||
successMessage: null,
|
||||
requiresApproval: false,
|
||||
|
||||
@computed()
|
||||
continueButtonText() {
|
||||
return I18n.t('password_reset.continue', {site_name: this.siteSettings.title});
|
||||
},
|
||||
|
||||
lockImageUrl: getUrl('/images/lock.svg'),
|
||||
|
||||
actions: {
|
||||
submit() {
|
||||
ajax({
|
||||
url: `/users/password-reset/${this.get('model.token')}.json`,
|
||||
type: 'PUT',
|
||||
data: {
|
||||
password: this.get('accountPassword')
|
||||
}
|
||||
}).then(result => {
|
||||
if (result.success) {
|
||||
this.set('successMessage', result.message);
|
||||
this.set('redirectTo', result.redirect_to);
|
||||
if (result.requires_approval) {
|
||||
this.set('requiresApproval', true);
|
||||
}
|
||||
} else {
|
||||
if (result.errors && result.errors.password && result.errors.password.length > 0) {
|
||||
this.get('rejectedPasswords').pushObject(this.get('accountPassword'));
|
||||
this.get('rejectedPasswordsMessages').set(this.get('accountPassword'), result.errors.password[0]);
|
||||
}
|
||||
if (result.message) {
|
||||
this.set('errorMessage', result.message);
|
||||
}
|
||||
}
|
||||
}).catch(response => {
|
||||
throw response;
|
||||
});
|
||||
},
|
||||
|
||||
done() {
|
||||
window.location.pathname = this.get('redirectTo') || Discourse.getURL("/");
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,71 @@
|
|||
import InputValidation from 'discourse/models/input-validation';
|
||||
import { default as computed } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Mixin.create({
|
||||
|
||||
rejectedPasswords: null,
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
this.set('rejectedPasswords', []);
|
||||
this.set('rejectedPasswordsMessages', Ember.Map.create());
|
||||
},
|
||||
|
||||
@computed('passwordMinLength')
|
||||
passwordInstructions() {
|
||||
return I18n.t('user.password.instructions', {count: this.get('passwordMinLength')});
|
||||
},
|
||||
|
||||
@computed('isDeveloper')
|
||||
passwordMinLength() {
|
||||
return this.get('isDeveloper') ? this.siteSettings.min_admin_password_length : this.siteSettings.min_password_length;
|
||||
},
|
||||
|
||||
@computed('accountPassword', 'passwordRequired', 'rejectedPasswords.[]', 'accountUsername', 'accountEmail', 'isDeveloper')
|
||||
passwordValidation(password, passwordRequired, rejectedPasswords, accountUsername, accountEmail, isDeveloper) {
|
||||
if (!passwordRequired) {
|
||||
return InputValidation.create({ ok: true });
|
||||
}
|
||||
|
||||
if (rejectedPasswords.includes(password)) {
|
||||
return InputValidation.create({
|
||||
failed: true,
|
||||
reason: this.get('rejectedPasswordsMessages').get(password) || I18n.t('user.password.common')
|
||||
});
|
||||
}
|
||||
|
||||
// If blank, fail without a reason
|
||||
if (Ember.isEmpty(password)) {
|
||||
return InputValidation.create({ failed: true });
|
||||
}
|
||||
|
||||
// If too short
|
||||
const passwordLength = isDeveloper ? this.siteSettings.min_admin_password_length : this.siteSettings.min_password_length;
|
||||
if (password.length < passwordLength) {
|
||||
return InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.password.too_short')
|
||||
});
|
||||
}
|
||||
|
||||
if (!Ember.isEmpty(accountUsername) && password === accountUsername) {
|
||||
return InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.password.same_as_username')
|
||||
});
|
||||
}
|
||||
|
||||
if (!Ember.isEmpty(accountEmail) && password === accountEmail) {
|
||||
return InputValidation.create({
|
||||
failed: true,
|
||||
reason: I18n.t('user.password.same_as_email')
|
||||
});
|
||||
}
|
||||
|
||||
// Looks good!
|
||||
return InputValidation.create({
|
||||
ok: true,
|
||||
reason: I18n.t('user.password.ok')
|
||||
});
|
||||
}
|
||||
});
|
|
@ -63,6 +63,7 @@ export default function() {
|
|||
|
||||
// User routes
|
||||
this.route('users', { resetNamespace: true });
|
||||
this.route('password-reset', { path: '/users/password-reset/:token' });
|
||||
this.route('user', { path: '/users/:username', resetNamespace: true }, function() {
|
||||
this.route('summary');
|
||||
this.route('userActivity', { path: '/activity', resetNamespace: true }, function() {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import PreloadStore from 'preload-store';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
titleToken() {
|
||||
return I18n.t('login.reset_password');
|
||||
},
|
||||
|
||||
model(params) {
|
||||
if (PreloadStore.get("password_reset")) {
|
||||
return PreloadStore.getAndRemove("password_reset").then(json => _.merge(params, json));
|
||||
}
|
||||
},
|
||||
|
||||
afterModel(model) {
|
||||
// confirm token here so email clients who crawl URLs don't invalidate the link
|
||||
if (model) {
|
||||
return ajax({ url: `/users/confirm-email-token/${model.token}.json`, dataType: 'json' });
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
<div class="container password-reset clearfix">
|
||||
<div class="pull-left col-image">
|
||||
<img src={{lockImageUrl}} class="password-reset-img">
|
||||
</div>
|
||||
|
||||
<div class="pull-left col-form">
|
||||
{{#if successMessage}}
|
||||
<p>{{successMessage}}</p>
|
||||
|
||||
{{#if requiresApproval}}
|
||||
<p>{{i18n 'login.not_approved'}}</p>
|
||||
{{else}}
|
||||
<a class="btn" href="{{redirectTo}}" {{action "done"}}>{{continueButtonText}}</a>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<form>
|
||||
|
||||
<h2>{{i18n 'user.change_password.choose'}}</h2>
|
||||
|
||||
<div class="input">
|
||||
{{password-field value=accountPassword type="password" id="new-account-password" capsLockOn=capsLockOn}}
|
||||
{{input-tip validation=passwordValidation}}
|
||||
</div>
|
||||
|
||||
<div class="instructions">
|
||||
<label>{{passwordInstructions}}</label>
|
||||
<div class="caps-lock-warning {{unless capsLockOn 'invisible'}}"><i class="fa fa-exclamation-triangle"></i> {{i18n 'login.caps_lock_warning'}}</div>
|
||||
</div>
|
||||
|
||||
<button class='btn' {{action "submit"}}>{{i18n 'user.change_password.set_password'}}</button>
|
||||
|
||||
{{#if errorMessage}}
|
||||
<br/><br/>
|
||||
<div class='alert alert-error'>{{errorMessage}}</div>
|
||||
{{/if}}
|
||||
|
||||
</form>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
|
@ -64,6 +64,15 @@ $input-width: 220px;
|
|||
}
|
||||
}
|
||||
|
||||
.password-reset {
|
||||
.instructions {
|
||||
label {
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// alternate login / create new account buttons should be de-emphasized
|
||||
|
||||
button#login-link, button#new-account-link
|
||||
|
|
|
@ -43,12 +43,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
tr.instructions {
|
||||
label {
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%));
|
||||
}
|
||||
}
|
||||
|
||||
.tos-agree {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
@ -57,4 +51,24 @@
|
|||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
label {
|
||||
color: dark-light-choose(scale-color($primary, $lightness: 50%), scale-color($secondary, $lightness: 50%));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.password-reset {
|
||||
.col-form {
|
||||
padding-top: 40px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
h2 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.password-reset-img {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,3 +84,32 @@ $input-width: 184px;
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.password-reset {
|
||||
margin-top: 30px;
|
||||
.col-image {
|
||||
padding-top: 12px;
|
||||
}
|
||||
.password-reset-img {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
.col-form {
|
||||
padding-left: 8px;
|
||||
}
|
||||
h2 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.tip {
|
||||
display: block;
|
||||
margin: 6px 0;
|
||||
max-width: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
.discourse-touch .password-reset {
|
||||
.instructions {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -405,8 +405,6 @@ class UsersController < ApplicationController
|
|||
user_id = secure_session["password-#{token}"].to_i
|
||||
@user = User.find(user_id) if user_id > 0
|
||||
end
|
||||
else
|
||||
@invalid_token = true
|
||||
end
|
||||
|
||||
if !@user
|
||||
|
@ -424,12 +422,42 @@ class UsersController < ApplicationController
|
|||
Invite.invalidate_for_email(@user.email) # invite link can't be used to log in anymore
|
||||
secure_session["password-#{token}"] = nil
|
||||
logon_after_password_reset
|
||||
|
||||
return redirect_to(wizard_path) if Wizard.user_requires_completion?(@user)
|
||||
end
|
||||
end
|
||||
end
|
||||
render layout: 'no_ember'
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
if @error
|
||||
render layout: 'no_ember'
|
||||
else
|
||||
store_preloaded("password_reset", MultiJson.dump({ is_developer: UsernameCheckerService.is_developer?(@user.email) }))
|
||||
end
|
||||
return redirect_to(wizard_path) if Wizard.user_requires_completion?(@user)
|
||||
end
|
||||
|
||||
format.json do
|
||||
if request.put?
|
||||
if @error || @user&.errors&.any?
|
||||
render json: {
|
||||
success: false,
|
||||
message: @error,
|
||||
errors: @user&.errors&.to_hash,
|
||||
is_developer: UsernameCheckerService.is_developer?(@user.email)
|
||||
}
|
||||
else
|
||||
render json: {
|
||||
success: true,
|
||||
message: @success,
|
||||
requires_approval: !Guardian.new(@user).can_access_forum?,
|
||||
redirect_to: Wizard.user_requires_completion?(@user) ? wizard_path : nil
|
||||
}
|
||||
end
|
||||
else
|
||||
render json: {is_developer: UsernameCheckerService.is_developer?(@user.email)}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def confirm_email_token
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html lang="<%= SiteSetting.default_locale %>">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><%=SiteSetting.title%></title>
|
||||
<title><%= content_for?(:title) ? yield(:title) + ' - ' + SiteSetting.title : SiteSetting.title %></title>
|
||||
<meta name="description" content="">
|
||||
<%= render partial: "layouts/head" %>
|
||||
<%= render partial: "common/special_font_face" %>
|
||||
|
|
|
@ -11,59 +11,18 @@
|
|||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%if @success%>
|
||||
<p>
|
||||
<%= @success %>
|
||||
<%- if @requires_approval %>
|
||||
<%= t 'login.not_approved' %>
|
||||
<% else %>
|
||||
<br>
|
||||
<br>
|
||||
<a class="btn" href="<%= path "/" %>"><%= t('password_reset.continue', site_name: SiteSetting.title) %></a>
|
||||
<% end %>
|
||||
</p>
|
||||
<% else %>
|
||||
<%if @user.present? %>
|
||||
<h3>
|
||||
<% if @user.has_password? %>
|
||||
<%= t 'password_reset.choose_new' %>
|
||||
<% else %>
|
||||
<%= t 'password_reset.choose' %>
|
||||
<% end %>
|
||||
</h3>
|
||||
|
||||
<%=form_tag({}, method: :put) do %>
|
||||
<p>
|
||||
<span style="display: none;"><input name="username" type="text" value="<%= @user.username %>"></span>
|
||||
<input id="user_password" name="password" size="30" type="password" maxlength="<%= User.max_password_length %>" onkeypress="capsLock(event)">
|
||||
<label><%= t('js.user.password.instructions', count: @user.admin? ? SiteSetting.min_admin_password_length : SiteSetting.min_password_length) %></label>
|
||||
</p>
|
||||
<div id="capsLockWarning" class="caps-lock-warning" style="visibility:hidden"><i class="fa fa-exclamation-triangle"></i> <%= t 'js.login.caps_lock_warning' %></div>
|
||||
<p>
|
||||
<%=submit_tag( @user.has_password? ? t('password_reset.update') : t('password_reset.save'), class: 'btn')%>
|
||||
</p>
|
||||
<%end%>
|
||||
<%end%>
|
||||
<%end%>
|
||||
</div>
|
||||
|
||||
<% content_for :title do %><%=t "password_reset.title" %><% end %>
|
||||
|
||||
<%- content_for(:no_ember_head) do %>
|
||||
<meta name="referrer" content="never">
|
||||
<%= script "ember_jquery" %>
|
||||
<%= render_google_universal_analytics_code %>
|
||||
<%- end %>
|
||||
|
||||
<script type="text/javascript">
|
||||
document.getElementById('user_password').focus();
|
||||
|
||||
function capsLock(e) {
|
||||
kc = e.keyCode?e.keyCode:e.which;
|
||||
sk = e.shiftKey?e.shiftKey:((kc == 16)?true:false);
|
||||
(((kc >= 65 && kc <= 90) && !sk)||((kc >= 97 && kc <= 122) && sk)) ? document.getElementById('capsLockWarning').style.visibility = 'visible' : document.getElementById('capsLockWarning').style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
$.ajax('<%= path "/users/confirm-email-token/#{params[:token]}" %>', {dataType: 'json'});
|
||||
</script>
|
||||
<%- content_for(:head) do %>
|
||||
<meta name="referrer" content="never">
|
||||
<%- end %>
|
||||
|
||||
<%= render_google_analytics_code %>
|
||||
|
|
|
@ -655,6 +655,8 @@ en:
|
|||
error: "(error)"
|
||||
action: "Send Password Reset Email"
|
||||
set_password: "Set Password"
|
||||
choose_new: "Choose a new password"
|
||||
choose: "Choose a password"
|
||||
|
||||
change_about:
|
||||
title: "Change About Me"
|
||||
|
@ -1041,6 +1043,7 @@ en:
|
|||
to_continue: "Please Log In"
|
||||
preferences: "You need to be logged in to change your user preferences."
|
||||
forgot: "I don't recall my account details"
|
||||
not_approved: "Your account hasn't been approved yet. You will be notified by email when you are ready to log in."
|
||||
google:
|
||||
title: "with Google"
|
||||
message: "Authenticating with Google (make sure pop up blockers are not enabled)"
|
||||
|
@ -1063,6 +1066,9 @@ en:
|
|||
title: "with GitHub"
|
||||
message: "Authenticating with GitHub (make sure pop up blockers are not enabled)"
|
||||
|
||||
password_reset:
|
||||
continue: "Continue to %{site_name}"
|
||||
|
||||
emoji_set:
|
||||
apple_international: "Apple/International"
|
||||
google: "Google"
|
||||
|
|
|
@ -580,7 +580,6 @@ en:
|
|||
title: 'Reset Password'
|
||||
success: "You successfully changed your password and are now logged in."
|
||||
success_unapproved: "You successfully changed your password."
|
||||
continue: "Continue to %{site_name}"
|
||||
|
||||
change_email:
|
||||
confirmed: "Your email has been updated."
|
||||
|
|
1
public/images/lock.svg
Normal file
1
public/images/lock.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 47.5 47.5" style="enable-background:new 0 0 47.5 47.5;" xml:space="preserve" version="1.1" id="svg2"><metadata id="metadata8"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/></cc:Work></rdf:RDF></metadata><defs id="defs6"><clipPath id="clipPath16" clipPathUnits="userSpaceOnUse"><path id="path18" d="M 0,38 38,38 38,0 0,0 0,38 Z"/></clipPath></defs><g transform="matrix(1.25,0,0,-1.25,0,47.5)" id="g10"><g id="g12"><g clip-path="url(#clipPath16)" id="g14"><g transform="translate(19,32.9995)" id="g20"><path id="path22" style="fill:#bcbec0;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c -5.523,0 -10,-4.477 -10,-10 l 0,-10 4,0 0,10 c 0,3.313 2.687,6 6,6 3.313,0 6,-2.687 6,-6 l 0,-10 4,0 0,10 C 10,-4.477 5.523,0 0,0"/></g><g transform="translate(31,5)" id="g24"><path id="path26" style="fill:#ffac33;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c 0,-2.209 -1.791,-4 -4,-4 l -16,0 c -2.209,0 -4,1.791 -4,4 l 0,10 c 0,2.209 1.791,4 4,4 l 16,0 c 2.209,0 4,-1.791 4,-4 L 0,0 Z"/></g></g></g></g></svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -218,8 +218,8 @@ describe UsersController do
|
|||
it 'disallows login' do
|
||||
expect(assigns[:error]).to be_present
|
||||
expect(session[:current_user_id]).to be_blank
|
||||
expect(assigns[:invalid_token]).to eq(nil)
|
||||
expect(response).to be_success
|
||||
expect(response).to render_template(layout: 'no_ember')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -231,8 +231,8 @@ describe UsersController do
|
|||
it 'disallows login' do
|
||||
expect(assigns[:error]).to be_present
|
||||
expect(session[:current_user_id]).to be_blank
|
||||
expect(assigns[:invalid_token]).to eq(true)
|
||||
expect(response).to be_success
|
||||
expect(response).to render_template(layout: 'no_ember')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
64
test/javascripts/acceptance/password-reset-test.js.es6
Normal file
64
test/javascripts/acceptance/password-reset-test.js.es6
Normal file
|
@ -0,0 +1,64 @@
|
|||
import { acceptance } from "helpers/qunit-helpers";
|
||||
import PreloadStore from 'preload-store';
|
||||
import { parsePostData } from "helpers/create-pretender";
|
||||
|
||||
acceptance("Password Reset", {
|
||||
setup() {
|
||||
const response = (object) => {
|
||||
return [
|
||||
200,
|
||||
{"Content-Type": "application/json"},
|
||||
object
|
||||
];
|
||||
};
|
||||
|
||||
server.get('/users/confirm-email-token/myvalidtoken.json', () => { //eslint-disable-line
|
||||
return response({success: "OK"});
|
||||
});
|
||||
|
||||
server.put('/users/password-reset/myvalidtoken.json', request => { //eslint-disable-line
|
||||
const body = parsePostData(request.requestBody);
|
||||
if (body.password === "jonesyAlienSlayer") {
|
||||
return response({success: false, errors: {password: ["is the name of your cat"]}});
|
||||
} else {
|
||||
return response({success: "OK", message: I18n.t('password_reset.success')});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("Password Reset Page", () => {
|
||||
PreloadStore.store('password_reset', {is_developer: false});
|
||||
|
||||
visit("/users/password-reset/myvalidtoken");
|
||||
andThen(() => {
|
||||
ok(exists(".password-reset input"), "shows the input");
|
||||
ok(find('.password-reset .instructions').html().trim().includes(`${Discourse.SiteSettings.min_password_length} char`), "shows correct min length");
|
||||
});
|
||||
|
||||
fillIn('.password-reset input', 'perf3ctly5ecur3');
|
||||
andThen(() => {
|
||||
ok(exists(".password-reset .tip.good"), "input looks good");
|
||||
});
|
||||
|
||||
fillIn('.password-reset input', '123');
|
||||
andThen(() => {
|
||||
ok(exists(".password-reset .tip.bad"), "input is not valid");
|
||||
ok(find(".password-reset .tip.bad").html().trim().includes(I18n.t('user.password.too_short')), "password too short");
|
||||
});
|
||||
|
||||
fillIn('.password-reset input', 'jonesyAlienSlayer');
|
||||
click('.password-reset form button');
|
||||
andThen(() => {
|
||||
ok(exists(".password-reset .tip.bad"), "input is not valid");
|
||||
ok(find(".password-reset .tip.bad").html().trim().includes("is the name of your cat"), "server validation error message shows");
|
||||
});
|
||||
|
||||
fillIn('.password-reset input', 'perf3ctly5ecur3');
|
||||
click('.password-reset form button');
|
||||
andThen(() => {
|
||||
ok(!exists(".password-reset form"), "form is gone");
|
||||
ok(exists(".password-reset .btn"), "button is shown");
|
||||
});
|
||||
});
|
||||
|
|
@ -11,7 +11,7 @@ test('basicUsernameValidation', function() {
|
|||
var subject = this.subject;
|
||||
|
||||
var testInvalidUsername = function(username, expectedReason) {
|
||||
var controller = subject();
|
||||
var controller = subject({ siteSettings: Discourse.SiteSettings });
|
||||
controller.set('accountUsername', username);
|
||||
equal(controller.get('basicUsernameValidation.failed'), true, 'username should be invalid: ' + username);
|
||||
equal(controller.get('basicUsernameValidation.reason'), expectedReason, 'username validation reason: ' + username + ', ' + expectedReason);
|
||||
|
@ -21,7 +21,7 @@ test('basicUsernameValidation', function() {
|
|||
testInvalidUsername('x', I18n.t('user.username.too_short'));
|
||||
testInvalidUsername('123456789012345678901', I18n.t('user.username.too_long'));
|
||||
|
||||
var controller = subject();
|
||||
var controller = subject({ siteSettings: Discourse.SiteSettings });
|
||||
controller.set('accountUsername', 'porkchops');
|
||||
controller.set('prefilledUsername', 'porkchops');
|
||||
equal(controller.get('basicUsernameValidation.ok'), true, 'Prefilled username is valid');
|
||||
|
@ -31,7 +31,7 @@ test('basicUsernameValidation', function() {
|
|||
test('passwordValidation', function() {
|
||||
var subject = this.subject;
|
||||
|
||||
var controller = subject();
|
||||
var controller = subject({ siteSettings: Discourse.SiteSettings });
|
||||
controller.set('passwordRequired', true);
|
||||
controller.set('accountEmail', 'pork@chops.com');
|
||||
controller.set('accountUsername', 'porkchops');
|
||||
|
@ -42,7 +42,7 @@ test('passwordValidation', function() {
|
|||
equal(controller.get('passwordValidation.reason'), I18n.t('user.password.ok'), 'Password is valid');
|
||||
|
||||
var testInvalidPassword = function(password, expectedReason) {
|
||||
var c = subject();
|
||||
var c = subject({ siteSettings: Discourse.SiteSettings });
|
||||
c.set('accountPassword', password);
|
||||
equal(c.get('passwordValidation.failed'), true, 'password should be invalid: ' + password);
|
||||
equal(c.get('passwordValidation.reason'), expectedReason, 'password validation reason: ' + password + ', ' + expectedReason);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import storePretender from 'helpers/store-pretender';
|
||||
import fixturePretender from 'helpers/fixture-pretender';
|
||||
|
||||
function parsePostData(query) {
|
||||
export function parsePostData(query) {
|
||||
const result = {};
|
||||
query.split("&").forEach(function(part) {
|
||||
const item = part.split("=");
|
||||
|
@ -18,7 +18,7 @@ function parsePostData(query) {
|
|||
|
||||
});
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
function response(code, obj) {
|
||||
if (typeof code === "object") {
|
||||
|
|
Loading…
Reference in New Issue
Block a user