import base64 import hashlib import hmac import json import os import time from typing import cast import streamlit as st from cryptography.hazmat.primitives import hashes, padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from dotenv import load_dotenv from snowflake.snowpark import Session from snowflake.snowpark.context import get_active_session from snowflake.snowpark.exceptions import SnowparkSessionException load_dotenv() def get_session() -> Session: try: return get_active_session() except SnowparkSessionException: from configuration.local_connection import get_connection_parameters connection_params = get_connection_parameters("streamlit_backoffice_connection") return cast(Session, Session.builder.configs(connection_params).create()) def get_current_user_name(session: Session) -> str: try: current_user = str(st.user.user_name or "") except AttributeError: current_user = "" if not current_user: current_user = (session.get_current_user() or "").strip('"') return current_user or "UNKNOWN" def resolve_env(session: Session) -> str: schema = (session.get_current_schema() or "QA").strip('"').upper() if schema == "PROD_STREAMLIT_FANSIFTER_BACKOFFICE": return "PROD" if schema == "PROD": return "PROD" return "QA" def _get_preference_center_secret_key() -> str: local_key = os.environ.get("PREFERENCE_CENTER_SECRET_KEY") if local_key: return local_key from snowflake.snowpark.secrets import get_generic_secret_string return get_generic_secret_string("preference_center") def _derive_encryption_key(password: bytes, salt: bytes) -> bytes: kdf = PBKDF2HMAC( algorithm=hashes.SHA1(), # noqa: S303 length=32, salt=salt, iterations=1000, ) return kdf.derive(password) def _b64url_encode(data: bytes) -> str: return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii") def _build_jwt(payload: dict[str, object], signing_key: bytes) -> str: header = _b64url_encode( json.dumps({"alg": "HS256", "typ": "JWT"}, separators=(",", ":")).encode() ) body = _b64url_encode(json.dumps(payload, separators=(",", ":")).encode()) signing_input = f"{header}.{body}".encode() sig = hmac.new(signing_key, signing_input, hashlib.sha256).digest() return f"{header}.{body}.{_b64url_encode(sig)}" def generate_profile_link( session: Session, profile_id: str, env: str = "QA", crm_id: str | None = None, ) -> str: secret_key = _get_preference_center_secret_key() signing_key_b85, password_b85, salt_hex = secret_key.split(" ") signing_key = signing_key_b85.encode("utf-8") # used as-is, not decoded password = password_b85.encode("utf-8") salt = bytes.fromhex(salt_hex) encryption_key = _derive_encryption_key(password, salt) user = get_current_user_name(session) iv = os.urandom(16) if crm_id: identifier = {"crmId": crm_id, "impersonatedBy": user} else: identifier = {"profileId": profile_id, "impersonatedBy": user} identifier_json = json.dumps(identifier, separators=(",", ":")).encode() padder = padding.PKCS7(128).padder() padded = padder.update(identifier_json) + padder.finalize() cipher = Cipher(algorithms.AES256(encryption_key), modes.CBC(iv)) encryptor = cipher.encryptor() ciphertext = encryptor.update(padded) + encryptor.finalize() encrypted_payload = iv.hex() + base64.b64encode(ciphertext).decode("utf-8") jwt_payload = {"payload": encrypted_payload, "iat": int(time.time())} token = _build_jwt(jwt_payload, signing_key) base_url = ( "https://fan-preferences.sonymusic.com" if env == "PROD" else "https://qa-preferences.theorchard.io" ) return f"{base_url}/en/profile/{token}/settings"