diff --git a/serve-proxy.js b/serve-proxy.js index 8c3d236e54..4773be090f 100644 --- a/serve-proxy.js +++ b/serve-proxy.js @@ -2,15 +2,14 @@ /* eslint-env node */ const http = require('http'); -const https = require('https'); const url = require('url'); const path = require('path'); const fs = require('fs'); const httpProxy = require('http-proxy'); // Account server configuration - switch between local and production -const ACCOUNT_SERVER = 'https://account.phcode.dev'; // Production -// const ACCOUNT_SERVER = 'http://localhost:5000'; // Local development +//const ACCOUNT_SERVER = 'https://account.phcode.dev'; // Production +const ACCOUNT_SERVER = 'http://localhost:5000'; // Local development // Default configuration let config = { @@ -90,8 +89,6 @@ proxy.on('proxyReq', (proxyReq, req) => { if (originalOrigin && originalOrigin.includes('localhost:8000')) { const newOrigin = originalOrigin.replace(/localhost:8000/g, 'phcode.dev'); proxyReq.setHeader('Origin', newOrigin); - } else if (!originalOrigin) { - proxyReq.setHeader('Origin', 'https://phcode.dev'); } // Ensure HTTPS scheme diff --git a/src/services/html/profile-popup.html b/src/services/html/profile-popup.html index a2f5d79144..29f906483a 100644 --- a/src/services/html/profile-popup.html +++ b/src/services/html/profile-popup.html @@ -4,9 +4,10 @@
{{initials}}
-
+
+
{{planName}}
diff --git a/src/services/login-browser.js b/src/services/login-browser.js index 336984f016..1c139b340d 100644 --- a/src/services/login-browser.js +++ b/src/services/login-browser.js @@ -413,6 +413,7 @@ define(function (require, exports, module) { secureExports.signOutAccount = signOutBrowser; secureExports.getProfile = getProfile; secureExports.verifyLoginStatus = () => _verifyBrowserLogin(false); + secureExports.getAccountBaseURL = _getAccountBaseURL; } // public exports diff --git a/src/services/login-desktop.js b/src/services/login-desktop.js index 4dfef4ee5a..b960d369d3 100644 --- a/src/services/login-desktop.js +++ b/src/services/login-desktop.js @@ -74,6 +74,14 @@ define(function (require, exports, module) { return userProfile; } + /** + * Get the account base URL for API calls + * For desktop apps, this directly uses the configured account URL + */ + function getAccountBaseURL() { + return Phoenix.config.account_url.replace(/\/$/, ''); // Remove trailing slash + } + const ERR_RETRY_LATER = "retry_later"; const ERR_INVALID = "invalid"; @@ -408,6 +416,7 @@ define(function (require, exports, module) { secureExports.signOutAccount = signOutAccount; secureExports.getProfile = getProfile; secureExports.verifyLoginStatus = () => _verifyLogin(false); + secureExports.getAccountBaseURL = getAccountBaseURL; } // public exports diff --git a/src/services/profile-menu.js b/src/services/profile-menu.js index aa10e15210..8da38fcfb1 100644 --- a/src/services/profile-menu.js +++ b/src/services/profile-menu.js @@ -1,6 +1,7 @@ define(function (require, exports, module) { const Mustache = require("thirdparty/mustache/mustache"), PopUpManager = require("widgets/PopUpManager"), + ThemeManager = require("view/ThemeManager"), Strings = require("strings"); const KernalModeTrust = window.KernalModeTrust; @@ -211,6 +212,78 @@ define(function (require, exports, module) { /* eslint-disable-next-line*/ customElements.define ('secure-name', SecureName); // space is must in define ( to prevent build fail + /** + * Load user details iframe with secure user information + */ + function _loadUserDetailsIframe() { + if (!Phoenix.isNativeApp && $popup) { + const $iframe = $popup.find("#user-details-frame"); + const $secureName = $popup.find(".user-name secure-name"); + const $secureEmail = $popup.find(".user-email secure-email"); + + if ($iframe.length) { + // Get account base URL for iframe using login service + const accountBaseURL = KernalModeTrust.loginService.getAccountBaseURL(); + const currentTheme = ThemeManager.getCurrentTheme(); + const nameColor = (currentTheme && currentTheme.dark) ? "FFFFFF" : "000000"; + + // Configure iframe URL with styling parameters + const iframeURL = `${accountBaseURL}/getUserDetailFrame?` + + `includeName=true&` + + `nameFontSize=14px&` + + `emailFontSize=12px&` + + `nameColor=%23${nameColor}&` + + `emailColor=%23666666&` + + `backgroundColor=transparent`; + + // Listen for iframe load events + const messageHandler = function(event) { + // Only accept messages from trusted account domain + // Handle proxy case where accountBaseURL is '/proxy/accounts' + let trustedOrigin; + if (accountBaseURL.startsWith('/proxy/accounts')) { + // For localhost with proxy, accept messages from current origin + trustedOrigin = window.location.origin; + } else { + // For production, get origin from account URL + trustedOrigin = new URL(accountBaseURL).origin; + } + + if (event.origin !== trustedOrigin) { + return; + } + + if (event.data && event.data.loaded) { + // Hide secure DOM elements and show iframe + $secureName.hide(); + $secureEmail.hide(); + $iframe.show(); + + // Adjust iframe height based on content + $iframe.css('height', '36px'); // Approximate height for name + email + + // Remove event listener + window.removeEventListener('message', messageHandler); + } + }; + + // Add message listener + window.addEventListener('message', messageHandler); + + // Set iframe source to load user details + $iframe.attr('src', iframeURL); + + // Fallback timeout - if iframe doesn't load in 5 seconds, keep secure elements + setTimeout(() => { + if ($iframe.is(':hidden')) { + console.log('User details iframe failed to load, keeping secure elements'); + window.removeEventListener('message', messageHandler); + } + }, 5000); + } + } + } + /** * Shows the user profile popup when the user is logged in */ @@ -271,6 +344,9 @@ define(function (require, exports, module) { }); _setupDocumentClickHandler(); + + // Load user details iframe for browser apps (after popup is created) + _loadUserDetailsIframe(); } /** diff --git a/src/services/readme-login-browser-no_dist.md b/src/services/readme-login-browser-no_dist.md index 87c8946fa4..85acca4f92 100644 --- a/src/services/readme-login-browser-no_dist.md +++ b/src/services/readme-login-browser-no_dist.md @@ -6,6 +6,11 @@ This document provides comprehensive documentation for integrating with the Phoe The Phoenix browser application uses a login service to authenticate users across the phcode.dev domain ecosystem. The login service handles user authentication, session management, and provides secure API endpoints for login operations. +**Key Features:** +- Domain-wide session management using session cookies +- Secure user profile display via iframe integration +- Proxy server support for localhost development + **Key Files:** - `src/services/login-browser.js` - Main browser login implementation - `serve-proxy.js` - Proxy server for localhost development @@ -122,9 +127,15 @@ The login service provides these key endpoints: ### Authentication - `POST /signOutPost` - Sign out user (new endpoint with proper JSON handling) -- `GET /resolveBrowserSession` - Validate and resolve current session +- `GET /resolveBrowserSession` - Validate and resolve current session (returns masked user data for security) - `GET /signOut` - Legacy signout endpoint (deprecated for browser use) +### User Profile Display +- `GET /getUserDetailFrame` - Returns HTML iframe with full user details for secure display + - Query parameters for styling: `includeName`, `nameFontSize`, `emailFontSize`, `nameColor`, `emailColor`, `backgroundColor` + - CSP-protected to only allow embedding in trusted domains + - Cross-origin communication via postMessage when loaded + ### Session Management - Session validation through `session` cookie - Automatic session invalidation on logout @@ -187,6 +198,12 @@ Browser (localhost:8000) → /proxy/accounts/* → serve-proxy.js - Session cookies should have appropriate expiration times - Logout should properly invalidate sessions on both client and server +### User Data Security +- **Masked API Data**: The `resolveBrowserSession` endpoint returns masked user data (e.g., "J***", "j***@g***.com") to prevent exposure to browser extensions +- **Secure iframe Display**: Full user details are displayed via iframe from trusted account server +- **CSP Protection**: iframe is protected by Content Security Policy headers restricting embedding domains +- **Cross-Origin Safety**: iframe communication uses secure postMessage protocol + --- -For browser implementation details, see the source code in `src/services/login-browser.js` and related files. For desktop authentication, see `src/services/login-desktop.js` and `readme-login-desktop-no_dist.md`. \ No newline at end of file +For browser implementation details, see the source code in `src/services/login-browser.js` and related files. For desktop authentication, see `src/services/login-desktop.js` and `readme-login-desktop-no_dist.md`.