diff --git a/src/gui/src/UI/Dashboard/TabAccount.js b/src/gui/src/UI/Dashboard/TabAccount.js index 9102a8bca..84bb8a892 100644 --- a/src/gui/src/UI/Dashboard/TabAccount.js +++ b/src/gui/src/UI/Dashboard/TabAccount.js @@ -21,34 +21,35 @@ import UIWindowChangePassword from '../UIWindowChangePassword.js'; import UIWindowChangeEmail from '../Settings/UIWindowChangeEmail.js'; import UIWindowChangeUsername from '../UIWindowChangeUsername.js'; import UIWindowConfirmUserDeletion from '../Settings/UIWindowConfirmUserDeletion.js'; +import UIWindowCopyToken from '../UIWindowCopyToken.js'; import UIWindow from '../UIWindow.js'; const TabAccount = { id: 'account', label: i18n('account'), - icon: ``, + icon: '', html () { let h = ''; h += '
'; - + // Profile section header h += '
'; - h += '

' + i18n('account') + '

'; - h += '

Manage your account settings and profile

'; + h += `

${ i18n('account') }

`; + h += '

Manage your account settings and profile

'; h += '
'; // Profile picture card h += '
'; - h += '
'; - h += `
`; - h += '
'; - h += '
'; - h += `

${html_encode(window.user?.username || 'User')}

`; - h += `

${html_encode(window.user?.email || '')}

`; - h += 'Click the avatar to change your profile picture'; - h += '
'; - h += '
'; + h += '
'; + h += `
`; + h += '
'; + h += '
'; + h += `

${html_encode(window.user?.username || 'User')}

`; + h += `

${html_encode(window.user?.email || '')}

`; + h += 'Click the avatar to change your profile picture'; + h += '
'; + h += '
'; h += '
'; // Account settings cards @@ -56,61 +57,75 @@ const TabAccount = { // Username card h += '
'; - h += '
'; - h += '
'; - h += ''; - h += '
'; - h += '
'; - h += `${i18n('username')}`; - h += `${html_encode(window.user.username)}`; - h += '
'; - h += '
'; - h += ``; + h += '
'; + h += '
'; + h += ''; + h += '
'; + h += '
'; + h += `${i18n('username')}`; + h += `${html_encode(window.user.username)}`; + h += '
'; + h += '
'; + h += ``; h += '
'; // Password card (only for non-temp users) - if ( !window.user.is_temp ) { + if ( ! window.user.is_temp ) { h += '
'; - h += '
'; - h += '
'; - h += ''; - h += '
'; - h += '
'; - h += `${i18n('password')}`; - h += '••••••••'; - h += '
'; - h += '
'; - h += ``; + h += '
'; + h += '
'; + h += ''; + h += '
'; + h += '
'; + h += `${i18n('password')}`; + h += '••••••••'; + h += '
'; + h += '
'; + h += ``; h += '
'; } // Email card (only if email exists) if ( window.user.email ) { h += '
'; - h += '
'; - h += '
'; - h += ''; - h += '
'; - h += '
'; - h += `${i18n('email')}`; - h += `${html_encode(window.user.email)}`; - h += '
'; - h += '
'; - h += ``; + h += '
'; + h += '
'; + h += ''; + h += '
'; + h += '
'; + h += `${i18n('email')}`; + h += `${html_encode(window.user.email)}`; + h += '
'; + h += '
'; + h += ``; h += '
'; } + // Auth token card + h += '
'; + h += '
'; + h += '
'; + h += ''; + h += '
'; + h += '
'; + h += `${i18n('auth_token')}`; + h += `${i18n('copy_token_description')}`; + h += '
'; + h += '
'; + h += ``; + h += '
'; + // Danger zone h += '
'; - h += '
'; - h += '
'; - h += '
'; - h += `${i18n('delete_account')}`; - h += 'Permanently delete your account and all associated data. This action cannot be undone.'; - h += '
'; - h += '
'; - h += ``; - h += '
'; + h += '
'; + h += '
'; + h += '
'; + h += `${i18n('delete_account')}`; + h += 'Permanently delete your account and all associated data. This action cannot be undone.'; + h += '
'; + h += '
'; + h += ``; + h += '
'; h += '
'; h += '
'; // end settings-grid @@ -156,6 +171,18 @@ const TabAccount = { }, }); }); + $el_window.find('.dashboard-section-account .copy-auth-token').on('click', function (e) { + UIWindowCopyToken({ + show_header: true, + window_options: { + parent_uuid: $el_window.attr('data-element_uuid'), + backdrop: true, + close_on_backdrop_click: false, + parent_center: true, + stay_on_top: true, + }, + }); + }); $el_window.find('.dashboard-section-account .delete-account').on('click', function (e) { UIWindowConfirmUserDeletion({ window_options: { @@ -218,4 +245,3 @@ const TabAccount = { }; export default TabAccount; - diff --git a/src/gui/src/UI/UIWindowCopyToken.js b/src/gui/src/UI/UIWindowCopyToken.js new file mode 100644 index 000000000..5d5f0e616 --- /dev/null +++ b/src/gui/src/UI/UIWindowCopyToken.js @@ -0,0 +1,119 @@ +/** + * Copyright (C) 2024-present Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import UIWindow from './UIWindow.js'; + +function UIWindowCopyToken (options = {}) { + return new Promise(async (resolve) => { + let h = ''; + + if ( options.show_header ) { + h += `
`; + h += `
`; + h += ` + + `; + h += '
'; + h += `

${i18n('auth_token')}

`; + h += `

${i18n('copy_token_message')}

`; + h += '
'; + } + + h += '
'; + if ( ! options.show_header ) { + h += `
${i18n('copy_token_message')}
`; + } + h += `
`; + h += ``; + h += ``; + h += '
'; + h += ''; + h += '
'; + + const el_window = await UIWindow({ + title: i18n('auth_token'), + app: 'copy-token', + single_instance: true, + icon: null, + uid: null, + is_dir: false, + body_content: h, + has_head: !options.show_header, + selectable_body: false, + draggable_body: options.show_header, + allow_context_menu: false, + is_resizable: false, + is_droppable: false, + init_center: true, + allow_native_ctxmenu: false, + allow_user_select: false, + width: 450, + height: 'auto', + dominant: true, + show_in_taskbar: false, + window_class: 'window-publishWebsite', + body_css: { + width: 'initial', + height: '100%', + padding: '0', + 'background-color': 'rgb(245 247 249)', + 'backdrop-filter': 'blur(3px)', + }, + ...options.window_options, + }); + + $(el_window).find('.copy-token-btn').on('click', function () { + const $btn = $(this); + navigator.clipboard.writeText(window.auth_token).then(() => { + $(el_window).find('.token-copied-msg').fadeIn(); + $btn.text(i18n('token_copied')); + setTimeout(() => { + $(el_window).find('.token-copied-msg').fadeOut(); + $btn.text(i18n('copy')); + }, 2000); + }); + }); + + $(el_window).on('close', () => { + resolve(); + }); + }); +} + +def(UIWindowCopyToken, 'ui.window.UIWindowCopyToken'); + +export default UIWindowCopyToken; diff --git a/src/gui/src/i18n/translations/en.js b/src/gui/src/i18n/translations/en.js index 77bda3853..44784b9af 100644 --- a/src/gui/src/i18n/translations/en.js +++ b/src/gui/src/i18n/translations/en.js @@ -550,6 +550,14 @@ const en = { 'error_user_or_path_not_found': 'User or path not found.', 'error_invalid_username': 'Invalid username.', + + // Auth token + auth_token: 'Auth Token', + token_copied: 'Token copied', + copy_auth_token: 'Copy Auth Token', + approve: 'Approve', + copy_token_message: 'Your authentication token is shown below. Keep it secret \u2014 anyone with this token can access your account.', + copy_token_description: 'View and copy your authentication token', }, }; diff --git a/src/gui/src/initgui.js b/src/gui/src/initgui.js index 2b00779bb..2f4ed4eb6 100644 --- a/src/gui/src/initgui.js +++ b/src/gui/src/initgui.js @@ -30,6 +30,7 @@ import UIWindowRequestPermission from './UI/UIWindowRequestPermission.js'; import UIWindowSaveAccount from './UI/UIWindowSaveAccount.js'; import UIWindowSessionList from './UI/UIWindowSessionList.js'; import UIWindowSignup from './UI/UIWindowSignup.js'; +import UIWindowCopyToken from './UI/UIWindowCopyToken.js'; import { PROCESS_RUNNING } from './definitions.js'; import item_icon from './helpers/item_icon.js'; import update_last_touch_coordinates from './helpers/update_last_touch_coordinates.js'; @@ -623,7 +624,7 @@ window.initgui = async function (options) { const approved = await UIAlert({ message: `Do you want to authorize and redirect to ${html_encode(redirectURL)}?`, buttons: [ - { label: i18n('approve') || 'Approve', value: 'approve', type: 'primary' }, + { label: i18n('approve'), value: 'approve', type: 'primary' }, { label: i18n('cancel'), value: 'cancel', type: 'secondary' }, ], type: 'confirm', @@ -637,6 +638,13 @@ window.initgui = async function (options) { } } + // ------------------------------------------------------------------------------------- + // Action: CopyAuth — show dialog to copy auth token + // ------------------------------------------------------------------------------------- + if ( action === 'copyauth' ) { + await UIWindowCopyToken({ show_header: true }); + } + // ------------------------------------------------------------------------------------- // Load desktop, only if we're not embedded in a popup and not on the dashboard page // ------------------------------------------------------------------------------------- @@ -968,15 +976,18 @@ window.initgui = async function (options) { // Un-authed but not first visit -> try to log in/sign up // ------------------------------------------------------------------------------------- if ( !window.is_auth() && (!window.first_visit_ever || window.disable_temp_users) ) { + const needs_action = action === 'authme' || action === 'copyauth'; + const reload_on_success = !needs_action; if ( window.logged_in_users.length > 0 ) { - UIWindowSessionList(); + await UIWindowSessionList({ + reload_on_success, + }); } else { const resp = await fetch(`${window.gui_origin }/whoarewe`); const whoarewe = await resp.json(); await UIWindowLogin({ - // show_signup_button: - reload_on_success: true, + reload_on_success, send_confirmation_code: false, show_signup_button: ( !whoarewe.disable_user_signup ), window_options: { @@ -984,6 +995,9 @@ window.initgui = async function (options) { }, }); } + if ( !reload_on_success && window.is_auth() ) { + document.dispatchEvent(new Event('login', { bubbles: true })); + } } // ------------------------------------------------------------------------------------- @@ -1153,7 +1167,7 @@ window.initgui = async function (options) { const approved = await UIAlert({ message: `Do you want to authorize and redirect to ${html_encode(redirectURL)}?`, buttons: [ - { label: i18n('approve') || 'Approve', value: 'approve', type: 'primary' }, + { label: i18n('approve'), value: 'approve', type: 'primary' }, { label: i18n('cancel'), value: 'cancel', type: 'secondary' }, ], type: 'confirm', @@ -1167,6 +1181,13 @@ window.initgui = async function (options) { } } + // ------------------------------------------------------------------------------------- + // Action: CopyAuth — show dialog to copy auth token + // ------------------------------------------------------------------------------------- + if ( action === 'copyauth' ) { + await UIWindowCopyToken({ show_header: true }); + } + // ------------------------------------------------------------------------------------- // Load desktop, if not embedded in a popup and not on the dashboard page // -------------------------------------------------------------------------------------