Rewrite: Reset Login Form (#18237)
Co-authored-by: Gabriel Henriques <gabriel.henriques@rocket.chat> Co-authored-by: Martin <martin.schoeler@rocket.chat> Co-authored-by: dougfabris <deefabris@gmail.com>pull/19366/head
parent
878c4c8fa7
commit
b950f17e42
@ -1,37 +0,0 @@ |
||||
<template name="resetPassword"> |
||||
<form id="login-card" class="content-background-color color-primary-font-color" action="/"> |
||||
<div class="fields"> |
||||
<header> |
||||
{{#if requirePasswordChange}} |
||||
{{#if requirePasswordChangeReason}} |
||||
<p>{{_ requirePasswordChangeReason}}</p> |
||||
{{else}} |
||||
<p>{{_ 'You_need_to_change_your_password'}}</p> |
||||
{{/if}} |
||||
{{else}} |
||||
<p>{{_ "Please_enter_your_new_password_below"}}</p> |
||||
{{/if}} |
||||
</header> |
||||
|
||||
<div class="rc-input"> |
||||
<label class="rc-input__label"> |
||||
<div class="rc-input__title">{{_ "Type_your_new_password"}}</div> |
||||
<div class="rc-input__wrapper"> |
||||
<input type="password" name="newPassword" id="newPassword" dir="auto" class="rc-input__element" autocomplete="off"> |
||||
</div> |
||||
</label> |
||||
</div> |
||||
|
||||
<div class="submit"> |
||||
<button data-loading-text="{{_ "Please_wait"}}..." class="rc-button rc-button--primary resetpass" {{disabled}} >{{_ "Reset"}}</button> |
||||
</div> |
||||
</div> |
||||
{{#if passwordPolicyEnabled}} |
||||
<div class="wrapper"> |
||||
{{#each passwordPolicy}} |
||||
<p>* {{_ this}}</p> |
||||
{{/each}} |
||||
</div> |
||||
{{/if}} |
||||
</form> |
||||
</template> |
||||
@ -1,123 +0,0 @@ |
||||
import { Meteor } from 'meteor/meteor'; |
||||
import { Accounts } from 'meteor/accounts-base'; |
||||
import { FlowRouter } from 'meteor/kadira:flow-router'; |
||||
import { Template } from 'meteor/templating'; |
||||
import toastr from 'toastr'; |
||||
import { ReactiveDict } from 'meteor/reactive-dict'; |
||||
import { ReactiveVar } from 'meteor/reactive-var'; |
||||
|
||||
import { modal, call } from '../../../ui-utils/client'; |
||||
import { t } from '../../../utils'; |
||||
import { callbacks } from '../../../callbacks'; |
||||
|
||||
Template.resetPassword.helpers({ |
||||
disabled() { |
||||
return Template.instance().state.get('password') ? '' : 'disabled'; |
||||
}, |
||||
requirePasswordChange() { |
||||
const user = Meteor.user(); |
||||
if (user) { |
||||
return user.requirePasswordChange; |
||||
} |
||||
}, |
||||
requirePasswordChangeReason() { |
||||
const user = Meteor.user(); |
||||
if (user) { |
||||
return user.requirePasswordChangeReason; |
||||
} |
||||
}, |
||||
passwordPolicyEnabled() { |
||||
return Template.instance().passwordPolicyEnabled.get(); |
||||
}, |
||||
passwordPolicy() { |
||||
return Template.instance().passwordPolicyRules.get(); |
||||
}, |
||||
}); |
||||
|
||||
const resetPassword = (token, password) => new Promise((resolve, reject) => { |
||||
Accounts.resetPassword(token, password, function(error, result) { |
||||
if (!error) { |
||||
FlowRouter.go('home'); |
||||
toastr.success(t('Password_changed_successfully')); |
||||
callbacks.run('userPasswordReset'); |
||||
resolve(result); |
||||
} |
||||
|
||||
if (error.error !== 'totp-required') { |
||||
reject(error); |
||||
} |
||||
|
||||
toastr.success(t('Password_changed_successfully')); |
||||
callbacks.run('userPasswordReset'); |
||||
FlowRouter.go('login'); |
||||
resolve(result); |
||||
}); |
||||
}); |
||||
|
||||
async function setUserPassword(password) { |
||||
try { |
||||
const result = await call('setUserPassword', password); |
||||
if (!result) { |
||||
return toastr.error(t('Error')); |
||||
} |
||||
|
||||
Meteor.users.update({ _id: Meteor.userId() }, { |
||||
$set: { |
||||
requirePasswordChange: false, |
||||
}, |
||||
}); |
||||
toastr.remove(); |
||||
toastr.success(t('Password_changed_successfully')); |
||||
} catch (e) { |
||||
console.error(e); |
||||
toastr.error(t('Error')); |
||||
} |
||||
} |
||||
|
||||
Template.resetPassword.events({ |
||||
'input #newPassword'(e, i) { |
||||
i.state.set('password', e.currentTarget.value); |
||||
}, |
||||
async 'submit #login-card'(event, i) { |
||||
event.preventDefault(); |
||||
|
||||
const password = i.state.get('password'); |
||||
const token = FlowRouter.getParam('token'); |
||||
|
||||
if (!password || !password.trim()) { |
||||
return; |
||||
} |
||||
|
||||
i.state.set('loading', true); |
||||
|
||||
try { |
||||
if (Meteor.userId() && !token) { |
||||
return setUserPassword(password); |
||||
} |
||||
await resetPassword(token, password); |
||||
} catch (error) { |
||||
modal.open({ |
||||
title: t('Error_changing_password'), |
||||
type: 'error', |
||||
}); |
||||
} finally { |
||||
i.state.set('loading', false); |
||||
} |
||||
}, |
||||
}); |
||||
|
||||
Template.resetPassword.onRendered(function() { |
||||
this.find('[name=newPassword]').focus(); |
||||
}); |
||||
|
||||
Template.resetPassword.onCreated(function() { |
||||
this.state = new ReactiveDict({ password: '' }); |
||||
this.passwordPolicyEnabled = new ReactiveVar(false); |
||||
this.passwordPolicyRules = new ReactiveVar(); |
||||
Meteor.call('getPasswordPolicy', (error, result) => { |
||||
if (result.enabled) { |
||||
this.passwordPolicyEnabled.set(true); |
||||
this.passwordPolicyRules.set(result.policy); |
||||
} |
||||
}); |
||||
}); |
||||
@ -0,0 +1,112 @@ |
||||
import { Meteor } from 'meteor/meteor'; |
||||
import React, { useState, useCallback, useMemo } from 'react'; |
||||
import { Button, TextInput, Field, Modal, Box, Throbber } from '@rocket.chat/fuselage'; |
||||
import { useSafely } from '@rocket.chat/fuselage-hooks'; |
||||
|
||||
import { useTranslation } from '../../contexts/TranslationContext'; |
||||
import { useUser } from '../../contexts/UserContext'; |
||||
import { useMethodData, useMethod } from '../../contexts/ServerContext'; |
||||
import { useRouteParameter, useRoute } from '../../contexts/RouterContext'; |
||||
|
||||
const getChangePasswordReason = ({ |
||||
requirePasswordChange, |
||||
requirePasswordChangeReason = requirePasswordChange |
||||
? 'You_need_to_change_your_password' |
||||
: 'Please_enter_your_new_password_below', |
||||
} = {}) => requirePasswordChangeReason; |
||||
|
||||
const ResetPassword = () => { |
||||
const user = useUser(); |
||||
const t = useTranslation(); |
||||
const setUserPassword = useMethod('setUserPassword'); |
||||
const resetPassword = useMethod('resetPassword'); |
||||
const token = useRouteParameter('token'); |
||||
const params = useMemo(() => [{ |
||||
token, |
||||
}], [token]); |
||||
|
||||
const [{ enabled: policyEnabled, policy: policies } = {}] = useMethodData('getPasswordPolicy', params); |
||||
|
||||
const router = useRoute('home'); |
||||
|
||||
const changePasswordReason = getChangePasswordReason(user || {}); |
||||
|
||||
const [newPassword, setNewPassword] = useState(''); |
||||
const [isLoading, setIsLoading] = useSafely(useState(false)); |
||||
const [error, setError] = useSafely(useState()); |
||||
|
||||
const handleOnChange = useCallback((event) => setNewPassword(event.currentTarget.value), [setNewPassword]); |
||||
|
||||
const isSubmitDisabled = !newPassword.trim() || isLoading; |
||||
|
||||
const handleSubmit = useCallback(async (e) => { |
||||
e.preventDefault(); |
||||
if (isSubmitDisabled) { |
||||
return; |
||||
} |
||||
setIsLoading(true); |
||||
try { |
||||
if (token && resetPassword) { |
||||
const result = await resetPassword(token, newPassword); |
||||
await Meteor.loginWithToken(result.token); |
||||
router.push({}); |
||||
} else { |
||||
await setUserPassword(newPassword); |
||||
} |
||||
} catch ({ error, reason = error }) { |
||||
setError(reason); |
||||
} finally { |
||||
setIsLoading(false); |
||||
} |
||||
}, [isSubmitDisabled, setIsLoading, token, resetPassword, newPassword, router, setUserPassword, setError]); |
||||
|
||||
return ( |
||||
<Modal is='form' onSubmit={handleSubmit}> |
||||
<Modal.Header> |
||||
<Modal.Title textAlign='start'>{t('Password')}</Modal.Title> |
||||
</Modal.Header> |
||||
<Modal.Content> |
||||
<Field> |
||||
<Field.Label> |
||||
{t(changePasswordReason)} |
||||
</Field.Label> |
||||
<Field.Row> |
||||
<TextInput |
||||
placeholder={t('Type_your_new_password')} |
||||
type='password' |
||||
name='newPassword' |
||||
id='newPassword' |
||||
dir='auto' |
||||
onChange={handleOnChange} |
||||
autoComplete='off' |
||||
value={newPassword} |
||||
/> |
||||
</Field.Row> |
||||
{error && <Field.Error>{error}</Field.Error>} |
||||
{policyEnabled && ( |
||||
<Field.Hint> |
||||
{policies.map((policy, index) => ( |
||||
<Box is='p' textAlign='start' key={index}>{t(...policy)}</Box> |
||||
))} |
||||
</Field.Hint> |
||||
)} |
||||
</Field> |
||||
</Modal.Content> |
||||
<Modal.Footer> |
||||
<Field> |
||||
<Field.Row> |
||||
<Button |
||||
primary |
||||
disabled={isSubmitDisabled} |
||||
type='submit' |
||||
> |
||||
{isLoading ? <Throbber size='x12' inheritColor /> : t('Reset')} |
||||
</Button> |
||||
</Field.Row> |
||||
</Field> |
||||
</Modal.Footer> |
||||
</Modal> |
||||
); |
||||
}; |
||||
|
||||
export default ResetPassword; |
||||
@ -0,0 +1,12 @@ |
||||
import React from 'react'; |
||||
|
||||
import ResetPassword from './ResetPassword'; |
||||
|
||||
export default { |
||||
title: 'components/Login/ResetPassword', |
||||
component: ResetPassword, |
||||
}; |
||||
|
||||
export const Basic = () => ( |
||||
<ResetPassword/> |
||||
); |
||||
@ -0,0 +1,12 @@ |
||||
import { HTML } from 'meteor/htmljs'; |
||||
|
||||
import { createTemplateForComponent } from '../reactAdapters'; |
||||
|
||||
createTemplateForComponent( |
||||
'resetPassword', |
||||
() => import('./ResetPassword/ResetPassword'), |
||||
{ |
||||
// eslint-disable-next-line new-cap
|
||||
renderContainerView: () => HTML.DIV({ style: 'display: flex;' }), |
||||
}, |
||||
); |
||||
Loading…
Reference in new issue