Merge remote-tracking branch 'origin/develop' into Marishka17-cache
commit
4c24237e8a
@ -0,0 +1,156 @@
|
|||||||
|
// 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),
|
||||||
|
);
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
// 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);
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
// 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);
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
// 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);
|
||||||
@ -1,16 +1,31 @@
|
|||||||
from rest_auth.registration.serializers import RegisterSerializer
|
from rest_auth.registration.serializers import RegisterSerializer
|
||||||
|
from rest_auth.serializers import PasswordResetSerializer
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
class RegisterSerializerEx(RegisterSerializer):
|
class RegisterSerializerEx(RegisterSerializer):
|
||||||
first_name = serializers.CharField(required=False)
|
first_name = serializers.CharField(required=False)
|
||||||
last_name = serializers.CharField(required=False)
|
last_name = serializers.CharField(required=False)
|
||||||
|
|
||||||
|
def get_cleaned_data(self):
|
||||||
|
data = super().get_cleaned_data()
|
||||||
|
data.update({
|
||||||
|
'first_name': self.validated_data.get('first_name', ''),
|
||||||
|
'last_name': self.validated_data.get('last_name', ''),
|
||||||
|
})
|
||||||
|
|
||||||
def get_cleaned_data(self):
|
return data
|
||||||
data = super().get_cleaned_data()
|
|
||||||
data.update({
|
|
||||||
'first_name': self.validated_data.get('first_name', ''),
|
|
||||||
'last_name': self.validated_data.get('last_name', ''),
|
|
||||||
})
|
|
||||||
|
|
||||||
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
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
{% 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 %}
|
||||||
Loading…
Reference in New Issue