login.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. /**
  2. * Script for login.ejs
  3. */
  4. // Validation Regexes.
  5. const validUsername = /^[a-zA-Z0-9_]{1,16}$/
  6. const basicEmail = /^\S+@\S+\.\S+$/
  7. //const validEmail = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i
  8. // Login Elements
  9. const loginEmailError = document.getElementById('loginEmailError')
  10. const loginUsername = document.getElementById('loginUsername')
  11. const loginPasswordError = document.getElementById('loginPasswordError')
  12. const loginPassword = document.getElementById('loginPassword')
  13. const checkmarkContainer = document.getElementById('checkmarkContainer')
  14. const loginRememberOption = document.getElementById('loginRememberOption')
  15. const loginButton = document.getElementById('loginButton')
  16. const loginForm = document.getElementById('loginForm')
  17. // Control variables.
  18. let lu = false, lp = false
  19. /**
  20. * Show a login error.
  21. *
  22. * @param {HTMLElement} element The element on which to display the error.
  23. * @param {string} value The error text.
  24. */
  25. function showError(element, value){
  26. element.innerHTML = value
  27. element.style.opacity = 1
  28. }
  29. /**
  30. * Shake a login error to add emphasis.
  31. *
  32. * @param {HTMLElement} element The element to shake.
  33. */
  34. function shakeError(element){
  35. if(element.style.opacity == 1){
  36. element.classList.remove('shake')
  37. void element.offsetWidth
  38. element.classList.add('shake')
  39. }
  40. }
  41. /**
  42. * Validate that an email field is neither empty nor invalid.
  43. *
  44. * @param {string} value The email value.
  45. */
  46. function validateEmail(value){
  47. if(value){
  48. if(!basicEmail.test(value) && !validUsername.test(value)){
  49. showError(loginEmailError, '* Invalid Value')
  50. loginDisabled(true)
  51. lu = false
  52. } else {
  53. loginEmailError.style.opacity = 0
  54. lu = true
  55. if(lp){
  56. loginDisabled(false)
  57. }
  58. }
  59. } else {
  60. lu = false
  61. showError(loginEmailError, '* Required')
  62. loginDisabled(true)
  63. }
  64. }
  65. /**
  66. * Validate that the password field is not empty.
  67. *
  68. * @param {string} value The password value.
  69. */
  70. function validatePassword(value){
  71. if(value){
  72. loginPasswordError.style.opacity = 0
  73. lp = true
  74. if(lu){
  75. loginDisabled(false)
  76. }
  77. } else {
  78. lp = false
  79. showError(loginPasswordError, '* Required')
  80. loginDisabled(true)
  81. }
  82. }
  83. // Emphasize errors with shake when focus is lost.
  84. loginUsername.addEventListener('focusout', (e) => {
  85. validateEmail(e.target.value)
  86. shakeError(loginEmailError)
  87. })
  88. loginPassword.addEventListener('focusout', (e) => {
  89. validatePassword(e.target.value)
  90. shakeError(loginPasswordError)
  91. })
  92. // Validate input for each field.
  93. loginUsername.addEventListener('input', (e) => {
  94. validateEmail(e.target.value)
  95. })
  96. loginPassword.addEventListener('input', (e) => {
  97. validatePassword(e.target.value)
  98. })
  99. /**
  100. * Enable or disable the login button.
  101. *
  102. * @param {boolean} v True to enable, false to disable.
  103. */
  104. function loginDisabled(v){
  105. if(loginButton.disabled !== v){
  106. loginButton.disabled = v
  107. }
  108. }
  109. /**
  110. * Enable or disable loading elements.
  111. *
  112. * @param {boolean} v True to enable, false to disable.
  113. */
  114. function loginLoading(v){
  115. if(v){
  116. loginButton.setAttribute('loading', v)
  117. loginButton.innerHTML = loginButton.innerHTML.replace('LOGIN', 'LOGGING IN')
  118. } else {
  119. loginButton.removeAttribute('loading')
  120. loginButton.innerHTML = loginButton.innerHTML.replace('LOGGING IN', 'LOGIN')
  121. }
  122. }
  123. /**
  124. * Enable or disable login form.
  125. *
  126. * @param {boolean} v True to enable, false to disable.
  127. */
  128. function formDisabled(v){
  129. loginDisabled(v)
  130. loginUsername.disabled = v
  131. loginPassword.disabled = v
  132. if(v){
  133. checkmarkContainer.setAttribute('disabled', v)
  134. } else {
  135. checkmarkContainer.removeAttribute('disabled')
  136. }
  137. loginRememberOption.disabled = v
  138. }
  139. /**
  140. * Parses an error and returns a user-friendly title and description
  141. * for our error overlay.
  142. *
  143. * @param {Error | {cause: string, error: string, errorMessage: string}} err A Node.js
  144. * error or Mojang error response.
  145. */
  146. function resolveError(err){
  147. // Mojang Response => err.cause | err.error | err.errorMessage
  148. // Node error => err.code | err.message
  149. if(err.cause != null && err.cause === 'UserMigratedException') {
  150. return {
  151. title: 'Error During Login:<br>Invalid Credentials',
  152. desc: 'You\'ve attempted to login with a migrated account. Try again using the account email as the username.'
  153. }
  154. } else {
  155. if(err.error != null){
  156. if(err.error === 'ForbiddenOperationException'){
  157. if(err.errorMessage != null){
  158. if(err.errorMessage === 'Invalid credentials. Invalid username or password.'){
  159. return {
  160. title: 'Error During Login:<br>Invalid Credentials',
  161. desc: 'The email or password you\'ve entered is incorrect. Please try again.'
  162. }
  163. } else if(err.errorMessage === 'Invalid credentials.'){
  164. return {
  165. title: 'Error During Login:<br>Too Many Attempts',
  166. desc: 'There have been too many login attempts with this account recently. Please try again later.'
  167. }
  168. }
  169. }
  170. }
  171. } else {
  172. // Request errors (from Node).
  173. if(err.code != null){
  174. if(err.code === 'ENOENT'){
  175. // No Internet.
  176. return {
  177. title: 'Error During Login:<br>No Internet Connection',
  178. desc: 'You must be connected to the internet in order to login. Please connect and try again.'
  179. }
  180. } else if(err.code === 'ENOTFOUND'){
  181. // Could not reach server.
  182. return {
  183. title: 'Error During Login:<br>Authentication Server Offline',
  184. desc: 'Mojang\'s authentication server is currently offline or unreachable. Please wait a bit and try again. You can check the status of the server on <a href="https://help.mojang.com/">Mojang\'s help portal</a>.'
  185. }
  186. }
  187. }
  188. }
  189. }
  190. if(err.message != null){
  191. // Unknown error with request.
  192. return {
  193. title: 'Error During Login:<br>Unknown Error',
  194. desc: err.message
  195. }
  196. } else {
  197. // Unknown Mojang error.
  198. return {
  199. title: err.error,
  200. desc: err.errorMessage
  201. }
  202. }
  203. }
  204. // Disable default form behavior.
  205. loginForm.onsubmit = () => { return false }
  206. // Bind login button behavior.
  207. loginButton.addEventListener('click', () => {
  208. // Disable form.
  209. formDisabled(true)
  210. // Show loading stuff.
  211. loginLoading(true)
  212. AuthManager.addAccount(loginUsername.value, loginPassword.value).then((value) => {
  213. updateSelectedAccount(value)
  214. loginButton.innerHTML = loginButton.innerHTML.replace('LOGGING IN', 'SUCCESS')
  215. $('.circle-loader').toggleClass('load-complete')
  216. $('.checkmark').toggle()
  217. //console.log(value)
  218. setTimeout(() => {
  219. switchView(VIEWS.login, VIEWS.landing, 500, 500, () => {
  220. loginUsername.value = ''
  221. loginPassword.value = ''
  222. loginLoading(false)
  223. loginButton.innerHTML = loginButton.innerHTML.replace('SUCCESS', 'LOGIN')
  224. formDisabled(false)
  225. })
  226. }, 1000)
  227. }).catch((err) => {
  228. loginLoading(false)
  229. const errF = resolveError(err)
  230. setOverlayContent(errF.title, errF.desc, 'Try Again')
  231. setOverlayHandler(() => {
  232. formDisabled(false)
  233. toggleOverlay(false)
  234. })
  235. toggleOverlay(true)
  236. console.log(err)
  237. })
  238. })