diff --git a/packages/design-system/src/components/OcModal/OcModal.vue b/packages/design-system/src/components/OcModal/OcModal.vue index 55d19f439d8..b657dc2aa12 100644 --- a/packages/design-system/src/components/OcModal/OcModal.vue +++ b/packages/design-system/src/components/OcModal/OcModal.vue @@ -43,14 +43,10 @@ :placeholder="inputPlaceholder" :label="inputLabel" :type="inputType" - :password-policy="inputPasswordPolicy" - :generate-password-method="inputGeneratePasswordMethod" :description-message="inputDescription" :disabled="inputDisabled" :fix-message-line="true" :selection-range="inputSelectionRange" - @password-challenge-completed="$emit('passwordChallengeCompleted')" - @password-challenge-failed="$emit('passwordChallengeFailed')" @update:model-value="inputOnInput" @keydown.enter.prevent="confirm" /> @@ -107,7 +103,6 @@ import OcIcon from '../OcIcon/OcIcon.vue' import OcTextInput from '../OcTextInput/OcTextInput.vue' import { FocusTrap } from 'focus-trap-vue' import { FocusTargetOrFalse, FocusTargetValueOrFalse } from 'focus-trap' -import { PasswordPolicy } from '../../helpers' /** * Modals are generally used to force the user to focus on confirming or completing a single action. @@ -364,22 +359,6 @@ export default defineComponent({ required: false, default: false }, - /** - * Password policy for the input - */ - inputPasswordPolicy: { - type: Object as PropType, - required: false, - default: () => ({}) - }, - /** - * Method to generate random password for the input - */ - inputGeneratePasswordMethod: { - type: Function as PropType<(...args: unknown[]) => string>, - required: false, - default: null - }, /** * Overwrite default focused element * Can be `#id, .class`. @@ -397,15 +376,7 @@ export default defineComponent({ default: false } }, - emits: [ - 'cancel', - 'confirm', - 'confirm-secondary', - 'input', - 'checkbox-changed', - 'passwordChallengeCompleted', - 'passwordChallengeFailed' - ], + emits: ['cancel', 'confirm', 'confirm-secondary', 'input', 'checkbox-changed'], setup() { const primaryButton = ref(null) const secondaryButton = ref(null) diff --git a/packages/web-app-files/src/components/Modals/SetLinkPasswordModal.vue b/packages/web-app-files/src/components/Modals/SetLinkPasswordModal.vue new file mode 100644 index 00000000000..b1fb9f2195a --- /dev/null +++ b/packages/web-app-files/src/components/Modals/SetLinkPasswordModal.vue @@ -0,0 +1,105 @@ + + + diff --git a/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue b/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue index 6f95a378125..d5702ab7952 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue @@ -143,6 +143,7 @@ import { import DetailsAndEdit from './Links/DetailsAndEdit.vue' import NameAndCopy from './Links/NameAndCopy.vue' import CreateQuickLink from './Links/CreateQuickLink.vue' +import SetLinkPasswordModal from '../../Modals/SetLinkPasswordModal.vue' import { getLocaleFromLanguage } from '@ownclouders/web-pkg' import { Resource, @@ -269,28 +270,14 @@ export default defineComponent({ } } - const showQuickLinkPasswordModal = (params) => { - const modal = { + const showPasswordModal = (params) => { + return store.dispatch('createModal', { variation: 'passive', title: $gettext('Set password'), - cancelText: $gettext('Cancel'), - confirmText: $gettext('Set'), - hasInput: true, - inputDescription: $gettext('Passwords for links are required.'), - inputPasswordPolicy: passwordPolicyService.getPolicy(), - inputGeneratePasswordMethod: () => passwordPolicyService.generatePassword(), - inputLabel: $gettext('Password'), - inputType: 'password', - onInput: () => store.dispatch('setModalInputErrorMessage', ''), - onPasswordChallengeCompleted: () => store.dispatch('setModalConfirmButtonDisabled', false), - onPasswordChallengeFailed: () => store.dispatch('setModalConfirmButtonDisabled', true), - onCancel: () => store.dispatch('hideModal'), - onConfirm: (newPassword: string) => { - return updatePublicLink({ params: { ...params, password: newPassword } }) - } - } - - return store.dispatch('createModal', modal) + hideActions: true, + customComponent: SetLinkPasswordModal, + customComponentAttrs: { link: params } + }) } return { @@ -316,7 +303,7 @@ export default defineComponent({ canEditLink, expirationRules, updatePublicLink, - showQuickLinkPasswordModal, + showPasswordModal, defaultLinkPermissions, addNewLink } @@ -465,7 +452,7 @@ export default defineComponent({ } if (!link.password && !this.canDeletePublicLinkPassword(link)) { - this.showQuickLinkPasswordModal(params) + this.showPasswordModal(params) } else { this.updatePublicLink({ params }) } diff --git a/packages/web-app-files/src/components/SideBar/Shares/Links/DetailsAndEdit.vue b/packages/web-app-files/src/components/SideBar/Shares/Links/DetailsAndEdit.vue index e1fa5f0a2c8..081bd9c6af4 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/Links/DetailsAndEdit.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/Links/DetailsAndEdit.vue @@ -154,7 +154,8 @@ import * as EmailValidator from 'email-validator' import { createLocationSpaces, useConfigurationManager, - LinkRoleDropdown + LinkRoleDropdown, + useStore } from '@ownclouders/web-pkg' import { linkRoleInternalFile, @@ -170,6 +171,7 @@ import { createFileRouteOptions } from '@ownclouders/web-pkg' import { OcDrop } from 'design-system/src/components' import { usePasswordPolicyService, ExpirationRules } from '@ownclouders/web-pkg' import { useGettext } from 'vue3-gettext' +import SetLinkPasswordModal from '../../../Modals/SetLinkPasswordModal.vue' export default defineComponent({ name: 'DetailsAndEdit', @@ -210,7 +212,8 @@ export default defineComponent({ }, emits: ['removePublicLink', 'updateLink'], setup(props, { emit }) { - const { current } = useGettext() + const store = useStore() + const { $gettext, current } = useGettext() const configurationManager = useConfigurationManager() const passwordPolicyService = usePasswordPolicyService() @@ -236,6 +239,16 @@ export default defineComponent({ }) } + const showPasswordModal = () => { + return store.dispatch('createModal', { + variation: 'passive', + title: props.link.password ? $gettext('Edit password') : $gettext('Add password'), + hideActions: true, + customComponent: SetLinkPasswordModal, + customComponentAttrs: { link: props.link } + }) + } + return { space: inject>('space'), resource: inject>('resource'), @@ -244,7 +257,8 @@ export default defineComponent({ updateLink, updateSelectedRole, currentLinkRole, - isRunningOnEos: computed(() => configurationManager.options.isRunningOnEos) + isRunningOnEos: computed(() => configurationManager.options.isRunningOnEos), + showPasswordModal } }, data() { @@ -503,38 +517,6 @@ export default defineComponent({ this.createModal(modal) }, - showPasswordModal() { - const modal = { - variation: 'passive', - title: this.link.password ? this.$gettext('Edit password') : this.$gettext('Add password'), - cancelText: this.$gettext('Cancel'), - confirmText: this.link.password ? this.$gettext('Apply') : this.$gettext('Set'), - hasInput: true, - confirmDisabled: true, - inputLabel: this.$gettext('Password'), - inputPasswordPolicy: this.passwordPolicyService.getPolicy(), - inputGeneratePasswordMethod: () => this.passwordPolicyService.generatePassword(), - inputPlaceholder: this.link.password ? '●●●●●●●●' : null, - inputType: 'password', - onCancel: this.hideModal, - onInput: () => this.setModalInputErrorMessage(''), - onPasswordChallengeCompleted: () => this.setModalConfirmButtonDisabled(false), - onPasswordChallengeFailed: () => this.setModalConfirmButtonDisabled(true), - onConfirm: (password) => { - this.updateLink({ - link: { - ...this.link, - password - }, - onSuccess: () => { - this.hideModal() - } - }) - } - } - this.createModal(modal) - }, - toggleNotifyUploads() { if (this.currentLinkNotifyUploads) { this.$emit('updateLink', { diff --git a/packages/web-app-files/tests/unit/components/Modals/SetLinkPasswordModal.spec.ts b/packages/web-app-files/tests/unit/components/Modals/SetLinkPasswordModal.spec.ts new file mode 100644 index 00000000000..7e98599e2a6 --- /dev/null +++ b/packages/web-app-files/tests/unit/components/Modals/SetLinkPasswordModal.spec.ts @@ -0,0 +1,54 @@ +import SetLinkPasswordModal from '../../../../src/components/Modals/SetLinkPasswordModal.vue' +import { + createStore, + defaultComponentMocks, + defaultPlugins, + defaultStoreMockOptions, + shallowMount +} from 'web-test-helpers' + +describe('SetLinkPasswordModal', () => { + it('should render a text input field for the password', () => { + const { wrapper } = getWrapper() + + expect(wrapper.find('oc-text-input-stub').exists()).toBeTruthy() + }) + describe('method "onConfirm"', () => { + it('updates the link', async () => { + const { wrapper, storeOptions } = getWrapper() + await wrapper.vm.onConfirm() + + expect(storeOptions.modules.Files.actions.updateLink).toHaveBeenCalled() + expect(storeOptions.actions.showMessage).toHaveBeenCalled() + }) + it('shows an error message on error', async () => { + const { wrapper, storeOptions } = getWrapper() + storeOptions.modules.Files.actions.updateLink.mockRejectedValue(new Error('')) + await wrapper.vm.onConfirm() + + expect(storeOptions.actions.showErrorMessage).toHaveBeenCalled() + }) + }) +}) + +function getWrapper({ link = {} } = {}) { + const mocks = { ...defaultComponentMocks() } + + const storeOptions = defaultStoreMockOptions + const store = createStore(storeOptions) + + return { + mocks, + storeOptions, + wrapper: shallowMount(SetLinkPasswordModal, { + props: { + link + }, + global: { + plugins: [...defaultPlugins(), store], + mocks, + provide: mocks + } + }) + } +} diff --git a/packages/web-runtime/src/App.vue b/packages/web-runtime/src/App.vue index a3c46b7b24d..8a2bf3612a3 100644 --- a/packages/web-runtime/src/App.vue +++ b/packages/web-runtime/src/App.vue @@ -16,8 +16,6 @@ :has-input="modal.hasInput" :input-description="modal.inputDescription" :input-placeholder="modal.inputPlaceholder" - :input-password-policy="modal.inputPasswordPolicy" - :input-generate-password-method="modal.inputGeneratePasswordMethod" :input-disabled="modal.inputDisabled" :input-error="modal.inputError" :input-label="modal.inputLabel" @@ -38,8 +36,6 @@ @input="modal.onInput" @checkbox-changed="modal.onCheckboxValueChanged" @confirm-secondary="onModalConfirmSecondary" - @passwordChallengeCompleted="modal.onPasswordChallengeCompleted" - @passwordChallengeFailed="modal.onPasswordChallengeFailed" @mounted="focusModal" @before-unmount="focusModal" > diff --git a/packages/web-runtime/src/store/modal.ts b/packages/web-runtime/src/store/modal.ts index 6a871a5a221..b0c063bc8b5 100644 --- a/packages/web-runtime/src/store/modal.ts +++ b/packages/web-runtime/src/store/modal.ts @@ -72,8 +72,6 @@ const mutations = { state.onConfirm = modal.onConfirm state.hasInput = modal.hasInput || false state.inputValue = modal.inputValue || null - state.inputPasswordPolicy = modal.inputPasswordPolicy || {} - state.inputGeneratePasswordMethod = modal.inputGeneratePasswordMethod || null state.inputSelectionRange = modal.inputSelectionRange state.inputDescription = modal.inputDescription || null state.inputPlaceholder = modal.inputPlaceholder || null @@ -89,8 +87,6 @@ const mutations = { state.customComponent = modal.customComponent state.customComponentAttrs = modal.customComponentAttrs state.customContent = modal.customContent || '' - state.onPasswordChallengeCompleted = modal.onPasswordChallengeCompleted - state.onPasswordChallengeFailed = modal.onPasswordChallengeFailed state.hideActions = modal.hideActions || false }, diff --git a/packages/web-test-helpers/src/mocks/store/filesModuleMockOptions.ts b/packages/web-test-helpers/src/mocks/store/filesModuleMockOptions.ts index 95f838c0b1a..7c64c74134c 100644 --- a/packages/web-test-helpers/src/mocks/store/filesModuleMockOptions.ts +++ b/packages/web-test-helpers/src/mocks/store/filesModuleMockOptions.ts @@ -47,6 +47,7 @@ export const filesModuleMockOptions = { loadVersions: jest.fn(), loadShares: jest.fn(), deleteShare: jest.fn(), + updateLink: jest.fn(), clearTrashBin: jest.fn(), removeFilesFromTrashbin: jest.fn(), changeShare: jest.fn(),