diff options
Diffstat (limited to 'apps/settings/src/components/AuthTokenSetupDialogue.vue')
-rw-r--r-- | apps/settings/src/components/AuthTokenSetupDialogue.vue | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/apps/settings/src/components/AuthTokenSetupDialogue.vue b/apps/settings/src/components/AuthTokenSetupDialogue.vue new file mode 100644 index 00000000000..99ef17dbd6c --- /dev/null +++ b/apps/settings/src/components/AuthTokenSetupDialogue.vue @@ -0,0 +1,239 @@ +<!-- + - @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at> + - + - @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + --> + +<template> + <div v-if="!adding" id="generate-app-token-section" class="row spacing"> + <!-- Port to TextField component when available --> + <NcTextField :value.sync="deviceName" + type="text" + :maxlength="120" + :disabled="loading" + class="app-name-text-field" + :label="t('settings', 'App name')" + :placeholder="t('settings', 'App name')" + @keydown.enter="submit" /> + <NcButton :disabled="loading || deviceName.length === 0" + type="primary" + @click="submit"> + {{ t('settings', 'Create new app password') }} + </NcButton> + </div> + <div v-else class="spacing"> + {{ t('settings', 'Use the credentials below to configure your app or device.') }} + {{ t('settings', 'For security reasons this password will only be shown once.') }} + <div class="app-password-row"> + <label for="app-username" class="app-password-label">{{ t('settings', 'Username') }}</label> + <input id="app-username" + :value="loginName" + type="text" + class="monospaced" + readonly="readonly" + @focus="selectInput"> + </div> + <div class="app-password-row"> + <label for="app-password" class="app-password-label">{{ t('settings', 'Password') }}</label> + <input id="app-password" + ref="appPassword" + :value="appPassword" + type="text" + class="monospaced" + readonly="readonly" + @focus="selectInput"> + <NcButton type="tertiary" + :title="copyTooltipOptions" + :aria-label="copyTooltipOptions" + @click="copyPassword"> + <template #icon> + <Check v-if="copied" :size="20" /> + <ContentCopy v-else :size="20" /> + </template> + </NcButton> + <NcButton @click="reset"> + {{ t('settings', 'Done') }} + </NcButton> + </div> + <div class="app-password-row"> + <span class="app-password-label" /> + <NcButton v-if="!showQR" + @click="showQR = true"> + {{ t('settings', 'Show QR code for mobile apps') }} + </NcButton> + <QR v-else + :value="qrUrl" /> + </div> + </div> +</template> + +<script> +import QR from '@chenfengyuan/vue-qrcode' +import { confirmPassword } from '@nextcloud/password-confirmation' +import '@nextcloud/password-confirmation/dist/style.css' +import { showError } from '@nextcloud/dialogs' +import { getRootUrl } from '@nextcloud/router' +import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' +import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' + +import Check from 'vue-material-design-icons/Check.vue' +import ContentCopy from 'vue-material-design-icons/ContentCopy.vue' + +export default { + name: 'AuthTokenSetupDialogue', + components: { + Check, + ContentCopy, + NcButton, + QR, + NcTextField, + }, + props: { + add: { + type: Function, + required: true, + }, + }, + data() { + return { + adding: false, + loading: false, + deviceName: '', + appPassword: '', + loginName: '', + copied: false, + showQR: false, + qrUrl: '', + } + }, + computed: { + copyTooltipOptions() { + if (this.copied) { + return t('settings', 'Copied!') + } + return t('settings', 'Copy') + }, + }, + methods: { + selectInput(e) { + e.currentTarget.select() + }, + submit() { + confirmPassword() + .then(() => { + this.loading = true + return this.add(this.deviceName) + }) + .then(token => { + this.adding = true + this.loginName = token.loginName + this.appPassword = token.token + + const server = window.location.protocol + '//' + window.location.host + getRootUrl() + this.qrUrl = `nc://login/user:${token.loginName}&password:${token.token}&server:${server}` + + this.$nextTick(() => { + this.$refs.appPassword.select() + }) + }) + .catch(err => { + console.error('could not create a new app password', err) + OC.Notification.showTemporary(t('settings', 'Error while creating device token')) + + this.reset() + }) + }, + async copyPassword() { + try { + await navigator.clipboard.writeText(this.appPassword) + this.copied = true + } catch (e) { + this.copied = false + console.error(e) + showError(t('settings', 'Could not copy app password. Please copy it manually.')) + } finally { + setTimeout(() => { + this.copied = false + }, 4000) + } + }, + reset() { + this.adding = false + this.loading = false + this.showQR = false + this.qrUrl = '' + this.deviceName = '' + this.appPassword = '' + this.loginName = '' + }, + }, +} +</script> + +<style lang="scss" scoped> + .app-password-row { + display: flex; + align-items: center; + flex-wrap: wrap; + margin-top: calc(var(--default-grid-baseline) * 2); + + .icon { + background-size: 16px 16px; + display: inline-block; + position: relative; + top: 3px; + margin-inline-start: 5px; + margin-inline-end: 8px; + } + + } + + .app-password-label { + display: table-cell; + margin-inline-end: 1em; + text-align: start; + vertical-align: middle; + width: 100px; + } + + .app-name-text-field { + height: 44px !important; + padding-left: 12px; + margin-inline-end: 12px; + width: 200px; + } + + .monospaced { + width: 245px; + font-family: monospace; + } + + .button-vue{ + display:inline-block; + margin: 3px 3px 3px 3px; + } + + .row { + display: flex; + align-items: center; + } + + .spacing { + padding-top: 16px; + } +</style> |