From 4b8c46efc86aa56f4b8f2483990ef4e4e204cf1f Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Sun, 1 Feb 2026 13:02:25 -0800 Subject: [PATCH] Add UI to view and copy auth token Introduce a new UIWindowCopyToken component to display the user's auth token with a copy-to-clipboard button and confirmation message. Integrate the token UI into the account dashboard (TabAccount) by adding an auth token card and click handler to open the window. Add corresponding i18n keys (auth_token, token_copied, copy_token_message, copy_token_description) and import the new window in initgui. Also wire the global action 'copyauth' to show the token dialog and adjust session/login flow so the session list/login windows respect the action (avoid auto-reload when showing the token dialog). Minor UI string cleanup to use i18n('approve') directly. --- src/gui/src/UI/Dashboard/TabAccount.js | 134 +++++++++++++++---------- src/gui/src/UI/UIWindowCopyToken.js | 119 ++++++++++++++++++++++ src/gui/src/i18n/translations/en.js | 8 ++ src/gui/src/initgui.js | 31 +++++- 4 files changed, 233 insertions(+), 59 deletions(-) create mode 100644 src/gui/src/UI/UIWindowCopyToken.js 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 // -------------------------------------------------------------------------------------