Skip to content

Commit

Permalink
Added ability to change user password (#1954)
Browse files Browse the repository at this point in the history
* added ability to change user password

* Update CHANGELOG.md

* fixed comments

* fixed linter warnings

* updated version of cvat-ui and cvat-core
  • Loading branch information
azhavoro authored Aug 7, 2020
1 parent 5da7d5d commit 4cb75a6
Show file tree
Hide file tree
Showing 20 changed files with 527 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support creating multiple jobs for each task through python cli (https://github.com/opencv/cvat/pull/1950)
- python cli over https (<https://github.com/opencv/cvat/pull/1942>)
- Error message when plugins weren't able to initialize instead of infinite loading (<https://github.com/opencv/cvat/pull/1966>)
- Ability to change user password (<https://github.com/opencv/cvat/pull/1954>)

### Changed
- Smaller object details (<https://github.com/opencv/cvat/pull/1877>)
Expand Down
2 changes: 1 addition & 1 deletion cvat-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cvat-core",
"version": "3.3.1",
"version": "3.4.0",
"description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "babel.config.js",
"scripts": {
Expand Down
4 changes: 4 additions & 0 deletions cvat-core/src/api-implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@
await serverProxy.server.logout();
};

cvat.server.changePassword.implementation = async (oldPassword, newPassword1, newPassword2) => {
await serverProxy.server.changePassword(oldPassword, newPassword1, newPassword2);
};

cvat.server.authorized.implementation = async () => {
const result = await serverProxy.server.authorized();
return result;
Expand Down
13 changes: 13 additions & 0 deletions cvat-core/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,19 @@ function build() {
.apiWrapper(cvat.server.logout);
return result;
},
/**
* Method allows to change user password
* @method changePassword
* @async
* @memberof module:API.cvat.server
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async changePassword(oldPassword, newPassword1, newPassword2) {
const result = await PluginRegistry
.apiWrapper(cvat.server.changePassword, oldPassword, newPassword1, newPassword2);
return result;
},
/**
* Method allows to know whether you are authorized on the server
* @method authorized
Expand Down
19 changes: 19 additions & 0 deletions cvat-core/src/server-proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,24 @@
Axios.defaults.headers.common.Authorization = '';
}

async function changePassword(oldPassword, newPassword1, newPassword2) {
try {
const data = JSON.stringify({
old_password: oldPassword,
new_password1: newPassword1,
new_password2:newPassword2,
});
await Axios.post(`${config.backendAPI}/auth/password/change`, data, {
proxy: config.proxy,
headers: {
'Content-Type': 'application/json',
},
});
} catch (errorData) {
throw generateError(errorData);
}
}

async function authorized() {
try {
await module.exports.users.getSelf();
Expand Down Expand Up @@ -768,6 +786,7 @@
exception,
login,
logout,
changePassword,
authorized,
register,
request: serverRequest,
Expand Down
2 changes: 1 addition & 1 deletion cvat-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cvat-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.6.7",
"version": "1.7.0",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {
Expand Down
52 changes: 51 additions & 1 deletion cvat-ui/src/actions/auth-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import { UserConfirmation } from 'components/register-page/register-form';
import getCore from 'cvat-core-wrapper';
import isReachable from 'utils/url-checker';

const cvat = getCore();

Expand All @@ -20,9 +21,16 @@ export enum AuthActionTypes {
LOGOUT = 'LOGOUT',
LOGOUT_SUCCESS = 'LOGOUT_SUCCESS',
LOGOUT_FAILED = 'LOGOUT_FAILED',
CHANGE_PASSWORD = 'CHANGE_PASSWORD',
CHANGE_PASSWORD_SUCCESS = 'CHANGE_PASSWORD_SUCCESS',
CHANGE_PASSWORD_FAILED = 'CHANGE_PASSWORD_FAILED',
SWITCH_CHANGE_PASSWORD_DIALOG = 'SWITCH_CHANGE_PASSWORD_DIALOG',
LOAD_AUTH_ACTIONS = 'LOAD_AUTH_ACTIONS',
LOAD_AUTH_ACTIONS_SUCCESS = 'LOAD_AUTH_ACTIONS_SUCCESS',
LOAD_AUTH_ACTIONS_FAILED = 'LOAD_AUTH_ACTIONS_FAILED',
}

const authActions = {
export const authActions = {
authorizeSuccess: (user: any) => createAction(AuthActionTypes.AUTHORIZED_SUCCESS, { user }),
authorizeFailed: (error: any) => createAction(AuthActionTypes.AUTHORIZED_FAILED, { error }),
login: () => createAction(AuthActionTypes.LOGIN),
Expand All @@ -34,6 +42,21 @@ const authActions = {
logout: () => createAction(AuthActionTypes.LOGOUT),
logoutSuccess: () => createAction(AuthActionTypes.LOGOUT_SUCCESS),
logoutFailed: (error: any) => createAction(AuthActionTypes.LOGOUT_FAILED, { error }),
changePassword: () => createAction(AuthActionTypes.CHANGE_PASSWORD),
changePasswordSuccess: () => createAction(AuthActionTypes.CHANGE_PASSWORD_SUCCESS),
changePasswordFailed: (error: any) => (
createAction(AuthActionTypes.CHANGE_PASSWORD_FAILED, { error })
),
switchChangePasswordDialog: (showChangePasswordDialog: boolean) => (
createAction(AuthActionTypes.SWITCH_CHANGE_PASSWORD_DIALOG, { showChangePasswordDialog })
),
loadServerAuthActions: () => createAction(AuthActionTypes.LOAD_AUTH_ACTIONS),
loadServerAuthActionsSuccess: (allowChangePassword: boolean) => (
createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_SUCCESS, { allowChangePassword })
),
loadServerAuthActionsFailed: (error: any) => (
createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_FAILED, { error })
),
};

export type AuthActions = ActionUnion<typeof authActions>;
Expand Down Expand Up @@ -100,3 +123,30 @@ export const authorizedAsync = (): ThunkAction => async (dispatch) => {
dispatch(authActions.authorizeFailed(error));
}
};

export const changePasswordAsync = (oldPassword: string,
newPassword1: string, newPassword2: string): ThunkAction => async (dispatch) => {
dispatch(authActions.changePassword());

try {
await cvat.server.changePassword(oldPassword, newPassword1, newPassword2);
dispatch(authActions.changePasswordSuccess());
} catch (error) {
dispatch(authActions.changePasswordFailed(error));
}
};

export const loadAuthActionsAsync = (): ThunkAction => async (dispatch) => {
dispatch(authActions.loadServerAuthActions());

try {
const promises: Promise<boolean>[] = [
isReachable(`${cvat.config.backendAPI}/auth/password/change`, 'OPTIONS'),
];
const [allowChangePassword] = await Promise.all(promises);

dispatch(authActions.loadServerAuthActionsSuccess(allowChangePassword));
} catch (error) {
dispatch(authActions.loadServerAuthActionsFailed(error));
}
};
166 changes: 166 additions & 0 deletions cvat-ui/src/components/change-password-modal/change-password-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// 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';

import patterns from 'utils/validation-patterns';

export interface ChangePasswordData {
oldPassword: string;
newPassword1: string;
newPassword2: string;
}

type ChangePasswordFormProps = {
fetching: boolean;
onSubmit(loginData: ChangePasswordData): void;
} & FormComponentProps;

class ChangePasswordFormComponent extends React.PureComponent<ChangePasswordFormProps> {
private validateConfirmation = (_: any, value: string, callback: Function): void => {
const { form } = this.props;
if (value && value !== form.getFieldValue('newPassword1')) {
callback('Two passwords that you enter is inconsistent!');
} 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,
} = this.props;

form.validateFields((error, values): void => {
if (!error) {
const validatedFields = {
...values,
confirmations: [],
};

onSubmit(validatedFields);
}
});
};

private renderOldPasswordField(): JSX.Element {
const { form } = this.props;

return (
<Form.Item hasFeedback>
{form.getFieldDecorator('oldPassword', {
rules: [{
required: true,
message: 'Please input your current password!',
}],
})(<Input.Password
autoComplete='new-password'
prefix={<Icon type='lock' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Current password'
/>)}
</Form.Item>
);
}

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='change-password-form'
>
{this.renderOldPasswordField()}
{this.renderNewPasswordField()}
{this.renderNewPasswordConfirmationField()}

<Form.Item>
<Button
type='primary'
htmlType='submit'
className='change-password-form-button'
loading={fetching}
disabled={fetching}
>
Submit
</Button>
</Form.Item>
</Form>
);
}
}

export default Form.create<ChangePasswordFormProps>()(ChangePasswordFormComponent);
Loading

0 comments on commit 4cb75a6

Please sign in to comment.