diff --git a/src/gui/src/UI/Dashboard/TabSecurity.js b/src/gui/src/UI/Dashboard/TabSecurity.js new file mode 100644 index 000000000..38c51413e --- /dev/null +++ b/src/gui/src/UI/Dashboard/TabSecurity.js @@ -0,0 +1,226 @@ +/** + * 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 TeePromise from '../../util/TeePromise.js'; +import UIComponentWindow from '../UIComponentWindow.js'; +import UIWindow2FASetup from '../UIWindow2FASetup.js'; +import UIWindowChangePassword from '../UIWindowChangePassword.js'; +import UIWindowManageSessions from '../UIWindowManageSessions.js'; + +const TabSecurity = { + id: 'security', + label: i18n('security'), + icon: ``, + + html () { + let h = ''; + let user = window.user; + + h += '
'; + + // Section header + h += '
'; + h += `

${i18n('security')}

`; + h += '

Manage your security settings and sessions

'; + h += '
'; + + // Security settings cards + h += '
'; + + // Password card (only for non-temp users) + if ( !user.is_temp ) { + h += '
'; + h += '
'; + h += '
'; + h += ''; + h += '
'; + h += '
'; + h += `${i18n('password')}`; + h += '••••••••'; + h += '
'; + h += '
'; + h += ``; + h += '
'; + } + + // Sessions card + h += '
'; + h += '
'; + h += '
'; + h += ''; + h += '
'; + h += '
'; + h += `${i18n('sessions')}`; + h += 'Manage active sessions'; + h += '
'; + h += '
'; + h += ``; + h += '
'; + + // 2FA card (only for non-temp users with confirmed email) + if ( !user.is_temp && user.email_confirmed ) { + const twoFaStatusClass = user.otp ? 'dashboard-settings-card-success' : 'dashboard-settings-card-warning'; + h += `
`; + h += '
'; + h += '
'; + h += ''; + h += '
'; + h += '
'; + h += `${i18n('two_factor')}`; + h += `${i18n(user.otp ? 'two_factor_enabled' : 'two_factor_disabled')}`; + h += '
'; + h += '
'; + h += ``; + h += ``; + h += '
'; + } + + h += '
'; // end settings-grid + + h += '
'; // end dashboard-tab-content + return h; + }, + + init ($el_window) { + $el_window.find('.dashboard-section-security .change-password').on('click', function (e) { + UIWindowChangePassword({ + window_options: { + parent_uuid: $el_window.attr('data-element_uuid'), + disable_parent_window: true, + parent_center: true, + }, + }); + }); + + $el_window.find('.dashboard-section-security .manage-sessions').on('click', function (e) { + UIWindowManageSessions({ + window_options: { + parent_uuid: $el_window.attr('data-element_uuid'), + disable_parent_window: true, + parent_center: true, + }, + }); + }); + + $el_window.find('.dashboard-section-security .enable-2fa').on('click', async function (e) { + const { promise } = await UIWindow2FASetup(); + const tfa_was_enabled = await promise; + + if ( tfa_was_enabled ) { + $el_window.find('.dashboard-section-security .enable-2fa').hide(); + $el_window.find('.dashboard-section-security .disable-2fa').show(); + $el_window.find('.dashboard-section-security .user-otp-state').text(i18n('two_factor_enabled')); + $el_window.find('.dashboard-section-security .dashboard-settings-card-2fa').removeClass('dashboard-settings-card-warning'); + $el_window.find('.dashboard-section-security .dashboard-settings-card-2fa').addClass('dashboard-settings-card-success'); + } + }); + + $el_window.find('.dashboard-section-security .disable-2fa').on('click', async function (e) { + let win; + const password_confirm_promise = new TeePromise(); + const try_password = async () => { + const value = $win.find('.password-entry').val(); + const resp = await fetch(`${window.api_origin}/user-protected/disable-2fa`, { + method: 'POST', + headers: { + Authorization: `Bearer ${puter.authToken}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + password: value, + }), + }); + if ( resp.status !== 200 ) { + /* eslint no-empty: ["error", { "allowEmptyCatch": true }] */ + let message; try { + message = (await resp.json()).message; + } catch (e) { + } + message = message || i18n('error_unknown_cause'); + $win.find('.password-entry').addClass('error'); + $win.find('.error-message').text(message).show(); + return; + } + password_confirm_promise.resolve(true); + $(win).close(); + }; + + let h = ''; + h += '
'; + h += '
'; + h += `

${i18n('disable_2fa_confirm')}

`; + h += `

${i18n('disable_2fa_instructions')}

`; + h += '
'; + h += '
'; + h += ''; + h += ''; + h += '
'; + h += '
'; + h += ``; + h += ``; + h += '
'; + h += '
'; + + win = await UIComponentWindow({ + html: h, + width: 500, + backdrop: true, + is_resizable: false, + body_css: { + width: 'initial', + 'background-color': 'rgb(245 247 249)', + 'backdrop-filter': 'blur(3px)', + padding: '20px', + }, + }); + + // Set up event listeners + const $win = $(win); + const $password_entry = $win.find('.password-entry'); + + $password_entry.on('keypress', (e) => { + if ( e.which === 13 ) { // Enter key + try_password(); + } + }); + + $win.find('.confirm-disable-2fa').on('click', () => { + try_password(); + }); + + $win.find('.cancel-disable-2fa').on('click', () => { + password_confirm_promise.resolve(false); + $win.close(); + }); + + $password_entry.focus(); + + const ok = await password_confirm_promise; + if ( ! ok ) return; + + $el_window.find('.dashboard-section-security .enable-2fa').show(); + $el_window.find('.dashboard-section-security .disable-2fa').hide(); + $el_window.find('.dashboard-section-security .user-otp-state').text(i18n('two_factor_disabled')); + $el_window.find('.dashboard-section-security .dashboard-settings-card-2fa').removeClass('dashboard-settings-card-success'); + $el_window.find('.dashboard-section-security .dashboard-settings-card-2fa').addClass('dashboard-settings-card-warning'); + }); + }, +}; + +export default TabSecurity; diff --git a/src/gui/src/UI/Dashboard/UIDashboard.js b/src/gui/src/UI/Dashboard/UIDashboard.js index 2a8cd11f9..096b0547f 100644 --- a/src/gui/src/UI/Dashboard/UIDashboard.js +++ b/src/gui/src/UI/Dashboard/UIDashboard.js @@ -31,6 +31,7 @@ import TabFiles from './TabFiles.js'; import TabApps from './TabApps.js'; import TabUsage from './TabUsage.js'; import TabAccount from './TabAccount.js'; +import TabSecurity from './TabSecurity.js'; // Registry of all available tabs const tabs = [ @@ -39,6 +40,7 @@ const tabs = [ // TabFiles, TabUsage, TabAccount, + TabSecurity, ]; async function UIDashboard (options) { diff --git a/src/gui/src/css/style.css b/src/gui/src/css/style.css index 11a11405b..dc14bc71d 100644 --- a/src/gui/src/css/style.css +++ b/src/gui/src/css/style.css @@ -6595,6 +6595,24 @@ html.dark-mode .usage-table-show-less:hover { flex-shrink: 0; } +.dashboard-settings-card-success { + border-color: #08bf4e; + background: #e6ffed; +} + +.dashboard-settings-card-success .dashboard-settings-card-info span { + color: #03933a; +} + +.dashboard-settings-card-warning { + border-color: #f0a500; + background: #fff7e6; +} + +.dashboard-settings-card-warning .dashboard-settings-card-info span { + color: #c98900; +} + /* Danger zone */ .dashboard-danger-zone { margin-top: 40px;