Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add password reset #233

Merged
merged 4 commits into from
May 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions src/app/app.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,18 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

import HomeTpl from './home/home.pug';
import Error401Tpl from './common/errors/error.401.pug';
import Error403Tpl from './common/errors/error.403.pug';
import Error404Tpl from './common/errors/error.404.pug';
import ConfigService from './common/config/config.service';

import { GAME_LIST, GAME_DETAILS } from './games/game.states';
import {GAME_DETAILS, GAME_LIST} from './games/game.states';
import {GAME_ADMIN_ADD, GAME_ADMIN_EDIT, GAME_ORIGINAL_ADD} from './games/admin/game.admin.states';
import { RELEASE_LIST, RELEASE_DETAILS } from './releases/release.states';
import { VPT_PREVIEW } from './releases/details/vpt/vpt.states';
import { RELEASE_ADD, RELEASE_VERSION_ADD, RELEASE_EDIT } from './releases/admin/release.admin.states';
import { BACKGLASS_ADD } from './backglasses/admin/backglass.admin.states';
import { AUTH_CALLBACK, CONFIRM_TOKEN } from './common/common.states';
import {RELEASE_DETAILS, RELEASE_LIST} from './releases/release.states';
import {VPT_PREVIEW} from './releases/details/vpt/vpt.states';
import {RELEASE_ADD, RELEASE_EDIT, RELEASE_VERSION_ADD} from './releases/admin/release.admin.states';
import {BACKGLASS_ADD} from './backglasses/admin/backglass.admin.states';
import {AUTH_CALLBACK, CONFIRM_TOKEN, HOME, RESET_PASSWORD} from './common/common.states';
import {
PROFILE_CONTENT,
PROFILE_DOWNLOADS,
Expand All @@ -38,8 +37,8 @@ import {
PROFILE_SETTINGS,
PROFILE_STATS
} from './profile/profile.states';
import { ADMIN_BUILDS, ADMIN_USERS, ADMIN_UPLOADS, ADMIN_TOKENS } from './admin/admin.states';
import { CONTENT_ABOUT, CONTENT_FAQ, CONTENT_LEGAL, CONTENT_PRIVACY, CONTENT_RULES } from './content/content.states';
import {ADMIN_BUILDS, ADMIN_TOKENS, ADMIN_UPLOADS, ADMIN_USERS} from './admin/admin.states';
import {CONTENT_ABOUT, CONTENT_FAQ, CONTENT_LEGAL, CONTENT_PRIVACY, CONTENT_RULES} from './content/content.states';

/**
* @param $urlRouterProvider
Expand All @@ -52,7 +51,7 @@ import { CONTENT_ABOUT, CONTENT_FAQ, CONTENT_LEGAL, CONTENT_PRIVACY, CONTENT_RUL
export default function routes($urlRouterProvider, $locationProvider, $stateProvider, $sceDelegateProvider, Config) {

// home
$stateProvider.state('home', { url: '/', templateUrl: HomeTpl, controller: 'HomeCtrl', controllerAs: 'vm' });
$stateProvider.state(HOME);

// games (lazy loaded)
$stateProvider.state(GAME_LIST);
Expand All @@ -77,6 +76,7 @@ export default function routes($urlRouterProvider, $locationProvider, $stateProv
// auth
$stateProvider.state(AUTH_CALLBACK);
$stateProvider.state(CONFIRM_TOKEN);
$stateProvider.state(RESET_PASSWORD);

// profile (lazy loaded)
$stateProvider.state(PROFILE_ROOT);
Expand Down
40 changes: 32 additions & 8 deletions src/app/common/auth/login.modal.ctrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,30 @@ export default class LoginModalCtrl {
* @param {Config} Config
* @param {ConfigService} ConfigService
* @param {LoginService} LoginService
* @param {ModalService} ModalService
* @param {App} App
* @param {ApiHelper} ApiHelper
* @param {AuthService} AuthService
* @param AuthResource
* @param ProfileResource
* @param UserResource
* @param {{ postLogin:{action:string, params:any}, headMessage:string, topMessage:string, message:string }} opts
* @ngInject
*/
constructor($window, $uibModalInstance, $localStorage, Config, ConfigService, LoginService,
App, ApiHelper, AuthService, AuthResource, UserResource, opts) {
constructor($window, $uibModalInstance, $localStorage, Config, ConfigService, LoginService, ModalService,
App, ApiHelper, AuthService, AuthResource, ProfileResource, UserResource, opts) {

opts = opts || {};
$localStorage.rememberMe = isUndefined($localStorage.rememberMe) ? true : $localStorage.rememberMe;

this.Config = Config;
this.ConfigService = ConfigService;
this.LoginService = LoginService;
this.ModalService = ModalService;
this.AuthService = AuthService;
this.ApiHelper = ApiHelper;
this.AuthResource = AuthResource;
this.ProfileResource = ProfileResource;
this.UserResource = UserResource;
this.$window = $window;
this.$uibModalInstance = $uibModalInstance;
Expand All @@ -62,7 +66,7 @@ export default class LoginModalCtrl {

/** @type {{ username:string, password:string}} */
this.userPass = {};
this.registering = false;
this.mode = 'login';
this.email = '';
this.message = opts.message || null;
this.error = null;
Expand Down Expand Up @@ -121,10 +125,30 @@ export default class LoginModalCtrl {
this.email = '';
this.message = 'Registration successful.';
this.message2 = 'You will get an email shortly.<br>Once you have confirmed it, you\'re good to go!';
this.registering = !this.registering;
this.mode = 'login';
}, this.ApiHelper.handleErrors(this));
}

/**
* Requests a password reset.
*/
reset() {
this.ProfileResource.requestResetPassword({ email: this.email }, result => {
this.errors = {};
this.error = null;
this.userPass = {};
this.email = '';
this.message = result.message;
this.mode = 'login';
this.$uibModalInstance.dismiss();
this.ModalService.info({
title: 'Password Reset',
subtitle: 'Success!',
message: result.message
});
}, this.ApiHelper.handleErrors(this, { hideGlobalValidationError: true }));
}

setRedirect() {
this.AuthService.setPostLoginRedirect();
if (this.opts.postLogin) {
Expand All @@ -133,13 +157,13 @@ export default class LoginModalCtrl {
}

/**
* Toggles between register and login view.
* Toggles between reset, register and login view.
*/
swap() {
this.registering = !this.registering;
swap(mode) {
this.mode = mode ? mode : this.mode === 'login' ? 'register' : 'login';
this.message = null;
this.message2 = null;
this.errors = {};
this.error = null;
}
}
}
27 changes: 20 additions & 7 deletions src/app/common/auth/login.modal.pug
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ include ../../template/_mixins
| {{ vm.topMessage }}

//- LOGIN
div(ng-hide="vm.registering")
div(ng-show="vm.mode === 'login'")
h3.h--shift-up(ng-hide="vm.LoginService.loginParams.localOnly") Or, with your account:
h3.h--shift-up(ng-show="vm.LoginService.loginParams.localOnly") Login with your account:
form.form-horizontal(role='form')
Expand All @@ -42,9 +42,10 @@ include ../../template/_mixins
label.col-sm-4.control-label Password
.col-sm-6
input#login-password.form-control(ng-model="vm.userPass.password", type="password", ng-keyup="$event.keyCode === 13 && vm.login()", autocomplete="current-password")
i.a.pull-right(ng-click="vm.swap('reset')") Forgot password?

//- REGISTER
div(ng-show="vm.registering")
div(ng-show="vm.mode === 'register'")
h3.h--shift-up(ng-hide="vm.LoginService.loginParams.localOnly") Or, register with your Email:
h3.h--shift-up(ng-show="vm.LoginService.loginParams.localOnly") Register with your Email:
form.form-horizontal(role="form")
Expand All @@ -61,6 +62,17 @@ include ../../template/_mixins
.col-sm-6
input#register-password.form-control(ng-model="vm.userPass.password", type="password", autocomplete="new-password")

//- RESET
div(ng-show="vm.mode === 'reset'")
h3.h--shift-up(ng-hide="vm.LoginService.loginParams.localOnly") Oh. So you forgot your password.
h3.h--shift-up(ng-show="vm.LoginService.loginParams.localOnly") Duh. So you forgot your password.
p.padder-top.padder-bottom.text-center Let's fix that. Enter your email address and we'll send you a link where you can create a new one.
form.form-horizontal(role="form")
.form-group(ng-class="{ error: vm.errors.email }")
label.col-sm-4.control-label Email
.col-sm-6
input#reset-email.form-control(ng-model="vm.email", ng-required, autocomplete="email")

//- ERRORS
div.alert.alert-danger(ng-show="vm.errors.email")
+icon('warning').space-right.shift-up
Expand All @@ -82,17 +94,18 @@ include ../../template/_mixins

.modal-footer

.form-group(ng-hide="vm.registering")
.form-group(ng-show="vm.mode === 'login'")
.col-sm-4
input#rememberMe.checkbox--check(ng-model="vm.$localStorage.rememberMe", type="checkbox")
label.checkbox--standalone.nested(for="rememberMe")
label.col-sm-8.control-value(for="rememberMe") Remember Me

.clearfix
hr.hr--light.padder-top-4x(ng-hide="vm.registering")
hr.hr--light.padder-top-4x(ng-show="vm.mode === 'login'")
.clearfix

h3#login-toggle.h--smaller.h--no-margin.a.a--lighter.pull-left(ng-click="vm.swap()") {{ vm.registering ? "Login" : "Register" }}
h3#login-toggle.h--smaller.h--no-margin.a.a--lighter.pull-left(ng-hide="vm.mode === 'reset'", ng-click="vm.swap()") {{ vm.mode === 'login' ? 'Register' : 'Login' }}
button#dismiss-button.btn.btn-default(ng-click="$close()") Cancel
button#login-submit.btn.btn--secondary(ng-hide="vm.registering", ng-click="vm.login()") Login
button#register-submit.btn.btn--secondary(ng-show="vm.registering", ng-click="vm.register()") Register
button#login-submit.btn.btn--secondary(ng-show="vm.mode === 'login'", ng-click="vm.login()") Login
button#register-submit.btn.btn--secondary(ng-show="vm.mode === 'register'", ng-click="vm.register()") Register
button#reset-submit.btn.btn--secondary(ng-show="vm.mode === 'reset'", ng-click="vm.reset()") Reset
57 changes: 57 additions & 0 deletions src/app/common/auth/reset.password.modal.ctrl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* VPDB - Virtual Pinball Database
* Copyright (C) 2019 freezy <freezy@vpdb.io>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

export default class ResetPasswordModalCtrl {

/**
* @param $state
* @param $stateParams
* @param $uibModalInstance
* @param {ApiHelper} ApiHelper
* @param {ModalService} ModalService
* @param ProfileResource
* @ngInject
*/
constructor($state, $stateParams, $uibModalInstance, ApiHelper, ModalService, ProfileResource) {
this.$state = $state;
this.$uibModalInstance = $uibModalInstance;
this.ApiHelper = ApiHelper;
this.ModalService = ModalService;
this.ProfileResource = ProfileResource;
this.token = $stateParams.token;
this.password = '';
}

update() {
this.ProfileResource.resetPassword({ password: this.password, token: this.token }, result => {
this.errors = {};
this.error = null;
this.userPass = {};
this.email = '';
this.ModalService.info({
title: 'Password Reset',
subtitle: 'Success!',
message: result.message
});
this.$uibModalInstance.dismiss();
this.$state.go('home');

}, this.ApiHelper.handleErrors(this, { hideGlobalValidationError: true }));
}
}
32 changes: 32 additions & 0 deletions src/app/common/auth/reset.password.modal.pug
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
include ../../template/_mixins
#password-reset-modal.theme-light
.modal-header
h1.h--no-margin.h--small
+icon('lock').space-right
| &nbsp;Reset Password
.modal-body

p.text-center.padder-bottom.padder-top What's your new password?
form.form-horizontal(role='form')
.form-group(ng-class="{ error: vm.errors.password }")
label.col-sm-4.control-label Password
.col-sm-6
input#register-password.form-control(ng-model="vm.password", type="password", autocomplete="new-password")

//- ERRORS
div.alert.alert-danger(ng-show="vm.errors.password")
+icon('warning').space-right.shift-up
| {{ vm.errors.password }}
div.alert.alert-danger(ng-show="vm.errors.token")
+icon('warning').space-right.shift-up
| {{ vm.errors.token }}
div.alert.alert-danger(ng-show="vm.error")
+icon('warning').space-right.shift-up
| {{ vm.error }}

.text-center.padder-top.i(ng-show="vm.message2", ng-bind-html="vm.message2")

.modal-footer

button#dismiss-button.btn.btn-default(ng-click="$close()") Cancel
button#reset-submit.btn.btn--secondary(ng-click="vm.update()") Update
11 changes: 10 additions & 1 deletion src/app/common/backend/apihelper.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,9 @@ export default class ApiHelper {
* variable is just set to the received error.
*
* @param {object} scope Controller instance to which the error values are applied to.
* @param {{ fieldPrefix?:string }} [opt] config Options
* @param {object} [opt] config options
* @param {string} [opt.fieldPrefix]
* @param {boolean} [opt.hideGlobalValidationError]
* @param {function(scope:object, response:object)} [postFct] Executed if provided with given scope as argument, after the errors object has been set
* @param {function(scope:object, response:object)} [preFct] Executed if provided with given scope as argument, before the errors object has been set.
* @return {function(response:object)}
Expand Down Expand Up @@ -267,6 +269,7 @@ export default class ApiHelper {
this.logError('Session timed out', response);
return;
}

this.logError('Request error', response);
if (scope.submitting) {
scope.submitting = false;
Expand All @@ -287,9 +290,15 @@ export default class ApiHelper {
if (response.data.error) {
scope.error = response.data.error;
}
if (response.status === 429 && response.data.wait) {
scope.error = 'Whoa, easy tiger. Try again in ' + response.data.wait + ' seconds.';
}
if (postFct) {
postFct(scope, response);
}
if (response.status === 422 && opt.hideGlobalValidationError) {
delete scope.error;
}
};
}

Expand Down
6 changes: 4 additions & 2 deletions src/app/common/backend/auth.resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,12 @@ export class ProfileResource {
* @ngInject
*/
constructor($resource, ConfigService) {
return $resource(ConfigService.apiUri('/v1/user/:action/:id'), {}, {
return $resource(ConfigService.apiUri('/v1/profile/:action/:id'), {}, {
patch: { method: 'PATCH' },
confirm: { method: 'GET', params: { action: 'confirm' }},
logs: { method: 'GET', params: { action: 'logs' }, isArray: true }
logs: { method: 'GET', params: { action: 'logs' }, isArray: true },
requestResetPassword: { method: 'POST', params: { action: 'request-password-reset' }},
resetPassword: { method: 'POST', params: { action: 'password-reset' }},
});
}
}
2 changes: 2 additions & 0 deletions src/app/common/common.module.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import ModalMarkdownFiddle from './modal/modal.markdown.fiddle';
import ModalFlashService from './modal/modal.flash.service';
import NetworkService from './backend/network.service';
import ReleaseService from './releases/release.service';
import ResetPasswordModalCtrl from './auth/reset.password.modal.ctrl';
import TrackerService from './util/tracker.service';
import UserInfoModalCtrl from './user/user.info.modal.ctrl';
import UserMergeModalCtrl from './user/user.merge.modal.ctrl';
Expand Down Expand Up @@ -119,6 +120,7 @@ export default angular.module('vpdb.common', [ uiTypeahead ])
.controller('AuthCallbackCtrl', AuthCallbackCtrl)
.controller('LoginModalCtrl', LoginModalCtrl)
.controller('EmailConfirmationCtrl', EmailConfirmationCtrl)
.controller('ResetPasswordModalCtrl', ResetPasswordModalCtrl)

// backend
.service('ApiHelper', ApiHelper)
Expand Down
Loading