Skip to content

Commit

Permalink
Add password reset functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
jackyyzhang03 committed Jun 20, 2023
1 parent a4c3f78 commit d13136d
Show file tree
Hide file tree
Showing 19 changed files with 12,539 additions and 1,586 deletions.
11,360 changes: 11,360 additions & 0 deletions rodan-client/code/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions rodan-client/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"jquery": "^3.5.0",
"jqueryui": "^1.11.1",
"json-editor": "0.7.28",
"marionette.approuter": "^1.0.2",
"moment": "^2.25.3",
"moment-timezone": "^0.5.28",
"popper.js": "^1.16.1",
Expand Down
6 changes: 6 additions & 0 deletions rodan-client/code/src/js/Application.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import GlobalResourceLabelCollection from './Collections/Global/GlobalResourceLa
import LayoutViewMaster from './Views/Master/LayoutViewMaster';
import UpdateManager from './Managers/UpdateManager';
import TransferManager from './Managers/TransferManager';
import Router from 'js/Router';
import Backbone from 'backbone';

/**
* Main application class.
Expand Down Expand Up @@ -195,6 +197,10 @@ export default class Application extends Marionette.Application

// Check authentication.
Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__AUTHENTICATION_CHECK);

// Start router.
new Router();
Backbone.history.start();
}

/**
Expand Down
76 changes: 72 additions & 4 deletions rodan-client/code/src/js/Controllers/ControllerAuthentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ export default class ControllerAuthentication extends BaseController
{
oldOnBeforeSend(xhr);
}
xhr.setRequestHeader('X-CSRFToken', that._token.value);
if (that._token.value)
{
xhr.setRequestHeader('X-CSRFToken', that._token.value);
}

};
}
else if(Configuration.SERVER_AUTHENTICATION_TYPE === 'token')
Expand All @@ -66,7 +70,10 @@ export default class ControllerAuthentication extends BaseController
{
oldOnBeforeSend(xhr);
}
xhr.setRequestHeader('Authorization', 'Token ' + that._token.value);
if (that._token.value)
{
xhr.setRequestHeader('Authorization', 'Token ' + that._token.value);
}
};
}
}
Expand All @@ -81,7 +88,11 @@ export default class ControllerAuthentication extends BaseController
{
Radio.channel('rodan').on(RODAN_EVENTS.EVENT__USER_SAVED, (options) => this._handleEventGeneric(options));
Radio.channel('rodan').on(RODAN_EVENTS.EVENT__USER_CHANGED_PASSWORD, (options) => this._handleEventGeneric(options));
Radio.channel('rodan').on(RODAN_EVENTS.EVENT__USER_PASSWORD_RESET_REQUESTED, (options) => this._handleEventGeneric(options));
Radio.channel('rodan').on(RODAN_EVENTS.EVENT__USER_PASSWORD_RESET_CONFIRMED, (options) => this._handleEventGeneric(options));
Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__USER_CHANGE_PASSWORD, (options) => this._handleRequestChangePassword(options));
Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__USER_RESET_PASSWORD, (options) => this._handleRequestResetPassword(options));
Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__USER_RESET_PASSWORD_CONFIRM, (options) => this._handleRequestResetPasswordConfirm(options));
Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__USER_SAVE, (options) => this._handleRequestSaveUser(options));

Radio.channel('rodan').reply(RODAN_EVENTS.REQUEST__AUTHENTICATION_USER, () => this._handleRequestUser());
Expand Down Expand Up @@ -234,11 +245,11 @@ export default class ControllerAuthentication extends BaseController
*/
_setAuthenticationData(request)
{
if (Configuration.SERVER_AUTHENTICATION_TYPE === 'token')
if (Configuration.SERVER_AUTHENTICATION_TYPE === 'token' && this._token.value)
{
request.setRequestHeader('Authorization', 'Token ' + this._token.value);
}
else if (Configuration.SERVER_AUTHENTICATION_TYPE === 'session')
else if (Configuration.SERVER_AUTHENTICATION_TYPE === 'session' && this._token.value)
{
request.withCredentials = true;
request.setRequestHeader('X-CSRFToken', this._token.value);
Expand Down Expand Up @@ -347,6 +358,36 @@ export default class ControllerAuthentication extends BaseController
Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, {title: 'Saving password', content: 'Please wait...'});
}

_handleRequestResetPassword(options)
{
const route = Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_GET_ROUTE, 'auth-reset-password');
const ajaxSettings = {
success: (response) => this._handleRequestPasswordResetSuccess(response),
type: 'POST',
url: route,
data: { email: options.email }
};
Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_REQUEST_AJAX, { settings: ajaxSettings });
Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, { title: 'Requesting password reset', content: 'Please wait...'});
}

/**
* Handles password reset confirmation.
*/
_handleRequestResetPasswordConfirm(options)
{
const route = Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_GET_ROUTE, 'auth-reset-password-confirm');
const ajaxSettings = {
success: (response) => this._handleResetPasswordConfirmationSuccess(response),
error: (response) => this._handleResetPasswordConfirmationError(response),
type: 'POST',
url: route,
data: { uid: options.uid, token: options.token, new_password: options.new_password }
};
Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__SERVER_REQUEST_AJAX, { settings: ajaxSettings });
Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW_IMPORTANT, { title: 'Resetting password', content: 'Please wait...'});
}

/**
* Handle response from saving user.
*/
Expand All @@ -363,4 +404,31 @@ export default class ControllerAuthentication extends BaseController
{
Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__USER_CHANGED_PASSWORD);
}

/**
* Handle reset password request success
*/
_handleRequestPasswordResetSuccess(response)
{
Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__USER_PASSWORD_RESET_REQUESTED);
}

/**
* Handle reset password confirm success.
*/
_handleResetPasswordConfirmationSuccess(response)
{
Backbone.history.navigate('');
Radio.channel('rodan').trigger(RODAN_EVENTS.EVENT__USER_PASSWORD_RESET_CONFIRMED);
}

/**
* Handle error response from reset password attempt.
*/
_handleResetPasswordConfirmationError(response)
{
const errors = Object.values(JSON.parse(response.responseText));
const content = errors.flat().join(" ");
Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_ERROR, { content });
}
}
2 changes: 2 additions & 0 deletions rodan-client/code/src/js/Controllers/ControllerServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export default class ControllerServer extends BaseController
oldOnBeforeSend(xhr);
}

xhr.setRequestHeader('Accept', 'application/json');

// Set a timeout for x seconds.
if (that._responseTimeout === null)
{
Expand Down
21 changes: 21 additions & 0 deletions rodan-client/code/src/js/Router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Radio from "backbone.radio";
import RODAN_EVENTS from "js/Shared/RODAN_EVENTS";
import ViewResetPassword from "js/Views/Master/Main/User/Individual/ViewResetPassword";
import AppRouter from "marionette.approuter";

const Router = AppRouter.extend({
controller: {
resetPassword(uid, token) {
const options = { uid, token };
const view = new ViewResetPassword(options);
Radio.channel("rodan").request(RODAN_EVENTS.REQUEST__MODAL_HIDE);
Radio.channel("rodan").request(RODAN_EVENTS.REQUEST__MODAL_SHOW, { title: "Reset Password", content: view });
}
},

appRoutes: {
"password-reset/:uid/:token": "resetPassword"
}
});

export default Router;
6 changes: 6 additions & 0 deletions rodan-client/code/src/js/Shared/RODAN_EVENTS.js
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,12 @@ class RODAN_EVENTS
///////////////////////////////////////////////////////////////////////////////////////
/** Triggered when User has changed password. */
this.EVENT__USER_CHANGED_PASSWORD = 'EVENT__USER_CHANGED_PASSWORD';
/** Triggered when the User requests a password reset email */
this.REQUEST__USER_RESET_PASSWORD = 'REQUEST__USER_RESET_PASSWORD';
/** Triggered when User attempts to reset password */
this.REQUEST__USER_RESET_PASSWORD_CONFIRM = 'REQUEST__USER_RESET_PASSWORD_CONFIRM';
/** Triggered when User has successfully reset password */
this.EVENT__USER_PASSWORD_RESET_CONFIRMED = 'EVENT__USER_PASSWORD_RESET_CONFIRMED';
/** Triggered when UserPreference for current User has been loaded. Sends {user_preference: UserPreference}. */
this.EVENT__USER_PREFERENCE_LOADED = 'EVENT__USER_PREFERENCE_LOADED';
/** Triggered when UserPreference saved. Sends {user_preference: UserPreference}. */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Marionette from "backbone.marionette";
import Radio from "backbone.radio";
import $ from "jquery";
import RODAN_EVENTS from "js/Shared/RODAN_EVENTS";
import _ from "underscore";

/**
* Forgot password view.
*/
export default class ViewForgotPassword extends Marionette.View {
///////////////////////////////////////////////////////////////////////////////////////
// PRIVATE METHODS
///////////////////////////////////////////////////////////////////////////////////////
/**
* Handle save button.
*/
_handleButtonSubmit() {
Radio.channel("rodan").request(RODAN_EVENTS.REQUEST__USER_RESET_PASSWORD, { email: this.ui.textEmail.val() });
}
}
ViewForgotPassword.prototype.modelEvents = {
"all": "render"
};
ViewForgotPassword.prototype.ui = {
buttonSubmit: "#button-request_password_reset",
textEmail: "#text-email",
textMessage: "#text-message",
};
ViewForgotPassword.prototype.events = {
"click @ui.buttonSubmit": "_handleButtonSubmit"
};
ViewForgotPassword.prototype.template = _.template($("#template-main_forgot-password").text());
16 changes: 14 additions & 2 deletions rodan-client/code/src/js/Views/Master/Main/Login/ViewLogin.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import _ from 'underscore';
import RODAN_EVENTS from 'js/Shared/RODAN_EVENTS';
import Marionette from 'backbone.marionette';
import Radio from 'backbone.radio';
import ViewForgotPassword from './ViewForgotPassword';

/**
* Login view.
Expand Down Expand Up @@ -30,16 +31,27 @@ export default class ViewLogin extends Marionette.View
{
Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__AUTHENTICATION_LOGIN, {username: this.ui.textUsername.val(), password: this.ui.textPassword.val()});
}

/**
* Handle forgot password button.
*/
_handleButtonForgotPassword()
{
const content = new ViewForgotPassword();
Radio.channel('rodan').request(RODAN_EVENTS.REQUEST__MODAL_SHOW, { title: 'Forgot Password', content });
}
}
ViewLogin.prototype.modelEvents = {
'all': 'render'
};
ViewLogin.prototype.ui = {
textUsername: '#text-login_username',
textPassword: '#text-login_password',
buttonLogin: '#button-login'
buttonLogin: '#button-login',
buttonForgotPassword: '#button-forgot_password'
};
ViewLogin.prototype.events = {
'click @ui.buttonLogin': '_handleButton'
'click @ui.buttonLogin': '_handleButton',
'click @ui.buttonForgotPassword': '_handleButtonForgotPassword'
};
ViewLogin.prototype.template = _.template($('#template-main_login').text());
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default class ViewPassword extends Marionette.CollectionView
var passwordError = this._checkPassword();
if (passwordError)
{
this.ui.textmessage.text(passwordError);
this.ui.textMessage.text(passwordError);
}
else
{
Expand Down Expand Up @@ -54,7 +54,7 @@ ViewPassword.prototype.ui = {
buttonSave: '#button-save_password',
textPassword: '#text-password',
textPasswordConfirm: '#text-password_confirm',
textmessage: '#text-message',
textMessage: '#text-message',
textPasswordCurrent: '#text-password_current'
};
ViewPassword.prototype.events = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import Marionette from "backbone.marionette";
import Radio from "backbone.radio";
import $ from "jquery";
import RODAN_EVENTS from "js/Shared/RODAN_EVENTS";
import _ from "underscore";

/**
* Password reset view.
*/
export default class ViewResetPassword extends Marionette.View {
initialize(options) {
this._uid = options.uid;
this._token = options.token;
}
///////////////////////////////////////////////////////////////////////////////////////
// PRIVATE METHODS
///////////////////////////////////////////////////////////////////////////////////////
/**
* Handle submit button.
*/
_handleButtonSubmit() {
var passwordError = this._checkPassword();
if (passwordError) {
this.ui.textMessage.text(passwordError);
}
else {
Radio.channel("rodan").request(RODAN_EVENTS.REQUEST__USER_RESET_PASSWORD_CONFIRM, { uid: this._uid, token: this._token, new_password: this.ui.textPassword.val() });
}
}

/**
* Checks password fields. Will provide error text if passwords no good, else null.
*/
_checkPassword() {
var password = this.ui.textPassword.val();
var confirm = this.ui.textPasswordConfirm.val();
if (!password) {
return "Your password cannot be empty. Come on, you must have done this before.";
}
else if (password !== confirm) {
return "Passwords do not match.";
}
return null;
}
}
ViewResetPassword.prototype.modelEvents = {
"all": "render"
};
ViewResetPassword.prototype.ui = {
buttonSubmit: "#button-reset_password",
textPassword: "#text-password",
textPasswordConfirm: "#text-password_confirm",
textMessage: "#text-message",
};
ViewResetPassword.prototype.events = {
"click @ui.buttonSubmit": "_handleButtonSubmit"
};
ViewResetPassword.prototype.template = _.template($("#template-main_user_reset-password").text());
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<div class="fixed_individual">
<label for="text-email">Email:</label>
<input type="email" class="form-control input-sm" id="text-email" value="" name="text-email">
<span id="text-message" class="text-danger"></span>
<br>
<br>
<div class="btn-group" role="group">
<button class="btn btn-xs btn-warning" id="button-request_password_reset">Submit</button>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<input type="password" class="form-control" id="text-login_password">
</div>
<button type="submit" class="btn btn-xs btn-success" id="button-login">Login</button>
<button type="button" class="btn btn-xs btn-warning" id="button-forgot_password">Forgot Password</button>
</form>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<div class="fixed_individual">
<label for="text-password">Password:</label>
<input type="password" class="form-control input-sm" id="text-password" value="" name="text-password">
<label for="text-password_confirm">Confirm password:</label>
<input type="password" class="form-control input-sm" id="text-password_confirm" value="" name="text-password_confirm">
<span id="text-message" class="text-danger"></span>
<br>
<br>
<div class="btn-group" role="group">
<button class="btn btn-xs btn-warning" id="button-reset_password">Submit</button>
</div>
</div>
Loading

0 comments on commit d13136d

Please sign in to comment.