You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AuthTokenSetupDialog.vue 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <!--
  2. - @copyright 2023 Ferdinand Thiessen <opensource@fthiessen.de>
  3. -
  4. - @author Ferdinand Thiessen <opensource@fthiessen.de>
  5. -
  6. - @license AGPL-3.0-or-later
  7. -
  8. - This program is free software: you can redistribute it and/or modify
  9. - it under the terms of the GNU Affero General Public License as
  10. - published by the Free Software Foundation, either version 3 of the
  11. - License, or (at your option) any later version.
  12. -
  13. - This program is distributed in the hope that it will be useful,
  14. - but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. - GNU Affero General Public License for more details.
  17. -
  18. - You should have received a copy of the GNU Affero General Public License
  19. - along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. -
  21. -->
  22. <template>
  23. <NcDialog :open.sync="open"
  24. :name="t('settings', 'New app password')"
  25. content-classes="token-dialog">
  26. <p>
  27. {{ t('settings', 'Use the credentials below to configure your app or device. For security reasons this password will only be shown once.') }}
  28. </p>
  29. <div class="token-dialog__name">
  30. <NcTextField :label="t('settings', 'Login')" :value="loginName" readonly />
  31. <NcButton type="tertiary"
  32. :title="copyLoginNameLabel"
  33. :aria-label="copyLoginNameLabel"
  34. @click="copyLoginName">
  35. <template #icon>
  36. <NcIconSvgWrapper :path="copyNameIcon" />
  37. </template>
  38. </NcButton>
  39. </div>
  40. <div class="token-dialog__password">
  41. <NcTextField ref="appPassword"
  42. :label="t('settings', 'Password')"
  43. :value="appPassword"
  44. readonly />
  45. <NcButton type="tertiary"
  46. :title="copyPasswordLabel"
  47. :aria-label="copyPasswordLabel"
  48. @click="copyPassword">
  49. <template #icon>
  50. <NcIconSvgWrapper :path="copyPasswordIcon" />
  51. </template>
  52. </NcButton>
  53. </div>
  54. <div class="token-dialog__qrcode">
  55. <NcButton v-if="!showQRCode" @click="showQRCode = true">
  56. {{ t('settings', 'Show QR code for mobile apps') }}
  57. </NcButton>
  58. <QR v-else :value="qrUrl" />
  59. </div>
  60. </NcDialog>
  61. </template>
  62. <script lang="ts">
  63. import type { ITokenResponse } from '../store/authtoken'
  64. import { mdiCheck, mdiContentCopy } from '@mdi/js'
  65. import { showError } from '@nextcloud/dialogs'
  66. import { translate as t } from '@nextcloud/l10n'
  67. import { getRootUrl } from '@nextcloud/router'
  68. import { defineComponent, type PropType } from 'vue'
  69. import QR from '@chenfengyuan/vue-qrcode'
  70. import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
  71. import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js'
  72. import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
  73. import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
  74. import logger from '../logger'
  75. export default defineComponent({
  76. name: 'AuthTokenSetupDialog',
  77. components: {
  78. NcButton,
  79. NcDialog,
  80. NcIconSvgWrapper,
  81. NcTextField,
  82. QR,
  83. },
  84. props: {
  85. token: {
  86. type: Object as PropType<ITokenResponse|null>,
  87. required: false,
  88. default: null,
  89. },
  90. },
  91. data() {
  92. return {
  93. isNameCopied: false,
  94. isPasswordCopied: false,
  95. showQRCode: false,
  96. }
  97. },
  98. computed: {
  99. open: {
  100. get() {
  101. return this.token !== null
  102. },
  103. set(value: boolean) {
  104. if (!value) {
  105. this.$emit('close')
  106. }
  107. },
  108. },
  109. copyPasswordIcon() {
  110. return this.isPasswordCopied ? mdiCheck : mdiContentCopy
  111. },
  112. copyNameIcon() {
  113. return this.isNameCopied ? mdiCheck : mdiContentCopy
  114. },
  115. appPassword() {
  116. return this.token?.token ?? ''
  117. },
  118. loginName() {
  119. return this.token?.loginName ?? ''
  120. },
  121. qrUrl() {
  122. const server = window.location.protocol + '//' + window.location.host + getRootUrl()
  123. return `nc://login/user:${this.loginName}&password:${this.appPassword}&server:${server}`
  124. },
  125. copyPasswordLabel() {
  126. if (this.isPasswordCopied) {
  127. return t('settings', 'App password copied!')
  128. }
  129. return t('settings', 'Copy app password')
  130. },
  131. copyLoginNameLabel() {
  132. if (this.isNameCopied) {
  133. return t('settings', 'Login name copied!')
  134. }
  135. return t('settings', 'Copy login name')
  136. },
  137. },
  138. watch: {
  139. token() {
  140. // reset showing the QR code on token change
  141. this.showQRCode = false
  142. },
  143. open() {
  144. if (this.open) {
  145. this.$nextTick(() => {
  146. this.$refs.appPassword!.select()
  147. })
  148. }
  149. },
  150. },
  151. methods: {
  152. t,
  153. async copyPassword() {
  154. try {
  155. await navigator.clipboard.writeText(this.appPassword)
  156. this.isPasswordCopied = true
  157. } catch (e) {
  158. this.isPasswordCopied = false
  159. logger.error(e as Error)
  160. showError(t('settings', 'Could not copy app password. Please copy it manually.'))
  161. } finally {
  162. setTimeout(() => {
  163. this.isPasswordCopied = false
  164. }, 4000)
  165. }
  166. },
  167. async copyLoginName() {
  168. try {
  169. await navigator.clipboard.writeText(this.loginName)
  170. this.isNameCopied = true
  171. } catch (e) {
  172. this.isNameCopied = false
  173. logger.error(e as Error)
  174. showError(t('settings', 'Could not copy login name. Please copy it manually.'))
  175. } finally {
  176. setTimeout(() => {
  177. this.isNameCopied = false
  178. }, 4000)
  179. }
  180. },
  181. },
  182. })
  183. </script>
  184. <style scoped lang="scss">
  185. :deep(.token-dialog) {
  186. display: flex;
  187. flex-direction: column;
  188. gap: 12px;
  189. padding-inline: 22px;
  190. padding-block-end: 20px;
  191. > * {
  192. box-sizing: border-box;
  193. }
  194. }
  195. .token-dialog {
  196. &__name, &__password {
  197. align-items: end;
  198. display: flex;
  199. gap: 10px;
  200. :deep(input) {
  201. font-family: monospace;
  202. }
  203. }
  204. &__qrcode {
  205. display: flex;
  206. justify-content: center;
  207. }
  208. }
  209. </style>