Files
agent-identity-provisioning/diagnose-vp-compliance.py
T
vptechops 1b8bd843d9 fix: get_totp exact-name resolution + all-agent login validator
bw's name search is fuzzy: every agent email contains "coo"
(tsgstaff-coo-*), so `bw get totp "coo Cloudron"` matched 10 items and
errored. get_totp now resolves via get_item_id (exact-name filter)
first, mirroring get_item_password.

Added validate-all-logins.py: per-agent fresh-browser-context login
(shared contexts carry session cookies and hide the login form),
asserts password+TOTP round-trip, then verifies every stored API
credential against its system. First full run: 9/10 PASS.

Known failure: vp-compliance stored password does not match the
account ("Incorrect username or password" pre-TOTP) -- enrollment
typed a different value than stored. Needs Cloudron admin reset,
then update_item and re-validate.
2026-08-14 11:48:06 -05:00

90 lines
3.2 KiB
Python

#!/usr/bin/env python3
"""
diagnose-vp-compliance.py -- Reproduce the vp-compliance login with full
DOM dumps at every step to find where it diverges from the other agents.
Usage:
docker compose run --rm --entrypoint python3 provision diagnose-vp-compliance.py
"""
import os, sys, time
from pathlib import Path
from playwright.sync_api import sync_playwright
sys.path.insert(0, os.path.dirname(__file__))
from bw_helper import BitwardenHelper
CLOUDRON_BASE = "https://my.knownelement.com"
STATE_DIR = Path("/app/state")
ITEM = "vp-compliance Cloudron"
EMAIL = "tsgstaff-coo-vpcompliance@turnsys.com"
def dump(page, label):
ts = time.strftime("%H%M%S")
page.screenshot(path=str(STATE_DIR / f"diag-vpc-{label}-{ts}.png"), full_page=True)
body = page.evaluate("() => document.body.innerText.substring(0, 300)")
els = page.evaluate("""() => Array.from(document.querySelectorAll(
'input, [role="button"], button, .ui.message')).map(el => ({
tag: el.tagName, id: el.id, type: el.type||'',
vis: el.offsetParent !== null, text: (el.textContent||'').trim().substring(0,50)}))""")
print(f"--- {label} ---")
print(f"URL: {page.url}")
print(f"BODY: {body[:200]}")
for e in els:
print(f" <{e['tag']}> id={e['id']} type={e['type']} vis={e['vis']} text={e['text']!r}")
def main():
bw = BitwardenHelper(
client_id=os.environ["BW_CLIENTID"],
client_secret=os.environ["BW_CLIENTSECRET"],
password=os.environ["BW_PASSWORD"],
server_url=os.environ.get("BW_SERVER", ""),
)
bw.login()
# Resolve by exact name to dodge the fuzzy-search ambiguity
item_id = bw.get_item_id(ITEM)
item = json.loads(bw._run_bw(["get", "item", item_id]))
password = item["login"]["password"]
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(viewport={"width": 1280, "height": 1024})
page = context.new_page()
page.goto(f"{CLOUDRON_BASE}/login.html", wait_until="networkidle", timeout=20000)
page.wait_for_timeout(2000)
dump(page, "01-login-page")
page.wait_for_selector("#inputPassword", timeout=15000)
page.click("#inputUsername")
page.keyboard.type(EMAIL)
page.click("#inputPassword")
page.keyboard.type(password)
dump(page, "02-filled")
btn = page.locator('[role="button"]:has-text("Log in")')
print(f"login button count={btn.count()} visible={btn.first.is_visible() if btn.count() else '?'}")
print(f"login button classes: {btn.first.get_attribute('class') if btn.count() else '?'}")
btn.first.click()
page.wait_for_timeout(5000)
dump(page, "03-after-submit")
totp = page.query_selector("#inputTotpToken")
print(f"TOTP field present={totp is not None} visible={totp.is_visible() if totp else False}")
if totp and totp.is_visible():
code = bw._run_bw(["get", "totp", item_id])
totp.click()
page.keyboard.type(code)
page.locator("#totpTokenSubmitButton").click()
page.wait_for_timeout(5000)
dump(page, "04-after-totp")
browser.close()
import json # noqa: E402
if __name__ == "__main__":
main()