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.

LoginForm.vue 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. <!--
  2. - @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
  3. -
  4. - @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
  5. -
  6. - @license GNU AGPL version 3 or any later version
  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. <template>
  22. <form ref="loginForm"
  23. method="post"
  24. name="login"
  25. :action="OC.generateUrl('login')"
  26. @submit="submit">
  27. <fieldset>
  28. <div v-if="apacheAuthFailed"
  29. class="warning">
  30. {{ t('core', 'Server side authentication failed!') }}<br>
  31. <small>{{ t('core', 'Please contact your administrator.') }}
  32. </small>
  33. </div>
  34. <div v-for="(message, index) in messages"
  35. :key="index"
  36. class="warning">
  37. {{ message }}<br>
  38. </div>
  39. <div v-if="internalException"
  40. class="warning">
  41. {{ t('core', 'An internal error occurred.') }}<br>
  42. <small>{{ t('core', 'Please try again or contact your administrator.') }}
  43. </small>
  44. </div>
  45. <div id="message"
  46. class="hidden">
  47. <img class="float-spinner"
  48. alt=""
  49. :src="OC.imagePath('core', 'loading-dark.gif')">
  50. <span id="messageText" />
  51. <!-- the following div ensures that the spinner is always inside the #message div -->
  52. <div style="clear: both;" />
  53. </div>
  54. <p class="grouptop"
  55. :class="{shake: invalidPassword}">
  56. <input id="user"
  57. ref="user"
  58. v-model="user"
  59. type="text"
  60. name="user"
  61. :autocomplete="autoCompleteAllowed ? 'on' : 'off'"
  62. :placeholder="t('core', 'Username or email')"
  63. :aria-label="t('core', 'Username or email')"
  64. required
  65. @change="updateUsername">
  66. <label for="user" class="infield">{{ t('core', 'Username or email') }}</label>
  67. </p>
  68. <p class="groupbottom"
  69. :class="{shake: invalidPassword}">
  70. <input id="password"
  71. ref="password"
  72. :type="passwordInputType"
  73. class="password-with-toggle"
  74. name="password"
  75. :autocomplete="autoCompleteAllowed ? 'on' : 'off'"
  76. :placeholder="t('core', 'Password')"
  77. :aria-label="t('core', 'Password')"
  78. required>
  79. <label for="password"
  80. class="infield">{{ t('Password') }}</label>
  81. <a href="#" class="toggle-password" @click.stop.prevent="togglePassword">
  82. <img :src="OC.imagePath('core', 'actions/toggle.svg')">
  83. </a>
  84. </p>
  85. <LoginButton :loading="loading" :inverted-colors="invertedColors" />
  86. <p v-if="invalidPassword"
  87. class="warning wrongPasswordMsg">
  88. {{ t('core', 'Wrong username or password.') }}
  89. </p>
  90. <p v-else-if="userDisabled"
  91. class="warning userDisabledMsg">
  92. {{ t('lib', 'User disabled') }}
  93. </p>
  94. <p v-if="throttleDelay && throttleDelay > 5000"
  95. class="warning throttledMsg">
  96. {{ t('core', 'We have detected multiple invalid login attempts from your IP. Therefore your next login is throttled up to 30 seconds.') }}
  97. </p>
  98. <input v-if="redirectUrl"
  99. type="hidden"
  100. name="redirect_url"
  101. :value="redirectUrl">
  102. <input type="hidden"
  103. name="timezone"
  104. :value="timezone">
  105. <input type="hidden"
  106. name="timezone_offset"
  107. :value="timezoneOffset">
  108. <input type="hidden"
  109. name="requesttoken"
  110. :value="OC.requestToken">
  111. <input v-if="directLogin"
  112. type="hidden"
  113. name="direct"
  114. value="1">
  115. </fieldset>
  116. </form>
  117. </template>
  118. <script>
  119. import jstz from 'jstimezonedetect'
  120. import LoginButton from './LoginButton'
  121. export default {
  122. name: 'LoginForm',
  123. components: { LoginButton },
  124. props: {
  125. username: {
  126. type: String,
  127. default: '',
  128. },
  129. redirectUrl: {
  130. type: String,
  131. },
  132. errors: {
  133. type: Array,
  134. default: () => [],
  135. },
  136. messages: {
  137. type: Array,
  138. default: () => [],
  139. },
  140. throttleDelay: {
  141. type: Number,
  142. },
  143. invertedColors: {
  144. type: Boolean,
  145. default: false,
  146. },
  147. autoCompleteAllowed: {
  148. type: Boolean,
  149. default: true,
  150. },
  151. directLogin: {
  152. type: Boolean,
  153. default: false,
  154. },
  155. },
  156. data() {
  157. return {
  158. loading: false,
  159. timezone: jstz.determine().name(),
  160. timezoneOffset: (-new Date().getTimezoneOffset() / 60),
  161. user: this.username,
  162. password: '',
  163. passwordInputType: 'password',
  164. }
  165. },
  166. computed: {
  167. apacheAuthFailed() {
  168. return this.errors.indexOf('apacheAuthFailed') !== -1
  169. },
  170. internalException() {
  171. return this.errors.indexOf('internalexception') !== -1
  172. },
  173. invalidPassword() {
  174. return this.errors.indexOf('invalidpassword') !== -1
  175. },
  176. userDisabled() {
  177. return this.errors.indexOf('userdisabled') !== -1
  178. },
  179. },
  180. mounted() {
  181. if (this.username === '') {
  182. this.$refs.user.focus()
  183. } else {
  184. this.$refs.password.focus()
  185. }
  186. },
  187. methods: {
  188. togglePassword() {
  189. if (this.passwordInputType === 'password') {
  190. this.passwordInputType = 'text'
  191. } else {
  192. this.passwordInputType = 'password'
  193. }
  194. },
  195. updateUsername() {
  196. this.$emit('update:username', this.user)
  197. },
  198. submit() {
  199. this.loading = true
  200. this.$emit('submit')
  201. },
  202. },
  203. }
  204. </script>
  205. <style scoped>
  206. </style>