aboutsummaryrefslogtreecommitdiffstats
path: root/apps/settings/src/components/AuthTokenSetupDialogue.vue
diff options
context:
space:
mode:
Diffstat (limited to 'apps/settings/src/components/AuthTokenSetupDialogue.vue')
-rw-r--r--apps/settings/src/components/AuthTokenSetupDialogue.vue239
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>