From 4cb75a6a7f9fc508730fb14ea85fa09784bdf05b Mon Sep 17 00:00:00 2001 From: Andrey Zhavoronkov <41117609+azhavoro@users.noreply.github.com> Date: Fri, 7 Aug 2020 07:09:18 +0300 Subject: [PATCH] Added ability to change user password (#1954) * added ability to change user password * Update CHANGELOG.md * fixed comments * fixed linter warnings * updated version of cvat-ui and cvat-core --- CHANGELOG.md | 1 + cvat-core/package.json | 2 +- cvat-core/src/api-implementation.js | 4 + cvat-core/src/api.js | 13 ++ cvat-core/src/server-proxy.js | 19 ++ cvat-ui/package-lock.json | 2 +- cvat-ui/package.json | 2 +- cvat-ui/src/actions/auth-actions.ts | 52 +++++- .../change-password-form.tsx | 166 ++++++++++++++++++ .../change-password-modal.tsx | 84 +++++++++ cvat-ui/src/components/cvat-app.tsx | 11 ++ cvat-ui/src/components/header/header.tsx | 25 +++ cvat-ui/src/containers/header/header.tsx | 15 +- cvat-ui/src/index.tsx | 14 +- cvat-ui/src/reducers/auth-reducer.ts | 47 +++++ cvat-ui/src/reducers/interfaces.ts | 9 + cvat-ui/src/reducers/notifications-reducer.ts | 47 +++++ cvat-ui/src/utils/plugin-checker.ts | 11 +- cvat-ui/src/utils/url-checker.ts | 18 ++ cvat/settings/base.py | 1 + 20 files changed, 527 insertions(+), 16 deletions(-) create mode 100644 cvat-ui/src/components/change-password-modal/change-password-form.tsx create mode 100644 cvat-ui/src/components/change-password-modal/change-password-modal.tsx create mode 100644 cvat-ui/src/utils/url-checker.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 99018445a385..a4cd6e416d02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 () - Error message when plugins weren't able to initialize instead of infinite loading () +- Ability to change user password () ### Changed - Smaller object details () diff --git a/cvat-core/package.json b/cvat-core/package.json index a34f53d06485..7d75dbc525e8 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -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": { diff --git a/cvat-core/src/api-implementation.js b/cvat-core/src/api-implementation.js index 7372ed3b6d48..32dd6c5bf85d 100644 --- a/cvat-core/src/api-implementation.js +++ b/cvat-core/src/api-implementation.js @@ -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; diff --git a/cvat-core/src/api.js b/cvat-core/src/api.js index 53c30d0d5058..ab5ba39bfae5 100644 --- a/cvat-core/src/api.js +++ b/cvat-core/src/api.js @@ -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 diff --git a/cvat-core/src/server-proxy.js b/cvat-core/src/server-proxy.js index 00f06d77c41d..4a0c9ebd8b69 100644 --- a/cvat-core/src/server-proxy.js +++ b/cvat-core/src/server-proxy.js @@ -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(); @@ -768,6 +786,7 @@ exception, login, logout, + changePassword, authorized, register, request: serverRequest, diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 0f2ece180fa9..256a48e6d4bb 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.6.7", + "version": "1.7.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/cvat-ui/package.json b/cvat-ui/package.json index b97d19fe0086..1ec122bdea0e 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -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": { diff --git a/cvat-ui/src/actions/auth-actions.ts b/cvat-ui/src/actions/auth-actions.ts index f7a0f3e9f246..3aff6ef367bf 100644 --- a/cvat-ui/src/actions/auth-actions.ts +++ b/cvat-ui/src/actions/auth-actions.ts @@ -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(); @@ -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), @@ -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; @@ -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[] = [ + isReachable(`${cvat.config.backendAPI}/auth/password/change`, 'OPTIONS'), + ]; + const [allowChangePassword] = await Promise.all(promises); + + dispatch(authActions.loadServerAuthActionsSuccess(allowChangePassword)); + } catch (error) { + dispatch(authActions.loadServerAuthActionsFailed(error)); + } +}; diff --git a/cvat-ui/src/components/change-password-modal/change-password-form.tsx b/cvat-ui/src/components/change-password-modal/change-password-form.tsx new file mode 100644 index 000000000000..f1548daad574 --- /dev/null +++ b/cvat-ui/src/components/change-password-modal/change-password-form.tsx @@ -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 { + 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.getFieldDecorator('oldPassword', { + rules: [{ + required: true, + message: 'Please input your current password!', + }], + })(} + placeholder='Current password' + />)} + + ); + } + + private renderNewPasswordField(): JSX.Element { + const { form } = this.props; + + return ( + + {form.getFieldDecorator('newPassword1', { + rules: [{ + required: true, + message: 'Please input new password!', + }, { + validator: this.validatePassword, + }], + })(} + placeholder='New password' + />)} + + ); + } + + private renderNewPasswordConfirmationField(): JSX.Element { + const { form } = this.props; + + return ( + + {form.getFieldDecorator('newPassword2', { + rules: [{ + required: true, + message: 'Please confirm your new password!', + }, { + validator: this.validateConfirmation, + }], + })(} + placeholder='Confirm new password' + />)} + + ); + } + + public render(): JSX.Element { + const { fetching } = this.props; + + return ( +
+ {this.renderOldPasswordField()} + {this.renderNewPasswordField()} + {this.renderNewPasswordConfirmationField()} + + + + +
+ ); + } +} + +export default Form.create()(ChangePasswordFormComponent); diff --git a/cvat-ui/src/components/change-password-modal/change-password-modal.tsx b/cvat-ui/src/components/change-password-modal/change-password-modal.tsx new file mode 100644 index 000000000000..2f4f538bf853 --- /dev/null +++ b/cvat-ui/src/components/change-password-modal/change-password-modal.tsx @@ -0,0 +1,84 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { connect } from 'react-redux'; +import Modal from 'antd/lib/modal'; +import Title from 'antd/lib/typography/Title'; + +import { changePasswordAsync } from 'actions/auth-actions'; +import { CombinedState } from 'reducers/interfaces'; +import ChangePasswordForm, { ChangePasswordData } from './change-password-form'; + + +interface StateToProps { + fetching: boolean; + visible: boolean; +} + +interface DispatchToProps { + onChangePassword( + oldPassword: string, + newPassword1: string, + newPassword2: string): void; +} + +interface ChangePasswordPageComponentProps { + fetching: boolean; + visible: boolean; + onChangePassword: (oldPassword: string, newPassword1: string, newPassword2: string) => void; + onClose(): void; +} + +function mapStateToProps(state: CombinedState): StateToProps { + return { + fetching: state.auth.fetching, + visible: state.auth.showChangePasswordDialog, + }; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return ({ + onChangePassword(oldPassword: string, newPassword1: string, newPassword2: string): void { + dispatch(changePasswordAsync(oldPassword, newPassword1, newPassword2)); + }, + }); +} + +function ChangePasswordComponent(props: ChangePasswordPageComponentProps): JSX.Element { + const { + fetching, + onChangePassword, + visible, + onClose, + } = props; + + return ( + Change password} + okType='primary' + okText='Submit' + footer={null} + visible={visible} + destroyOnClose + onCancel={onClose} + > + { + onChangePassword( + changePasswordData.oldPassword, + changePasswordData.newPassword1, + changePasswordData.newPassword2, + ); + }} + fetching={fetching} + /> + + ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(ChangePasswordComponent); diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index 19c26f66a072..3417b4b451b5 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -38,6 +38,7 @@ interface CVATAppProps { resetMessages: () => void; switchShortcutsDialog: () => void; switchSettingsDialog: () => void; + loadAuthActions: () => void; keyMap: Record; userInitialized: boolean; userFetching: boolean; @@ -51,6 +52,9 @@ interface CVATAppProps { aboutFetching: boolean; userAgreementsFetching: boolean; userAgreementsInitialized: boolean; + authActionsFetching: boolean; + authActionsInitialized: boolean; + allowChangePassword: boolean; notifications: NotificationsState; user: any; } @@ -84,6 +88,7 @@ class CVATApplication extends React.PureComponent void; switchSettingsDialog: (show: boolean) => void; + switchChangePasswordDialog: (show: boolean) => void; logoutFetching: boolean; + changePasswordFetching: boolean; installedAnalytics: boolean; serverHost: string; username: string; @@ -34,6 +37,8 @@ interface HeaderContainerProps { uiVersion: string; switchSettingsShortcut: string; settingsDialogShown: boolean; + changePasswordDialogShown: boolean; + renderChangePasswordItem: boolean; } type Props = HeaderContainerProps & RouteComponentProps; @@ -51,9 +56,12 @@ function HeaderContainer(props: Props): JSX.Element { uiVersion, onLogout, logoutFetching, + changePasswordFetching, settingsDialogShown, switchSettingsShortcut, switchSettingsDialog, + switchChangePasswordDialog, + renderChangePasswordItem, } = props; const { @@ -136,6 +144,16 @@ function HeaderContainer(props: Props): JSX.Element { About + {renderChangePasswordItem && ( + switchChangePasswordDialog(true)} + disabled={changePasswordFetching} + > + {changePasswordFetching ? : } + Change password + + )} + switchSettingsDialog(false)} /> + { renderChangePasswordItem + && ( + switchChangePasswordDialog(false)} + /> + )} + ); } diff --git a/cvat-ui/src/containers/header/header.tsx b/cvat-ui/src/containers/header/header.tsx index abda8b465cd7..ec20a6248ce0 100644 --- a/cvat-ui/src/containers/header/header.tsx +++ b/cvat-ui/src/containers/header/header.tsx @@ -7,7 +7,7 @@ import { connect } from 'react-redux'; import getCore from 'cvat-core-wrapper'; import HeaderComponent from 'components/header/header'; import { SupportedPlugins, CombinedState } from 'reducers/interfaces'; -import { logoutAsync } from 'actions/auth-actions'; +import { logoutAsync, authActions } from 'actions/auth-actions'; import { switchSettingsDialog } from 'actions/settings-actions'; const core = getCore(); @@ -25,20 +25,27 @@ interface StateToProps { uiVersion: string; switchSettingsShortcut: string; settingsDialogShown: boolean; + changePasswordDialogShown: boolean; + changePasswordFetching: boolean; + renderChangePasswordItem: boolean; } interface DispatchToProps { onLogout: () => void; switchSettingsDialog: (show: boolean) => void; + switchChangePasswordDialog: (show: boolean) => void; } function mapStateToProps(state: CombinedState): StateToProps { const { auth: { fetching: logoutFetching, + fetching: changePasswordFetching, user: { username, }, + showChangePasswordDialog: changePasswordDialogShown, + allowChangePassword: renderChangePasswordItem, }, plugins: { list, @@ -68,6 +75,9 @@ function mapStateToProps(state: CombinedState): StateToProps { uiVersion: packageVersion.ui, switchSettingsShortcut: normalizedKeyMap.SWITCH_SETTINGS, settingsDialogShown, + changePasswordFetching, + changePasswordDialogShown, + renderChangePasswordItem, }; } @@ -75,6 +85,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { return { onLogout: (): void => dispatch(logoutAsync()), switchSettingsDialog: (show: boolean): void => dispatch(switchSettingsDialog(show)), + switchChangePasswordDialog: (show: boolean): void => ( + dispatch(authActions.switchChangePasswordDialog(show)) + ), }; } diff --git a/cvat-ui/src/index.tsx b/cvat-ui/src/index.tsx index 6d6148372c56..d246dc0eacc1 100644 --- a/cvat-ui/src/index.tsx +++ b/cvat-ui/src/index.tsx @@ -14,7 +14,10 @@ import createRootReducer from 'reducers/root-reducer'; import createCVATStore, { getCVATStore } from 'cvat-store'; import logger, { LogType } from 'cvat-logger'; -import { authorizedAsync } from 'actions/auth-actions'; +import { + authorizedAsync, + loadAuthActionsAsync, +} from 'actions/auth-actions'; import { getFormatsAsync } from 'actions/formats-actions'; import { checkPluginsAsync } from 'actions/plugins-actions'; import { getUsersAsync } from 'actions/users-actions'; @@ -27,6 +30,7 @@ import { resetMessages, } from './actions/notification-actions'; + import { CombinedState, NotificationsState, @@ -48,6 +52,9 @@ interface StateToProps { formatsFetching: boolean; userAgreementsInitialized: boolean; userAgreementsFetching: boolean; + authActionsFetching: boolean; + authActionsInitialized: boolean; + allowChangePassword: boolean; notifications: NotificationsState; user: any; keyMap: Record; @@ -64,6 +71,7 @@ interface DispatchToProps { switchShortcutsDialog: () => void; loadUserAgreements: () => void; switchSettingsDialog: () => void; + loadAuthActions: () => void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -88,6 +96,9 @@ function mapStateToProps(state: CombinedState): StateToProps { formatsFetching: formats.fetching, userAgreementsInitialized: userAgreements.initialized, userAgreementsFetching: userAgreements.fetching, + authActionsFetching: auth.authActionsFetching, + authActionsInitialized: auth.authActionsInitialized, + allowChangePassword: auth.allowChangePassword, notifications: state.notifications, user: auth.user, keyMap: shortcuts.keyMap, @@ -106,6 +117,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { resetMessages: (): void => dispatch(resetMessages()), switchShortcutsDialog: (): void => dispatch(shortcutsActions.switchShortcutsDialog()), switchSettingsDialog: (): void => dispatch(switchSettingsDialog()), + loadAuthActions: (): void => dispatch(loadAuthActionsAsync()), }; } diff --git a/cvat-ui/src/reducers/auth-reducer.ts b/cvat-ui/src/reducers/auth-reducer.ts index b679d4b1db05..d433e7d58bd9 100644 --- a/cvat-ui/src/reducers/auth-reducer.ts +++ b/cvat-ui/src/reducers/auth-reducer.ts @@ -10,6 +10,10 @@ const defaultState: AuthState = { initialized: false, fetching: false, user: null, + authActionsFetching: false, + authActionsInitialized: false, + allowChangePassword: false, + showChangePasswordDialog: false, }; export default function (state = defaultState, action: AuthActions | boundariesActions): AuthState { @@ -69,6 +73,49 @@ export default function (state = defaultState, action: AuthActions | boundariesA ...state, fetching: false, }; + case AuthActionTypes.CHANGE_PASSWORD: + return { + ...state, + fetching: true, + }; + case AuthActionTypes.CHANGE_PASSWORD_SUCCESS: + return { + ...state, + fetching: false, + showChangePasswordDialog: false, + + }; + case AuthActionTypes.CHANGE_PASSWORD_FAILED: + return { + ...state, + fetching: false, + }; + case AuthActionTypes.SWITCH_CHANGE_PASSWORD_DIALOG: + return { + ...state, + showChangePasswordDialog: typeof action.payload.showChangePasswordDialog === 'undefined' + ? !state.showChangePasswordDialog + : action.payload.showChangePasswordDialog, + }; + case AuthActionTypes.LOAD_AUTH_ACTIONS: + return { + ...state, + authActionsFetching: true, + }; + case AuthActionTypes.LOAD_AUTH_ACTIONS_SUCCESS: + return { + ...state, + authActionsFetching: false, + authActionsInitialized: true, + allowChangePassword: action.payload.allowChangePassword, + }; + case AuthActionTypes.LOAD_AUTH_ACTIONS_FAILED: + return { + ...state, + authActionsFetching: false, + authActionsInitialized: true, + allowChangePassword: false, + }; case BoundariesActionTypes.RESET_AFTER_ERROR: { return { ...defaultState }; } diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 5e7961c79291..b7cf3cd4dc9e 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -13,6 +13,10 @@ export interface AuthState { initialized: boolean; fetching: boolean; user: any; + authActionsFetching: boolean; + authActionsInitialized: boolean; + showChangePasswordDialog: boolean; + allowChangePassword: boolean; } export interface TasksQuery { @@ -176,6 +180,8 @@ export interface NotificationsState { login: null | ErrorState; logout: null | ErrorState; register: null | ErrorState; + changePassword: null | ErrorState; + loadAuthActions: null | ErrorState; }; tasks: { fetching: null | ErrorState; @@ -246,6 +252,9 @@ export interface NotificationsState { models: { inferenceDone: string; }; + auth: { + changePasswordDone: string; + }; }; } diff --git a/cvat-ui/src/reducers/notifications-reducer.ts b/cvat-ui/src/reducers/notifications-reducer.ts index ee08de33b16f..d9b39153f6ee 100644 --- a/cvat-ui/src/reducers/notifications-reducer.ts +++ b/cvat-ui/src/reducers/notifications-reducer.ts @@ -27,6 +27,8 @@ const defaultState: NotificationsState = { login: null, logout: null, register: null, + changePassword: null, + loadAuthActions: null, }, tasks: { fetching: null, @@ -97,6 +99,9 @@ const defaultState: NotificationsState = { models: { inferenceDone: '', }, + auth: { + changePasswordDone: '', + }, }, }; @@ -162,6 +167,48 @@ export default function (state = defaultState, action: AnyAction): Notifications }, }; } + case AuthActionTypes.CHANGE_PASSWORD_SUCCESS: { + return { + ...state, + messages: { + ...state.messages, + auth: { + ...state.messages.auth, + changePasswordDone: 'New password has been saved.', + }, + }, + }; + } + case AuthActionTypes.CHANGE_PASSWORD_FAILED: { + return { + ...state, + errors: { + ...state.errors, + auth: { + ...state.errors.auth, + changePassword: { + message: 'Could not change password', + reason: action.payload.error.toString(), + }, + }, + }, + }; + } + case AuthActionTypes.LOAD_AUTH_ACTIONS_FAILED: { + return { + ...state, + errors: { + ...state.errors, + auth: { + ...state.errors.auth, + loadAuthActions: { + message: 'Could not check available auth actions', + reason: action.payload.error.toString(), + }, + }, + }, + }; + } case TasksActionTypes.EXPORT_DATASET_FAILED: { const taskID = action.payload.task.id; return { diff --git a/cvat-ui/src/utils/plugin-checker.ts b/cvat-ui/src/utils/plugin-checker.ts index eb9f7a5d5737..7bb016ae4d65 100644 --- a/cvat-ui/src/utils/plugin-checker.ts +++ b/cvat-ui/src/utils/plugin-checker.ts @@ -4,6 +4,7 @@ import getCore from 'cvat-core-wrapper'; import { SupportedPlugins } from 'reducers/interfaces'; +import isReachable from './url-checker'; const core = getCore(); @@ -11,16 +12,6 @@ const core = getCore(); class PluginChecker { public static async check(plugin: SupportedPlugins): Promise { const serverHost = core.config.backendAPI.slice(0, -7); - const isReachable = async (url: string, method: string): Promise => { - try { - await core.server.request(url, { - method, - }); - return true; - } catch (error) { - return ![0, 404].includes(error.code); - } - }; switch (plugin) { case SupportedPlugins.GIT_INTEGRATION: { diff --git a/cvat-ui/src/utils/url-checker.ts b/cvat-ui/src/utils/url-checker.ts new file mode 100644 index 000000000000..3909f3fef081 --- /dev/null +++ b/cvat-ui/src/utils/url-checker.ts @@ -0,0 +1,18 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import getCore from 'cvat-core-wrapper'; + +const core = getCore(); + +export default async (url: string, method: string): Promise => { + try { + await core.server.request(url, { + method, + }); + return true; + } catch (error) { + return ![0, 404].includes(error.code); + } +}; diff --git a/cvat/settings/base.py b/cvat/settings/base.py index a224ff04fae6..fb0d29136c3d 100644 --- a/cvat/settings/base.py +++ b/cvat/settings/base.py @@ -216,6 +216,7 @@ def generate_ssh_keys(): # https://github.com/pennersr/django-allauth ACCOUNT_EMAIL_VERIFICATION = 'none' +OLD_PASSWORD_FIELD_ENABLED = True # Django-RQ # https://github.com/rq/django-rq