From 67fcb017cc85eac86ea8126fd3967664498f84d7 Mon Sep 17 00:00:00 2001 From: Vadym Samoilenko Date: Wed, 11 Mar 2026 20:17:49 +0000 Subject: [PATCH] Prepare for production: remove hardcoded credentials and fix bugs - Move service account credentials to .env (loaded server-side only) - server.js: inject credentials in proxy, strip any client-provided creds, replace deprecated url.parse with new URL - auth.js / dashboard.js: remove all hardcoded passwords from client code - dashboard.js: remove broken category filter, fix redundant user.info call (use stored userId), add HTML escaping against XSS - login.html: remove unused password field - dashboard.html: remove broken category filter UI - Add .gitignore to exclude .env and node_modules - Add .env.example as configuration template Co-Authored-By: Claude Sonnet 4.6 --- .env.example | 3 + .gitignore | 50 +----- auth.js | 156 +++++----------- dashboard.html | 12 -- dashboard.js | 473 ++++++++++++++----------------------------------- login.html | 11 -- server.js | 286 ++++++++++++------------------ 7 files changed, 295 insertions(+), 696 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4dc673e --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +SERVICE_USERNAME= +SERVICE_PASSWORD= +PORT=3000 diff --git a/.gitignore b/.gitignore index b24d71e..713d500 100644 --- a/.gitignore +++ b/.gitignore @@ -1,50 +1,2 @@ -# These are some examples of commonly ignored file patterns. -# You should customize this list as applicable to your project. -# Learn more about .gitignore: -# https://www.atlassian.com/git/tutorials/saving-changes/gitignore - -# Node artifact files node_modules/ -dist/ - -# Compiled Java class files -*.class - -# Compiled Python bytecode -*.py[cod] - -# Log files -*.log - -# Package files -*.jar - -# Maven -target/ -dist/ - -# JetBrains IDE -.idea/ - -# Unit test reports -TEST*.xml - -# Generated by MacOS -.DS_Store - -# Generated by Windows -Thumbs.db - -# Applications -*.app -*.exe -*.war - -# Large media files -*.mp4 -*.tiff -*.avi -*.flv -*.mov -*.wmv - +.env diff --git a/auth.js b/auth.js index a55e0ce..1912755 100644 --- a/auth.js +++ b/auth.js @@ -1,163 +1,95 @@ -// Authentication Configurations const OLIVER_AUTH_CONFIG = { - apiBaseUrl: '/api', // Use local proxy to avoid CORS issues - authDomain: 'local', - authUsername: 'portal@oliver.agency', - authPassword: 'Sp1d3r26!', + apiBaseUrl: '/api', clientId: '7', - userId: '9' }; const TMM_AUTH_CONFIG = { - apiBaseUrl: '/api', // Use local proxy to avoid CORS issues - authDomain: 'local', - authUsername: 'portal@oliver.agency', - authPassword: 'Sp1d3r26!', + apiBaseUrl: '/api', clientId: '7', - userId: '9' }; -// Handle login with external session async function handleLogin(authConfig, loginType) { - const username = document.getElementById('username').value; - const password = document.getElementById('password').value; + const username = document.getElementById('username').value.trim(); const errorMessage = document.getElementById('errorMessage'); - - // Get the correct button elements based on login type const isOliver = loginType === 'oliver'; const loginButton = document.getElementById(isOliver ? 'oliverLoginButton' : 'tmmLoginButton'); - const buttonText = document.getElementById(isOliver ? 'oliverButtonText' : 'tmmButtonText'); - const spinner = document.getElementById(isOliver ? 'oliverSpinner' : 'tmmSpinner'); + const buttonText = document.getElementById(isOliver ? 'oliverButtonText' : 'tmmButtonText'); + const spinner = document.getElementById(isOliver ? 'oliverSpinner' : 'tmmSpinner'); - // Hide any previous error messages errorMessage.style.display = 'none'; - // Disable button and show spinner + if (!username) { + errorMessage.textContent = 'Please enter your username.'; + errorMessage.style.display = 'block'; + return; + } + loginButton.disabled = true; buttonText.textContent = 'Signing in...'; spinner.style.display = 'inline-block'; try { - // First, call user.info to get the real userId for the entered username + // Step 1: Resolve username → userId const userInfoParams = new URLSearchParams({ - command: 'user.info', + command: 'user.info', authDomain: 'local', - authUsername: 'portal@oliver.agency', - authPassword: 'Sp1d3r26!', - clientId: '6', - username: username, - domain: 'local' + clientId: '6', + username, + domain: 'local', }); - const userInfoUrl = `${authConfig.apiBaseUrl}?${userInfoParams.toString()}`; - - const userInfoResponse = await fetch(userInfoUrl, { - method: 'GET', - headers: { - 'Accept': 'text/xml' - } + const userInfoRes = await fetch(`${authConfig.apiBaseUrl}?${userInfoParams}`, { + headers: { Accept: 'text/xml' }, }); + const userInfoDoc = new DOMParser().parseFromString(await userInfoRes.text(), 'text/xml'); - const userInfoXml = await userInfoResponse.text(); - console.log('User Info API Response:', userInfoXml); - - const userInfoParser = new DOMParser(); - const userInfoDoc = userInfoParser.parseFromString(userInfoXml, 'text/xml'); - - // Check for errors const userInfoError = userInfoDoc.querySelector('error'); if (userInfoError) { - const errorMsg = userInfoError.querySelector('message')?.textContent || 'User not found'; - throw new Error(errorMsg); + throw new Error(userInfoError.querySelector('message')?.textContent || 'User not found'); } - // Extract userId from user.info response const userIdNode = userInfoDoc.querySelector('user > id') || userInfoDoc.querySelector('id'); - if (!userIdNode) { - throw new Error('User not found. Please check your username.'); - } + if (!userIdNode) throw new Error('User not found. Please check your username.'); const userId = userIdNode.textContent; - console.log('User ID from user.info:', userId); - // Build the API URL for user.session.extern.add - const params = new URLSearchParams({ - command: 'user.session.extern.add', - authDomain: authConfig.authDomain, - authUsername: authConfig.authUsername, - authPassword: authConfig.authPassword, - clientId: authConfig.clientId, - username: username, - userId: userId + // Step 2: Create external session for the user + const sessionParams = new URLSearchParams({ + command: 'user.session.extern.add', + authDomain: 'local', + clientId: authConfig.clientId, + username, + userId, }); - const apiUrl = `${authConfig.apiBaseUrl}?${params.toString()}`; - - // Make the API call - const response = await fetch(apiUrl, { - method: 'GET', - headers: { - 'Accept': 'text/xml' - } + const sessionRes = await fetch(`${authConfig.apiBaseUrl}?${sessionParams}`, { + headers: { Accept: 'text/xml' }, }); + const sessionDoc = new DOMParser().parseFromString(await sessionRes.text(), 'text/xml'); - const xmlText = await response.text(); - console.log('API Response:', xmlText); - - // Parse XML response - const parser = new DOMParser(); - const xmlDoc = parser.parseFromString(xmlText, 'text/xml'); - - // Check for errors in XML - const errorNode = xmlDoc.querySelector('error'); - if (errorNode) { - const errorMsg = errorNode.querySelector('message')?.textContent || 'Login failed'; - throw new Error(errorMsg); + const sessionError = sessionDoc.querySelector('error'); + if (sessionError) { + throw new Error(sessionError.querySelector('message')?.textContent || 'Login failed'); } - // Extract externSessionId from the response - const externSessionIdNode = xmlDoc.querySelector('externSessionId'); - const externSessionId = externSessionIdNode?.textContent; + const externSessionId = sessionDoc.querySelector('externSessionId')?.textContent; + if (!externSessionId) throw new Error('No session received from server'); - if (!externSessionId) { - throw new Error('No externSessionId received from server'); - } + sessionStorage.setItem('isAuthenticated', 'true'); + sessionStorage.setItem('username', username); + sessionStorage.setItem('userId', userId); + sessionStorage.setItem('externSessionId', externSessionId); + sessionStorage.setItem('authConfig', JSON.stringify(authConfig)); - console.log('Extern Session ID:', externSessionId); - - // Check if login was successful - if (response.ok && !errorNode) { - // Store session data in sessionStorage - sessionStorage.setItem('isAuthenticated', 'true'); - sessionStorage.setItem('username', username); - sessionStorage.setItem('userId', userId); - sessionStorage.setItem('externSessionId', externSessionId); - sessionStorage.setItem('authConfig', JSON.stringify(authConfig)); - - // Redirect to dashboard - window.location.href = 'dashboard.html'; - } else { - throw new Error('Login failed. Please check your credentials.'); - } + window.location.href = 'dashboard.html'; } catch (error) { console.error('Login error:', error); - - // Show error message errorMessage.textContent = error.message || 'An error occurred. Please try again.'; errorMessage.style.display = 'block'; - - // Re-enable button loginButton.disabled = false; buttonText.textContent = isOliver ? 'Oliver Login' : '3M Login'; spinner.style.display = 'none'; } } -// Handle Oliver login button click -document.getElementById('oliverLoginButton').addEventListener('click', function() { - handleLogin(OLIVER_AUTH_CONFIG, 'oliver'); -}); - -// Handle 3M login button click -document.getElementById('tmmLoginButton').addEventListener('click', function() { - handleLogin(TMM_AUTH_CONFIG, '3m'); -}); +document.getElementById('oliverLoginButton').addEventListener('click', () => handleLogin(OLIVER_AUTH_CONFIG, 'oliver')); +document.getElementById('tmmLoginButton').addEventListener('click', () => handleLogin(TMM_AUTH_CONFIG, '3m')); diff --git a/dashboard.html b/dashboard.html index ee38351..e3b8c45 100644 --- a/dashboard.html +++ b/dashboard.html @@ -53,18 +53,6 @@ - -
- - -
-