"""Define some utilities to work with SimpliSafe's authentication mechanism.""" from __future__ import annotations import base64 import hashlib import os import re import urllib.parse from uuid import uuid4 AUTH_URL_HOSTNAME = "auth.simplisafe.com" AUTH_URL_BASE = f"https://{AUTH_URL_HOSTNAME}" AUTH_URL_LOGIN = f"{AUTH_URL_BASE}/authorize" DEFAULT_AUTH0_CLIENT = ( "eyJ2ZXJzaW9uIjoiMi4zLjIiLCJuYW1lIjoiQXV0aDAuc3dpZnQiLCJlbnYiOnsic3dpZnQiOiI1LngiLC" "JpT1MiOiIxNi4zIn19" ) DEFAULT_CLIENT_ID = "42aBZ5lYrVW12jfOuu3CQROitwxg9sN5" DEFAULT_REDIRECT_URI = ( "com.simplisafe.mobile://auth.simplisafe.com/ios/com.simplisafe.mobile/callback" ) DEFAULT_SCOPE = ( "offline_access email openid https://api.simplisafe.com/scopes/user:platform" ) def get_auth_url(code_challenge: str, *, device_id: str | None = None) -> str: """Get a SimpliSafe authorization URL to visit in a browser. Args: code_challenge: A code challenge generated by :meth:`simplipy.util.auth.get_auth0_code_challenge`. device_id: A UUID to identify the device getting the auth URL. If not provided, a random UUID will be generated. Returns: An authorization URL. """ params = { "audience": "https://api.simplisafe.com/", "auth0Client": DEFAULT_AUTH0_CLIENT, "client_id": DEFAULT_CLIENT_ID, "code_challenge": code_challenge, "code_challenge_method": "S256", "device": "iPhone", "device_id": (device_id or str(uuid4())).upper(), "redirect_uri": DEFAULT_REDIRECT_URI, "response_type": "code", "scope": DEFAULT_SCOPE, } return f"{AUTH_URL_LOGIN}?{urllib.parse.urlencode(params)}" def get_auth0_code_challenge(code_verifier: str) -> str: """Get an Auth0 code challenge from a code verifier. Args: code_verifier: A code challenge generated by :meth:`simplipy.util.auth.get_auth0_code_verifier`. Returns: A code challenge. """ verifier = hashlib.sha256(code_verifier.encode("utf-8")).digest() challenge = base64.urlsafe_b64encode(verifier).decode("utf-8") return challenge.replace("=", "") def get_auth0_code_verifier() -> str: """Get an Auth0 code verifier. Returns: A code verifier. """ verifier = base64.urlsafe_b64encode(os.urandom(40)).decode("utf-8") return re.sub("[^a-zA-Z0-9]+", "", verifier)