0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-20 21:32:31 -05:00

feat(core,console): change admin user password (#1268)

This commit is contained in:
Wang Sijie 2022-06-29 11:13:26 +08:00 committed by GitHub
parent b75fea9258
commit a4d0a940bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 160 additions and 1 deletions

View file

@ -0,0 +1,24 @@
@use '@/scss/underscore' as _;
.changePassword {
border: 1px solid var(--color-divider);
border-radius: 8px;
padding: _.unit(4);
display: flex;
align-items: center;
width: 554px;
.description {
font: var(--font-body-medium);
color: var(--color-text);
margin-right: _.unit(4);
}
}
.modal {
.description {
font: var(--font-body-medium);
color: var(--color-caption);
margin-bottom: _.unit(4);
}
}

View file

@ -0,0 +1,88 @@
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import ReactModal from 'react-modal';
import Button from '@/components/Button';
import FormField from '@/components/FormField';
import ModalLayout from '@/components/ModalLayout';
import TextInput from '@/components/TextInput';
import useApi from '@/hooks/use-api';
import * as modalStyles from '@/scss/modal.module.scss';
import * as styles from './ChangePassword.module.scss';
type FormFields = {
password: string;
confirmPassword: string;
};
const ChangePassword = () => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [isOpen, setIsOpen] = useState(false);
const { watch, register, reset } = useForm<FormFields>();
const [isLoading, setIsLoading] = useState(false);
const api = useApi();
const password = watch('password');
const confirmPassword = watch('confirmPassword');
const isDisabled = !password || password !== confirmPassword;
const onSubmit = async () => {
setIsLoading(true);
await api.patch(`/api/me/password`, { json: { password } }).json();
setIsLoading(false);
setIsOpen(false);
toast.success(t('settings.password_changed'));
reset({});
};
return (
<>
<FormField title="admin_console.settings.change_password">
<div className={styles.changePassword}>
<div className={styles.description}>{t('settings.change_password_description')}</div>
<Button
title="admin_console.settings.change_password"
type="default"
onClick={() => {
setIsOpen(true);
}}
/>
</div>
</FormField>
<ReactModal
isOpen={isOpen}
className={modalStyles.content}
overlayClassName={modalStyles.overlay}
>
<ModalLayout
title="settings.change_modal_title"
footer={
<Button
type="primary"
title="admin_console.general.confirm"
disabled={isDisabled || isLoading}
onClick={onSubmit}
/>
}
onClose={() => {
setIsOpen(false);
}}
>
<div className={styles.modal}>
<div className={styles.description}>{t('settings.change_modal_description')}</div>
<FormField title="admin_console.settings.new_password">
<TextInput {...register('password', { required: true })} type="password" />
</FormField>
<FormField title="admin_console.settings.confirm_password">
<TextInput {...register('confirmPassword', { required: true })} type="password" />
</FormField>
</div>
</ModalLayout>
</ReactModal>
</>
);
};
export default ChangePassword;

View file

@ -15,6 +15,7 @@ import TabNav, { TabNavItem } from '@/components/TabNav';
import useUserPreferences, { UserPreferences } from '@/hooks/use-user-preferences';
import * as detailsStyles from '@/scss/details.module.scss';
import ChangePassword from './components/ChangePassword';
import * as styles from './index.module.scss';
const Settings = () => {
@ -94,6 +95,7 @@ const Settings = () => {
)}
/>
</FormField>
<ChangePassword />
</div>
<div className={detailsStyles.footer}>
<div className={detailsStyles.footerMain}>

View file

@ -1,6 +1,8 @@
import { arbitraryObjectGuard } from '@logto/schemas';
import { object } from 'zod';
import { passwordRegEx } from '@logto/shared';
import { object, string } from 'zod';
import { encryptUserPassword } from '@/lib/user';
import koaGuard from '@/middleware/koa-guard';
import { findUserById, updateUserById } from '@/queries/user';
@ -34,4 +36,25 @@ export default function meRoutes<T extends AuthedRouter>(router: T) {
return next();
}
);
router.patch(
'/me/password',
koaGuard({ body: object({ password: string().regex(passwordRegEx) }) }),
async (ctx, next) => {
const {
body: { password },
} = ctx.guard;
const { passwordEncrypted, passwordEncryptionMethod } = await encryptUserPassword(password);
await updateUserById(ctx.auth, {
passwordEncrypted,
passwordEncryptionMethod,
});
ctx.status = 204;
return next();
}
);
}

View file

@ -417,6 +417,18 @@ const translation = {
appearance_system: 'Sync with system',
appearance_light: 'Light mode',
appearance_dark: 'Dark mode',
saved: 'Saved!',
change_password: 'Change password',
change_password_description:
'You can change your password for this account. You will use your current username with new password to sign in Admin Console.',
change_modal_title: 'Change Account Password',
change_modal_description:
'You will use your current username with new password to sign in Admin Console.',
new_password: 'New password',
new_password_placeholder: 'Enter your password',
confirm_password: 'Confirm password',
confirm_password_placeholder: 'Confirm your password',
password_changed: 'Password changed!',
},
dashboard: {
title: 'Dashboard',

View file

@ -403,6 +403,16 @@ const translation = {
appearance_system: '跟随系统',
appearance_light: '浅色模式',
appearance_dark: '深色模式',
saved: '已保存!',
change_password: '修改密码',
change_password_description: '修改本账号密码。生效后使用当前用户名和新密码登录管理控制台。',
change_modal_title: '修改账号密码',
change_modal_description: '生效后使用当前用户名和新密码登录管理控制台。',
new_password: '新密码',
new_password_placeholder: '输入密码',
confirm_password: '确认密码',
confirm_password_placeholder: '确认密码',
password_changed: '密码已修改!',
},
dashboard: {
title: '仪表盘',