Files
Investidor-V2/tools/portal-qa-auth-diagnosis.py
T
nala 6ddafc95ba
tests / ci (push) Canceled after 0s
oportunidades 4
2026-10-10 07:03:59 +02:00

64 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Analyze the user's bounded manual attempt; never send authentication requests."""
import hashlib
import json
import re
from pathlib import Path
root = Path(__file__).resolve().parent.parent
private = root / 'storage/app/private/investmoz-v2/portal-audit'
file = private / 'auth-session-trace-qa.jsonl'
all_rows = [json.loads(line) for line in file.read_text(encoding='utf-8').splitlines()]
rows = [row for row in all_rows if '2026-10-09T21:22:00' <= row['at_utc'] <= '2026-10-09T21:24:59']
assert len(rows) == 6, 'Manual interval changed: inspect before classifying.'
assert [(r['method'], r['path']) for r in rows] == [
('GET', '/investimentos'), ('GET', '/login'), ('POST', '/login'),
('GET', '/two-factor-challenge'), ('POST', '/two-factor-challenge'), ('GET', '/login')]
assert all(row['qa_target'] == 'desk_restore_20261009_174726_test:55441' for row in rows)
login, challenge, mfa, next_request = rows[2:]
assert login['after']['pending_mfa_user'] == challenge['loaded']['pending_mfa_user'] == mfa['loaded']['pending_mfa_user'] == 13
assert login['after']['session'] == challenge['loaded']['session'] == mfa['loaded']['session']
assert mfa['status'] == 302 and mfa['redirect']['path'] == '/login'
assert all(not row['after']['authenticated_user'] for row in rows)
assert next_request['loaded']['session'] == mfa['after']['session'] == next_request['loaded']['accepted_cookie_session']
# Export only the correlated structured warning; no arguments, headers or bodies.
log = root / 'storage/logs/laravel.log'
with log.open('rb') as f:
f.seek(max(0, log.stat().st_size - 2 * 1024 * 1024))
lines = f.read().decode('utf-8', errors='replace').splitlines()
warnings = []
for line in lines:
match = re.match(r'^\[2026-10-09 21:23:04\] testing\.WARNING: Two-factor credential could not be decrypted; authentication refused\. (\{.*\})\s*$', line)
if match:
data = json.loads(match.group(1))
warnings.append({'at_utc': '2026-10-09T21:23:04+00:00', 'reference': data['reference'], 'pending_user_id': data['pending_user_id'], 'category': 'decryption_failure'})
snapshot_hash = hashlib.sha256(json.dumps(rows, sort_keys=True).encode()).hexdigest()
archive = private / 'c37-manual-2122-2124-qa.json'
if not warnings and archive.exists():
retained = json.loads(archive.read_text(encoding='utf-8'))
assert retained['manual_trace_snapshot_sha256'] == snapshot_hash, 'Archived proof must match the same six requests.'
warnings = [retained['warning']]
assert len(warnings) == 1 and warnings[0]['pending_user_id'] == 13
diagnosis = json.loads((private / 'mfa-key-diagnosis-qa.json').read_text(encoding='utf-8-sig'))
assert diagnosis['encrypted_field_checks']['two_factor_secret'] == {'qa': False, 'source_env': True}
assert diagnosis['encrypted_field_checks']['two_factor_recovery_codes'] == {'qa': False, 'source_env': True}
result = {'case': 'C37', 'manual_interval_utc': '2026-10-09 21:22:00–21:24:59',
'reported_expected_actor': 18, 'observed_challenged_actor': 13,
'mfa_screen_reached': True, 'manual_mfa_screen_observed_by_user': True,
'timeline': [{key: row[key] for key in ['at_utc', 'trace', 'method', 'path', 'status', 'redirect', 'loaded', 'after', 'persisted']} for row in rows],
'first_failure': {'at_utc': mfa['at_utc'], 'trace': mfa['trace'], 'path': mfa['path'], 'category': 'decryption_failure',
'result': 'Authentication refused before completion; controlled302 to /login and intentional session invalidation.'},
'setup_mismatch_first_seen_at': login['at_utc'], 'warning': warnings[0],
'mfa_invalid_code_rejection': False, 'authentication_completed': False,
'session_lost_after_authentication': False, 'next_request_accepts_regenerated_session': True,
'cookie_conflict_observed': False, 'server_key_or_guard_changed': False,
'request_code_or_recovery_value_recorded': False,
'exact_mfa_input_mode_in_manual_trace': 'not recorded; no request bodies retained',
'cause_confirmed': True, 'cause': 'Restored non-synthetic account13 selected; its MFA credentials use the source key, incompatible with the stable isolated QA key. Account18 was not challenged in this attempt.',
'browser_acceptance_passed': False, 'active_writes': 0,
'manual_trace_snapshot_sha256': snapshot_hash}
(private / 'c37-manual-2122-2124-qa.json').write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding='utf-8')
(private / 'auth-session-diagnosis-qa.json').write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding='utf-8')
print(json.dumps({key: value for key, value in result.items() if key != 'timeline'}, ensure_ascii=True, indent=2))