aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristopher Ng <chrng8@gmail.com>2024-12-16 15:53:12 -0800
committerChristopher Ng <chrng8@gmail.com>2025-01-15 15:50:43 -0800
commitcdc65b69854a7a87130b290566c992a4b1881498 (patch)
treeb1754810056505aeb8aab6db6099d516a7a27f28
parentfa110e237353252c3c40abf150b5bc9f90c23837 (diff)
downloadnextcloud-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.ts7
-rw-r--r--apps/files_sharing/src/services/TokenService.ts20
-rw-r--r--apps/files_sharing/src/views/SharingDetailsTab.vue47
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);