LoginTwoFactor.vue 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import Form from '#shared/components/Form/Form.vue'
  4. import type {
  5. FormSubmitData,
  6. FormSchemaNode,
  7. } from '#shared/components/Form/types.ts'
  8. import UserError from '#shared/errors/UserError.ts'
  9. import { useAuthenticationStore } from '#shared/stores/authentication.ts'
  10. import { computed, onMounted, ref } from 'vue'
  11. import CommonLabel from '#shared/components/CommonLabel/CommonLabel.vue'
  12. import CommonLoader from '#desktop/components/CommonLoader/CommonLoader.vue'
  13. import CommonButton from '#desktop/components/CommonButton/CommonButton.vue'
  14. import MutationHandler from '#shared/server/apollo/handler/MutationHandler.ts'
  15. import { useTwoFactorMethodInitiateAuthenticationMutation } from '#shared/graphql/mutations/twoFactorMethodInitiateAuthentication.api.ts'
  16. import type {
  17. TwoFactorFormData,
  18. LoginCredentials,
  19. TwoFactorPlugin,
  20. } from '#shared/entities/two-factor/types.ts'
  21. export interface Props {
  22. credentials: LoginCredentials
  23. twoFactor: TwoFactorPlugin
  24. }
  25. const props = defineProps<Props>()
  26. const emit = defineEmits<{
  27. (e: 'finish'): void
  28. (e: 'error', error: UserError): void
  29. (e: 'clear-error'): void
  30. }>()
  31. const schema: FormSchemaNode[] = [
  32. {
  33. type: 'text',
  34. name: 'code',
  35. label: __('Security Code'),
  36. required: true,
  37. props: {
  38. help: computed(() => props.twoFactor.helpMessage),
  39. autocomplete: 'one-time-code',
  40. autofocus: true,
  41. inputmode: 'numeric',
  42. pattern: '[0-9]*',
  43. },
  44. },
  45. ]
  46. const authentication = useAuthenticationStore()
  47. const loading = ref(false)
  48. const error = ref<string | null>(null)
  49. const canRetry = ref(true)
  50. const login = (payload: unknown) => {
  51. emit('clear-error')
  52. const { login, password, rememberMe } = props.credentials
  53. return authentication
  54. .login({
  55. login,
  56. password,
  57. rememberMe,
  58. twoFactorAuthentication: {
  59. payload,
  60. method: props.twoFactor.name,
  61. },
  62. })
  63. .then(() => {
  64. canRetry.value = false
  65. emit('finish')
  66. })
  67. .catch((error: UserError) => {
  68. if (error instanceof UserError) {
  69. emit('error', error)
  70. }
  71. })
  72. }
  73. const tryMethod = async () => {
  74. if (!props.twoFactor.setup) return
  75. const initialDataMutation = new MutationHandler(
  76. useTwoFactorMethodInitiateAuthenticationMutation(),
  77. )
  78. emit('clear-error')
  79. error.value = null
  80. loading.value = true
  81. try {
  82. const initiated = await initialDataMutation.send({
  83. twoFactorMethod: props.twoFactor.name,
  84. password: props.credentials.password,
  85. login: props.credentials.login,
  86. })
  87. if (!initiated?.twoFactorMethodInitiateAuthentication?.initiationData) {
  88. error.value = __(
  89. 'Two-factor authentication method could not be initiated.',
  90. )
  91. return
  92. }
  93. const result = await props.twoFactor.setup(
  94. initiated.twoFactorMethodInitiateAuthentication.initiationData,
  95. )
  96. canRetry.value = result.retry ?? true
  97. if (result?.success) {
  98. await login(result.payload)
  99. } else if (result?.error) {
  100. error.value = result.error
  101. }
  102. } catch (err) {
  103. if (err instanceof UserError) {
  104. error.value = err.errors[0].message
  105. }
  106. } finally {
  107. loading.value = false
  108. }
  109. }
  110. onMounted(async () => {
  111. await tryMethod()
  112. })
  113. </script>
  114. <template>
  115. <Form
  116. v-if="twoFactor.form !== false"
  117. :schema="schema"
  118. @submit="login(($event as FormSubmitData<TwoFactorFormData>).code)"
  119. >
  120. <template #after-fields>
  121. <CommonButton
  122. type="submit"
  123. variant="submit"
  124. size="large"
  125. class="mt-8"
  126. block
  127. :disabled="loading"
  128. >
  129. {{ $t('Sign in') }}
  130. </CommonButton>
  131. </template>
  132. </Form>
  133. <section
  134. v-else-if="twoFactor.setup"
  135. class="flex flex-col items-center justify-center"
  136. >
  137. <CommonLabel v-if="error && twoFactor.errorHelpMessage" class="mt-5">
  138. {{ $t(twoFactor.errorHelpMessage) }}
  139. </CommonLabel>
  140. <CommonLabel v-else-if="twoFactor.helpMessage" class="mt-5">
  141. {{ $t(twoFactor.helpMessage) }}
  142. </CommonLabel>
  143. <CommonLoader class="mt-8 mb-3" :loading="loading" :error="error" />
  144. <CommonButton
  145. v-if="!loading && canRetry"
  146. size="large"
  147. variant="primary"
  148. class="mt-5"
  149. block
  150. @click="tryMethod"
  151. >
  152. {{ $t('Retry') }}
  153. </CommonButton>
  154. </section>
  155. </template>