Login.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. <template>
  2. <div v-if="username">
  3. <!-- webauthn authentication -->
  4. <form-wrapper v-if="showWebauthn" :title="$t('auth.forms.webauthn_login')" :punchline="punchline">
  5. <div class="field">
  6. {{ $t('auth.webauthn.use_security_device_to_sign_in') }}
  7. </div>
  8. <div class="control">
  9. <button type="button" class="button is-link" @click="webauthnLogin">{{ $t('commons.continue') }}</button>
  10. </div>
  11. <div class="nav-links">
  12. <p>{{ $t('auth.webauthn.lost_your_device') }}&nbsp;<router-link :to="{ name: 'webauthn.lost' }" class="is-link">{{ $t('auth.webauthn.recover_your_account') }}</router-link></p>
  13. <p v-if="!this.$root.appSettings.useWebauthnOnly">{{ $t('auth.sign_in_using') }}&nbsp;<a class="is-link" @click="showWebauthn = false">{{ $t('auth.login_and_password') }}</a></p>
  14. </div>
  15. </form-wrapper>
  16. <!-- login/password legacy form -->
  17. <form-wrapper v-else :title="$t('auth.forms.login')" :punchline="punchline">
  18. <div v-if="isDemo" class="notification is-info has-text-centered is-radiusless" v-html="$t('auth.forms.welcome_to_demo_app_use_those_credentials')" />
  19. <form @submit.prevent="handleSubmit" @keydown="form.onKeydown($event)">
  20. <form-field :form="form" fieldName="email" inputType="email" :label="$t('auth.forms.email')" autofocus />
  21. <form-field :form="form" fieldName="password" inputType="password" :label="$t('auth.forms.password')" />
  22. <form-buttons :isBusy="form.isBusy" :caption="$t('auth.sign_in')" />
  23. </form>
  24. <div class="nav-links">
  25. <div v-if="!username">
  26. <p>{{ $t('auth.forms.dont_have_account_yet') }}&nbsp;<router-link :to="{ name: 'register' }" class="is-link">{{ $t('auth.register') }}</router-link></p>
  27. </div>
  28. <div v-else>
  29. <p>{{ $t('auth.forms.forgot_your_password') }}&nbsp;<router-link :to="{ name: 'password.request' }" class="is-link">{{ $t('auth.forms.request_password_reset') }}</router-link></p>
  30. <p >{{ $t('auth.sign_in_using') }}&nbsp;<a class="is-link" @click="showWebauthn = true">{{ $t('auth.webauthn.security_device') }}</a></p>
  31. </div>
  32. </div>
  33. </form-wrapper>
  34. </div>
  35. </template>
  36. <script>
  37. import Form from './../../components/Form'
  38. export default {
  39. data(){
  40. return {
  41. username: null,
  42. isDemo: this.$root.isDemoApp,
  43. form: new Form({
  44. email: '',
  45. password: ''
  46. }),
  47. isBusy: false,
  48. showWebauthn: this.$root.appSettings.useWebauthnAsDefault || this.$root.appSettings.useWebauthnOnly,
  49. }
  50. },
  51. computed : {
  52. punchline: function() {
  53. return this.isDemo ? '' : this.$t('auth.welcome_back_x', [this.username])
  54. }
  55. },
  56. methods : {
  57. /**
  58. * Sign in using the login/password form
  59. */
  60. handleSubmit(e) {
  61. e.preventDefault()
  62. this.form.post('/user/login', {returnError: true})
  63. .then(response => {
  64. this.$router.push({ name: 'accounts', params: { toRefresh: true } })
  65. })
  66. .catch(error => {
  67. if( error.response.status === 401 ) {
  68. this.$notify({ type: 'is-danger', text: this.$t('auth.forms.authentication_failed'), duration:-1 })
  69. }
  70. else if( error.response.status !== 422 ) {
  71. this.$router.push({ name: 'genericError', params: { err: error.response } });
  72. }
  73. });
  74. },
  75. /**
  76. * Sign in using the WebAuthn API
  77. */
  78. async webauthnLogin() {
  79. this.isBusy = false
  80. // Check https context
  81. if (!window.isSecureContext) {
  82. this.$notify({ type: 'is-danger', text: this.$t('errors.https_required') })
  83. return false
  84. }
  85. // Check browser support
  86. if (!window.PublicKeyCredential) {
  87. this.$notify({ type: 'is-danger', text: this.$t('errors.browser_does_not_support_webauthn') })
  88. return false
  89. }
  90. const loginOptions = await this.axios.post('/webauthn/login/options').then(res => res.data)
  91. const publicKey = this.parseIncomingServerOptions(loginOptions)
  92. const credentials = await navigator.credentials.get({ publicKey: publicKey })
  93. .catch(error => {
  94. this.$notify({ type: 'is-danger', text: this.$t('auth.webauthn.unknown_device') })
  95. })
  96. if (!credentials) return false
  97. const publicKeyCredential = this.parseOutgoingCredentials(credentials)
  98. this.axios.post('/webauthn/login', publicKeyCredential, {returnError: true}).then(response => {
  99. this.$router.push({ name: 'accounts', params: { toRefresh: true } })
  100. })
  101. .catch(error => {
  102. if( error.response.status === 401 ) {
  103. this.$notify({ type: 'is-danger', text: this.$t('auth.forms.authentication_failed'), duration:-1 })
  104. }
  105. else if( error.response.status !== 422 ) {
  106. this.$router.push({ name: 'genericError', params: { err: error.response } });
  107. }
  108. });
  109. this.isBusy = false
  110. },
  111. },
  112. beforeRouteEnter (to, from, next) {
  113. next(async vm => {
  114. const { data } = await vm.axios.get('api/v1/user/name')
  115. if( data.name ) {
  116. // The email property is only sent when the user is logged in.
  117. // In this case we push the user to the index view.
  118. if( data.email ) {
  119. return next({ name: 'accounts' });
  120. }
  121. vm.username = data.name
  122. }
  123. else {
  124. return next({ name: 'register' });
  125. }
  126. });
  127. next();
  128. },
  129. beforeRouteLeave (to, from, next) {
  130. this.$notify({
  131. clean: true
  132. })
  133. next()
  134. }
  135. }
  136. </script>