Fix GST operator agent localhost HTTPS detection
This commit is contained in:
@@ -1,18 +1,27 @@
|
||||
ARRR GST Operator Agent 1.0.0
|
||||
ARRR GST Operator Agent 1.0.1
|
||||
|
||||
Purpose
|
||||
- Runs only on the ERP operator workstation.
|
||||
- Listens on 127.0.0.1:8791 only.
|
||||
- Opens installed Chrome/Edge visibly.
|
||||
- Receives only a short-lived ERP job token from the ERP page.
|
||||
- Redeems GST username/password directly from ERP Credential Vault over HTTPS.
|
||||
- Autofills username/password; CAPTCHA/OTP remain manual.
|
||||
- Downloads selected GST return data and uploads it back to ERP for transfer to the configured Storage Agent.
|
||||
- Does not provide Tally, Accounting Mirror or client storage services.
|
||||
- Runs only on the GST operator's Windows workstation.
|
||||
- Listens only on 127.0.0.1:8791.
|
||||
- Exposes a trusted local HTTPS endpoint at https://localhost:8791.
|
||||
- Opens visible Chrome/Edge for GST Portal authentication and return download.
|
||||
- Autofills the selected Credential Vault username/password supplied through the ERP short-lived job token.
|
||||
- CAPTCHA/OTP remains interactive with the operator.
|
||||
- Does not run Tally and does not hold the client storage.
|
||||
- Completed GST packages continue through the ERP to the already configured Local Storage Agent.
|
||||
|
||||
Install
|
||||
Right-click PowerShell and run:
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\install_gst_operator_agent.ps1
|
||||
1. Extract the downloaded ZIP.
|
||||
2. Run PowerShell in the extracted ARRR_GST_Operator_Agent folder:
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\install_gst_operator_agent.ps1
|
||||
3. The installer creates a localhost TLS certificate and trusts it only for the current Windows user.
|
||||
4. Verify https://localhost:8791/api/status.
|
||||
5. Refresh the ERP GST Return Reconciliation page.
|
||||
|
||||
Requirement
|
||||
Python 3.11 or later and installed Google Chrome or Microsoft Edge.
|
||||
Upgrade from 1.0.0
|
||||
- Download the current agent ZIP from ERP and run install_gst_operator_agent.ps1 again.
|
||||
- The installer stops the old HTTP listener, replaces the runtime, installs the trusted localhost certificate and starts v1.0.1 over HTTPS.
|
||||
|
||||
Uninstall
|
||||
- Run uninstall_gst_operator_agent.ps1 from the installed/downloaded package.
|
||||
- The installer-created certificate is also removed from the current user's trusted root store.
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import ssl
|
||||
import threading
|
||||
import time
|
||||
import zipfile
|
||||
@@ -13,7 +14,7 @@ from urllib.parse import parse_qs, urlsplit
|
||||
|
||||
import requests
|
||||
|
||||
VERSION = "1.0.0"
|
||||
VERSION = "1.0.1"
|
||||
PORT = 8791
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
DATA = ROOT / "data"
|
||||
@@ -36,7 +37,7 @@ def read_config() -> dict:
|
||||
try:
|
||||
return json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return {"erp_base_url": "https://office.arrrassociates.com", "port": PORT}
|
||||
return {"erp_base_url": "https://office.arrrassociates.com", "port": PORT, "certfile": str(ROOT / "localhost-cert.pem"), "keyfile": str(ROOT / "localhost-key.pem")}
|
||||
|
||||
|
||||
def safe(value: str, fallback: str = "item") -> str:
|
||||
@@ -340,7 +341,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
path = urlsplit(self.path)
|
||||
if path.path == "/api/status":
|
||||
return self.reply({"ok": True, "name": "ARRR GST Operator Agent", "version": VERSION, "port": PORT})
|
||||
return self.reply({"ok": True, "name": "ARRR GST Operator Agent", "version": VERSION, "port": PORT, "transport": "https", "host": "localhost"})
|
||||
if path.path == "/api/gst/status":
|
||||
job_id = str((parse_qs(path.query).get("job_id") or [""])[0])
|
||||
return self.reply({"ok": True, "job": read_json(job_path(job_id))})
|
||||
@@ -362,7 +363,15 @@ def main():
|
||||
DATA.mkdir(parents=True, exist_ok=True)
|
||||
cfg = read_config()
|
||||
port = int(cfg.get("port") or PORT)
|
||||
certfile = Path(str(cfg.get("certfile") or (ROOT / "localhost-cert.pem")))
|
||||
keyfile = Path(str(cfg.get("keyfile") or (ROOT / "localhost-key.pem")))
|
||||
if not certfile.is_file() or not keyfile.is_file():
|
||||
raise RuntimeError("GST Operator Agent localhost TLS certificate is missing. Re-run the agent installer.")
|
||||
server = ThreadingHTTPServer(("127.0.0.1", port), Handler)
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
context.minimum_version = ssl.TLSVersion.TLSv1_2
|
||||
context.load_cert_chain(certfile=str(certfile), keyfile=str(keyfile))
|
||||
server.socket = context.wrap_socket(server.socket, server_side=True)
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
|
||||
@@ -6,8 +6,9 @@ $Source = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$InstallRoot = Join-Path $env:LOCALAPPDATA "ARRR\GSTOperatorAgent"
|
||||
$Venv = Join-Path $InstallRoot ".venv"
|
||||
$Startup = Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs\Startup\ARRR GST Operator Agent.cmd"
|
||||
$ThumbprintFile = Join-Path $InstallRoot "cert_thumbprint.txt"
|
||||
|
||||
Write-Host "ARRR GST Operator Agent 1.0.0" -ForegroundColor Cyan
|
||||
Write-Host "ARRR GST Operator Agent 1.0.1" -ForegroundColor Cyan
|
||||
Write-Host "Install root: $InstallRoot"
|
||||
|
||||
$Python = $null
|
||||
@@ -16,10 +17,25 @@ elseif (Get-Command python -ErrorAction SilentlyContinue) { $Python = @("python"
|
||||
else { throw "Python 3 is required on this workstation. Install Python 3.11+ and run this installer again." }
|
||||
|
||||
New-Item -ItemType Directory -Path $InstallRoot -Force | Out-Null
|
||||
Copy-Item (Join-Path $Source "gst_operator_agent.py") (Join-Path $InstallRoot "gst_operator_agent.py") -Force
|
||||
Copy-Item (Join-Path $Source "requirements.txt") (Join-Path $InstallRoot "requirements.txt") -Force
|
||||
$RuntimeFiles = @(
|
||||
"gst_operator_agent.py",
|
||||
"generate_localhost_cert.py",
|
||||
"requirements.txt",
|
||||
"README.txt",
|
||||
"uninstall_gst_operator_agent.ps1"
|
||||
)
|
||||
foreach ($Name in $RuntimeFiles) {
|
||||
$From = Join-Path $Source $Name
|
||||
if (-not (Test-Path $From)) { throw "Required agent file is missing: $Name" }
|
||||
Copy-Item $From (Join-Path $InstallRoot $Name) -Force
|
||||
}
|
||||
|
||||
$config = @{ erp_base_url = $ErpBaseUrl.TrimEnd('/'); port = 8791 } | ConvertTo-Json
|
||||
$config = @{
|
||||
erp_base_url = $ErpBaseUrl.TrimEnd('/')
|
||||
port = 8791
|
||||
certfile = (Join-Path $InstallRoot "localhost-cert.pem")
|
||||
keyfile = (Join-Path $InstallRoot "localhost-key.pem")
|
||||
} | ConvertTo-Json
|
||||
[System.IO.File]::WriteAllText((Join-Path $InstallRoot "config.json"), $config, [System.Text.UTF8Encoding]::new($false))
|
||||
|
||||
if (-not (Test-Path (Join-Path $Venv "Scripts\python.exe"))) {
|
||||
@@ -34,11 +50,26 @@ if ($LASTEXITCODE -ne 0) { throw "pip upgrade failed." }
|
||||
& $VenvPython -m pip install --disable-pip-version-check -r (Join-Path $InstallRoot "requirements.txt")
|
||||
if ($LASTEXITCODE -ne 0) { throw "GST Operator Agent dependencies could not be installed." }
|
||||
|
||||
# Remove only the certificate previously installed by this agent instance.
|
||||
if (Test-Path $ThumbprintFile) {
|
||||
$OldThumbprint = (Get-Content $ThumbprintFile -Raw -ErrorAction SilentlyContinue).Trim()
|
||||
if ($OldThumbprint) {
|
||||
Remove-Item "Cert:\CurrentUser\Root\$OldThumbprint" -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
# Create a localhost-only TLS certificate and trust it for the current Windows user.
|
||||
& $VenvPython (Join-Path $InstallRoot "generate_localhost_cert.py")
|
||||
if ($LASTEXITCODE -ne 0) { throw "Could not create the GST Operator Agent localhost certificate." }
|
||||
$CertFile = Join-Path $InstallRoot "localhost-cert.cer"
|
||||
$ImportedCert = Import-Certificate -FilePath $CertFile -CertStoreLocation "Cert:\CurrentUser\Root"
|
||||
if (-not $ImportedCert -or -not $ImportedCert.Thumbprint) { throw "Could not trust the GST Operator Agent localhost certificate." }
|
||||
[System.IO.File]::WriteAllText($ThumbprintFile, $ImportedCert.Thumbprint, [System.Text.ASCIIEncoding]::new())
|
||||
|
||||
$Pythonw = Join-Path $Venv "Scripts\pythonw.exe"
|
||||
$Cmd = "@echo off`r`nstart `"`" `"$Pythonw`" `"$InstallRoot\gst_operator_agent.py`"`r`n"
|
||||
[System.IO.File]::WriteAllText($Startup, $Cmd, [System.Text.ASCIIEncoding]::new())
|
||||
|
||||
# Stop an older copy bound to the operator port, if present.
|
||||
try {
|
||||
$Connections = Get-NetTCPConnection -LocalPort 8791 -State Listen -ErrorAction SilentlyContinue
|
||||
foreach ($Connection in $Connections) {
|
||||
@@ -47,11 +78,12 @@ try {
|
||||
} catch {}
|
||||
|
||||
Start-Process -FilePath $Pythonw -ArgumentList @((Join-Path $InstallRoot "gst_operator_agent.py")) -WorkingDirectory $InstallRoot
|
||||
Start-Sleep -Seconds 2
|
||||
Start-Sleep -Seconds 3
|
||||
try {
|
||||
$Status = Invoke-RestMethod -Uri "http://127.0.0.1:8791/api/status" -TimeoutSec 5
|
||||
Write-Host "Agent status: $($Status.name) v$($Status.version) - Online" -ForegroundColor Green
|
||||
$Status = Invoke-RestMethod -Uri "https://localhost:8791/api/status" -TimeoutSec 8
|
||||
Write-Host "Agent status: $($Status.name) v$($Status.version) - Online via HTTPS" -ForegroundColor Green
|
||||
} catch {
|
||||
throw "GST Operator Agent was installed but did not answer on http://127.0.0.1:8791. $($_.Exception.Message)"
|
||||
throw "GST Operator Agent was installed but did not answer on https://localhost:8791. $($_.Exception.Message)"
|
||||
}
|
||||
Write-Host "Installed successfully. It will start automatically when this Windows user logs in." -ForegroundColor Green
|
||||
Write-Host "Local status URL: https://localhost:8791/api/status" -ForegroundColor Cyan
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
$InstallRoot = Join-Path $env:LOCALAPPDATA "ARRR\GSTOperatorAgent"
|
||||
$Startup = Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs\Startup\ARRR GST Operator Agent.cmd"
|
||||
$ThumbprintFile = Join-Path $InstallRoot "cert_thumbprint.txt"
|
||||
|
||||
try {
|
||||
Get-NetTCPConnection -LocalPort 8791 -State Listen -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
if ($_.OwningProcess) { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (Test-Path $ThumbprintFile) {
|
||||
$Thumbprint = (Get-Content $ThumbprintFile -Raw -ErrorAction SilentlyContinue).Trim()
|
||||
if ($Thumbprint) {
|
||||
Remove-Item "Cert:\CurrentUser\Root\$Thumbprint" -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
Remove-Item $Startup -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item $InstallRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Write-Host "ARRR GST Operator Agent removed." -ForegroundColor Green
|
||||
|
||||
@@ -147,7 +147,7 @@ router = APIRouter(prefix="/tools/accounting/gst-reconciliation", tags=["account
|
||||
_TOKEN_PURPOSE = "gst_lightweight_operator_v2"
|
||||
_TOKEN_MINUTES = 15
|
||||
_UPLOAD_ROOT = Path(tempfile.gettempdir()) / "audit_firm_gst_operator_uploads"
|
||||
_OPERATOR_AGENT_VERSION = "1.0.0"
|
||||
_OPERATOR_AGENT_VERSION = "1.0.1"
|
||||
_OPERATOR_AGENT_PORT = 8791
|
||||
_OPERATOR_RUNTIME_ROOT = Path(__file__).resolve().parent / "gst_operator_agent_runtime"
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
<script>
|
||||
(() => {
|
||||
const port = {{ operator_agent_port|int }};
|
||||
const base = `http://127.0.0.1:${port}`;
|
||||
const base = `https://localhost:${port}`;
|
||||
const statusEl = document.getElementById('gst-agent-status');
|
||||
const panel = document.getElementById('gst-job-panel');
|
||||
const bar = document.getElementById('gst-job-bar');
|
||||
@@ -154,8 +154,9 @@
|
||||
statusEl.className = 'font-semibold text-emerald-700';
|
||||
return true;
|
||||
} catch (e) {
|
||||
statusEl.textContent = 'Not installed / not running';
|
||||
statusEl.className = 'font-semibold text-red-600';
|
||||
statusEl.textContent = 'Browser cannot reach local agent';
|
||||
statusEl.className = 'font-semibold text-amber-700';
|
||||
statusEl.title = e && e.message ? e.message : 'Local HTTPS connection failed';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -194,7 +195,7 @@
|
||||
if (msg) msg.textContent = 'Starting visible GST login on this workstation…';
|
||||
try {
|
||||
const online = await agentStatus();
|
||||
if (!online) throw new Error('Install/start the GST Operator Agent using the button above, then retry Start GST Download.');
|
||||
if (!online) throw new Error('The GST Operator Agent is not reachable over local HTTPS. Install/update the agent using the button above, then refresh this page.');
|
||||
const r = await fetch(base + '/api/gst/start', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({token:prepared.token})
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user