Source code for app.propertyme.oauth

"""OAuth helpers for connecting Alba Core to PropertyMe.

These helpers build URLs and token request payloads only. They do not store
secrets; real credentials belong in .env or a secret manager.
"""

from __future__ import annotations

import secrets
from dataclasses import dataclass
from typing import Mapping
from urllib.parse import urlencode


[docs] @dataclass(frozen=True, slots=True) class PropertyMeOAuthConfig: """OAuth settings required to start or refresh a PropertyMe session.""" client_id: str client_secret: str redirect_uri: str auth_url: str token_url: str scope: str
[docs] def is_ready(self) -> bool: """Return True when all required OAuth fields are present.""" return all( [ self.client_id.strip(), self.client_secret.strip(), self.redirect_uri.strip(), self.auth_url.strip(), self.token_url.strip(), self.scope.strip(), ] )
[docs] def build_authorization_url(config: PropertyMeOAuthConfig, state: str | None = None) -> tuple[str, str]: """Build the PropertyMe authorisation URL and state token.""" if not config.is_ready(): raise ValueError("PropertyMe OAuth config is incomplete") state = state or secrets.token_urlsafe(24) query = urlencode( { "response_type": "code", "state": state, "client_id": config.client_id, "scope": config.scope, "redirect_uri": config.redirect_uri, } ) return f"{config.auth_url}?{query}", state
[docs] def token_exchange_payload(config: PropertyMeOAuthConfig, code: str) -> Mapping[str, str]: """Return the form payload for exchanging an auth code for tokens.""" if not config.is_ready(): raise ValueError("PropertyMe OAuth config is incomplete") return { "grant_type": "authorization_code", "code": code, "client_id": config.client_id, "client_secret": config.client_secret, "redirect_uri": config.redirect_uri, }
[docs] def refresh_payload(config: PropertyMeOAuthConfig, refresh_token: str) -> Mapping[str, str]: """Return the form payload for refreshing an access token.""" if not config.is_ready(): raise ValueError("PropertyMe OAuth config is incomplete") return { "grant_type": "refresh_token", "refresh_token": refresh_token, "client_id": config.client_id, "client_secret": config.client_secret, }