psnawp_api.core.authenticator — PSNAWP 1.1.0 documentation

# Source code for psnawp_api.core.authenticator

    from __future__ import annotations

    import time
    from typing import Any
    from urllib.parse import urlparse, parse_qs

    import requests

    from psnawp_api.core import psnawp_exceptions
    from psnawp_api.utils.endpoints import BASE_PATH, API_PATH
    from psnawp_api.utils.misc import create_logger

    class Authenticator:
        """Provides an interface for PSN Authentication."""

        __PARAMS = {
            "CLIENT_ID": "ac8d161a-d966-4728-b0ea-ffec22f69edc",
            "SCOPE": "psn:clientapp psn:mobile.v1",
            "REDIRECT_URI": "com.playstation.PlayStationApp://redirect",
        }
        __AUTH_HEADER = {"Authorization": "Basic YWM4ZDE2MWEtZDk2Ni00NzI4LWIwZWEtZmZlYzIyZjY5ZWRjOkRFaXhFcVhYQ2RYZHdqMHY="}

        def __init__(self, npsso_cookie: str):
            """Represents a single authentication to PSN API.

            :param npsso_cookie: npsso cookie obtained from PSN website.

            :raises: ``PSNAWPAuthenticationError`` If npsso code is expired or is incorrect.

            """
            self._npsso_token = npsso_cookie
            self._auth_properties: dict[str, Any] = {}
            self._authenticate()
            self._authenticator_logger = create_logger(__file__)

        def obtain_fresh_access_token(self) -> Any:
            """Gets a new access token from refresh token.

            :returns: access token

            """

            if self._auth_properties["access_token_expires_at"] > time.time():
                return self._auth_properties["access_token"]

            data = {
                "refresh_token": self._auth_properties["refresh_token"],
                "grant_type": "refresh_token",
                "scope": Authenticator.__PARAMS["SCOPE"],
                "token_format": "jwt",
            }
            response = requests.post(
                f"{BASE_PATH['base_uri']}{API_PATH['access_token']}",
                headers=Authenticator.__AUTH_HEADER,
                data=data,
            )
            self._auth_properties = response.json()
            self._auth_properties["access_token_expires_at"] = self._auth_properties["expires_in"] + time.time()
            if self._auth_properties["refresh_token_expires_in"] <= 60 * 60 * 24 * 3:
                self._authenticator_logger.warning("Warning: Your refresh token is going to expire in less than 3 days. Please renew you npsso token!")
            return self._auth_properties["access_token"]

        def oauth_token(self, code: str) -> None:
            """Obtain the access token using oauth code for the first time, after this the access token is obtained via refresh token.

            :param code: Code obtained using npsso code.

            """

            data = {
                "code": code,
                "grant_type": "authorization_code",
                "redirect_uri": Authenticator.__PARAMS["REDIRECT_URI"],
                "scope": Authenticator.__PARAMS["SCOPE"],
                "token_format": "jwt",
            }

            response = requests.post(
                f"{BASE_PATH['base_uri']}{API_PATH['access_token']}",
                headers=Authenticator.__AUTH_HEADER,
                data=data,
            )
            self._auth_properties = response.json()
            self._auth_properties["access_token_expires_at"] = self._auth_properties["expires_in"] + time.time()
            if self._auth_properties["refresh_token_expires_in"] <= 60 * 60 * 24 * 3:
                self._authenticator_logger.warning("Warning: Your refresh token is going to expire in less than 3 days. Please renew you npsso token!")

        def _authenticate(self) -> None:
            """Authenticate using the npsso code provided in the constructor.

            Obtains the access code and the refresh code. Access code lasts about 1 hour. While the refresh code lasts about 2 months. After 2 months a new npsso
            code is needed.

            :raises: ``PSNAWPAuthenticationError`` If authentication is not successful.

            """
            cookies = {"Cookie": f"npsso={self._npsso_token}"}
            params = {
                "access_type": "offline",
                "client_id": Authenticator.__PARAMS["CLIENT_ID"],
                "scope": Authenticator.__PARAMS["SCOPE"],
                "redirect_uri": Authenticator.__PARAMS["REDIRECT_URI"],
                "response_type": "code",
            }
            response = requests.get(
                f"{BASE_PATH['base_uri']}{API_PATH['oauth_code']}",
                headers=cookies,
                params=params,
                allow_redirects=False,
            )
            response.raise_for_status()
            location_url = response.headers["location"]
            parsed_url = urlparse(location_url)
            parsed_query = parse_qs(parsed_url.query)
            if "error" in parsed_query.keys():
                if "4165" in parsed_query["error_code"]:
                    raise psnawp_exceptions.PSNAWPAuthenticationError("Your npsso code has expired or is incorrect. Please generate a new code!")
                else:
                    raise psnawp_exceptions.PSNAWPAuthenticationError("Something went wrong while authenticating")
            self.oauth_token(parsed_query["code"][0])