diff --git a/scm-ui/src/users/components/PasswordConfirmation.js b/scm-ui-components/packages/ui-components/src/forms/PasswordConfirmation.js similarity index 74% rename from scm-ui/src/users/components/PasswordConfirmation.js rename to scm-ui-components/packages/ui-components/src/forms/PasswordConfirmation.js index 6db00b899f..23d738edbb 100644 --- a/scm-ui/src/users/components/PasswordConfirmation.js +++ b/scm-ui-components/packages/ui-components/src/forms/PasswordConfirmation.js @@ -1,10 +1,8 @@ // @flow import React from "react"; -import { InputField } from "@scm-manager/ui-components"; -import { compose } from "redux"; import { translate } from "react-i18next"; -import * as userValidator from "./userValidation"; +import InputField from "./InputField"; type State = { password: string, @@ -14,6 +12,7 @@ type State = { }; type Props = { passwordChanged: string => void, + passwordValidator?: string => boolean, // Context props t: string => string }; @@ -43,27 +42,36 @@ class PasswordConfirmation extends React.Component { return ( <> ); } + validatePassword = password => { + const { passwordValidator } = this.props; + if (passwordValidator) { + return passwordValidator(password); + } + + return password.length >= 6 && password.length < 32; + }; + handlePasswordValidationChange = (confirmedPassword: string) => { const passwordConfirmed = this.state.password === confirmedPassword; @@ -82,7 +90,7 @@ class PasswordConfirmation extends React.Component { this.setState( { - passwordValid: userValidator.isPasswordValid(password), + passwordValid: this.validatePassword(password), passwordConfirmationFailed, password: password }, @@ -101,4 +109,4 @@ class PasswordConfirmation extends React.Component { }; } -export default compose(translate("users"))(PasswordConfirmation); +export default translate("commons")(PasswordConfirmation); diff --git a/scm-ui-components/packages/ui-components/src/forms/index.js b/scm-ui-components/packages/ui-components/src/forms/index.js index 24e52daa1d..04f1a40aca 100644 --- a/scm-ui-components/packages/ui-components/src/forms/index.js +++ b/scm-ui-components/packages/ui-components/src/forms/index.js @@ -5,4 +5,5 @@ export { default as Checkbox } from "./Checkbox.js"; export { default as InputField } from "./InputField.js"; export { default as Select } from "./Select.js"; export { default as Textarea } from "./Textarea.js"; +export { default as PasswordConfirmation } from "./PasswordConfirmation.js"; diff --git a/scm-ui/public/locales/en/commons.json b/scm-ui/public/locales/en/commons.json index 9471c994c1..776259fb08 100644 --- a/scm-ui/public/locales/en/commons.json +++ b/scm-ui/public/locales/en/commons.json @@ -49,5 +49,13 @@ "error-subtitle": "Cannot display profile", "error": "Error", "error-message": "'me' is undefined" + }, + "password": { + "label": "Password", + "passwordHelpText": "Plain text password of the user.", + "passwordConfirmHelpText": "Repeat the password for confirmation.", + "confirmPassword": "Confirm password", + "passwordInvalid": "Password has to be between 6 and 32 characters", + "passwordConfirmFailed": "Passwords have to be identical" } } diff --git a/scm-ui/public/locales/en/users.json b/scm-ui/public/locales/en/users.json index ea285a1cec..2a9ee7b79d 100644 --- a/scm-ui/public/locales/en/users.json +++ b/scm-ui/public/locales/en/users.json @@ -50,22 +50,15 @@ "validation": { "mail-invalid": "This email is invalid", "name-invalid": "This name is invalid", - "displayname-invalid": "This displayname is invalid", - "password-invalid": "Password has to be between 6 and 32 characters", - "passwordValidation-invalid": "Passwords have to be identical", - "validatePassword": "Confirm password" + "displayname-invalid": "This displayname is invalid" }, "password": { - "current-password": "Current password", "set-password-successful": "Password successfully set" }, "help": { "usernameHelpText": "Unique name of the user.", "displayNameHelpText": "Display name of the user.", "mailHelpText": "Email address of the user.", - "currentPasswordHelpText": "Enter your current password", - "passwordHelpText": "Plain text password of the user.", - "passwordConfirmHelpText": "Repeat the password for confirmation.", "adminHelpText": "An administrator is able to create, modify and delete repositories, groups and users.", "activeHelpText": "Activate or deactive the user." } diff --git a/scm-ui/src/containers/ChangeUserPassword.js b/scm-ui/src/containers/ChangeUserPassword.js index 6e6f0806e0..36c262897f 100644 --- a/scm-ui/src/containers/ChangeUserPassword.js +++ b/scm-ui/src/containers/ChangeUserPassword.js @@ -4,12 +4,12 @@ import { SubmitButton, Notification, ErrorNotification, - InputField -} from "../../../scm-ui-components/packages/ui-components/src/index"; + InputField, + PasswordConfirmation +} from "@scm-manager/ui-components"; import { translate } from "react-i18next"; -import { updatePassword } from "../users/components/changePassword"; -import PasswordConfirmation from "../users/components/PasswordConfirmation"; import type { Me } from "@scm-manager/ui-types"; +import { changePassword } from "../modules/changePassword"; type Props = { me: Me, @@ -69,7 +69,7 @@ class ChangeUserPassword extends React.Component { if (this.state.password) { const { oldPassword, password } = this.state; this.setLoadingState(); - updatePassword(this.props.me._links.password.href, oldPassword, password) + changePassword(this.props.me._links.password.href, oldPassword, password) .then(result => { if (result.error) { this.setErrorState(result.error); diff --git a/scm-ui/src/modules/changePassword.js b/scm-ui/src/modules/changePassword.js new file mode 100644 index 0000000000..604df040f6 --- /dev/null +++ b/scm-ui/src/modules/changePassword.js @@ -0,0 +1,19 @@ +// @flow +import { apiClient } from "@scm-manager/ui-components"; + +export const CONTENT_TYPE_PASSWORD_CHANGE = + "application/vnd.scmm-passwordChange+json;v=2"; +export function changePassword( + url: string, + oldPassword: string, + newPassword: string +) { + return apiClient + .put(url, { oldPassword, newPassword }, CONTENT_TYPE_PASSWORD_CHANGE) + .then(response => { + return response; + }) + .catch(err => { + return { error: err }; + }); +} diff --git a/scm-ui/src/modules/changePassword.test.js b/scm-ui/src/modules/changePassword.test.js new file mode 100644 index 0000000000..ea2263217e --- /dev/null +++ b/scm-ui/src/modules/changePassword.test.js @@ -0,0 +1,25 @@ +import fetchMock from "fetch-mock"; +import { changePassword, CONTENT_TYPE_PASSWORD_CHANGE } from "./changePassword"; + +describe("change password", () => { + const CHANGE_PASSWORD_URL = "/me/password"; + const oldPassword = "old"; + const newPassword = "new"; + + afterEach(() => { + fetchMock.reset(); + fetchMock.restore(); + }); + + it("should update password", done => { + fetchMock.put("/api/v2" + CHANGE_PASSWORD_URL, 204, { + headers: { "content-type": CONTENT_TYPE_PASSWORD_CHANGE } + }); + + changePassword(CHANGE_PASSWORD_URL, oldPassword, newPassword).then( + content => { + done(); + } + ); + }); +}); diff --git a/scm-ui/src/users/components/SetUserPassword.js b/scm-ui/src/users/components/SetUserPassword.js index 0f8e8e56da..6c2c1ca25d 100644 --- a/scm-ui/src/users/components/SetUserPassword.js +++ b/scm-ui/src/users/components/SetUserPassword.js @@ -4,11 +4,11 @@ import type { User } from "@scm-manager/ui-types"; import { SubmitButton, Notification, - ErrorNotification + ErrorNotification, + PasswordConfirmation } from "@scm-manager/ui-components"; import { translate } from "react-i18next"; -import { setPassword } from "./changePassword"; -import PasswordConfirmation from "./PasswordConfirmation"; +import { setPassword } from "./setPassword"; type Props = { user: User, diff --git a/scm-ui/src/users/components/UserForm.js b/scm-ui/src/users/components/UserForm.js index 2b8aa7b1e8..5f7d1a629a 100644 --- a/scm-ui/src/users/components/UserForm.js +++ b/scm-ui/src/users/components/UserForm.js @@ -5,6 +5,7 @@ import type { User } from "@scm-manager/ui-types"; import { Checkbox, InputField, + PasswordConfirmation, SubmitButton, validation as validator } from "@scm-manager/ui-components"; @@ -21,10 +22,7 @@ type State = { user: User, mailValidationError: boolean, nameValidationError: boolean, - displayNameValidationError: boolean, - passwordConfirmationError: boolean, - validatePasswordError: boolean, - validatePassword: string + displayNameValidationError: boolean }; class UserForm extends React.Component { @@ -43,10 +41,7 @@ class UserForm extends React.Component { }, mailValidationError: false, displayNameValidationError: false, - nameValidationError: false, - passwordConfirmationError: false, - validatePasswordError: false, - validatePassword: "" + nameValidationError: false }; } @@ -67,13 +62,13 @@ class UserForm extends React.Component { isValid = () => { const user = this.state.user; return !( - this.state.validatePasswordError || this.state.nameValidationError || this.state.mailValidationError || - this.state.passwordConfirmationError || this.state.displayNameValidationError || this.isFalsy(user.name) || - this.isFalsy(user.displayName) + this.isFalsy(user.displayName) || + this.isFalsy(user.mail) || + this.isFalsy(user.password) ); }; @@ -89,7 +84,6 @@ class UserForm extends React.Component { const user = this.state.user; let nameField = null; - let passwordFields = null; if (!this.props.user) { nameField = ( { helpText={t("help.usernameHelpText")} /> ); - passwordFields = ( - <> - - - - ); } return (
@@ -143,7 +115,7 @@ class UserForm extends React.Component { errorMessage={t("validation.mail-invalid")} helpText={t("help.mailHelpText")} /> - {passwordFields} + { }; handlePasswordChange = (password: string) => { - const validatePasswordError = !this.checkPasswords( - password, - this.state.validatePassword - ); this.setState({ - validatePasswordError: !userValidator.isPasswordValid(password), - passwordConfirmationError: validatePasswordError, user: { ...this.state.user, password } }); }; - handlePasswordValidationChange = (validatePassword: string) => { - const validatePasswordError = this.checkPasswords( - this.state.user.password, - validatePassword - ); - this.setState({ - validatePassword, - passwordConfirmationError: !validatePasswordError - }); - }; - - checkPasswords = (password1: string, password2: string) => { - return password1 === password2; - }; - handleAdminChange = (admin: boolean) => { this.setState({ user: { ...this.state.user, admin } }); }; diff --git a/scm-ui/src/users/components/changePassword.js b/scm-ui/src/users/components/changePassword.js deleted file mode 100644 index 8df632308f..0000000000 --- a/scm-ui/src/users/components/changePassword.js +++ /dev/null @@ -1,32 +0,0 @@ -//@flow -import { apiClient } from "@scm-manager/ui-components"; -const CONTENT_TYPE_PASSWORD_OVERWRITE = - "application/vnd.scmm-passwordOverwrite+json;v=2"; -const CONTENT_TYPE_PASSWORD_CHANGE = - "application/vnd.scmm-passwordChange+json;v=2"; - -export function setPassword(url: string, password: string) { - return apiClient - .put(url, { newPassword: password }, CONTENT_TYPE_PASSWORD_OVERWRITE) - .then(response => { - return response; - }) - .catch(err => { - return { error: err }; - }); -} - -export function updatePassword( - url: string, - oldPassword: string, - newPassword: string -) { - return apiClient - .put(url, { oldPassword, newPassword }, CONTENT_TYPE_PASSWORD_CHANGE) - .then(response => { - return response; - }) - .catch(err => { - return { error: err }; - }); -} diff --git a/scm-ui/src/users/components/changePassword.test.js b/scm-ui/src/users/components/changePassword.test.js deleted file mode 100644 index 4a525cc2a5..0000000000 --- a/scm-ui/src/users/components/changePassword.test.js +++ /dev/null @@ -1,35 +0,0 @@ -//@flow -import fetchMock from "fetch-mock"; -import { setPassword, updatePassword } from "./changePassword"; - -describe("password change", () => { - const SET_PASSWORD_URL = "/users/testuser/password"; - const CHANGE_PASSWORD_URL = "/me/password"; - const oldPassword = "old"; - const newPassword = "testpw123"; - - afterEach(() => { - fetchMock.reset(); - fetchMock.restore(); - }); - - // TODO: Verify content type - it("should set password", done => { - fetchMock.put("/api/v2" + SET_PASSWORD_URL, 204); - - setPassword(SET_PASSWORD_URL, newPassword).then(content => { - done(); - }); - }); - - // TODO: Verify content type - it("should update password", done => { - fetchMock.put("/api/v2" + CHANGE_PASSWORD_URL, 204); - - updatePassword(CHANGE_PASSWORD_URL, oldPassword, newPassword).then( - content => { - done(); - } - ); - }); -}); diff --git a/scm-ui/src/users/components/setPassword.js b/scm-ui/src/users/components/setPassword.js new file mode 100644 index 0000000000..2f055ca7c8 --- /dev/null +++ b/scm-ui/src/users/components/setPassword.js @@ -0,0 +1,15 @@ +//@flow +import { apiClient } from "@scm-manager/ui-components"; +export const CONTENT_TYPE_PASSWORD_OVERWRITE = + "application/vnd.scmm-passwordOverwrite+json;v=2"; + +export function setPassword(url: string, password: string) { + return apiClient + .put(url, { newPassword: password }, CONTENT_TYPE_PASSWORD_OVERWRITE) + .then(response => { + return response; + }) + .catch(err => { + return { error: err }; + }); +} diff --git a/scm-ui/src/users/components/setPassword.test.js b/scm-ui/src/users/components/setPassword.test.js new file mode 100644 index 0000000000..8414010c36 --- /dev/null +++ b/scm-ui/src/users/components/setPassword.test.js @@ -0,0 +1,25 @@ +//@flow +import fetchMock from "fetch-mock"; +import { CONTENT_TYPE_PASSWORD_OVERWRITE, setPassword } from "./setPassword"; + +describe("password change", () => { + const SET_PASSWORD_URL = "/users/testuser/password"; + const newPassword = "testpw123"; + + afterEach(() => { + fetchMock.reset(); + fetchMock.restore(); + }); + + it("should set password", done => { + fetchMock.put("/api/v2" + SET_PASSWORD_URL, 204, { + headers: { + "content-type": CONTENT_TYPE_PASSWORD_OVERWRITE + } + }); + + setPassword(SET_PASSWORD_URL, newPassword).then(content => { + done(); + }); + }); +});