Add Chrome extension cookie bridge for session import
Users logged into Alta in Chrome can now send their session cookie to the running Electron app via a local HTTP server on port 18247, eliminating the need for re-authentication. - main.js: HTTP cookie server with CORS, token, domain validation - preload.js: onExtensionCookie push-pattern IPC bridge - renderer.js: handleExtensionCookie sets session, fetches devices - chrome-extension/: Manifest V3 extension with popup UI - CLAUDE.md: updated architecture docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,9 +6,13 @@ const axios = require('axios');
|
||||
const CryptoJS = require('crypto-js');
|
||||
const { spawn } = require('child_process');
|
||||
const crypto = require('crypto');
|
||||
const http = require('http');
|
||||
|
||||
let mainWindow;
|
||||
let activeProxyProcesses = new Map(); // Track active camera proxy processes
|
||||
let cookieServer = null;
|
||||
const COOKIE_SERVER_PORT = 18247;
|
||||
const COOKIE_SERVER_TOKEN = 'apt-local-bridge-token';
|
||||
|
||||
// Sanitize strings before embedding in batch files to prevent command injection
|
||||
function sanitizeBatchInput(input) {
|
||||
@@ -113,6 +117,121 @@ function saveProfiles(profiles) {
|
||||
}
|
||||
}
|
||||
|
||||
function startCookieServer() {
|
||||
cookieServer = http.createServer((req, res) => {
|
||||
// CORS headers — only allow Chrome extension origins
|
||||
const origin = req.headers.origin || '';
|
||||
if (origin.startsWith('chrome-extension://')) {
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
}
|
||||
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-APT-Token');
|
||||
|
||||
// Handle preflight
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.writeHead(204);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Only accept POST /cookie
|
||||
if (req.method !== 'POST' || req.url !== '/cookie') {
|
||||
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Not found' }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify shared token
|
||||
if (req.headers['x-apt-token'] !== COOKIE_SERVER_TOKEN) {
|
||||
res.writeHead(403, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Forbidden' }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read body with 64KB size limit
|
||||
let body = '';
|
||||
let bodySize = 0;
|
||||
const MAX_BODY_SIZE = 65536;
|
||||
|
||||
req.on('data', (chunk) => {
|
||||
bodySize += chunk.length;
|
||||
if (bodySize > MAX_BODY_SIZE) {
|
||||
res.writeHead(413, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Payload too large' }));
|
||||
req.destroy();
|
||||
return;
|
||||
}
|
||||
body += chunk;
|
||||
});
|
||||
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const data = JSON.parse(body);
|
||||
const { deploymentUrl, cookieValue } = data;
|
||||
|
||||
if (!deploymentUrl || !cookieValue) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Missing deploymentUrl or cookieValue' }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate types and lengths
|
||||
if (typeof deploymentUrl !== 'string' || typeof cookieValue !== 'string' ||
|
||||
deploymentUrl.length > 512 || cookieValue.length > 4096) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Invalid parameter types or lengths' }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate deployment URL is an Alta domain
|
||||
try {
|
||||
const parsed = new URL(deploymentUrl);
|
||||
const isAltaDomain = parsed.hostname.endsWith('.avasecurity.com') ||
|
||||
parsed.hostname.endsWith('.avigilon.com');
|
||||
if (!isAltaDomain || parsed.protocol !== 'https:') {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Invalid deployment URL domain' }));
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Invalid deployment URL' }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
const cookies = ['va=' + cookieValue];
|
||||
mainWindow.webContents.send('extension-cookie-received', {
|
||||
deploymentUrl: deploymentUrl.replace(/\/$/, ''),
|
||||
cookies,
|
||||
cookieValue
|
||||
});
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: true, message: 'Cookie received' }));
|
||||
} else {
|
||||
res.writeHead(503, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Application window not available' }));
|
||||
}
|
||||
} catch (e) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, message: 'Invalid JSON' }));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
cookieServer.listen(COOKIE_SERVER_PORT, '127.0.0.1', () => {
|
||||
console.log(`Cookie server listening on http://127.0.0.1:${COOKIE_SERVER_PORT}`);
|
||||
});
|
||||
|
||||
cookieServer.on('error', (err) => {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
console.error(`Cookie server error: Port ${COOKIE_SERVER_PORT} is already in use`);
|
||||
} else {
|
||||
console.error('Cookie server error:', err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1400,
|
||||
@@ -134,7 +253,16 @@ function createWindow() {
|
||||
}
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow);
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
startCookieServer();
|
||||
});
|
||||
|
||||
app.on('before-quit', () => {
|
||||
if (cookieServer) {
|
||||
cookieServer.close();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
|
||||
Reference in New Issue
Block a user