bachya/simplisafe-python

View on GitHub
simplipy/util/auth.py

Summary

Maintainability
A
0 mins
Test Coverage
"""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)