mirror of
https://github.com/fleetbase/fleetbase.git
synced 2026-01-08 07:16:49 +00:00
added cache to github card component, add password verification for changing email or password, ensured reset password link is is invalidated when expired or deleted
This commit is contained in:
1
console/app/components/console-wormhole.hbs
Normal file
1
console/app/components/console-wormhole.hbs
Normal file
@@ -0,0 +1 @@
|
||||
<div id="console-wormhole" />
|
||||
@@ -1,6 +1,6 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action, computed } from '@ember/object';
|
||||
import { computed } from '@ember/object';
|
||||
import { isArray } from '@ember/array';
|
||||
import { isBlank } from '@ember/utils';
|
||||
import { task } from 'ember-concurrency';
|
||||
|
||||
13
console/app/components/modals/validate-password.hbs
Normal file
13
console/app/components/modals/validate-password.hbs
Normal file
@@ -0,0 +1,13 @@
|
||||
<Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
|
||||
<div class="px-5">
|
||||
{{#if @options.body}}
|
||||
<p class="dark:text-gray-400 text-gray-700 mb-4">{{@options.body}}</p>
|
||||
{{/if}}
|
||||
<InputGroup>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<InputGroup @name="Password" @type="password" @value={{this.password}} @wrapperClass="mb-0i" />
|
||||
<InputGroup @name="Confirm Password" @type="password" @value={{this.confirmPassword}} @wrapperClass="mb-0i" />
|
||||
</div>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</Modal::Default>
|
||||
49
console/app/components/modals/validate-password.js
Normal file
49
console/app/components/modals/validate-password.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { task } from 'ember-concurrency';
|
||||
|
||||
export default class ModalsValidatePasswordComponent extends Component {
|
||||
@service fetch;
|
||||
@service notifications;
|
||||
@tracked options = {};
|
||||
@tracked password;
|
||||
@tracked confirmPassword;
|
||||
|
||||
constructor(owner, { options }) {
|
||||
super(...arguments);
|
||||
this.options = options;
|
||||
this.setupOptions();
|
||||
}
|
||||
|
||||
setupOptions() {
|
||||
this.options.title = 'Validate Current Password';
|
||||
this.options.acceptButtonText = 'Validate Password';
|
||||
this.options.declineButtonHidden = true;
|
||||
this.options.confirm = (modal) => {
|
||||
modal.startLoading();
|
||||
return this.validatePassword.perform();
|
||||
};
|
||||
}
|
||||
|
||||
@task *validatePassword() {
|
||||
let isPasswordValid = false;
|
||||
|
||||
try {
|
||||
yield this.fetch.post('users/validate-password', {
|
||||
password: this.password,
|
||||
password_confirmation: this.confirmPassword,
|
||||
});
|
||||
|
||||
isPasswordValid = true;
|
||||
} catch (error) {
|
||||
this.notifications.serverError(error, 'Invalid current password.');
|
||||
}
|
||||
|
||||
if (typeof this.options.onValidated === 'function') {
|
||||
this.options.onValidated(isPasswordValid);
|
||||
}
|
||||
|
||||
return isPasswordValid;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import { task } from 'ember-concurrency';
|
||||
|
||||
export default class AuthResetPasswordController extends Controller {
|
||||
/**
|
||||
@@ -54,38 +54,23 @@ export default class AuthResetPasswordController extends Controller {
|
||||
@tracked password_confirmation;
|
||||
|
||||
/**
|
||||
* Loading stae of password reset.
|
||||
* The reset password task.
|
||||
*
|
||||
* @memberof AuthResetPasswordController
|
||||
*/
|
||||
@tracked isLoading;
|
||||
|
||||
/**
|
||||
* The reset password action.
|
||||
*
|
||||
* @memberof AuthResetPasswordController
|
||||
*/
|
||||
@action resetPassword(event) {
|
||||
// firefox patch
|
||||
@task *resetPassword(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const { code, password, password_confirmation } = this;
|
||||
const { id } = this.model;
|
||||
|
||||
this.isLoading = true;
|
||||
try {
|
||||
yield this.fetch.post('auth/reset-password', { link: id, code, password, password_confirmation });
|
||||
} catch (error) {
|
||||
return this.notifications.serverError(error);
|
||||
}
|
||||
|
||||
this.fetch
|
||||
.post('auth/reset-password', { link: id, code, password, password_confirmation })
|
||||
.then(() => {
|
||||
this.notifications.success(this.intl.t('auth.reset-password.success-message'));
|
||||
|
||||
return this.router.transitionTo('auth.login');
|
||||
})
|
||||
.catch((error) => {
|
||||
this.notifications.serverError(error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
this.notifications.success(this.intl.t('auth.reset-password.success-message'));
|
||||
yield this.router.transitionTo('auth.login');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,17 +34,11 @@ export default class ConsoleAccountAuthController extends Controller {
|
||||
@service router;
|
||||
|
||||
/**
|
||||
* The user's current password.
|
||||
* @type {string}
|
||||
*/
|
||||
@tracked password;
|
||||
|
||||
/**
|
||||
* The user's confirmation of the new password.
|
||||
* Service for managing modals.
|
||||
*
|
||||
* @type {string}
|
||||
* @type {router}
|
||||
*/
|
||||
@tracked confirmPassword;
|
||||
@service modalsManager;
|
||||
|
||||
/**
|
||||
* The new password the user intends to set.
|
||||
@@ -60,13 +54,6 @@ export default class ConsoleAccountAuthController extends Controller {
|
||||
*/
|
||||
@tracked newConfirmPassword;
|
||||
|
||||
/**
|
||||
* Flag indicating whether the current password has been validated.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
@tracked isPasswordValidated = false;
|
||||
|
||||
/**
|
||||
* System-wide two-factor authentication configuration.
|
||||
*
|
||||
@@ -106,28 +93,6 @@ export default class ConsoleAccountAuthController extends Controller {
|
||||
this.loadUserTwoFaSettings.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the user's current password.
|
||||
*
|
||||
* @method validatePassword
|
||||
* @param {Event} event - The event object triggering the action.
|
||||
*/
|
||||
@action validatePassword(event) {
|
||||
event.preventDefault();
|
||||
this.validatePasswordTask.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to change the user's password asynchronously.
|
||||
*
|
||||
* @method changeUserPasswordTask
|
||||
* @param {Event} event - The event object triggering the action.
|
||||
*/
|
||||
@action changeUserPassword(event) {
|
||||
event.preventDefault();
|
||||
this.changeUserPasswordTask.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when two-factor authentication is toggled.
|
||||
*
|
||||
@@ -163,6 +128,58 @@ export default class ConsoleAccountAuthController extends Controller {
|
||||
this.saveUserTwoFaSettings.perform(this.twoFaSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to change the user's password asynchronously.
|
||||
*
|
||||
* @method changePassword
|
||||
*/
|
||||
@task *changePassword(event) {
|
||||
// If from event fired
|
||||
if (event instanceof Event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// Validate current password
|
||||
const isPasswordValid = yield this.validatePassword.perform();
|
||||
if (!isPasswordValid) {
|
||||
this.newPassword = undefined;
|
||||
this.newConfirmPassword = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
yield this.fetch.post('users/change-password', {
|
||||
password: this.newPassword,
|
||||
password_confirmation: this.newConfirmPassword,
|
||||
});
|
||||
|
||||
this.notifications.success('Password change successfully.');
|
||||
} catch (error) {
|
||||
this.notifications.serverError(error, 'Failed to change password.');
|
||||
}
|
||||
|
||||
this.newPassword = undefined;
|
||||
this.newConfirmPassword = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task to validate current password
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
@task *validatePassword() {
|
||||
let isPasswordValid = false;
|
||||
|
||||
yield this.modalsManager.show('modals/validate-password', {
|
||||
body: 'You must validate your current password before it can be changed.',
|
||||
onValidated: (isValid) => {
|
||||
isPasswordValid = isValid;
|
||||
},
|
||||
});
|
||||
|
||||
return isPasswordValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to save user-specific two-factor authentication settings asynchronously.
|
||||
*
|
||||
@@ -209,40 +226,4 @@ export default class ConsoleAccountAuthController extends Controller {
|
||||
}
|
||||
return twoFaConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to validate the user's current password asynchronously.
|
||||
*
|
||||
* @method validatePasswordTask
|
||||
*/
|
||||
@task *validatePasswordTask() {
|
||||
try {
|
||||
yield this.fetch.post('users/validate-password', {
|
||||
password: this.password,
|
||||
password_confirmation: this.confirmPassword,
|
||||
});
|
||||
|
||||
this.isPasswordValidated = true;
|
||||
} catch (error) {
|
||||
this.notifications.serverError(error, 'Invalid current password.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the task to change the user's password asynchronously.
|
||||
*
|
||||
* @method changeUserPasswordTask
|
||||
*/
|
||||
@task *changeUserPasswordTask() {
|
||||
try {
|
||||
yield this.fetch.post('users/change-password', {
|
||||
password: this.newPassword,
|
||||
password_confirmation: this.newConfirmPassword,
|
||||
});
|
||||
|
||||
this.notifications.success('Password change successfully.');
|
||||
} catch (error) {
|
||||
this.notifications.error('Failed to change password');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { task } from 'ember-concurrency';
|
||||
|
||||
export default class ConsoleAccountIndexController extends Controller {
|
||||
/**
|
||||
@@ -18,6 +18,7 @@ export default class ConsoleAccountIndexController extends Controller {
|
||||
* @memberof ConsoleAccountIndexController
|
||||
*/
|
||||
@service fetch;
|
||||
|
||||
/**
|
||||
* Inject the `notifications` service.
|
||||
*
|
||||
@@ -26,11 +27,11 @@ export default class ConsoleAccountIndexController extends Controller {
|
||||
@service notifications;
|
||||
|
||||
/**
|
||||
* The loading state of request.
|
||||
* Inject the `modalsManager` service.
|
||||
*
|
||||
* @memberof ConsoleAccountIndexController
|
||||
*/
|
||||
@tracked isLoading = false;
|
||||
@service modalsManager;
|
||||
|
||||
/**
|
||||
* Alias to the currentUser service user record.
|
||||
@@ -66,27 +67,64 @@ export default class ConsoleAccountIndexController extends Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the Profile settings.
|
||||
* Starts the task to change password
|
||||
*
|
||||
* @return {Promise}
|
||||
* @param {Event} event
|
||||
* @memberof ConsoleAccountIndexController
|
||||
*/
|
||||
@action saveProfile() {
|
||||
const user = this.user;
|
||||
@task *saveProfile(event) {
|
||||
// If from event fired
|
||||
if (event instanceof Event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
let canUpdateProfile = true;
|
||||
// If email has been changed prompt for password validation
|
||||
if (this.changedUserAttribute('email')) {
|
||||
canUpdateProfile = yield this.validatePassword.perform();
|
||||
}
|
||||
|
||||
return user
|
||||
.save()
|
||||
.then((user) => {
|
||||
if (canUpdateProfile === true) {
|
||||
try {
|
||||
const user = yield this.user.save();
|
||||
this.notifications.success('Profile changes saved.');
|
||||
this.currentUser.set('user', user);
|
||||
})
|
||||
.catch((error) => {
|
||||
} catch (error) {
|
||||
this.notifications.serverError(error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.user.rollbackAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task to validate current password
|
||||
*
|
||||
* @return {boolean}
|
||||
* @memberof ConsoleAccountIndexController
|
||||
*/
|
||||
@task *validatePassword() {
|
||||
let isPasswordValid = false;
|
||||
|
||||
yield this.modalsManager.show('modals/validate-password', {
|
||||
body: 'You must validate your password to update the account email address.',
|
||||
onValidated: (isValid) => {
|
||||
isPasswordValid = isValid;
|
||||
},
|
||||
});
|
||||
|
||||
return isPasswordValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any user attribute has been changed
|
||||
*
|
||||
* @param {string} attributeKey
|
||||
* @return {boolean}
|
||||
* @memberof ConsoleAccountIndexController
|
||||
*/
|
||||
changedUserAttribute(attributeKey) {
|
||||
const changedAttributes = this.user.changedAttributes();
|
||||
return changedAttributes[attributeKey] !== undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,21 @@ import { inject as service } from '@ember/service';
|
||||
|
||||
export default class AuthResetPasswordRoute extends Route {
|
||||
@service store;
|
||||
@service fetch;
|
||||
@service router;
|
||||
@service notifications;
|
||||
@service intl;
|
||||
|
||||
async model(params) {
|
||||
return params;
|
||||
async model({ id }) {
|
||||
return this.fetch.get('auth/validate-verification', { id });
|
||||
}
|
||||
|
||||
async setupController(controller) {
|
||||
async setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
if (model.is_valid === false) {
|
||||
this.notifications.warning(this.intl.t('auth.reset-password.invalid-verification-code'));
|
||||
return this.router.transitionTo('auth');
|
||||
}
|
||||
|
||||
// set brand to controller
|
||||
controller.brand = await this.store.findRecord('brand', 1);
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<form class="space-y-6" {{on "submit" this.resetPassword}}>
|
||||
<form class="space-y-6" {{on "submit" (perform this.resetPassword)}}>
|
||||
<InputGroup @name={{t "auth.reset-password.form.code.label"}} @value={{this.code}} @inputClass="form-input-lg" @helpText={{t "auth.reset-password.form.code.help-text"}} />
|
||||
<InputGroup @name={{t "auth.reset-password.form.password.label"}} @value={{this.password}} @type="password" @inputClass="form-input-lg" @helpText={{t "auth.reset-password.form.password.help-text"}} />
|
||||
<InputGroup @name={{t "auth.reset-password.form.confirm-password.label"}} @value={{this.password_confirmation}} @type="password" @inputClass="form-input-lg" @helpText={{t "auth.reset-password.form.confirm-password.help-text"}} />
|
||||
|
||||
<div class="flex space-x-2">
|
||||
<Button @icon="check" @size="lg" @type="primary" @buttonType="submit" @text={{t "auth.reset-password.form.submit-button"}} @onClick={{this.resetPassword}} @isLoading={{this.isLoading}} />
|
||||
<Button @size="lg" @buttonType="button" @text={{t "auth.reset-password.form.back-button"}} @onClick={{fn (transition-to "auth.login")}} @disabled={{this.isLoading}} />
|
||||
<Button @icon="check" @size="lg" @type="primary" @buttonType="submit" @text={{t "auth.reset-password.form.submit-button"}} @onClick={{perform this.resetPassword}} @isLoading={{not this.resetPassword.isIdle}} />
|
||||
<Button @size="lg" @buttonType="button" @text={{t "auth.reset-password.form.back-button"}} @onClick={{fn (transition-to "auth.login")}} @disabled={{not this.resetPassword.isIdle}} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -19,4 +19,4 @@
|
||||
<EmberWormhole @to="view-header-actions">
|
||||
<LocaleSelector class="mr-0.5" />
|
||||
</EmberWormhole>
|
||||
<div id="console-wormhole" />
|
||||
<ConsoleWormhole />
|
||||
@@ -5,51 +5,28 @@
|
||||
<div class="container mx-auto h-screen" {{increase-height-by 500}}>
|
||||
<div class="max-w-3xl my-10 mx-auto space-y-6">
|
||||
<ContentPanel @title="Change Password" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
{{#if this.isPasswordValidated}}
|
||||
<form id="change-password-form" aria-label="change-password" {{on "submit" this.changeUserPassword}}>
|
||||
<legend class="mb-3">Change Password</legend>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<InputGroup @name="Enter new Password" @type="password" @value={{this.newPassword}} />
|
||||
<InputGroup @name="Confirm Password" @type="password" @value={{this.newConfirmPassword}} />
|
||||
</div>
|
||||
<Button @type="primary" @buttonType="submit" @text="Confirm & Change Password" @icon="save" {{on "click" this.changeUserPassword}} />
|
||||
</form>
|
||||
{{else}}
|
||||
<form id="validate-password-form" aria-label="validate-password" {{on "submit" this.validatePassword}}>
|
||||
<legend class="mb-3">Validate Current Password</legend>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<InputGroup @name="Password" @type="password" @value={{this.password}} />
|
||||
<InputGroup @name="Confirm Password" @type="password" @value={{this.confirmPassword}} />
|
||||
</div>
|
||||
<Button @type="primary" @buttonType="submit" @text="Continue" @icon="check" {{on "click" this.validatePassword}} />
|
||||
</form>
|
||||
{{/if}}
|
||||
<form id="change-password-form" aria-label="change-password" {{on "submit" (perform this.changePassword)}}>
|
||||
<legend class="mb-3">Change Password</legend>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<InputGroup @name="Enter new Password" @type="password" @value={{this.newPassword}} />
|
||||
<InputGroup @name="Confirm Password" @type="password" @value={{this.newConfirmPassword}} />
|
||||
</div>
|
||||
<Button @type="primary" @buttonType="submit" @text="Confirm & Change Password" @icon="save" {{on "click" (perform this.changePassword)}} />
|
||||
</form>
|
||||
</ContentPanel>
|
||||
|
||||
{{#if this.isSystemTwoFaEnabled}}
|
||||
<ContentPanel @title="2FA Settings" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<div class="mb-3">
|
||||
{{#if this.loadUserTwoFaSettings.isIdle}}
|
||||
<TwoFaSettings
|
||||
@twoFaMethods={{this.methods}}
|
||||
@twoFaSettings={{this.twoFaSettings}}
|
||||
@onTwoFaToggled={{this.onTwoFaToggled}}
|
||||
@onTwoFaMethodSelected={{this.onTwoFaMethodSelected}}
|
||||
/>
|
||||
<TwoFaSettings @twoFaMethods={{this.methods}} @twoFaSettings={{this.twoFaSettings}} @onTwoFaToggled={{this.onTwoFaToggled}} @onTwoFaMethodSelected={{this.onTwoFaMethodSelected}} />
|
||||
{{else}}
|
||||
<div class="flex items-center justify-center p-4">
|
||||
<Spinner @loadingMessage="Loading User 2FA Settings..." @wrapperClass="flex flex-row" @iconClass="mr-2" />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<Button
|
||||
@type="primary"
|
||||
@buttonType="submit"
|
||||
@text="Save 2FA Settings"
|
||||
@icon="save"
|
||||
@onClick={{this.saveTwoFactorAuthSettings}}
|
||||
@isLoading={{this.saveUserTwoFaSettings.isRunning}}
|
||||
/>
|
||||
<Button @type="primary" @buttonType="submit" @text="Save 2FA Settings" @icon="save" @onClick={{this.saveTwoFactorAuthSettings}} @isLoading={{this.saveUserTwoFaSettings.isRunning}} />
|
||||
</ContentPanel>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="container mx-auto h-screen">
|
||||
<div class="max-w-3xl my-10 mx-auto">
|
||||
<ContentPanel @title={{t "common.your-profile"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
|
||||
<form class="flex flex-col md:flex-row" {{on "submit" this.saveProfile}}>
|
||||
<form class="flex flex-col md:flex-row" {{on "submit" (perform this.saveProfile)}}>
|
||||
<div class="w-32 flex flex-col justify-center mb-6 mr-6">
|
||||
<Image src={{this.user.avatar_url}} @fallbackSrc={{config "defaultValues.userImage"}} alt={{this.user.name}} class="w-32 h-32 rounded-md" />
|
||||
<FileUpload @name={{t "console.account.index.photos"}} @accept="image/*" @onFileAdded={{this.uploadNewPhoto}} @labelClass="flex flex-row items-center justify-center" as |queue|>
|
||||
@@ -35,7 +35,7 @@
|
||||
<InputGroup @name={{t "common.date-of-birth"}} @type="date" @value={{this.user.date_of_birth}} />
|
||||
</div>
|
||||
<div class="mt-3 flex items-center justify-end">
|
||||
<Button @buttonType="submit" @type="primary" @size="lg" @icon="save" @text={{t "common.save-button-text"}} @onClick={{this.saveProfile}} @isLoading={{this.isLoading}} />
|
||||
<Button @buttonType="submit" @type="primary" @size="lg" @icon="save" @text={{t "common.save-button-text"}} @onClick={{perform this.saveProfile}} @isLoading={{not this.saveProfile.isIdle}} />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -179,6 +179,7 @@ auth:
|
||||
slow-connection-message: Experiencing connectivity issues.
|
||||
reset-password:
|
||||
success-message: Your password has been reset! Login to continue.
|
||||
invalid-verification-code: This reset password link is invalid or expired.
|
||||
title: Reset your password
|
||||
form:
|
||||
code:
|
||||
|
||||
Reference in New Issue
Block a user