Skip to content

Instantly share code, notes, and snippets.

@mdz
Created December 10, 2025 05:00
Show Gist options
  • Select an option

  • Save mdz/96251cb12d48e2871b56ea0e28b67aa0 to your computer and use it in GitHub Desktop.

Select an option

Save mdz/96251cb12d48e2871b56ea0e28b67aa0 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""
SmartTub Authentication Diagnostic Script
This script helps diagnose authentication issues with the SmartTub API.
It will attempt to authenticate and report the exact error response
(without exposing your credentials).
Usage:
python diagnose_auth.py
You will be prompted for your SmartTub email and password.
The password is not displayed as you type.
"""
import asyncio
import getpass
import json
import sys
try:
import aiohttp
except ImportError:
print("Error: aiohttp is required. Install with: pip install aiohttp")
sys.exit(1)
# Auth0 configuration (same as SmartTub app)
AUTH_URL = "https://smarttub.auth0.com/oauth/token"
AUTH_AUDIENCE = "https://api.operation-link.com/"
AUTH_CLIENT_ID = "dB7Rcp3rfKKh0vHw2uqkwOZmRb5WNjQC"
AUTH_REALM = "Username-Password-Authentication"
AUTH_GRANT_TYPE = "http://auth0.com/oauth/grant-type/password-realm"
AUTH_SCOPE = "openid email offline_access User Admin"
async def diagnose_auth(username: str, password: str) -> dict:
"""Attempt authentication and return diagnostic info."""
result = {
"success": False,
"http_status": None,
"error_code": None,
"error_description": None,
"raw_response": None,
"exception": None,
}
async with aiohttp.ClientSession() as session:
try:
payload = {
"audience": AUTH_AUDIENCE,
"client_id": AUTH_CLIENT_ID,
"grant_type": AUTH_GRANT_TYPE,
"realm": AUTH_REALM,
"scope": AUTH_SCOPE,
"username": username,
"password": password,
}
async with session.post(AUTH_URL, json=payload) as response:
result["http_status"] = response.status
# Try to parse response as JSON
try:
body = await response.json()
if response.status == 200:
result["success"] = True
# Don't include tokens in output
result["raw_response"] = {
"token_type": body.get("token_type"),
"expires_in": body.get("expires_in"),
"scope": body.get("scope"),
# Redact sensitive fields
"access_token": "[REDACTED]" if "access_token" in body else None,
"refresh_token": "[REDACTED]" if "refresh_token" in body else None,
"id_token": "[REDACTED]" if "id_token" in body else None,
}
else:
# Error response
result["error_code"] = body.get("error")
result["error_description"] = body.get("error_description")
result["raw_response"] = body
except aiohttp.ContentTypeError:
# Not JSON
result["raw_response"] = await response.text()
except aiohttp.ClientError as e:
result["exception"] = f"{type(e).__name__}: {e}"
except Exception as e:
result["exception"] = f"{type(e).__name__}: {e}"
return result
def print_diagnostic_report(result: dict):
"""Print a formatted diagnostic report."""
print("\n" + "=" * 60)
print("SmartTub Authentication Diagnostic Report")
print("=" * 60)
if result["success"]:
print("\n✅ Authentication SUCCESSFUL")
print(f" HTTP Status: {result['http_status']}")
print("\n Your credentials are working correctly.")
print(" If you're having issues with Home Assistant, the problem")
print(" may be elsewhere (network, HA configuration, etc.)")
else:
print("\n❌ Authentication FAILED")
print(f"\n HTTP Status: {result['http_status']}")
if result["error_code"]:
print(f" Error Code: {result['error_code']}")
if result["error_description"]:
print(f" Error Description: {result['error_description']}")
if result["exception"]:
print(f" Exception: {result['exception']}")
if result["raw_response"]:
print(f"\n Raw Response:")
if isinstance(result["raw_response"], dict):
print(f" {json.dumps(result['raw_response'], indent=2)}")
else:
print(f" {result['raw_response'][:500]}")
print("\n" + "=" * 60)
print("Please share this report (above the line) when reporting issues.")
print("Do NOT share your email or password.")
print("=" * 60 + "\n")
async def main():
print("SmartTub Authentication Diagnostic Tool")
print("-" * 40)
print()
# Get credentials
email = input("Enter your SmartTub email: ").strip()
if not email:
print("Error: Email is required")
sys.exit(1)
password = getpass.getpass("Enter your SmartTub password: ")
if not password:
print("Error: Password is required")
sys.exit(1)
print("\nAttempting authentication...")
result = await diagnose_auth(email, password)
print_diagnostic_report(result)
# Return exit code based on success
sys.exit(0 if result["success"] else 1)
if __name__ == "__main__":
asyncio.run(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment