UIWindowLogin.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. /**
  2. * Copyright (C) 2024 Puter Technologies Inc.
  3. *
  4. * This file is part of Puter.
  5. *
  6. * Puter is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published
  8. * by the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. import UIWindow from './UIWindow.js'
  20. import UIWindowSignup from './UIWindowSignup.js'
  21. import UIWindowRecoverPassword from './UIWindowRecoverPassword.js'
  22. import { fetchServerInfo } from '../services/VersionService.js';
  23. async function UIWindowLogin(options){
  24. options = options ?? {};
  25. options.reload_on_success = options.reload_on_success ?? false;
  26. options.has_head = options.has_head ?? true;
  27. options.send_confirmation_code = options.send_confirmation_code ?? false;
  28. options.show_password = options.show_password ?? false;
  29. return new Promise(async (resolve) => {
  30. const internal_id = window.uuidv4();
  31. let h = ``;
  32. h += `<div style="max-width: 500px; min-width: 340px;">`;
  33. if(!options.has_head && options.show_close_button !== false)
  34. h += `<div class="generic-close-window-button"> &times; </div>`;
  35. h += `<div style="padding: 20px; border-bottom: 1px solid #ced7e1; width: 100%; box-sizing: border-box;">`;
  36. // title
  37. h += `<h1 class="login-form-title">${i18n('log_in')}</h1>`;
  38. // login form
  39. h += `<form class="login-form">`;
  40. // error msg
  41. h += `<div class="login-error-msg"></div>`;
  42. // username/email
  43. h += `<div style="overflow: hidden;">`;
  44. h += `<label for="email_or_username-${internal_id}">${i18n('email_or_username')}</label>`;
  45. h += `<input id="email_or_username-${internal_id}" class="email_or_username" type="text" name="email_or_username" spellcheck="false" autocorrect="off" autocapitalize="off" data-gramm_editor="false" autocomplete="username"/>`;
  46. h += `</div>`;
  47. // password with conditional type based based on options.show_password
  48. h += `<div style="overflow: hidden; margin-top: 20px; margin-bottom: 20px; position: relative;">`;
  49. h += `<label for="password-${internal_id}">${i18n('password')}</label>`;
  50. h += `<input id="password-${internal_id}" class="password" type="${options.show_password ? "text" : "password"}" name="password" autocomplete="current-password"/>`;
  51. // show/hide icon
  52. h += `<span style="position: absolute; right: 5%; top: 50%; cursor: pointer;" id="toggle-show-password-${internal_id}">
  53. <img class="toggle-show-password-icon" src="${options.show_password ? window.icons["eye-closed.svg"] : window.icons["eye-open.svg"]}" width="20" height="20">
  54. </span>`;
  55. h += `</div>`;
  56. // login
  57. h += `<button class="login-btn button button-primary button-block button-normal">${i18n('log_in')}</button>`;
  58. // password recovery
  59. h += `<p style="text-align:center; margin-bottom: 0;"><span class="forgot-password-link">${i18n('forgot_pass_c2a')}</span></p>`;
  60. // server and version info
  61. h += `<div id="version-placeholder" class="version" style="text-align:center;"></div>`;
  62. h += `</form>`;
  63. h += `</div>`;
  64. // create account link
  65. if(options.show_signup_button === undefined || options.show_signup_button){
  66. h += `<div class="c2a-wrapper" style="padding:20px;">`;
  67. h += `<button class="signup-c2a-clickable">${i18n('create_free_account')}</button>`;
  68. h += `</div>`;
  69. }
  70. h += `</div>`;
  71. // server and version infomration
  72. fetchServerInfo(api_origin, auth_token)
  73. .then(res => {
  74. const deployed_date = new Date(res.deployTimestamp).toLocaleString();
  75. $("#version-placeholder").html(`Version: ${res.version} &bull; Server: ${res.location} &bull; Deployed: ${deployed_date}`);
  76. })
  77. .catch(() => {
  78. $("#version-placeholder").html("Failed to load version or server information.");
  79. });
  80. const el_window = await UIWindow({
  81. title: null,
  82. app: 'login',
  83. single_instance: true,
  84. icon: null,
  85. uid: null,
  86. is_dir: false,
  87. body_content: h,
  88. has_head: true,
  89. selectable_body: false,
  90. draggable_body: false,
  91. allow_context_menu: false,
  92. is_draggable: options.is_draggable ?? true,
  93. is_droppable: false,
  94. is_resizable: false,
  95. stay_on_top: false,
  96. allow_native_ctxmenu: true,
  97. allow_user_select: true,
  98. ...options.window_options,
  99. width: 350,
  100. dominant: true,
  101. on_close: ()=>{
  102. resolve(false)
  103. },
  104. onAppend: function(this_window){
  105. $(this_window).find(`.email_or_username`).get(0).focus({preventScroll:true});
  106. },
  107. window_class: 'window-login',
  108. window_css:{
  109. height: 'initial',
  110. },
  111. body_css: {
  112. width: 'initial',
  113. padding: '0',
  114. 'background-color': 'rgb(255 255 255)',
  115. 'backdrop-filter': 'blur(3px)',
  116. 'display': 'flex',
  117. 'flex-direction': 'column',
  118. 'justify-content': 'center',
  119. 'align-items': 'center',
  120. }
  121. })
  122. $(el_window).find('.forgot-password-link').on('click', function(e){
  123. UIWindowRecoverPassword({
  124. window_options: {
  125. backdrop: true,
  126. close_on_backdrop_click: false,
  127. }
  128. });
  129. })
  130. $(el_window).find('.login-btn').on('click', function(e){
  131. const email_username = $(el_window).find('.email_or_username').val();
  132. const password = $(el_window).find('.password').val();
  133. let data;
  134. if(is_email(email_username)){
  135. data = JSON.stringify({
  136. email: email_username,
  137. password: password
  138. })
  139. }else{
  140. data = JSON.stringify({
  141. username: email_username,
  142. password: password
  143. })
  144. }
  145. $(el_window).find('.login-error-msg').hide();
  146. let headers = {};
  147. if(window.custom_headers)
  148. headers = window.custom_headers;
  149. $.ajax({
  150. url: gui_origin + "/login",
  151. type: 'POST',
  152. async: false,
  153. headers: headers,
  154. contentType: "application/json",
  155. data: data,
  156. success: function (data){
  157. update_auth_data(data.token, data.user);
  158. if(options.reload_on_success){
  159. window.onbeforeunload = null;
  160. window.location.replace('/');
  161. }else
  162. resolve(true);
  163. $(el_window).close();
  164. },
  165. error: function (err){
  166. const $errorMessage = $(el_window).find('.login-error-msg');
  167. if (err.status === 404) {
  168. // Don't include the whole 404 page
  169. $errorMessage.html(`Error 404: "${gui_origin}/login" not found`);
  170. } else if (err.responseText) {
  171. $errorMessage.html(err.responseText);
  172. } else {
  173. // No message was returned. *Probably* this means we couldn't reach the server.
  174. // If this is a self-hosted instance, it's probably a configuration issue.
  175. if (app_domain !== 'puter.com') {
  176. $errorMessage.html(`<div style="text-align: left;">
  177. <p>Error reaching "${gui_origin}/login". This is likely to be a configuration issue.</p>
  178. <p>Make sure of the following:</p>
  179. <ul style="padding-left: 2em;">
  180. <li><code>domain</code> in config.json is set to the domain you're using to access puter</li>
  181. <li>DNS resolves for the domain, and the <code>api.</code> subdomain on that domain</li>
  182. <li><code>http_port</code> is set to the port Puter is listening on (<code>auto</code> will use <code>4100</code> unless that port is in use)</li>
  183. <li><code>pub_port</code> is set to the external port (ex: <code>443</code> if you're using a reverse proxy that serves over https)</li>
  184. </ul>
  185. </div>`);
  186. } else {
  187. $errorMessage.html(`Failed to log in: Error ${err.status}`);
  188. }
  189. }
  190. $(el_window).find('.login-error-msg').fadeIn();
  191. }
  192. });
  193. })
  194. $(el_window).find('.login-form').on('submit', function(e){
  195. e.preventDefault();
  196. e.stopPropagation();
  197. return false;
  198. })
  199. $(el_window).find('.signup-c2a-clickable').on('click', async function(e){
  200. //destroy this window
  201. $(el_window).close();
  202. // create Signup window
  203. const signup = await UIWindowSignup({
  204. referrer: options.referrer,
  205. show_close_button: options.show_close_button,
  206. reload_on_success: options.reload_on_success,
  207. window_options: options.window_options,
  208. send_confirmation_code: options.send_confirmation_code,
  209. });
  210. if(signup)
  211. resolve(true);
  212. })
  213. $(el_window).find(`#toggle-show-password-${internal_id}`).on("click", function (e) {
  214. options.show_password = !options.show_password;
  215. // hide/show password and update icon
  216. $(el_window).find(".password").attr("type", options.show_password ? "text" : "password");
  217. $(el_window).find(".toggle-show-password-icon").attr("src", options.show_password ? window.icons["eye-closed.svg"] : window.icons["eye-open.svg"],
  218. )
  219. })
  220. })
  221. }
  222. export default UIWindowLogin