diff options
author | Christopher Ng <chrng8@gmail.com> | 2024-12-16 15:53:12 -0800 |
---|---|---|
committer | Christopher Ng <chrng8@gmail.com> | 2025-01-15 15:50:43 -0800 |
commit | cdc65b69854a7a87130b290566c992a4b1881498 (patch) | |
tree | b1754810056505aeb8aab6db6099d516a7a27f28 | |
parent | fa110e237353252c3c40abf150b5bc9f90c23837 (diff) | |
download | nextcloud-server-cdc65b69854a7a87130b290566c992a4b1881498.tar.gz nextcloud-server-cdc65b69854a7a87130b290566c992a4b1881498.zip |
feat(sharing): Make it possible to customize share link tokens
Signed-off-by: Christopher Ng <chrng8@gmail.com>
-rw-r--r-- | apps/files_sharing/src/models/Share.ts | 7 | ||||
-rw-r--r-- | apps/files_sharing/src/services/TokenService.ts | 20 | ||||
-rw-r--r-- | apps/files_sharing/src/views/SharingDetailsTab.vue | 47 |
3 files changed, 73 insertions, 1 deletions
diff --git a/apps/files_sharing/src/models/Share.ts b/apps/files_sharing/src/models/Share.ts index bfc6357240d..28631dc7288 100644 --- a/apps/files_sharing/src/models/Share.ts +++ b/apps/files_sharing/src/models/Share.ts @@ -193,6 +193,13 @@ export default class Share { } /** + * Set the public share token + */ + set token(token: string) { + this._share.token = token + } + + /** * Get the share note if any */ get note(): string { diff --git a/apps/files_sharing/src/services/TokenService.ts b/apps/files_sharing/src/services/TokenService.ts new file mode 100644 index 00000000000..c497531dfdb --- /dev/null +++ b/apps/files_sharing/src/services/TokenService.ts @@ -0,0 +1,20 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import axios from '@nextcloud/axios' +import { generateOcsUrl } from '@nextcloud/router' + +interface TokenData { + ocs: { + data: { + token: string, + } + } +} + +export const generateToken = async (): Promise<string> => { + const { data } = await axios.get<TokenData>(generateOcsUrl('/apps/files_sharing/api/v1/token')) + return data.ocs.data.token +} diff --git a/apps/files_sharing/src/views/SharingDetailsTab.vue b/apps/files_sharing/src/views/SharingDetailsTab.vue index a50a3abf6bf..4d46ec32796 100644 --- a/apps/files_sharing/src/views/SharingDetailsTab.vue +++ b/apps/files_sharing/src/views/SharingDetailsTab.vue @@ -105,9 +105,23 @@ role="region"> <section> <NcInputField v-if="isPublicShare" + class="sharingTabDetailsView__label" autocomplete="off" :label="t('files_sharing', 'Share label')" :value.sync="share.label" /> + <NcInputField v-if="isPublicShare && !isNewShare" + autocomplete="off" + :label="t('files_sharing', 'Share link token')" + :helper-text="t('files_sharing', 'Set the public share link token to something easy to remember or generate a new token. It is not recommended to use a guessable token for shares which contain sensitive information.')" + show-trailing-button + :trailing-button-label="loadingToken ? t('files_sharing', 'Generating…') : t('files_sharing', 'Generate new token')" + @trailing-button-click="generateNewToken" + :value.sync="share.token"> + <template #trailing-button-icon> + <NcLoadingIcon v-if="loadingToken" /> + <Refresh v-else :size="20" /> + </template> + </NcInputField> <template v-if="isPublicShare"> <NcCheckboxRadioSwitch :checked.sync="isPasswordProtected" :disabled="isPasswordEnforced"> {{ t('files_sharing', 'Set password') }} @@ -228,7 +242,7 @@ <div class="sharingTabDetailsView__footer"> <div class="button-group"> <NcButton data-cy-files-sharing-share-editor-action="cancel" - @click="$emit('close-sharing-details')"> + @click="cancel"> {{ t('files_sharing', 'Cancel') }} </NcButton> <NcButton type="primary" @@ -248,6 +262,7 @@ import { emit } from '@nextcloud/event-bus' import { getLanguage } from '@nextcloud/l10n' import { ShareType } from '@nextcloud/sharing' +import { showError } from '@nextcloud/dialogs' import moment from '@nextcloud/moment' import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js' @@ -272,6 +287,7 @@ import UploadIcon from 'vue-material-design-icons/Upload.vue' import MenuDownIcon from 'vue-material-design-icons/MenuDown.vue' import MenuUpIcon from 'vue-material-design-icons/MenuUp.vue' import DotsHorizontalIcon from 'vue-material-design-icons/DotsHorizontal.vue' +import Refresh from 'vue-material-design-icons/Refresh.vue' import ExternalShareAction from '../components/ExternalShareAction.vue' @@ -279,6 +295,7 @@ import GeneratePassword from '../utils/GeneratePassword.ts' import Share from '../models/Share.ts' import ShareRequests from '../mixins/ShareRequests.js' import SharesMixin from '../mixins/SharesMixin.js' +import { generateToken } from '../services/TokenService.ts' import logger from '../services/logger.ts' import { @@ -311,6 +328,7 @@ export default { MenuDownIcon, MenuUpIcon, DotsHorizontalIcon, + Refresh, }, mixins: [ShareRequests, SharesMixin], props: { @@ -339,6 +357,8 @@ export default { isFirstComponentLoad: true, test: false, creating: false, + initialToken: this.share.token, + loadingToken: false, ExternalShareActions: OCA.Sharing.ExternalShareActions.state, } @@ -766,6 +786,24 @@ export default { }, methods: { + async generateNewToken() { + if (this.loadingToken) { + return + } + this.loadingToken = true + try { + this.share.token = await generateToken() + } catch (error) { + showError(t('files_sharing', 'Failed to generate a new token')) + } + this.loadingToken = false + }, + + cancel() { + this.share.token = this.initialToken + this.$emit('close-sharing-details') + }, + updateAtomicPermissions({ isReadChecked = this.hasRead, isEditChecked = this.canEdit, @@ -876,6 +914,9 @@ export default { async saveShare() { const permissionsAndAttributes = ['permissions', 'attributes', 'note', 'expireDate'] const publicShareAttributes = ['label', 'password', 'hideDownload'] + if (this.config.allowCustomTokens) { + publicShareAttributes.push('token') + } if (this.isPublicShare) { permissionsAndAttributes.push(...publicShareAttributes) } @@ -1174,6 +1215,10 @@ export default { } } + &__label { + padding-block-end: 6px; + } + &__delete { > button:first-child { color: rgb(223, 7, 7); |