提交 4c24237e 编写于 作者: N Nikita Manovich

Merge remote-tracking branch 'origin/develop' into Marishka17-cache

......@@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [1.2.0] - Unreleased
### Added
-
- Added password reset functionality (<https://github.com/opencv/cvat/pull/2058>)
### Changed
- UI models (like DEXTR) were redesigned to be more interactive (<https://github.com/opencv/cvat/pull/2054>)
......
......@@ -99,6 +99,14 @@
await serverProxy.server.changePassword(oldPassword, newPassword1, newPassword2);
};
cvat.server.requestPasswordReset.implementation = async (email) => {
await serverProxy.server.requestPasswordReset(email);
};
cvat.server.resetPassword.implementation = async(newPassword1, newPassword2, uid, token) => {
await serverProxy.server.resetPassword(newPassword1, newPassword2, uid, token);
};
cvat.server.authorized.implementation = async () => {
const result = await serverProxy.server.authorized();
return result;
......
......@@ -199,6 +199,9 @@ function build() {
* @method changePassword
* @async
* @memberof module:API.cvat.server
* @param {string} oldPassword Current password for the account
* @param {string} newPassword1 New password for the account
* @param {string} newPassword2 Confirmation password for the account
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
......@@ -207,6 +210,38 @@ function build() {
.apiWrapper(cvat.server.changePassword, oldPassword, newPassword1, newPassword2);
return result;
},
/**
* Method allows to reset user password
* @method requestPasswordReset
* @async
* @memberof module:API.cvat.server
* @param {string} email A email address for the account
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async requestPasswordReset(email) {
const result = await PluginRegistry
.apiWrapper(cvat.server.requestPasswordReset, email);
return result;
},
/**
* Method allows to confirm reset user password
* @method resetPassword
* @async
* @memberof module:API.cvat.server
* @param {string} newPassword1 New password for the account
* @param {string} newPassword2 Confirmation password for the account
* @param {string} uid User id
* @param {string} token Request authentication token
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async resetPassword(newPassword1, newPassword2, uid, token) {
const result = await PluginRegistry
.apiWrapper(cvat.server.resetPassword, newPassword1, newPassword2,
uid, token);
return result;
},
/**
* Method allows to know whether you are authorized on the server
* @method authorized
......
......@@ -264,6 +264,41 @@
}
}
async function requestPasswordReset(email) {
try {
const data = JSON.stringify({
email,
});
await Axios.post(`${config.backendAPI}/auth/password/reset`, data, {
proxy: config.proxy,
headers: {
'Content-Type': 'application/json',
},
});
} catch (errorData) {
throw generateError(errorData);
}
}
async function resetPassword(newPassword1, newPassword2, uid, token) {
try {
const data = JSON.stringify({
new_password1: newPassword1,
new_password2: newPassword2,
uid,
token,
});
await Axios.post(`${config.backendAPI}/auth/password/reset/confirm`, data, {
proxy: config.proxy,
headers: {
'Content-Type': 'application/json',
},
});
} catch (errorData) {
throw generateError(errorData);
}
}
async function authorized() {
try {
await module.exports.users.getSelf();
......@@ -787,6 +822,8 @@
login,
logout,
changePassword,
requestPasswordReset,
resetPassword,
authorized,
register,
request: serverRequest,
......
......@@ -25,6 +25,12 @@ export enum AuthActionTypes {
CHANGE_PASSWORD_SUCCESS = 'CHANGE_PASSWORD_SUCCESS',
CHANGE_PASSWORD_FAILED = 'CHANGE_PASSWORD_FAILED',
SWITCH_CHANGE_PASSWORD_DIALOG = 'SWITCH_CHANGE_PASSWORD_DIALOG',
REQUEST_PASSWORD_RESET = 'REQUEST_PASSWORD_RESET',
REQUEST_PASSWORD_RESET_SUCCESS = 'REQUEST_PASSWORD_RESET_SUCCESS',
REQUEST_PASSWORD_RESET_FAILED = 'REQUEST_PASSWORD_RESET_FAILED',
RESET_PASSWORD = 'RESET_PASSWORD_CONFIRM',
RESET_PASSWORD_SUCCESS = 'RESET_PASSWORD_CONFIRM_SUCCESS',
RESET_PASSWORD_FAILED = 'RESET_PASSWORD_CONFIRM_FAILED',
LOAD_AUTH_ACTIONS = 'LOAD_AUTH_ACTIONS',
LOAD_AUTH_ACTIONS_SUCCESS = 'LOAD_AUTH_ACTIONS_SUCCESS',
LOAD_AUTH_ACTIONS_FAILED = 'LOAD_AUTH_ACTIONS_FAILED',
......@@ -50,9 +56,22 @@ export const authActions = {
switchChangePasswordDialog: (showChangePasswordDialog: boolean) => (
createAction(AuthActionTypes.SWITCH_CHANGE_PASSWORD_DIALOG, { showChangePasswordDialog })
),
requestPasswordReset: () => createAction(AuthActionTypes.REQUEST_PASSWORD_RESET),
requestPasswordResetSuccess: () => createAction(AuthActionTypes.REQUEST_PASSWORD_RESET_SUCCESS),
requestPasswordResetFailed: (error: any) => (
createAction(AuthActionTypes.REQUEST_PASSWORD_RESET_FAILED, { error })
),
resetPassword: () => createAction(AuthActionTypes.RESET_PASSWORD),
resetPasswordSuccess: () => createAction(AuthActionTypes.RESET_PASSWORD_SUCCESS),
resetPasswordFailed: (error: any) => (
createAction(AuthActionTypes.RESET_PASSWORD_FAILED, { error })
),
loadServerAuthActions: () => createAction(AuthActionTypes.LOAD_AUTH_ACTIONS),
loadServerAuthActionsSuccess: (allowChangePassword: boolean) => (
createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_SUCCESS, { allowChangePassword })
loadServerAuthActionsSuccess: (allowChangePassword: boolean, allowResetPassword: boolean) => (
createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_SUCCESS, {
allowChangePassword,
allowResetPassword,
})
),
loadServerAuthActionsFailed: (error: any) => (
createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_FAILED, { error })
......@@ -135,16 +154,49 @@ export const changePasswordAsync = (oldPassword: string,
}
};
export const requestPasswordResetAsync = (email: string): ThunkAction => async (dispatch) => {
dispatch(authActions.requestPasswordReset());
try {
await cvat.server.requestPasswordReset(email);
dispatch(authActions.requestPasswordResetSuccess());
} catch (error) {
dispatch(authActions.requestPasswordResetFailed(error));
}
};
export const resetPasswordAsync = (
newPassword1: string,
newPassword2: string,
uid: string,
token: string,
): ThunkAction => async (dispatch) => {
dispatch(authActions.resetPassword());
try {
await cvat.server.resetPassword(newPassword1, newPassword2, uid, token);
dispatch(authActions.resetPasswordSuccess());
} catch (error) {
dispatch(authActions.resetPasswordFailed(error));
}
};
export const loadAuthActionsAsync = (): ThunkAction => async (dispatch) => {
dispatch(authActions.loadServerAuthActions());
try {
const promises: Promise<boolean>[] = [
isReachable(`${cvat.config.backendAPI}/auth/password/change`, 'OPTIONS'),
isReachable(`${cvat.config.backendAPI}/auth/password/reset`, 'OPTIONS'),
];
const [allowChangePassword] = await Promise.all(promises);
dispatch(authActions.loadServerAuthActionsSuccess(allowChangePassword));
const [
allowChangePassword,
allowResetPassword] = await Promise.all(promises);
dispatch(authActions.loadServerAuthActionsSuccess(
allowChangePassword,
allowResetPassword,
));
} catch (error) {
dispatch(authActions.loadServerAuthActionsFailed(error));
}
......
......@@ -23,6 +23,8 @@ import ModelsPageContainer from 'containers/models-page/models-page';
import AnnotationPageContainer from 'containers/annotation-page/annotation-page';
import LoginPageContainer from 'containers/login-page/login-page';
import RegisterPageContainer from 'containers/register-page/register-page';
import ResetPasswordPageComponent from 'components/reset-password-page/reset-password-page';
import ResetPasswordPageConfirmComponent from 'components/reset-password-confirm-page/reset-password-confirm-page';
import Header from 'components/header/header';
import { customWaViewHit } from 'utils/enviroment';
import showPlatformNotification, { stopNotifications, platformInfo } from 'utils/platform-checker';
......@@ -61,7 +63,6 @@ interface CVATAppProps {
userAgreementsInitialized: boolean;
authActionsFetching: boolean;
authActionsInitialized: boolean;
allowChangePassword: boolean;
notifications: NotificationsState;
user: any;
}
......@@ -332,6 +333,8 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
<Switch>
<Route exact path='/auth/register' component={RegisterPageContainer} />
<Route exact path='/auth/login' component={LoginPageContainer} />
<Route exact path='/auth/password/reset' component={ResetPasswordPageComponent} />
<Route exact path='/auth/password/reset/confirm' component={ResetPasswordPageConfirmComponent} />
<Redirect to='/auth/login' />
</Switch>
</GlobalErrorBoundary>
......
......@@ -14,6 +14,7 @@ import CookieDrawer from './cookie-policy-drawer';
interface LoginPageComponentProps {
fetching: boolean;
renderResetPassword: boolean;
onLogin: (username: string, password: string) => void;
}
......@@ -29,6 +30,7 @@ function LoginPageComponent(props: LoginPageComponentProps & RouteComponentProps
const {
fetching,
onLogin,
renderResetPassword,
} = props;
return (
......@@ -50,6 +52,16 @@ function LoginPageComponent(props: LoginPageComponentProps & RouteComponentProps
</Text>
</Col>
</Row>
{ renderResetPassword
&& (
<Row type='flex' justify='start' align='top'>
<Col>
<Text strong>
<Link to='/auth/password/reset'>Forgot your password?</Link>
</Text>
</Col>
</Row>
)}
</Col>
</Row>
<CookieDrawer />
......
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import Form, { FormComponentProps } from 'antd/lib/form/Form';
import Button from 'antd/lib/button';
import Icon from 'antd/lib/icon';
import Input from 'antd/lib/input';
import patterns from 'utils/validation-patterns';
export interface ResetPasswordConfirmData {
newPassword1: string;
newPassword2: string;
uid: string;
token: string;
}
type ResetPasswordConfirmFormProps = {
fetching: boolean;
onSubmit(resetPasswordConfirmData: ResetPasswordConfirmData): void;
} & FormComponentProps & RouteComponentProps;
class ResetPasswordConfirmFormComponent extends React.PureComponent<ResetPasswordConfirmFormProps> {
private validateConfirmation = (_: any, value: string, callback: Function): void => {
const { form } = this.props;
if (value && value !== form.getFieldValue('newPassword1')) {
callback('Passwords do not match!');
} else {
callback();
}
};
private validatePassword = (_: any, value: string, callback: Function): void => {
const { form } = this.props;
if (!patterns.validatePasswordLength.pattern.test(value)) {
callback(patterns.validatePasswordLength.message);
}
if (!patterns.passwordContainsNumericCharacters.pattern.test(value)) {
callback(patterns.passwordContainsNumericCharacters.message);
}
if (!patterns.passwordContainsUpperCaseCharacter.pattern.test(value)) {
callback(patterns.passwordContainsUpperCaseCharacter.message);
}
if (!patterns.passwordContainsLowerCaseCharacter.pattern.test(value)) {
callback(patterns.passwordContainsLowerCaseCharacter.message);
}
if (value) {
form.validateFields(['newPassword2'], { force: true });
}
callback();
};
private handleSubmit = (e: React.FormEvent): void => {
e.preventDefault();
const {
form,
onSubmit,
location,
} = this.props;
const params = new URLSearchParams(location.search);
const uid = params.get('uid');
const token = params.get('token');
form.validateFields((error, values): void => {
if (!error) {
const validatedFields = {
...values,
uid,
token,
};
onSubmit(validatedFields);
}
});
};
private renderNewPasswordField(): JSX.Element {
const { form } = this.props;
return (
<Form.Item hasFeedback>
{form.getFieldDecorator('newPassword1', {
rules: [{
required: true,
message: 'Please input new password!',
}, {
validator: this.validatePassword,
}],
})(<Input.Password
autoComplete='new-password'
prefix={<Icon type='lock' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='New password'
/>)}
</Form.Item>
);
}
private renderNewPasswordConfirmationField(): JSX.Element {
const { form } = this.props;
return (
<Form.Item hasFeedback>
{form.getFieldDecorator('newPassword2', {
rules: [{
required: true,
message: 'Please confirm your new password!',
}, {
validator: this.validateConfirmation,
}],
})(<Input.Password
autoComplete='new-password'
prefix={<Icon type='lock' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Confirm new password'
/>)}
</Form.Item>
);
}
public render(): JSX.Element {
const { fetching } = this.props;
return (
<Form
onSubmit={this.handleSubmit}
className='cvat-reset-password-confirm-form'
>
{this.renderNewPasswordField()}
{this.renderNewPasswordConfirmationField()}
<Form.Item>
<Button
type='primary'
htmlType='submit'
className='cvat-reset-password-confirm-form-button'
loading={fetching}
disabled={fetching}
>
Change password
</Button>
</Form.Item>
</Form>
);
}
}
export default withRouter(
Form.create<ResetPasswordConfirmFormProps>()(ResetPasswordConfirmFormComponent),
);
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import Title from 'antd/lib/typography/Title';
import Text from 'antd/lib/typography/Text';
import { Row, Col } from 'antd/lib/grid';
import { CombinedState } from 'reducers/interfaces';
import { resetPasswordAsync } from 'actions/auth-actions';
import ResetPasswordConfirmForm, { ResetPasswordConfirmData } from './reset-password-confirm-form';
interface StateToProps {
fetching: boolean;
}
interface DispatchToProps {
onResetPasswordConfirm: typeof resetPasswordAsync;
}
interface ResetPasswordConfirmPageComponentProps {
fetching: boolean;
onResetPasswordConfirm: (
newPassword1: string,
newPassword2: string,
uid: string,
token: string) => void;
}
function mapStateToProps(state: CombinedState): StateToProps {
return {
fetching: state.auth.fetching,
};
}
const mapDispatchToProps: DispatchToProps = {
onResetPasswordConfirm: resetPasswordAsync,
};
function ResetPasswordPagePageComponent(
props: ResetPasswordConfirmPageComponentProps,
): JSX.Element {
const sizes = {
xs: { span: 14 },
sm: { span: 14 },
md: { span: 10 },
lg: { span: 4 },
xl: { span: 4 },
};
const {
fetching,
onResetPasswordConfirm,
} = props;
return (
<Row type='flex' justify='center' align='middle'>
<Col {...sizes}>
<Title level={2}> Change password </Title>
<ResetPasswordConfirmForm
fetching={fetching}
onSubmit={(resetPasswordConfirmData: ResetPasswordConfirmData): void => {
onResetPasswordConfirm(
resetPasswordConfirmData.newPassword1,
resetPasswordConfirmData.newPassword2,
resetPasswordConfirmData.uid,
resetPasswordConfirmData.token,
);
}}
/>
</Col>
</Row>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ResetPasswordPagePageComponent);
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import Form, { FormComponentProps } from 'antd/lib/form/Form';
import Button from 'antd/lib/button';
import Icon from 'antd/lib/icon';
import Input from 'antd/lib/input';
export interface ResetPasswordData {
email: string;
}
type ResetPasswordFormProps = {
fetching: boolean;
onSubmit(resetPasswordData: ResetPasswordData): void;
} & FormComponentProps;
class ResetPasswordFormComponent extends React.PureComponent<ResetPasswordFormProps> {
private handleSubmit = (e: React.FormEvent): void => {
e.preventDefault();
const {
form,
onSubmit,
} = this.props;
form.validateFields((error, values): void => {
if (!error) {
onSubmit(values);
}
});
};
private renderEmailField(): JSX.Element {
const { form } = this.props;
return (
<Form.Item hasFeedback>
{form.getFieldDecorator('email', {
rules: [{
type: 'email',
message: 'The input is not valid E-mail!',
}, {
required: true,
message: 'Please specify an email address',
}],
})(
<Input
autoComplete='email'
prefix={<Icon type='mail' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Email address'
/>,
)}
</Form.Item>
);
}
public render(): JSX.Element {
const { fetching } = this.props;
return (
<Form onSubmit={this.handleSubmit} className='cvat-reset-password-form'>
{this.renderEmailField()}
<Form.Item>
<Button
type='primary'
loading={fetching}
disabled={fetching}
htmlType='submit'
className='cvat-reset-password-form-button'
>
Reset password
</Button>
</Form.Item>
</Form>
);
}
}
export default Form.create<ResetPasswordFormProps>()(ResetPasswordFormComponent);
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import Title from 'antd/lib/typography/Title';
import Text from 'antd/lib/typography/Text';
import { Row, Col } from 'antd/lib/grid';
import { requestPasswordResetAsync } from 'actions/auth-actions';
import { CombinedState } from 'reducers/interfaces';
import ResetPasswordForm, { ResetPasswordData } from './reset-password-form';
interface StateToProps {
fetching: boolean;
}
interface DispatchToProps {
onResetPassword: typeof requestPasswordResetAsync;
}
interface ResetPasswordPageComponentProps {
fetching: boolean;
onResetPassword: (email: string) => void;
}
function mapStateToProps(state: CombinedState): StateToProps {
return {
fetching: state.auth.fetching,
};
}
const mapDispatchToProps: DispatchToProps = {
onResetPassword: requestPasswordResetAsync,
};
function ResetPasswordPagePageComponent(props: ResetPasswordPageComponentProps): JSX.Element {
const sizes = {
xs: { span: 14 },
sm: { span: 14 },
md: { span: 10 },
lg: { span: 4 },
xl: { span: 4 },
};
const {
fetching,
onResetPassword,
} = props;
return (
<Row type='flex' justify='center' align='middle'>
<Col {...sizes}>
<Title level={2}> Reset password </Title>
<ResetPasswordForm
fetching={fetching}
onSubmit={(resetPasswordData: ResetPasswordData): void => {
onResetPassword(resetPasswordData.email);
}}
/>
<Row type='flex' justify='start' align='top'>
<Col>
<Text strong>
Go to
<Link to='/auth/login'> login page </Link>
</Text>
</Col>
</Row>
</Col>
</Row>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ResetPasswordPagePageComponent);
......@@ -9,6 +9,7 @@ import { loginAsync } from 'actions/auth-actions';
interface StateToProps {
fetching: boolean;
renderResetPassword: boolean;
}
interface DispatchToProps {
......@@ -18,6 +19,7 @@ interface DispatchToProps {
function mapStateToProps(state: CombinedState): StateToProps {
return {
fetching: state.auth.fetching,
renderResetPassword: state.auth.allowResetPassword,
};
}
......
......@@ -57,6 +57,7 @@ interface StateToProps {
authActionsFetching: boolean;
authActionsInitialized: boolean;
allowChangePassword: boolean;
allowResetPassword: boolean;
notifications: NotificationsState;
user: any;
keyMap: Record<string, ExtendedKeyMapOptions>;
......@@ -105,6 +106,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
authActionsFetching: auth.authActionsFetching,
authActionsInitialized: auth.authActionsInitialized,
allowChangePassword: auth.allowChangePassword,
allowResetPassword: auth.allowResetPassword,
notifications: state.notifications,
user: auth.user,
keyMap: shortcuts.keyMap,
......
......@@ -14,6 +14,7 @@ const defaultState: AuthState = {
authActionsInitialized: false,
allowChangePassword: false,
showChangePasswordDialog: false,
allowResetPassword: false,
};
export default function (state = defaultState, action: AuthActions | boundariesActions): AuthState {
......@@ -83,7 +84,6 @@ export default function (state = defaultState, action: AuthActions | boundariesA
...state,
fetching: false,
showChangePasswordDialog: false,
};
case AuthActionTypes.CHANGE_PASSWORD_FAILED:
return {
......@@ -97,6 +97,36 @@ export default function (state = defaultState, action: AuthActions | boundariesA
? !state.showChangePasswordDialog
: action.payload.showChangePasswordDialog,
};
case AuthActionTypes.REQUEST_PASSWORD_RESET:
return {
...state,
fetching: true,
};
case AuthActionTypes.REQUEST_PASSWORD_RESET_SUCCESS:
return {
...state,
fetching: false,
};
case AuthActionTypes.REQUEST_PASSWORD_RESET_FAILED:
return {
...state,
fetching: false,
};
case AuthActionTypes.RESET_PASSWORD:
return {
...state,
fetching: true,
};
case AuthActionTypes.RESET_PASSWORD_SUCCESS:
return {
...state,
fetching: false,
};
case AuthActionTypes.RESET_PASSWORD_FAILED:
return {
...state,
fetching: false,
};
case AuthActionTypes.LOAD_AUTH_ACTIONS:
return {
...state,
......@@ -108,6 +138,7 @@ export default function (state = defaultState, action: AuthActions | boundariesA
authActionsFetching: false,
authActionsInitialized: true,
allowChangePassword: action.payload.allowChangePassword,
allowResetPassword: action.payload.allowResetPassword,
};
case AuthActionTypes.LOAD_AUTH_ACTIONS_FAILED:
return {
......@@ -115,6 +146,7 @@ export default function (state = defaultState, action: AuthActions | boundariesA
authActionsFetching: false,
authActionsInitialized: true,
allowChangePassword: false,
allowResetPassword: false,
};
case BoundariesActionTypes.RESET_AFTER_ERROR: {
return { ...defaultState };
......
......@@ -17,6 +17,7 @@ export interface AuthState {
authActionsInitialized: boolean;
showChangePasswordDialog: boolean;
allowChangePassword: boolean;
allowResetPassword: boolean;
}
export interface TasksQuery {
......@@ -184,6 +185,8 @@ export interface NotificationsState {
logout: null | ErrorState;
register: null | ErrorState;
changePassword: null | ErrorState;
requestPasswordReset: null | ErrorState;
resetPassword: null | ErrorState;
loadAuthActions: null | ErrorState;
};
tasks: {
......@@ -253,6 +256,8 @@ export interface NotificationsState {
auth: {
changePasswordDone: string;
registerDone: string;
requestPasswordResetDone: string;
resetPasswordDone: string;
};
};
}
......
......@@ -27,6 +27,8 @@ const defaultState: NotificationsState = {
logout: null,
register: null,
changePassword: null,
requestPasswordReset: null,
resetPassword: null,
loadAuthActions: null,
},
tasks: {
......@@ -96,6 +98,8 @@ const defaultState: NotificationsState = {
auth: {
changePasswordDone: '',
registerDone: '',
requestPasswordResetDone: '',
resetPasswordDone: '',
},
},
};
......@@ -208,6 +212,61 @@ export default function (state = defaultState, action: AnyAction): Notifications
},
};
}
case AuthActionTypes.REQUEST_PASSWORD_RESET_SUCCESS: {
return {
...state,
messages: {
...state.messages,
auth: {
...state.messages.auth,
requestPasswordResetDone: `Check your email for a link to reset your password.
If it doesn’t appear within a few minutes, check your spam folder.`,
},
},
};
}
case AuthActionTypes.REQUEST_PASSWORD_RESET_FAILED: {
return {
...state,
errors: {
...state.errors,
auth: {
...state.errors.auth,
requestPasswordReset: {
message: 'Could not reset password on the server.',
reason: action.payload.error.toString(),
},
},
},
};
}
case AuthActionTypes.RESET_PASSWORD_SUCCESS: {
return {
...state,
messages: {
...state.messages,
auth: {
...state.messages.auth,
resetPasswordDone: 'Password has been reset with the new password.',
},
},
};
}
case AuthActionTypes.RESET_PASSWORD_FAILED: {
return {
...state,
errors: {
...state.errors,
auth: {
...state.errors.auth,
resetPassword: {
message: 'Could not set new password on the server.',
reason: action.payload.error.toString(),
},
},
},
};
}
case AuthActionTypes.LOAD_AUTH_ACTIONS_FAILED: {
return {
...state,
......
from rest_auth.registration.serializers import RegisterSerializer
from rest_auth.serializers import PasswordResetSerializer
from rest_framework import serializers
from django.conf import settings
class RegisterSerializerEx(RegisterSerializer):
first_name = serializers.CharField(required=False)
......@@ -14,3 +17,15 @@ class RegisterSerializerEx(RegisterSerializer):
})
return data
class PasswordResetSerializerEx(PasswordResetSerializer):
def get_email_options(self):
domain = None
if hasattr(settings, 'UI_HOST') and settings.UI_HOST:
domain = settings.UI_HOST
if hasattr(settings, 'UI_PORT') and settings.UI_PORT:
domain += ':{}'.format(settings.UI_PORT)
return {
'email_template_name': 'authentication/password_reset_email.html',
'domain_override': domain
}
{% load i18n %}{% autoescape off %}
{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
{{ protocol }}://{{ domain }}/auth/password/reset/confirm?uid={{ uid }}&token={{ token }}
{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
{% trans "Thanks for using our site!" %}
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
{% endautoescape %}
......@@ -154,7 +154,11 @@ REST_FRAMEWORK = {
}
REST_AUTH_REGISTER_SERIALIZERS = {
'REGISTER_SERIALIZER': 'cvat.apps.restrictions.serializers.RestrictedRegisterSerializer'
'REGISTER_SERIALIZER': 'cvat.apps.restrictions.serializers.RestrictedRegisterSerializer',
}
REST_AUTH_SERIALIZERS = {
'PASSWORD_RESET_SERIALIZER': 'cvat.apps.authentication.serializers.PasswordResetSerializerEx',
}
if os.getenv('DJANGO_LOG_VIEWER_HOST'):
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册