diff --git a/.env.example b/.env.example index f571ade..db0e3b7 100644 --- a/.env.example +++ b/.env.example @@ -133,3 +133,21 @@ LIGHTNING_RESERVE_FEE_MIN=2000 # MINT_GLOBAL_RATE_LIMIT_PER_MINUTE=60 # Determines the number of transactions (mint, melt, swap) allowed per minute per IP # MINT_TRANSACTION_RATE_LIMIT_PER_MINUTE=20 + +# Authentication +# These settings allow you to enable blind authentication to limit the user of your mint to a group of authenticated users. +# To use this, you need to set up an OpenID Connect provider like Keycloak, Auth0, or Hydra. +# - Add the client ID "cashu-client" +# - Enable the ES256 and RS256 algorithms for this client +# - If you want to use the authorization flow, you must add the redirect URI "http://localhost:33388/callback". +# - To support other wallets, use the well-known list of allowed redirect URIs here: https://...TODO.md +# +# Turn on authentication +# MINT_REQUIRE_AUTH=TRUE +# OpenID Connect discovery URL of the authentication provider +# MINT_AUTH_OICD_DISCOVERY_URL=http://localhost:8080/realms/nutshell/.well-known/openid-configuration +# MINT_AUTH_OICD_CLIENT_ID=cashu-client +# Number of authentication attempts allowed per minute per user +# MINT_AUTH_RATE_LIMIT_PER_MINUTE=5 +# Maximum number of blind auth tokens per authentication request +# MINT_AUTH_MAX_BLIND_TOKENS=100 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be2627b..4b1e78c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,21 @@ jobs: poetry-version: ${{ matrix.poetry-version }} mint-database: ${{ matrix.mint-database }} + tests_keycloak_auth: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + python-version: ["3.10"] + poetry-version: ["1.8.5"] + mint-database: ["./test_data/test_mint", "postgres://cashu:cashu@localhost:5432/cashu"] + uses: ./.github/workflows/tests_keycloak_auth.yml + with: + os: ${{ matrix.os }} + python-version: ${{ matrix.python-version }} + poetry-version: ${{ matrix.poetry-version }} + mint-database: ${{ matrix.mint-database }} + regtest: uses: ./.github/workflows/regtest.yml strategy: diff --git a/.github/workflows/tests_keycloak_auth.yml b/.github/workflows/tests_keycloak_auth.yml new file mode 100644 index 0000000..56a8239 --- /dev/null +++ b/.github/workflows/tests_keycloak_auth.yml @@ -0,0 +1,77 @@ +name: tests_keycloak + +on: + workflow_call: + inputs: + python-version: + default: "3.10.4" + type: string + poetry-version: + default: "1.8.5" + type: string + mint-database: + default: "" + type: string + os: + default: "ubuntu-latest" + type: string + +jobs: + poetry: + name: Auth tests with Keycloak (db ${{ inputs.mint-database }}) + runs-on: ${{ inputs.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Prepare environment + uses: ./.github/actions/prepare + with: + python-version: ${{ inputs.python-version }} + poetry-version: ${{ inputs.poetry-version }} + + - name: Start PostgreSQL service + if: contains(inputs.mint-database, 'postgres') + run: | + docker run -d --name postgres \ + -e POSTGRES_USER=cashu \ + -e POSTGRES_PASSWORD=cashu \ + -e POSTGRES_DB=cashu \ + -p 5432:5432 postgres:16.4 + until docker exec postgres pg_isready; do sleep 1; done + + - name: Prepare environment + uses: ./.github/actions/prepare + with: + python-version: ${{ inputs.python-version }} + poetry-version: ${{ inputs.poetry-version }} + + - name: Start Keycloak with Backup + run: | + docker compose -f tests/keycloak_data/docker-compose-restore.yml up -d + until docker logs $(docker ps -q --filter "ancestor=quay.io/keycloak/keycloak:25.0.6") | grep "Keycloak 25.0.6 on JVM (powered by Quarkus 3.8.5) started"; do sleep 1; done + + - name: Verify Keycloak Import + run: | + docker logs $(docker ps -q --filter "ancestor=quay.io/keycloak/keycloak:25.0.6") | grep "Imported" + + - name: Run tests + env: + MINT_BACKEND_BOLT11_SAT: FakeWallet + WALLET_NAME: test_wallet + MINT_HOST: localhost + MINT_PORT: 3337 + MINT_TEST_DATABASE: ${{ inputs.mint-database }} + TOR: false + MINT_REQUIRE_AUTH: TRUE + MINT_AUTH_OICD_DISCOVERY_URL: http://localhost:8080/realms/nutshell/.well-known/openid-configuration + MINT_AUTH_OICD_CLIENT_ID: cashu-client + run: | + poetry run pytest tests/test_wallet_auth.py -v --cov=mint --cov-report=xml + + - name: Stop and clean up Docker Compose + run: | + docker compose -f tests/keycloak_data/docker-compose-restore.yml down + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 diff --git a/cashu/core/base.py b/cashu/core/base.py index d08680f..5c6c59d 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -6,7 +6,7 @@ from abc import ABC, abstractmethod from dataclasses import dataclass, field from enum import Enum from sqlite3 import Row -from typing import Any, Dict, List, Optional, Union +from typing import Any, ClassVar, Dict, List, Optional, Union import cbor2 from loguru import logger @@ -19,7 +19,7 @@ from .crypto.aes import AESCipher from .crypto.b_dhke import hash_to_curve from .crypto.keys import ( derive_keys, - derive_keys_sha256, + derive_keys_deprecated_pre_0_15, derive_keyset_id, derive_keyset_id_deprecated, derive_pubkeys, @@ -173,6 +173,9 @@ class Proof(BaseModel): return return_dict + def to_base64(self): + return base64.b64encode(cbor2.dumps(self.to_dict(include_dleq=True))).decode() + def to_dict_no_dleq(self): # dictionary without the fields that don't need to be send to Carol return dict(id=self.id, amount=self.amount, secret=self.secret, C=self.C) @@ -541,6 +544,7 @@ class Unit(Enum): usd = 2 eur = 3 btc = 4 + auth = 999 def str(self, amount: int) -> str: if self == Unit.sat: @@ -553,6 +557,8 @@ class Unit(Enum): return f"{amount/100:.2f} EUR" elif self == Unit.btc: return f"{amount/1e8:.8f} BTC" + elif self == Unit.auth: + return f"{amount} AUTH" else: raise Exception("Invalid unit") @@ -724,6 +730,7 @@ class MintKeyset: valid_to: Optional[str] = None first_seen: Optional[str] = None version: Optional[str] = None + amounts: List[int] duplicate_keyset_id: Optional[str] = None # BACKWARDS COMPATIBILITY < 0.15.0 @@ -734,6 +741,7 @@ class MintKeyset: seed: Optional[str] = None, encrypted_seed: Optional[str] = None, seed_encryption_method: Optional[str] = None, + amounts: Optional[List[int]] = None, valid_from: Optional[str] = None, valid_to: Optional[str] = None, first_seen: Optional[str] = None, @@ -762,6 +770,12 @@ class MintKeyset: assert self.seed, "seed not set" + if amounts: + self.amounts = amounts + else: + # use 2^n amounts by default + self.amounts = [2**i for i in range(settings.max_order)] + self.id = id self.valid_from = valid_from self.valid_to = valid_to @@ -805,6 +819,24 @@ class MintKeyset: logger.trace(f"Loaded keyset id: {self.id} ({self.unit.name})") + @classmethod + def from_row(cls, row: Row): + return cls( + id=row["id"], + derivation_path=row["derivation_path"], + seed=row["seed"], + encrypted_seed=row["encrypted_seed"], + seed_encryption_method=row["seed_encryption_method"], + valid_from=row["valid_from"], + valid_to=row["valid_to"], + first_seen=row["first_seen"], + active=row["active"], + unit=row["unit"], + version=row["version"], + input_fee_ppk=row["input_fee_ppk"], + amounts=json.loads(row["amounts"]), + ) + @property def public_keys_hex(self) -> Dict[int, str]: assert self.public_keys, "public keys not set" @@ -830,23 +862,27 @@ class MintKeyset: self.private_keys = derive_keys_backwards_compatible_insecure_pre_0_12( self.seed, self.derivation_path ) - self.public_keys = derive_pubkeys(self.private_keys) # type: ignore + self.public_keys = derive_pubkeys(self.private_keys, self.amounts) # type: ignore logger.trace( f"WARNING: Using weak key derivation for keyset {self.id} (backwards" " compatibility < 0.12)" ) self.id = id_in_db or derive_keyset_id_deprecated(self.public_keys) # type: ignore elif self.version_tuple < (0, 15): - self.private_keys = derive_keys_sha256(self.seed, self.derivation_path) + self.private_keys = derive_keys_deprecated_pre_0_15( + self.seed, self.amounts, self.derivation_path + ) logger.trace( f"WARNING: Using non-bip32 derivation for keyset {self.id} (backwards" " compatibility < 0.15)" ) - self.public_keys = derive_pubkeys(self.private_keys) # type: ignore + self.public_keys = derive_pubkeys(self.private_keys, self.amounts) # type: ignore self.id = id_in_db or derive_keyset_id_deprecated(self.public_keys) # type: ignore else: - self.private_keys = derive_keys(self.seed, self.derivation_path) - self.public_keys = derive_pubkeys(self.private_keys) # type: ignore + self.private_keys = derive_keys( + self.seed, self.derivation_path, self.amounts + ) + self.public_keys = derive_pubkeys(self.private_keys, self.amounts) # type: ignore self.id = id_in_db or derive_keyset_id(self.public_keys) # type: ignore @@ -1254,3 +1290,48 @@ class TokenV4(Token): t=[TokenV4Token(**t) for t in token_dict["t"]], d=token_dict.get("d", None), ) + + +class AuthProof(BaseModel): + """ + Blind authentication token + """ + + id: str + secret: str # secret + C: str # signature + amount: int = 1 # default amount + + prefix: ClassVar[str] = "authA" + + @classmethod + def from_proof(cls, proof: Proof): + return cls(id=proof.id, secret=proof.secret, C=proof.C) + + def to_base64(self): + serialize_dict = self.dict() + serialize_dict.pop("amount", None) + return ( + self.prefix + base64.b64encode(json.dumps(serialize_dict).encode()).decode() + ) + + @classmethod + def from_base64(cls, base64_str: str): + assert base64_str.startswith(cls.prefix), Exception( + f"Token prefix not valid. Expected {cls.prefix}." + ) + base64_str = base64_str[len(cls.prefix) :] + return cls.parse_obj(json.loads(base64.b64decode(base64_str).decode())) + + def to_proof(self): + return Proof(id=self.id, secret=self.secret, C=self.C, amount=self.amount) + + +class WalletMint(BaseModel): + url: str + info: str + updated: Optional[str] = None + access_token: Optional[str] = None + refresh_token: Optional[str] = None + username: Optional[str] = None + password: Optional[str] = None diff --git a/cashu/core/crypto/keys.py b/cashu/core/crypto/keys.py index 95abac6..45860eb 100644 --- a/cashu/core/crypto/keys.py +++ b/cashu/core/crypto/keys.py @@ -1,54 +1,56 @@ import base64 import hashlib import random -from typing import Dict +from typing import Dict, List from bip32 import BIP32 -from ..settings import settings from .secp import PrivateKey, PublicKey -def derive_keys(mnemonic: str, derivation_path: str): +def derive_keys(mnemonic: str, derivation_path: str, amounts: List[int]): """ Deterministic derivation of keys for 2^n values. """ bip32 = BIP32.from_seed(mnemonic.encode()) - orders_str = [f"/{i}'" for i in range(settings.max_order)] + orders_str = [f"/{a}'" for a in range(len(amounts))] return { - 2**i: PrivateKey( + a: PrivateKey( bip32.get_privkey_from_path(derivation_path + orders_str[i]), raw=True, ) - for i in range(settings.max_order) + for i, a in enumerate(amounts) } -def derive_keys_sha256(seed: str, derivation_path: str = ""): +def derive_keys_deprecated_pre_0_15( + seed: str, amounts: List[int], derivation_path: str = "" +): """ Deterministic derivation of keys for 2^n values. - TODO: Implement BIP32. """ return { - 2**i: PrivateKey( + a: PrivateKey( hashlib.sha256((seed + derivation_path + str(i)).encode("utf-8")).digest()[ :32 ], raw=True, ) - for i in range(settings.max_order) + for i, a in enumerate(amounts) } -def derive_pubkey(seed: str): - return PrivateKey( +def derive_pubkey(seed: str) -> PublicKey: + pubkey = PrivateKey( hashlib.sha256((seed).encode("utf-8")).digest()[:32], raw=True, ).pubkey + assert pubkey + return pubkey -def derive_pubkeys(keys: Dict[int, PrivateKey]): - return {amt: keys[amt].pubkey for amt in [2**i for i in range(settings.max_order)]} +def derive_pubkeys(keys: Dict[int, PrivateKey], amounts: List[int]): + return {amt: keys[amt].pubkey for amt in amounts} def derive_keyset_id(keys: Dict[int, PublicKey]): diff --git a/cashu/core/db.py b/cashu/core/db.py index dc9dc21..47469a2 100644 --- a/cashu/core/db.py +++ b/cashu/core/db.py @@ -339,11 +339,21 @@ class Database(Compat): raise Exception("Timestamp is None") return timestamp - def to_timestamp(self, timestamp_str: str) -> Union[str, datetime.datetime]: - if not timestamp_str: - timestamp_str = self.timestamp_now_str() + def to_timestamp( + self, timestamp: Union[str, datetime.datetime] + ) -> Union[str, datetime.datetime]: + if not timestamp: + timestamp = self.timestamp_now_str() if self.type in {POSTGRES, COCKROACH}: - return datetime.datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S") + # return datetime.datetime + if isinstance(timestamp, datetime.datetime): + return timestamp + elif isinstance(timestamp, str): + return datetime.datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S") elif self.type == SQLITE: - return timestamp_str + # return str + if isinstance(timestamp, datetime.datetime): + return timestamp.strftime("%Y-%m-%d %H:%M:%S") + elif isinstance(timestamp, str): + return timestamp return "" diff --git a/cashu/core/errors.py b/cashu/core/errors.py index e0f3f94..d1d0c6d 100644 --- a/cashu/core/errors.py +++ b/cashu/core/errors.py @@ -18,6 +18,7 @@ class NotAllowedError(CashuError): def __init__(self, detail: Optional[str] = None, code: Optional[int] = None): super().__init__(detail or self.detail, code=code or self.code) + class OutputsAlreadySignedError(CashuError): detail = "outputs have already been signed before." code = 10002 @@ -25,6 +26,7 @@ class OutputsAlreadySignedError(CashuError): def __init__(self, detail: Optional[str] = None, code: Optional[int] = None): super().__init__(detail or self.detail, code=code or self.code) + class InvalidProofsError(CashuError): detail = "proofs could not be verified" code = 10003 @@ -32,6 +34,7 @@ class InvalidProofsError(CashuError): def __init__(self, detail: Optional[str] = None, code: Optional[int] = None): super().__init__(detail or self.detail, code=code or self.code) + class TransactionError(CashuError): detail = "transaction error" code = 11000 @@ -76,12 +79,14 @@ class TransactionUnitError(TransactionError): def __init__(self, detail): super().__init__(detail, code=self.code) + class TransactionAmountExceedsLimitError(TransactionError): code = 11006 def __init__(self, detail): super().__init__(detail, code=self.code) + class KeysetError(CashuError): detail = "keyset error" code = 12000 @@ -113,7 +118,7 @@ class QuoteNotPaidError(CashuError): code = 20001 def __init__(self): - super().__init__(self.detail, code=2001) + super().__init__(self.detail, code=self.code) class QuoteSignatureInvalidError(CashuError): @@ -121,7 +126,7 @@ class QuoteSignatureInvalidError(CashuError): code = 20008 def __init__(self): - super().__init__(self.detail, code=20008) + super().__init__(self.detail, code=self.code) class QuoteRequiresPubkeyError(CashuError): @@ -129,4 +134,52 @@ class QuoteRequiresPubkeyError(CashuError): code = 20009 def __init__(self): - super().__init__(self.detail, code=20009) + super().__init__(self.detail, code=self.code) + + +class ClearAuthRequiredError(CashuError): + detail = "Endpoint requires clear auth" + code = 80001 + + def __init__(self): + super().__init__(self.detail, code=self.code) + + +class ClearAuthFailedError(CashuError): + detail = "Clear authentication failed" + code = 80002 + + def __init__(self): + super().__init__(self.detail, code=self.code) + + +class BlindAuthRequiredError(CashuError): + detail = "Endpoint requires blind auth" + code = 81001 + + def __init__(self): + super().__init__(self.detail, code=self.code) + + +class BlindAuthFailedError(CashuError): + detail = "Blind authentication failed" + code = 81002 + + def __init__(self): + super().__init__(self.detail, code=self.code) + + +class BlindAuthAmountExceededError(CashuError): + detail = "Maximum blind auth amount exceeded" + code = 81003 + + def __init__(self, detail: Optional[str] = None): + super().__init__(detail or self.detail, code=self.code) + + +class BlindAuthRateLimitExceededError(CashuError): + detail = "Blind auth token mint rate limit exceeded" + code = 81004 + + def __init__(self): + super().__init__(self.detail, code=self.code) diff --git a/cashu/core/mint_info.py b/cashu/core/mint_info.py new file mode 100644 index 0000000..93e118f --- /dev/null +++ b/cashu/core/mint_info.py @@ -0,0 +1,125 @@ +import json +import re +from typing import Any, Dict, List, Optional + +from pydantic import BaseModel + +from .base import Method, Unit +from .models import MintInfoContact, MintInfoProtectedEndpoint, Nut15MppSupport +from .nuts.nuts import BLIND_AUTH_NUT, CLEAR_AUTH_NUT, MPP_NUT, WEBSOCKETS_NUT + + +class MintInfo(BaseModel): + name: Optional[str] + pubkey: Optional[str] + version: Optional[str] + description: Optional[str] + description_long: Optional[str] + contact: Optional[List[MintInfoContact]] + motd: Optional[str] + icon_url: Optional[str] + time: Optional[int] + nuts: Dict[int, Any] + + def __str__(self): + return f"{self.name} ({self.description})" + + @classmethod + def from_json_str(cls, json_str: str): + return cls.parse_obj(json.loads(json_str)) + + def supports_nut(self, nut: int) -> bool: + if self.nuts is None: + return False + return nut in self.nuts + + def supports_mpp(self, method: str, unit: Unit) -> bool: + if not self.nuts: + return False + nut_15 = self.nuts.get(MPP_NUT) + if not nut_15 or not self.supports_nut(MPP_NUT) or not nut_15.get("methods"): + return False + + for entry in nut_15["methods"]: + entry_obj = Nut15MppSupport.parse_obj(entry) + if entry_obj.method == method and entry_obj.unit == unit.name: + return True + + return False + + def supports_websocket_mint_quote(self, method: Method, unit: Unit) -> bool: + if not self.nuts or not self.supports_nut(WEBSOCKETS_NUT): + return False + websocket_settings = self.nuts[WEBSOCKETS_NUT] + if not websocket_settings or "supported" not in websocket_settings: + return False + websocket_supported = websocket_settings["supported"] + for entry in websocket_supported: + if entry["method"] == method.name and entry["unit"] == unit.name: + if "bolt11_mint_quote" in entry["commands"]: + return True + return False + + def requires_clear_auth(self) -> bool: + return self.supports_nut(CLEAR_AUTH_NUT) + + def oidc_discovery_url(self) -> str: + if not self.requires_clear_auth(): + raise Exception( + "Could not get OIDC discovery URL. Mint info does not support clear auth." + ) + return self.nuts[CLEAR_AUTH_NUT]["openid_discovery"] + + def oidc_client_id(self) -> str: + if not self.requires_clear_auth(): + raise Exception( + "Could not get client_id. Mint info does not support clear auth." + ) + return self.nuts[CLEAR_AUTH_NUT]["client_id"] + + def required_clear_auth_endpoints(self) -> List[MintInfoProtectedEndpoint]: + if not self.requires_clear_auth(): + return [] + return [ + MintInfoProtectedEndpoint.parse_obj(e) + for e in self.nuts[CLEAR_AUTH_NUT]["protected_endpoints"] + ] + + def requires_clear_auth_path(self, method: str, path: str) -> bool: + if not self.requires_clear_auth(): + return False + path = "/" + path if not path.startswith("/") else path + for endpoint in self.required_clear_auth_endpoints(): + if method == endpoint.method and re.match(endpoint.path, path): + return True + return False + + def requires_blind_auth(self) -> bool: + return self.supports_nut(BLIND_AUTH_NUT) + + @property + def bat_max_mint(self) -> int: + if not self.requires_blind_auth(): + raise Exception( + "Could not get max mint. Mint info does not support blind auth." + ) + if not self.nuts[BLIND_AUTH_NUT].get("bat_max_mint"): + raise Exception("Could not get max mint. bat_max_mint not set.") + return self.nuts[BLIND_AUTH_NUT]["bat_max_mint"] + + def required_blind_auth_paths(self) -> List[MintInfoProtectedEndpoint]: + if not self.requires_blind_auth(): + return [] + return [ + MintInfoProtectedEndpoint.parse_obj(e) + for e in self.nuts[BLIND_AUTH_NUT]["protected_endpoints"] + ] + + def requires_blind_auth_path(self, method: str, path: str) -> bool: + if not self.requires_blind_auth(): + return False + path = "/" + path if not path.startswith("/") else path + for endpoint in self.required_blind_auth_paths(): + if method == endpoint.method and re.match(endpoint.path, path): + return True + return False diff --git a/cashu/core/models.py b/cashu/core/models.py index e840e4e..0438165 100644 --- a/cashu/core/models.py +++ b/cashu/core/models.py @@ -38,6 +38,11 @@ class MintInfoContact(BaseModel): info: str +class MintInfoProtectedEndpoint(BaseModel): + method: str + path: str + + class GetInfoResponse(BaseModel): name: Optional[str] = None pubkey: Optional[str] = None @@ -57,7 +62,7 @@ class GetInfoResponse(BaseModel): # BEGIN DEPRECATED: NUT-06 contact field change # NUT-06 PR: https://github.com/cashubtc/nuts/pull/117 @root_validator(pre=True) - def preprocess_deprecated_contact_field(cls, values): + def preprocess_deprecated_contact_field(cls, values: dict): if "contact" in values and values["contact"]: if isinstance(values["contact"][0], list): values["contact"] = [ @@ -346,3 +351,16 @@ class PostRestoreResponse(BaseModel): def __init__(self, **data): super().__init__(**data) self.promises = self.signatures + + +# ------- API: BLIND AUTH ------- +class PostAuthBlindMintRequest(BaseModel): + outputs: List[BlindedMessage] = Field( + ..., + max_items=settings.mint_max_request_length, + description="Blinded messages for creating blind auth tokens.", + ) + + +class PostAuthBlindMintResponse(BaseModel): + signatures: List[BlindedSignature] = [] diff --git a/cashu/core/nuts/nuts.py b/cashu/core/nuts/nuts.py index 69c78a0..1b3613e 100644 --- a/cashu/core/nuts/nuts.py +++ b/cashu/core/nuts/nuts.py @@ -14,3 +14,5 @@ MPP_NUT = 15 WEBSOCKETS_NUT = 17 CACHE_NUT = 19 MINT_QUOTE_SIGNATURE_NUT = 20 +CLEAR_AUTH_NUT = 21 +BLIND_AUTH_NUT = 22 diff --git a/cashu/core/settings.py b/cashu/core/settings.py index 0ff92fe..81611e8 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -68,6 +68,8 @@ class MintSettings(CashuSettings): class MintDeprecationFlags(MintSettings): mint_inactivate_base64_keysets: bool = Field(default=False) + auth_database: str = Field(default="data/mint") + class MintBackends(MintSettings): mint_lightning_backend: str = Field(default="") # deprecated @@ -231,6 +233,27 @@ class CoreLightningRestFundingSource(MintSettings): mint_corelightning_rest_cert: Optional[str] = Field(default=None) +class AuthSettings(MintSettings): + mint_require_auth: bool = Field(default=False) + mint_auth_oicd_discovery_url: Optional[str] = Field(default=None) + mint_auth_oicd_client_id: str = Field(default="cashu-client") + mint_auth_rate_limit_per_minute: int = Field( + default=5, + title="Auth rate limit per minute", + description="Number of requests a user can authenticate per minute.", + ) + mint_auth_max_blind_tokens: int = Field(default=100, gt=0) + mint_require_clear_auth_paths: List[List[str]] = [ + ["POST", "/v1/auth/blind/mint"], + ] + mint_require_blind_auth_paths: List[List[str]] = [ + ["POST", "/v1/swap"], + ["POST", "/v1/mint/quote/bolt11"], + ["POST", "/v1/mint/bolt11"], + ["POST", "/v1/melt/bolt11"], + ] + + class MintRedisCache(MintSettings): mint_redis_cache_enabled: bool = Field(default=False) mint_redis_cache_url: Optional[str] = Field(default=None) @@ -246,6 +269,7 @@ class Settings( FakeWalletSettings, MintLimits, MintBackends, + AuthSettings, MintRedisCache, MintDeprecationFlags, MintSettings, diff --git a/cashu/mint/app.py b/cashu/mint/app.py index 16ad1a4..b71b136 100644 --- a/cashu/mint/app.py +++ b/cashu/mint/app.py @@ -13,10 +13,10 @@ from starlette.requests import Request from ..core.errors import CashuError from ..core.logging import configure_logger from ..core.settings import settings +from .auth.router import auth_router from .router import redis, router from .router_deprecated import router_deprecated -from .startup import shutdown_mint as shutdown_mint_init -from .startup import start_mint_init +from .startup import shutdown_mint, start_auth, start_mint if settings.debug_profiling: pass @@ -29,7 +29,9 @@ from .middleware import add_middlewares, request_validation_exception_handler @asynccontextmanager async def lifespan(_: FastAPI) -> AsyncIterator[None]: - await start_mint_init() + await start_mint() + if settings.mint_require_auth: + await start_auth() try: yield except asyncio.CancelledError: @@ -38,7 +40,7 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]: finally: try: await redis.disconnect() - await shutdown_mint_init() + await shutdown_mint() except asyncio.CancelledError: logger.info("CancelledError during shutdown, shutting down forcefully") @@ -110,3 +112,6 @@ if settings.debug_mint_only_deprecated: else: app.include_router(router=router, tags=["Mint"]) app.include_router(router=router_deprecated, tags=["Deprecated"], deprecated=True) + +if settings.mint_require_auth: + app.include_router(auth_router, tags=["Auth"]) diff --git a/cashu/mint/auth/base.py b/cashu/mint/auth/base.py new file mode 100644 index 0000000..e406496 --- /dev/null +++ b/cashu/mint/auth/base.py @@ -0,0 +1,13 @@ +import datetime +from typing import Optional + + +class User: + id: str + last_access: Optional[datetime.datetime] + + def __init__(self, id: str, last_access: Optional[datetime.datetime] = None): + self.id = id + if isinstance(last_access, int): + last_access = datetime.datetime.fromtimestamp(last_access) + self.last_access = last_access diff --git a/cashu/mint/auth/crud.py b/cashu/mint/auth/crud.py new file mode 100644 index 0000000..f3b650a --- /dev/null +++ b/cashu/mint/auth/crud.py @@ -0,0 +1,669 @@ +import json +from abc import ABC, abstractmethod +from typing import Any, Dict, List, Optional + +from ...core.base import ( + BlindedSignature, + MeltQuote, + MintKeyset, + MintQuote, + Proof, +) +from ...core.db import ( + Connection, + Database, +) +from .base import User + + +class AuthLedgerCrud(ABC): + """ + Database interface for Nutshell auth ledger. + """ + + @abstractmethod + async def create_user( + self, + *, + db: Database, + user: User, + conn: Optional[Connection] = None, + ) -> None: ... + + @abstractmethod + async def get_user( + self, + *, + db: Database, + user_id: str, + conn: Optional[Connection] = None, + ) -> Optional[User]: ... + + async def update_user( + self, + *, + db: Database, + user_id: str, + conn: Optional[Connection] = None, + ) -> None: ... + + @abstractmethod + async def get_keyset( + self, + *, + db: Database, + id: str = "", + derivation_path: str = "", + seed: str = "", + conn: Optional[Connection] = None, + ) -> List[MintKeyset]: ... + + @abstractmethod + async def get_proofs_used( + self, + *, + Ys: List[str], + db: Database, + conn: Optional[Connection] = None, + ) -> List[Proof]: ... + + @abstractmethod + async def invalidate_proof( + self, + *, + db: Database, + proof: Proof, + quote_id: Optional[str] = None, + conn: Optional[Connection] = None, + ) -> None: ... + + @abstractmethod + async def get_proofs_pending( + self, + *, + Ys: List[str], + db: Database, + conn: Optional[Connection] = None, + ) -> List[Proof]: ... + + @abstractmethod + async def set_proof_pending( + self, + *, + db: Database, + proof: Proof, + quote_id: Optional[str] = None, + conn: Optional[Connection] = None, + ) -> None: ... + + @abstractmethod + async def unset_proof_pending( + self, + *, + proof: Proof, + db: Database, + conn: Optional[Connection] = None, + ) -> None: ... + + @abstractmethod + async def store_keyset( + self, + *, + db: Database, + keyset: MintKeyset, + conn: Optional[Connection] = None, + ) -> None: ... + + @abstractmethod + async def store_promise( + self, + *, + db: Database, + amount: int, + b_: str, + c_: str, + id: str, + e: str = "", + s: str = "", + conn: Optional[Connection] = None, + ) -> None: ... + + @abstractmethod + async def get_promise( + self, + *, + db: Database, + b_: str, + conn: Optional[Connection] = None, + ) -> Optional[BlindedSignature]: ... + + @abstractmethod + async def get_promises( + self, + *, + db: Database, + b_s: List[str], + conn: Optional[Connection] = None, + ) -> List[BlindedSignature]: ... + + +class AuthLedgerCrudSqlite(AuthLedgerCrud): + """Implementation of AuthLedgerCrud for sqlite. + + Args: + AuthLedgerCrud (ABC): Abstract base class for AuthLedgerCrud. + """ + + async def create_user( + self, + *, + db: Database, + user: User, + conn: Optional[Connection] = None, + ) -> None: + await (conn or db).execute( + f""" + INSERT INTO {db.table_with_schema('users')} + (id) + VALUES (:id) + """, + {"id": user.id}, + ) + + async def get_user( + self, + *, + db: Database, + user_id: str, + conn: Optional[Connection] = None, + ) -> Optional[User]: + row = await (conn or db).fetchone( + f""" + SELECT * from {db.table_with_schema('users')} + WHERE id = :user_id + """, + {"user_id": user_id}, + ) + return User(**row) if row else None + + async def update_user( + self, + *, + db: Database, + user_id: str, + conn: Optional[Connection] = None, + ) -> None: + await (conn or db).execute( + f""" + UPDATE {db.table_with_schema('users')} + SET last_access = :last_access + WHERE id = :user_id + """, + { + "last_access": db.to_timestamp(db.timestamp_now_str()), + "user_id": user_id, + }, + ) + + async def store_promise( + self, + *, + db: Database, + amount: int, + b_: str, + c_: str, + id: str, + e: str = "", + s: str = "", + conn: Optional[Connection] = None, + ) -> None: + await (conn or db).execute( + f""" + INSERT INTO {db.table_with_schema('promises')} + (amount, b_, c_, dleq_e, dleq_s, id, created) + VALUES (:amount, :b_, :c_, :dleq_e, :dleq_s, :id, :created) + """, + { + "amount": amount, + "b_": b_, + "c_": c_, + "dleq_e": e, + "dleq_s": s, + "id": id, + "created": db.to_timestamp(db.timestamp_now_str()), + }, + ) + + async def get_promise( + self, + *, + db: Database, + b_: str, + conn: Optional[Connection] = None, + ) -> Optional[BlindedSignature]: + row = await (conn or db).fetchone( + f""" + SELECT * from {db.table_with_schema('promises')} + WHERE b_ = :b_ + """, + {"b_": str(b_)}, + ) + return BlindedSignature.from_row(row) if row else None + + async def get_promises( + self, + *, + db: Database, + b_s: List[str], + conn: Optional[Connection] = None, + ) -> List[BlindedSignature]: + rows = await (conn or db).fetchall( + f""" + SELECT * from {db.table_with_schema('promises')} + WHERE b_ IN ({','.join([':b_' + str(i) for i in range(len(b_s))])}) + """, + {f"b_{i}": b_s[i] for i in range(len(b_s))}, + ) + return [BlindedSignature.from_row(r) for r in rows] if rows else [] + + async def invalidate_proof( + self, + *, + db: Database, + proof: Proof, + quote_id: Optional[str] = None, + conn: Optional[Connection] = None, + ) -> None: + await (conn or db).execute( + f""" + INSERT INTO {db.table_with_schema('proofs_used')} + (amount, c, secret, y, id, witness, created, melt_quote) + VALUES (:amount, :c, :secret, :y, :id, :witness, :created, :melt_quote) + """, + { + "amount": proof.amount, + "c": proof.C, + "secret": proof.secret, + "y": proof.Y, + "id": proof.id, + "witness": proof.witness, + "created": db.to_timestamp(db.timestamp_now_str()), + "melt_quote": quote_id, + }, + ) + + async def get_all_melt_quotes_from_pending_proofs( + self, + *, + db: Database, + conn: Optional[Connection] = None, + ) -> List[MeltQuote]: + rows = await (conn or db).fetchall( + f""" + SELECT * from {db.table_with_schema('melt_quotes')} WHERE quote in (SELECT DISTINCT melt_quote FROM {db.table_with_schema('proofs_pending')}) + """ + ) + return [MeltQuote.from_row(r) for r in rows] + + async def get_pending_proofs_for_quote( + self, + *, + quote_id: str, + db: Database, + conn: Optional[Connection] = None, + ) -> List[Proof]: + rows = await (conn or db).fetchall( + f""" + SELECT * from {db.table_with_schema('proofs_pending')} + WHERE melt_quote = :quote_id + """, + {"quote_id": quote_id}, + ) + return [Proof(**r) for r in rows] + + async def get_proofs_pending( + self, + *, + Ys: List[str], + db: Database, + conn: Optional[Connection] = None, + ) -> List[Proof]: + query = f""" + SELECT * from {db.table_with_schema('proofs_pending')} + WHERE y IN ({','.join([':y_' + str(i) for i in range(len(Ys))])}) + """ + values = {f"y_{i}": Ys[i] for i in range(len(Ys))} + rows = await (conn or db).fetchall(query, values) + return [Proof(**r) for r in rows] + + async def set_proof_pending( + self, + *, + db: Database, + proof: Proof, + quote_id: Optional[str] = None, + conn: Optional[Connection] = None, + ) -> None: + await (conn or db).execute( + f""" + INSERT INTO {db.table_with_schema('proofs_pending')} + (amount, c, secret, y, id, witness, created, melt_quote) + VALUES (:amount, :c, :secret, :y, :id, :witness, :created, :melt_quote) + """, + { + "amount": proof.amount, + "c": proof.C, + "secret": proof.secret, + "y": proof.Y, + "id": proof.id, + "witness": proof.witness, + "created": db.to_timestamp(db.timestamp_now_str()), + "melt_quote": quote_id, + }, + ) + + async def unset_proof_pending( + self, + *, + proof: Proof, + db: Database, + conn: Optional[Connection] = None, + ) -> None: + await (conn or db).execute( + f""" + DELETE FROM {db.table_with_schema('proofs_pending')} + WHERE secret = :secret + """, + {"secret": proof.secret}, + ) + + async def store_mint_quote( + self, + *, + quote: MintQuote, + db: Database, + conn: Optional[Connection] = None, + ) -> None: + await (conn or db).execute( + f""" + INSERT INTO {db.table_with_schema('mint_quotes')} + (quote, method, request, checking_id, unit, amount, issued, paid, state, created_time, paid_time) + VALUES (:quote, :method, :request, :checking_id, :unit, :amount, :issued, :paid, :state, :created_time, :paid_time) + """, + { + "quote": quote.quote, + "method": quote.method, + "request": quote.request, + "checking_id": quote.checking_id, + "unit": quote.unit, + "amount": quote.amount, + "issued": quote.issued, + "paid": quote.paid, + "state": quote.state.name, + "created_time": db.to_timestamp( + db.timestamp_from_seconds(quote.created_time) or "" + ), + "paid_time": db.to_timestamp( + db.timestamp_from_seconds(quote.paid_time) or "" + ), + }, + ) + + async def get_mint_quote( + self, + *, + quote_id: Optional[str] = None, + checking_id: Optional[str] = None, + request: Optional[str] = None, + db: Database, + conn: Optional[Connection] = None, + ) -> Optional[MintQuote]: + clauses = [] + values: Dict[str, Any] = {} + if quote_id: + clauses.append("quote = :quote_id") + values["quote_id"] = quote_id + if checking_id: + clauses.append("checking_id = :checking_id") + values["checking_id"] = checking_id + if request: + clauses.append("request = :request") + values["request"] = request + if not any(clauses): + raise ValueError("No search criteria") + where = f"WHERE {' AND '.join(clauses)}" + row = await (conn or db).fetchone( + f""" + SELECT * from {db.table_with_schema('mint_quotes')} + {where} + """, + values, + ) + if row is None: + return None + return MintQuote.from_row(row) if row else None + + async def get_mint_quote_by_request( + self, + *, + request: str, + db: Database, + conn: Optional[Connection] = None, + ) -> Optional[MintQuote]: + row = await (conn or db).fetchone( + f""" + SELECT * from {db.table_with_schema('mint_quotes')} + WHERE request = :request + """, + {"request": request}, + ) + return MintQuote.from_row(row) if row else None + + async def update_mint_quote( + self, + *, + quote: MintQuote, + db: Database, + conn: Optional[Connection] = None, + ) -> None: + await (conn or db).execute( + f"UPDATE {db.table_with_schema('mint_quotes')} SET issued = :issued, paid = :paid, state = :state, paid_time = :paid_time WHERE quote = :quote", + { + "issued": quote.issued, + "paid": quote.paid, + "state": quote.state.name, + "paid_time": db.to_timestamp( + db.timestamp_from_seconds(quote.paid_time) or "" + ), + "quote": quote.quote, + }, + ) + + async def store_melt_quote( + self, + *, + quote: MeltQuote, + db: Database, + conn: Optional[Connection] = None, + ) -> None: + await (conn or db).execute( + f""" + INSERT INTO {db.table_with_schema('melt_quotes')} + (quote, method, request, checking_id, unit, amount, fee_reserve, paid, state, created_time, paid_time, fee_paid, proof, change, expiry) + VALUES (:quote, :method, :request, :checking_id, :unit, :amount, :fee_reserve, :paid, :state, :created_time, :paid_time, :fee_paid, :proof, :change, :expiry) + """, + { + "quote": quote.quote, + "method": quote.method, + "request": quote.request, + "checking_id": quote.checking_id, + "unit": quote.unit, + "amount": quote.amount, + "fee_reserve": quote.fee_reserve or 0, + "paid": quote.paid, + "state": quote.state.name, + "created_time": db.to_timestamp( + db.timestamp_from_seconds(quote.created_time) or "" + ), + "paid_time": db.to_timestamp( + db.timestamp_from_seconds(quote.paid_time) or "" + ), + "fee_paid": quote.fee_paid, + "proof": quote.payment_preimage, + "change": json.dumps(quote.change) if quote.change else None, + "expiry": db.to_timestamp( + db.timestamp_from_seconds(quote.expiry) or "" + ), + }, + ) + + async def get_melt_quote( + self, + *, + quote_id: Optional[str] = None, + checking_id: Optional[str] = None, + request: Optional[str] = None, + db: Database, + conn: Optional[Connection] = None, + ) -> Optional[MeltQuote]: + clauses = [] + values: Dict[str, Any] = {} + if quote_id: + clauses.append("quote = :quote_id") + values["quote_id"] = quote_id + if checking_id: + clauses.append("checking_id = :checking_id") + values["checking_id"] = checking_id + if request: + clauses.append("request = :request") + values["request"] = request + if not any(clauses): + raise ValueError("No search criteria") + where = f"WHERE {' AND '.join(clauses)}" + row = await (conn or db).fetchone( + f""" + SELECT * from {db.table_with_schema('melt_quotes')} + {where} + """, + values, + ) + if row is None: + return None + return MeltQuote.from_row(row) if row else None + + async def update_melt_quote( + self, + *, + quote: MeltQuote, + db: Database, + conn: Optional[Connection] = None, + ) -> None: + await (conn or db).execute( + f""" + UPDATE {db.table_with_schema('melt_quotes')} SET paid = :paid, state = :state, fee_paid = :fee_paid, paid_time = :paid_time, proof = :proof, change = :change WHERE quote = :quote + """, + { + "paid": quote.paid, + "state": quote.state.name, + "fee_paid": quote.fee_paid, + "paid_time": db.to_timestamp( + db.timestamp_from_seconds(quote.paid_time) or "" + ), + "proof": quote.payment_preimage, + "change": ( + json.dumps([s.dict() for s in quote.change]) + if quote.change + else None + ), + "quote": quote.quote, + }, + ) + + async def store_keyset( + self, + *, + db: Database, + keyset: MintKeyset, + conn: Optional[Connection] = None, + ) -> None: + await (conn or db).execute( + f""" + INSERT INTO {db.table_with_schema('keysets')} + (id, seed, encrypted_seed, seed_encryption_method, derivation_path, valid_from, valid_to, first_seen, active, version, unit, input_fee_ppk) + VALUES (:id, :seed, :encrypted_seed, :seed_encryption_method, :derivation_path, :valid_from, :valid_to, :first_seen, :active, :version, :unit, :input_fee_ppk) + """, + { + "id": keyset.id, + "seed": keyset.seed, + "encrypted_seed": keyset.encrypted_seed, + "seed_encryption_method": keyset.seed_encryption_method, + "derivation_path": keyset.derivation_path, + "valid_from": db.to_timestamp( + keyset.valid_from or db.timestamp_now_str() + ), + "valid_to": db.to_timestamp(keyset.valid_to or db.timestamp_now_str()), + "first_seen": db.to_timestamp( + keyset.first_seen or db.timestamp_now_str() + ), + "active": True, + "version": keyset.version, + "unit": keyset.unit.name, + "input_fee_ppk": keyset.input_fee_ppk, + }, + ) + + async def get_keyset( + self, + *, + db: Database, + id: Optional[str] = None, + derivation_path: Optional[str] = None, + seed: Optional[str] = None, + unit: Optional[str] = None, + active: Optional[bool] = None, + conn: Optional[Connection] = None, + ) -> List[MintKeyset]: + clauses = [] + values: Dict = {} + if active is not None: + clauses.append("active = :active") + values["active"] = active + if id is not None: + clauses.append("id = :id") + values["id"] = id + if derivation_path is not None: + clauses.append("derivation_path = :derivation_path") + values["derivation_path"] = derivation_path + if seed is not None: + clauses.append("seed = :seed") + values["seed"] = seed + if unit is not None: + clauses.append("unit = :unit") + values["unit"] = unit + where = "" + if clauses: + where = f"WHERE {' AND '.join(clauses)}" + + rows = await (conn or db).fetchall( # type: ignore + f""" + SELECT * from {db.table_with_schema('keysets')} + {where} + """, + values, + ) + return [MintKeyset(**row) for row in rows] + + async def get_proofs_used( + self, + *, + Ys: List[str], + db: Database, + conn: Optional[Connection] = None, + ) -> List[Proof]: + query = f""" + SELECT * from {db.table_with_schema('proofs_used')} + WHERE y IN ({','.join([':y_' + str(i) for i in range(len(Ys))])}) + """ + values = {f"y_{i}": Ys[i] for i in range(len(Ys))} + rows = await (conn or db).fetchall(query, values) + return [Proof(**r) for r in rows] if rows else [] diff --git a/cashu/mint/auth/migrations.py b/cashu/mint/auth/migrations.py new file mode 100644 index 0000000..c5c785a --- /dev/null +++ b/cashu/mint/auth/migrations.py @@ -0,0 +1,100 @@ +from ...core.db import Connection, Database + + +async def m000_create_migrations_table(conn: Connection): + await conn.execute( + f""" + CREATE TABLE IF NOT EXISTS {conn.table_with_schema('dbversions')} ( + db TEXT PRIMARY KEY, + version INT NOT NULL + ) + """ + ) + + +async def m001_initial(db: Database): + async with db.connect() as conn: + await conn.execute( + f""" + CREATE TABLE IF NOT EXISTS {db.table_with_schema('users')} ( + id TEXT PRIMARY KEY, + last_access TIMESTAMP, + + UNIQUE (id) + ); + """ + ) + # columns: (id, seed, encrypted_seed, seed_encryption_method, derivation_path, valid_from, valid_to, first_seen, active, version, unit, input_fee_ppk) + await conn.execute( + f""" + CREATE TABLE IF NOT EXISTS {db.table_with_schema('keysets')} ( + id TEXT NOT NULL, + seed TEXT NOT NULL, + encrypted_seed TEXT, + seed_encryption_method TEXT, + derivation_path TEXT NOT NULL, + valid_from TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, + valid_to TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, + first_seen TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, + active BOOL DEFAULT TRUE, + version TEXT, + unit TEXT NOT NULL, + input_fee_ppk INT, + amounts TEXT, + + UNIQUE (derivation_path) + ); + """ + ) + + await conn.execute( + f""" + CREATE TABLE IF NOT EXISTS {db.table_with_schema('promises')} ( + id TEXT NOT NULL, + amount {db.big_int} NOT NULL, + b_ TEXT NOT NULL, + c_ TEXT NOT NULL, + dleq_e TEXT, + dleq_s TEXT, + created TIMESTAMP, + + UNIQUE (b_) + + ); + """ + ) + await conn.execute( + f""" + CREATE TABLE IF NOT EXISTS {db.table_with_schema('proofs_used')} ( + id TEXT NOT NULL, + amount {db.big_int} NOT NULL, + c TEXT NOT NULL, + secret TEXT NOT NULL, + y TEXT NOT NULL, + witness TEXT, + created TIMESTAMP, + melt_quote TEXT, + + UNIQUE (secret) + + ); + """ + ) + + await conn.execute( + f""" + CREATE TABLE IF NOT EXISTS {db.table_with_schema('proofs_pending')} ( + id TEXT NOT NULL, + amount {db.big_int} NOT NULL, + c TEXT NOT NULL, + secret TEXT NOT NULL, + y TEXT NOT NULL, + witness TEXT, + created TIMESTAMP, + melt_quote TEXT, + + UNIQUE (secret) + + ); + """ + ) diff --git a/cashu/mint/auth/router.py b/cashu/mint/auth/router.py new file mode 100644 index 0000000..d8a39be --- /dev/null +++ b/cashu/mint/auth/router.py @@ -0,0 +1,106 @@ +from fastapi import APIRouter, Request +from loguru import logger + +from ...core.errors import KeysetNotFoundError +from ...core.models import ( + KeysetsResponse, + KeysetsResponseKeyset, + KeysResponse, + KeysResponseKeyset, + PostAuthBlindMintRequest, + PostAuthBlindMintResponse, +) +from ...mint.startup import auth_ledger + +auth_router: APIRouter = APIRouter() + + +@auth_router.get( + "/v1/auth/blind/keys", + name="Mint public keys", + summary="Get the public keys of the newest mint keyset", + response_description=( + "All supported token values their associated public keys for all active keysets" + ), + response_model=KeysResponse, +) +async def keys(): + """This endpoint returns a dictionary of all supported token values of the mint and their associated public key.""" + logger.trace("> GET /v1/auth/blind/keys") + keyset = auth_ledger.keyset + keyset_for_response = [] + for keyset in auth_ledger.keysets.values(): + if keyset.active: + keyset_for_response.append( + KeysResponseKeyset( + id=keyset.id, + unit=keyset.unit.name, + keys={k: v for k, v in keyset.public_keys_hex.items()}, + ) + ) + return KeysResponse(keysets=keyset_for_response) + + +@auth_router.get( + "/v1/auth/blind/keys/{keyset_id}", + name="Keyset public keys", + summary="Public keys of a specific keyset", + response_description=( + "All supported token values of the mint and their associated" + " public key for a specific keyset." + ), + response_model=KeysResponse, +) +async def keyset_keys(keyset_id: str) -> KeysResponse: + """ + Get the public keys of the mint from a specific keyset id. + """ + logger.trace(f"> GET /v1/auth/blind/keys/{keyset_id}") + + keyset = auth_ledger.keysets.get(keyset_id) + if keyset is None: + raise KeysetNotFoundError(keyset_id) + + keyset_for_response = KeysResponseKeyset( + id=keyset.id, + unit=keyset.unit.name, + keys={k: v for k, v in keyset.public_keys_hex.items()}, + ) + return KeysResponse(keysets=[keyset_for_response]) + + +@auth_router.get( + "/v1/auth/blind/keysets", + name="Active keysets", + summary="Get all active keyset id of the mind", + response_model=KeysetsResponse, + response_description="A list of all active keyset ids of the mint.", +) +async def keysets() -> KeysetsResponse: + """This endpoint returns a list of keysets that the mint currently supports and will accept tokens from.""" + logger.trace("> GET /v1/auth/blind/keysets") + keysets = [] + for id, keyset in auth_ledger.keysets.items(): + keysets.append( + KeysetsResponseKeyset( + id=keyset.id, + unit=keyset.unit.name, + active=keyset.active, + ) + ) + return KeysetsResponse(keysets=keysets) + + +@auth_router.post( + "/v1/auth/blind/mint", + name="Mint blind auth tokens", + summary="Mint blind auth tokens for a user.", + response_model=PostAuthBlindMintResponse, +) +async def auth_blind_mint( + request_data: PostAuthBlindMintRequest, request: Request +) -> PostAuthBlindMintResponse: + signatures = await auth_ledger.mint_blind_auth( + outputs=request_data.outputs, user=request.state.user + ) + return PostAuthBlindMintResponse(signatures=signatures) diff --git a/cashu/mint/auth/server.py b/cashu/mint/auth/server.py new file mode 100644 index 0000000..a2f6430 --- /dev/null +++ b/cashu/mint/auth/server.py @@ -0,0 +1,235 @@ +from contextlib import asynccontextmanager +from typing import Any, List, Optional + +import httpx +import jwt +from loguru import logger + +from ...core.base import AuthProof +from ...core.db import Database +from ...core.errors import ( + BlindAuthAmountExceededError, + BlindAuthFailedError, + BlindAuthRateLimitExceededError, + ClearAuthFailedError, +) +from ...core.models import BlindedMessage, BlindedSignature +from ...core.settings import settings +from ..crud import LedgerCrudSqlite +from ..ledger import Ledger +from ..limit import assert_limit +from .base import User +from .crud import AuthLedgerCrud, AuthLedgerCrudSqlite + + +class AuthLedger(Ledger): + auth_crud: AuthLedgerCrud + jwks_url: str + jwks_client: jwt.PyJWKClient + issuer: str + oicd_discovery_json: dict + + def __init__( + self, + db: Database, + seed: str, + seed_decryption_key: Optional[str] = None, + derivation_path="", + amounts: Optional[List[int]] = None, + crud=LedgerCrudSqlite(), + ): + super().__init__( + db=db, + seed=seed, + backends=None, + seed_decryption_key=seed_decryption_key, + derivation_path=derivation_path, + crud=crud, + amounts=amounts, + ) + self.oicd_discovery_url = settings.mint_auth_oicd_discovery_url or "" + + async def init_auth(self): + if not self.oicd_discovery_url: + raise Exception("Missing OpenID Connect discovery URL.") + logger.info(f"Initializing OpenID Connect: {self.oicd_discovery_url}") + self.oicd_discovery_json = self._get_oicd_discovery_json() + self.jwks_url = self.oicd_discovery_json["jwks_uri"] + self.jwks_client = jwt.PyJWKClient(self.jwks_url) + logger.info(f"Getting JWKS from: {self.jwks_url}") + self.auth_crud = AuthLedgerCrudSqlite() + self.issuer: str = self.oicd_discovery_json["issuer"] + logger.info(f"Initialized OpenID Connect: {self.issuer}") + + def _get_oicd_discovery_json(self) -> dict: + resp = httpx.get(self.oicd_discovery_url) + resp.raise_for_status() + return resp.json() + + def _verify_oicd_issuer(self, clear_auth_token: str) -> None: + """Verify the issuer of the clear-auth token. + + Args: + clear_auth_token (str): JWT token. + + Raises: + Exception: Invalid issuer. + """ + try: + decoded = jwt.decode( + clear_auth_token, + options={"verify_signature": False}, + ) + issuer = decoded["iss"] + if issuer != self.issuer: + raise Exception(f"Invalid issuer: {issuer}. Expected: {self.issuer}") + except Exception as e: + raise e + + def _verify_decode_jwt(self, clear_auth_token: str) -> Any: + """Verify the clear-auth JWT token. + + Args: + clear_auth_token (str): JWT token. + + Raises: + jwt.ExpiredSignatureError: Token has expired. + jwt.InvalidSignatureError: Invalid signature. + jwt.InvalidTokenError: Invalid token. + + Returns: + Any: Decoded JWT. + """ + try: + # Use PyJWKClient to fetch the appropriate key based on the token's header + signing_key = self.jwks_client.get_signing_key_from_jwt(clear_auth_token) + decoded = jwt.decode( + clear_auth_token, + signing_key.key, + algorithms=["RS256", "ES256"], + options={"verify_aud": False}, + issuer=self.issuer, + ) + logger.trace(f"Decoded JWT: {decoded}") + except jwt.ExpiredSignatureError as e: + logger.error("Token has expired") + raise e + except jwt.InvalidSignatureError as e: + logger.error("Invalid signature") + raise e + except jwt.InvalidTokenError as e: + logger.error("Invalid token") + raise e + except Exception as e: + raise e + + return decoded + + async def _get_user(self, decoded_token: Any) -> User: + """Get the user from the decoded token. If the user does not exist, create a new one. + + Args: + decoded_token (Any): decoded JWT from PyJWT.decode + + Returns: + User: User object + """ + user_id = decoded_token["sub"] + user = await self.auth_crud.get_user(user_id=user_id, db=self.db) + if not user: + logger.info(f"Creating new user: {user_id}") + user = User(id=user_id) + await self.auth_crud.create_user(user=user, db=self.db) + return user + + async def verify_clear_auth(self, clear_auth_token: str) -> User: + """Verify the clear-auth JWT token and return the user. + + Checks: + - Token not expired. + - Token signature valid. + - User exists. + + Args: + auth_token (str): JWT token. + + Returns: + User: Authenticated user. + """ + try: + self._verify_oicd_issuer(clear_auth_token) + decoded = self._verify_decode_jwt(clear_auth_token) + user = await self._get_user(decoded) + except Exception: + raise ClearAuthFailedError() + + logger.info(f"User authenticated: {user.id}") + try: + assert_limit(user.id) + except Exception: + raise BlindAuthRateLimitExceededError() + + return user + + async def mint_blind_auth( + self, + *, + outputs: List[BlindedMessage], + user: User, + ) -> List[BlindedSignature]: + """Mints auth tokens. Returns a list of promises. + + Args: + outputs (List[BlindedMessage]): Outputs to sign. + user (User): Authenticated user. + + Raises: + Exception: Invalid auth. + Exception: Output verification failed. + Exception: Output quota exceeded. + + Returns: + List[BlindedSignature]: List of blinded signatures. + """ + + if len(outputs) > settings.mint_auth_max_blind_tokens: + raise BlindAuthAmountExceededError( + f"Too many outputs. You can only mint {settings.mint_auth_max_blind_tokens} tokens." + ) + + await self._verify_outputs(outputs) + promises = await self._generate_promises(outputs) + + # update last_access timestamp of the user + await self.auth_crud.update_user(user_id=user.id, db=self.db) + + return promises + + @asynccontextmanager + async def verify_blind_auth(self, blind_auth_token): + """Wrapper context that puts blind auth tokens into pending list and + melts them if the wrapped call succeeds. If it fails, the blind auth + token is not invalidated. + + Args: + blind_auth_token (str): Blind auth token. + + Raises: + Exception: Blind auth token validation failed. + """ + try: + proof = AuthProof.from_base64(blind_auth_token).to_proof() + await self.verify_inputs_and_outputs(proofs=[proof]) + await self.db_write._verify_spent_proofs_and_set_pending([proof]) + except Exception as e: + logger.error(f"Blind auth error: {e}") + raise BlindAuthFailedError() + + try: + yield + await self._invalidate_proofs(proofs=[proof]) + except Exception as e: + logger.error(f"Blind auth error: {e}") + raise BlindAuthFailedError() + finally: + await self.db_write._unset_proofs_pending([proof]) diff --git a/cashu/mint/crud.py b/cashu/mint/crud.py index d7b17bd..64cc5a1 100644 --- a/cashu/mint/crud.py +++ b/cashu/mint/crud.py @@ -642,8 +642,8 @@ class LedgerCrudSqlite(LedgerCrud): await (conn or db).execute( f""" INSERT INTO {db.table_with_schema('keysets')} - (id, seed, encrypted_seed, seed_encryption_method, derivation_path, valid_from, valid_to, first_seen, active, version, unit, input_fee_ppk) - VALUES (:id, :seed, :encrypted_seed, :seed_encryption_method, :derivation_path, :valid_from, :valid_to, :first_seen, :active, :version, :unit, :input_fee_ppk) + (id, seed, encrypted_seed, seed_encryption_method, derivation_path, valid_from, valid_to, first_seen, active, version, unit, input_fee_ppk, amounts) + VALUES (:id, :seed, :encrypted_seed, :seed_encryption_method, :derivation_path, :valid_from, :valid_to, :first_seen, :active, :version, :unit, :input_fee_ppk, :amounts) """, { "id": keyset.id, @@ -662,6 +662,7 @@ class LedgerCrudSqlite(LedgerCrud): "version": keyset.version, "unit": keyset.unit.name, "input_fee_ppk": keyset.input_fee_ppk, + "amounts": json.dumps(keyset.amounts), }, ) @@ -720,7 +721,7 @@ class LedgerCrudSqlite(LedgerCrud): """, values, ) - return [MintKeyset(**row) for row in rows] + return [MintKeyset.from_row(row) for row in rows] # type: ignore async def update_keyset( self, diff --git a/cashu/mint/features.py b/cashu/mint/features.py index fb9c319..63bc5df 100644 --- a/cashu/mint/features.py +++ b/cashu/mint/features.py @@ -1,12 +1,17 @@ from typing import Any, Dict, List, Union from ..core.base import Method +from ..core.mint_info import MintInfo from ..core.models import ( MeltMethodSetting, + MintInfoContact, + MintInfoProtectedEndpoint, MintMethodSetting, ) from ..core.nuts.nuts import ( + BLIND_AUTH_NUT, CACHE_NUT, + CLEAR_AUTH_NUT, DLEQ_NUT, FEE_RETURN_NUT, HTLC_NUT, @@ -21,10 +26,46 @@ from ..core.nuts.nuts import ( WEBSOCKETS_NUT, ) from ..core.settings import settings -from ..mint.protocols import SupportsBackends +from ..mint.protocols import SupportsBackends, SupportsPubkey + +_VERSION_PREFIX = "Nutshell" +_SUPPORTED = "supported" +_METHOD = "method" +_UNIT = "unit" +_BOLT11 = "bolt11" +_MPP = "mpp" +_COMMANDS = "commands" +_BOLT11_MINT_QUOTE = "bolt11_mint_quote" +_BOLT11_MELT_QUOTE = "bolt11_melt_quote" +_PROOF_STATE = "proof_state" +_PROTECTED_ENDPOINTS = "protected_endpoints" +_BAT_MAX_MINT = "bat_max_mint" +_OPENID_DISCOVERY = "openid_discovery" +_CLIENT_ID = "client_id" -class LedgerFeatures(SupportsBackends): +class LedgerFeatures(SupportsBackends, SupportsPubkey): + @property + def mint_info(self) -> MintInfo: + contact_info = [ + MintInfoContact(method=m, info=i) + for m, i in settings.mint_info_contact + if m and i + ] + return MintInfo( + name=settings.mint_info_name, + pubkey=self.pubkey.serialize().hex() if self.pubkey else None, + version=f"{_VERSION_PREFIX}/{settings.version}", + description=settings.mint_info_description, + description_long=settings.mint_info_description_long, + contact=contact_info, + nuts=self.mint_features, + icon_url=settings.mint_info_icon_url, + motd=settings.mint_info_motd, + time=None, + ) + + @property def mint_features(self) -> Dict[int, Union[List[Any], Dict[str, Any]]]: mint_features = self.create_mint_features() mint_features = self.add_supported_features(mint_features) @@ -100,30 +141,62 @@ class LedgerFeatures(SupportsBackends): # specify which websocket features are supported # these two are supported by default websocket_features: Dict[str, List[Dict[str, Union[str, List[str]]]]] = { - "supported": [] + _SUPPORTED: [] } # we check the backend to see if "bolt11_mint_quote" is supported as well for method, unit_dict in self.backends.items(): - if method == Method["bolt11"]: + if method == Method[_BOLT11]: for unit in unit_dict.keys(): - websocket_features["supported"].append( + websocket_features[_SUPPORTED].append( { - "method": method.name, - "unit": unit.name, - "commands": ["bolt11_melt_quote", "proof_state"], + _METHOD: method.name, + _UNIT: unit.name, + _COMMANDS: [_BOLT11_MELT_QUOTE, _PROOF_STATE], } ) if unit_dict[unit].supports_incoming_payment_stream: supported_features: List[str] = list( - websocket_features["supported"][-1]["commands"] + websocket_features[_SUPPORTED][-1][_COMMANDS] ) - websocket_features["supported"][-1]["commands"] = ( - supported_features + ["bolt11_mint_quote"] + websocket_features[_SUPPORTED][-1][_COMMANDS] = ( + supported_features + [_BOLT11_MINT_QUOTE] ) if websocket_features: mint_features[WEBSOCKETS_NUT] = websocket_features + # signal authentication features + if settings.mint_require_auth: + if not settings.mint_auth_oicd_discovery_url: + raise Exception( + "Missing OpenID Connect discovery URL: MINT_AUTH_OICD_DISCOVERY_URL" + ) + clear_auth_features: Dict[str, Union[bool, str, List[str]]] = { + _OPENID_DISCOVERY: settings.mint_auth_oicd_discovery_url, + _CLIENT_ID: settings.mint_auth_oicd_client_id, + _PROTECTED_ENDPOINTS: [], + } + + for endpoint in [ + MintInfoProtectedEndpoint(method=e[0], path=e[1]) + for e in settings.mint_require_clear_auth_paths + ]: + clear_auth_features[_PROTECTED_ENDPOINTS].append(endpoint.dict()) # type: ignore + + mint_features[CLEAR_AUTH_NUT] = clear_auth_features + + blind_auth_features: Dict[str, Union[bool, int, str, List[str]]] = { + _BAT_MAX_MINT: settings.mint_auth_max_blind_tokens, + _PROTECTED_ENDPOINTS: [], + } + for endpoint in [ + MintInfoProtectedEndpoint(method=e[0], path=e[1]) + for e in settings.mint_require_blind_auth_paths + ]: + blind_auth_features[_PROTECTED_ENDPOINTS].append(endpoint.dict()) # type: ignore + + mint_features[BLIND_AUTH_NUT] = blind_auth_features + return mint_features def add_cache_features( diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index aeb76ba..9c08242 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -75,16 +75,26 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe db_read: DbReadHelper invoice_listener_tasks: List[asyncio.Task] = [] disable_melt: bool = False + pubkey: PublicKey def __init__( self, + *, db: Database, seed: str, - backends: Mapping[Method, Mapping[Unit, LightningBackend]], - seed_decryption_key: Optional[str] = None, derivation_path="", + amounts: Optional[List[int]] = None, + backends: Optional[Mapping[Method, Mapping[Unit, LightningBackend]]] = None, + seed_decryption_key: Optional[str] = None, crud=LedgerCrudSqlite(), - ): + ) -> None: + self.keysets: Dict[str, MintKeyset] = {} + self.backends: Mapping[Method, Mapping[Unit, LightningBackend]] = {} + self.events = LedgerEventManager() + self.db_read: DbReadHelper + self.locks: Dict[str, asyncio.Lock] = {} # holds multiprocessing locks + self.invoice_listener_tasks: List[asyncio.Task] = [] + if not seed: raise Exception("seed not set") @@ -103,24 +113,33 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe self.db = db self.crud = crud - self.backends = backends + + if backends: + self.backends = backends + + if amounts: + self.amounts = amounts + else: + self.amounts = [2**n for n in range(settings.max_order)] + self.pubkey = derive_pubkey(self.seed) self.db_read = DbReadHelper(self.db, self.crud) self.db_write = DbWriteHelper(self.db, self.crud, self.events, self.db_read) # ------- STARTUP ------- - async def startup_ledger(self): - await self._startup_ledger() + async def startup_ledger(self) -> None: + await self._startup_keysets() + await self._check_backends() await self._check_pending_proofs_and_melt_quotes() self.invoice_listener_tasks = await self.dispatch_listeners() - async def _startup_ledger(self): + async def _startup_keysets(self) -> None: await self.init_keysets() - for derivation_path in settings.mint_derivation_path_list: await self.activate_keyset(derivation_path=derivation_path) + async def _check_backends(self) -> None: for method in self.backends: for unit in self.backends[method]: logger.info( @@ -139,7 +158,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe logger.info(f"Data dir: {settings.cashu_dir}") - async def shutdown_ledger(self): + async def shutdown_ledger(self) -> None: await self.db.engine.dispose() for task in self.invoice_listener_tasks: task.cancel() @@ -169,57 +188,65 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe version: Optional[str] = None, autosave=True, ) -> MintKeyset: - """Load the keyset for a derivation path if it already exists. If not generate new one and store in the db. + """ + Load an existing keyset for the specified derivation path or generate a new one if it doesn't exist. + Optionally store the newly created keyset in the database. Args: - derivation_path (_type_): Derivation path from which the keyset is generated. - autosave (bool, optional): Store newly-generated keyset if not already in database. Defaults to True. + derivation_path (str): Derivation path for keyset generation. + seed (Optional[str], optional): Seed value. Defaults to None. + version (Optional[str], optional): Version identifier. Defaults to None. + autosave (bool, optional): Whether to store the keyset if newly created. Defaults to True. Returns: - MintKeyset: Keyset + MintKeyset: The activated keyset. """ if not derivation_path: - raise Exception("derivation path not set") + raise ValueError("Derivation path must be provided.") + seed = seed or self.seed - tmp_keyset_local = MintKeyset( + version = version or settings.version + # Initialize a temporary keyset to derive the ID + temp_keyset = MintKeyset( seed=seed, derivation_path=derivation_path, - version=version or settings.version, + version=version, + amounts=self.amounts, ) logger.debug( - f"Activating keyset for derivation path {derivation_path} with id" - f" {tmp_keyset_local.id}." + f"Activating keyset for derivation path '{derivation_path}' with ID '{temp_keyset.id}'." ) - # load the keyset from db - logger.trace(f"crud: loading keyset for {derivation_path}") - tmp_keysets_local: List[MintKeyset] = await self.crud.get_keyset( - id=tmp_keyset_local.id, db=self.db + + # Attempt to retrieve existing keysets from the database + existing_keysets: List[MintKeyset] = await self.crud.get_keyset( + id=temp_keyset.id, db=self.db ) - logger.trace(f"crud: loaded {len(tmp_keysets_local)} keysets") - if tmp_keysets_local: - # we have a keyset with this derivation path in the database - keyset = tmp_keysets_local[0] + logger.trace( + f"Retrieved {len(existing_keysets)} keyset(s) for derivation path '{derivation_path}'." + ) + + if existing_keysets: + keyset = existing_keysets[0] else: - # no keyset for this derivation path yet - # we create a new keyset (keys will be generated at instantiation) + # Create a new keyset if none exists keyset = MintKeyset( - seed=seed or self.seed, + seed=seed, derivation_path=derivation_path, - version=version or settings.version, + amounts=self.amounts, + version=version, input_fee_ppk=settings.mint_input_fee_ppk, ) - logger.debug(f"Generated new keyset {keyset.id}.") + logger.debug(f"Generated new keyset with ID '{keyset.id}'.") + if autosave: - logger.debug(f"crud: storing new keyset {keyset.id}.") + logger.debug(f"Storing new keyset with ID '{keyset.id}'.") await self.crud.store_keyset(keyset=keyset, db=self.db) - logger.trace(f"crud: stored new keyset {keyset.id}.") - # activate this keyset + # Activate the keyset keyset.active = True - # load the new keyset in self.keysets self.keysets[keyset.id] = keyset + logger.debug(f"Keyset with ID '{keyset.id}' is now active.") - logger.debug(f"Loaded keyset {keyset.id}") return keyset async def init_keysets(self, autosave: bool = True) -> None: diff --git a/cashu/mint/limit.py b/cashu/mint/limit.py index b0840ff..e66b22f 100644 --- a/cashu/mint/limit.py +++ b/cashu/mint/limit.py @@ -1,3 +1,5 @@ +from typing import Optional + from fastapi import WebSocket, status from fastapi.responses import JSONResponse from limits import RateLimitItemPerMinute @@ -42,26 +44,26 @@ limiter = Limiter( ) -def assert_limit(identifier: str): +def assert_limit(identifier: str, limit: Optional[int] = None): """Custom rate limit handler that accepts a string identifier and raises an exception if the rate limit is exceeded. Uses the setting `mint_transaction_rate_limit_per_minute` for the rate limit. Args: identifier (str): The identifier to use for the rate limit. IP address for example. + limit (Optional[int], optional): The rate limit per minute to use. Defaults to None Raises: Exception: If the rate limit is exceeded. """ global limiter + limit_per_minute = limit or settings.mint_transaction_rate_limit_per_minute success = limiter._limiter.hit( - RateLimitItemPerMinute(settings.mint_transaction_rate_limit_per_minute), + RateLimitItemPerMinute(limit_per_minute), identifier, ) if not success: - logger.warning( - f"Rate limit {settings.mint_transaction_rate_limit_per_minute}/minute exceeded: {identifier}" - ) + logger.warning(f"Rate limit {limit_per_minute}/minute exceeded: {identifier}") raise Exception("Rate limit exceeded") diff --git a/cashu/mint/middleware.py b/cashu/mint/middleware.py index 1fb93e7..867ee35 100644 --- a/cashu/mint/middleware.py +++ b/cashu/mint/middleware.py @@ -10,7 +10,10 @@ from fastapi.exception_handlers import ( from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse from loguru import logger -from starlette.middleware.base import BaseHTTPMiddleware +from starlette.middleware.base import ( + BaseHTTPMiddleware, + RequestResponseEndpoint, +) from starlette.middleware.cors import CORSMiddleware from ..core.settings import settings @@ -22,6 +25,8 @@ if settings.debug_profiling: from slowapi.errors import RateLimitExceeded from slowapi.middleware import SlowAPIMiddleware +from .startup import auth_ledger + def add_middlewares(app: FastAPI): app.add_middleware( @@ -42,6 +47,52 @@ def add_middlewares(app: FastAPI): app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) app.add_middleware(SlowAPIMiddleware) + if settings.mint_require_auth: + app.add_middleware(BlindAuthMiddleware) + app.add_middleware(ClearAuthMiddleware) + + +class ClearAuthMiddleware(BaseHTTPMiddleware): + async def dispatch( + self, request: Request, call_next: RequestResponseEndpoint + ) -> Response: + if ( + settings.mint_require_auth + and auth_ledger.mint_info.requires_clear_auth_path( + method=request.method, path=request.url.path + ) + ): + clear_auth_token = request.headers.get("clear-auth") + if not clear_auth_token: + raise Exception("Missing clear auth token.") + try: + user = await auth_ledger.verify_clear_auth( + clear_auth_token=clear_auth_token + ) + request.state.user = user + except Exception as e: + raise e + return await call_next(request) + + +class BlindAuthMiddleware(BaseHTTPMiddleware): + async def dispatch( + self, request: Request, call_next: RequestResponseEndpoint + ) -> Response: + if ( + settings.mint_require_auth + and auth_ledger.mint_info.requires_blind_auth_path( + method=request.method, path=request.url.path + ) + ): + blind_auth_token = request.headers.get("blind-auth") + if not blind_auth_token: + raise Exception("Missing blind auth token.") + async with auth_ledger.verify_blind_auth(blind_auth_token): + return await call_next(request) + else: + return await call_next(request) + async def request_validation_exception_handler( request: Request, exc: RequestValidationError @@ -66,11 +117,11 @@ class CompressionMiddleware(BaseHTTPMiddleware): response = await call_next(request) # Handle streaming responses differently - if response.__class__.__name__ == 'StreamingResponse': + if response.__class__.__name__ == "StreamingResponse": return response - response_body = b'' - async for chunk in response.body_iterator: + response_body = b"" + async for chunk in response.body_iterator: # type: ignore response_body += chunk accept_encoding = request.headers.get("Accept-Encoding", "") @@ -97,5 +148,5 @@ class CompressionMiddleware(BaseHTTPMiddleware): content=content, status_code=response.status_code, headers=dict(response.headers), - media_type=response.media_type + media_type=response.media_type, ) diff --git a/cashu/mint/migrations.py b/cashu/mint/migrations.py index 61c7b12..b5bedd8 100644 --- a/cashu/mint/migrations.py +++ b/cashu/mint/migrations.py @@ -858,3 +858,13 @@ async def m024_add_melt_quote_outputs(db: Database): ADD COLUMN outputs TEXT DEFAULT NULL """ ) + + +async def m025_add_amounts_to_keysets(db: Database): + async with db.connect() as conn: + await conn.execute( + f"ALTER TABLE {db.table_with_schema('keysets')} ADD COLUMN amounts TEXT" + ) + await conn.execute( + f"UPDATE {db.table_with_schema('keysets')} SET amounts = '[]'" + ) diff --git a/cashu/mint/protocols.py b/cashu/mint/protocols.py index 0e84ea3..3661b63 100644 --- a/cashu/mint/protocols.py +++ b/cashu/mint/protocols.py @@ -1,6 +1,7 @@ from typing import Dict, Mapping, Protocol from ..core.base import Method, MintKeyset, Unit +from ..core.crypto.secp import PublicKey from ..core.db import Database from ..lightning.base import LightningBackend from ..mint.crud import LedgerCrud @@ -18,6 +19,10 @@ class SupportsBackends(Protocol): backends: Mapping[Method, Mapping[Unit, LightningBackend]] = {} +class SupportsPubkey(Protocol): + pubkey: PublicKey + + class SupportsDb(Protocol): db: Database db_read: DbReadHelper diff --git a/cashu/mint/router.py b/cashu/mint/router.py index d1f514d..3b4c52d 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -11,7 +11,6 @@ from ..core.models import ( KeysetsResponseKeyset, KeysResponse, KeysResponseKeyset, - MintInfoContact, PostCheckStateRequest, PostCheckStateResponse, PostMeltQuoteRequest, @@ -44,23 +43,18 @@ redis = RedisCache() ) async def info() -> GetInfoResponse: logger.trace("> GET /v1/info") - mint_features = ledger.mint_features() - contact_info = [ - MintInfoContact(method=m, info=i) - for m, i in settings.mint_info_contact - if m and i - ] + mint_info = ledger.mint_info return GetInfoResponse( - name=settings.mint_info_name, - pubkey=ledger.pubkey.serialize().hex() if ledger.pubkey else None, - version=f"Nutshell/{settings.version}", - description=settings.mint_info_description, - description_long=settings.mint_info_description_long, - contact=contact_info, - nuts=mint_features, - icon_url=settings.mint_info_icon_url, + name=mint_info.name, + pubkey=mint_info.pubkey, + version=mint_info.version, + description=mint_info.description, + description_long=mint_info.description_long, + contact=mint_info.contact, + nuts=mint_info.nuts, + icon_url=mint_info.icon_url, urls=settings.mint_info_urls, - motd=settings.mint_info_motd, + motd=mint_info.motd, time=int(time.time()), ) diff --git a/cashu/mint/startup.py b/cashu/mint/startup.py index 7820d17..d07f58f 100644 --- a/cashu/mint/startup.py +++ b/cashu/mint/startup.py @@ -12,7 +12,9 @@ from ..core.db import Database from ..core.migrations import migrate_databases from ..core.settings import settings from ..lightning.base import LightningBackend -from ..mint import migrations +from ..mint import migrations as mint_migrations +from ..mint.auth import migrations as auth_migrations +from ..mint.auth.server import AuthLedger from ..mint.crud import LedgerCrudSqlite from ..mint.ledger import Ledger @@ -76,6 +78,15 @@ ledger = Ledger( crud=LedgerCrudSqlite(), ) +# start auth ledger +auth_ledger = AuthLedger( + db=Database("auth", settings.auth_database), + seed="auth seed here", + amounts=[1], + derivation_path="m/0'/999'/0'", + crud=LedgerCrudSqlite(), +) + async def rotate_keys(n_seconds=60): """Rotate keyset epoch every n_seconds. @@ -93,8 +104,17 @@ async def rotate_keys(n_seconds=60): await asyncio.sleep(n_seconds) -async def start_mint_init(): - await migrate_databases(ledger.db, migrations) +async def start_auth(): + await migrate_databases(auth_ledger.db, auth_migrations) + logger.info("Starting auth ledger.") + await auth_ledger.init_keysets() + await auth_ledger.init_auth() + logger.info("Auth ledger started.") + + +async def start_mint(): + await migrate_databases(ledger.db, mint_migrations) + logger.info("Starting mint ledger.") await ledger.startup_ledger() logger.info("Mint started.") # asyncio.create_task(rotate_keys()) diff --git a/cashu/mint/tasks.py b/cashu/mint/tasks.py index a62e75d..7e0bb5f 100644 --- a/cashu/mint/tasks.py +++ b/cashu/mint/tasks.py @@ -1,22 +1,14 @@ import asyncio -from typing import List, Mapping +from typing import List from loguru import logger -from ..core.base import Method, MintQuoteState, Unit -from ..core.db import Database +from ..core.base import MintQuoteState from ..lightning.base import LightningBackend -from ..mint.crud import LedgerCrud -from .events.events import LedgerEventManager from .protocols import SupportsBackends, SupportsDb, SupportsEvents class LedgerTasks(SupportsDb, SupportsBackends, SupportsEvents): - backends: Mapping[Method, Mapping[Unit, LightningBackend]] = {} - db: Database - crud: LedgerCrud - events: LedgerEventManager - async def dispatch_listeners(self) -> List[asyncio.Task]: tasks = [] for method, unitbackends in self.backends.items(): diff --git a/cashu/mint/verification.py b/cashu/mint/verification.py index d726476..6561e41 100644 --- a/cashu/mint/verification.py +++ b/cashu/mint/verification.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Literal, Optional, Tuple, Union +from typing import List, Literal, Optional, Tuple, Union from loguru import logger @@ -6,14 +6,13 @@ from ..core.base import ( BlindedMessage, BlindedSignature, Method, - MintKeyset, MintQuote, Proof, Unit, ) from ..core.crypto import b_dhke from ..core.crypto.secp import PublicKey -from ..core.db import Connection, Database +from ..core.db import Connection from ..core.errors import ( InvalidProofsError, NoSecretInProofsError, @@ -25,11 +24,7 @@ from ..core.errors import ( ) from ..core.nuts import nut20 from ..core.settings import settings -from ..lightning.base import LightningBackend -from ..mint.crud import LedgerCrud from .conditions import LedgerSpendingConditions -from .db.read import DbReadHelper -from .db.write import DbWriteHelper from .protocols import SupportsBackends, SupportsDb, SupportsKeysets @@ -38,14 +33,6 @@ class LedgerVerification( ): """Verification functions for the ledger.""" - keyset: MintKeyset - keysets: Dict[str, MintKeyset] - crud: LedgerCrud - db: Database - db_read: DbReadHelper - db_write: DbWriteHelper - lightning: Dict[Unit, LightningBackend] - async def verify_inputs_and_outputs( self, *, @@ -55,6 +42,8 @@ class LedgerVerification( ): """Checks all proofs and outputs for validity. + Warning: Does NOT check if the proofs were already spent. Use `db_write._verify_proofs_spendable` for that. + Args: proofs (List[Proof]): List of proofs to check. outputs (Optional[List[BlindedMessage]], optional): List of outputs to check. diff --git a/cashu/nostr/client/client.py b/cashu/nostr/client/client.py index 3af7303..090ba1a 100644 --- a/cashu/nostr/client/client.py +++ b/cashu/nostr/client/client.py @@ -22,14 +22,11 @@ class NostrClient: relays = [ "wss://nostr-pub.wellorder.net", "wss://relay.damus.io", - "wss://nostr.zebedee.cloud", - "wss://relay.snort.social", "wss://nostr.fmt.wiz.biz", "wss://nos.lol", "wss://nostr.oxtr.dev", "wss://relay.current.fyi", - "wss://relay.snort.social", - ] # ["wss://nostr.oxtr.dev"] # ["wss://relay.nostr.info"] "wss://nostr-pub.wellorder.net" "ws://91.237.88.218:2700", "wss://nostrrr.bublina.eu.org", ""wss://nostr-relay.freeberty.net"", , "wss://nostr.oxtr.dev", "wss://relay.nostr.info", "wss://nostr-pub.wellorder.net" , "wss://relayer.fiatjaf.com", "wss://nodestr.fmt.wiz.biz/", "wss://no.str.cr" + ] relay_manager = RelayManager() private_key: PrivateKey public_key: PublicKey diff --git a/cashu/wallet/auth/__init__.py b/cashu/wallet/auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cashu/wallet/auth/auth.py b/cashu/wallet/auth/auth.py new file mode 100644 index 0000000..8c9af6b --- /dev/null +++ b/cashu/wallet/auth/auth.py @@ -0,0 +1,240 @@ +import hashlib +import os +from typing import List, Optional + +from loguru import logger + +from cashu.core.helpers import sum_proofs +from cashu.core.mint_info import MintInfo + +from ...core.base import Proof +from ...core.crypto.secp import PrivateKey +from ...core.db import Database +from ..crud import get_mint_by_url, update_mint +from ..wallet import Wallet +from .openid_connect.openid_client import AuthorizationFlow, OpenIDClient + + +class WalletAuth(Wallet): + oidc_discovery_url: str + oidc_client: OpenIDClient + wallet_db: Database + auth_flow: AuthorizationFlow + username: str | None + password: str | None + # API prefix for all requests + api_prefix = "/v1/auth/blind" + + def __init__( + self, url: str, db: str, name: str = "auth", unit: str = "auth", **kwargs + ): + """Authentication wallet. + + Args: + url (str): Mint url. + db (str): Auth wallet db location. + wallet_db (str): Wallet db location. + name (str, optional): Wallet name. Defaults to "auth". + unit (str, optional): Wallet unit. Defaults to "auth". + kwargs: Additional keyword arguments. + client_id (str, optional): OpenID client id. Defaults to "cashu-client". + client_secret (str, optional): OpenID client secret. Defaults to "". + username (str, optional): OpenID username. When set, the username and + password flow will be used to authenticate. If a username is already + stored in the database, it will be used. Will be stored in the + database if not already stored. + password (str, optional): OpenID password. Used if username is set. Will + be read from the database if already stored. Will be stored in the + database if not already stored. + """ + super().__init__(url, db, name, unit) + self.client_id = kwargs.get("client_id", "cashu-client") + logger.trace(f"client_id: {self.client_id}") + self.client_secret = kwargs.get("client_secret", "") + self.username = kwargs.get("username") + self.password = kwargs.get("password") + + if self.username: + if self.password is None: + raise Exception("Password must be set if username is set.") + self.auth_flow = AuthorizationFlow.PASSWORD + else: + self.auth_flow = AuthorizationFlow.AUTHORIZATION_CODE + # self.auth_flow = AuthorizationFlow.DEVICE_CODE + + self.access_token = kwargs.get("access_token") + self.refresh_token = kwargs.get("refresh_token") + + # overload with_db + @classmethod + async def with_db(cls, *args, **kwargs) -> "WalletAuth": + """Create a new wallet with a database. + Keyword arguments: + url (str): Mint url. + db (str): Wallet db location. + name (str, optional): Wallet name. Defaults to "auth". + username (str, optional): OpenID username. When set, the username and + password flow will be used to authenticate. If a username is already + stored in the database, it will be used. Will be stored in the + database if not already stored. + password (str, optional): OpenID password. Used if username is set. Will + be read from the database if already stored. Will be stored in the + database if not already stored. + client_id (str, optional): OpenID client id. Defaults to "cashu-client". + client_secret (str, optional): OpenID client secret. Defaults to "". + access_token (str, optional): OpenID access token. Defaults to None. + refresh_token (str, optional): OpenID refresh token. Defaults to None. + Returns: + WalletAuth: WalletAuth instance. + """ + + url: str = kwargs.get("url", "") + db = kwargs.get("db", "") + kwargs["name"] = kwargs.get("name", "auth") + name = kwargs["name"] + username = kwargs.get("username") + password = kwargs.get("password") + wallet_db = Database(name, db) + + # run migrations etc + kwargs.update(dict(skip_db_read=True)) + await super().with_db(*args, **kwargs) + + # the wallet might not have been created yet + # if it was though, we load the username, password, + # access token and refresh token from the database + try: + mint_db = await get_mint_by_url(wallet_db, url) + if mint_db: + kwargs.update( + { + "username": username or mint_db.username, + "password": password or mint_db.password, + "access_token": mint_db.access_token, + "refresh_token": mint_db.refresh_token, + } + ) + except Exception: + pass + + return cls(*args, **kwargs) + + async def init_auth_wallet( + self, + mint_info: Optional[MintInfo] = None, + mint_auth_proofs=True, + force_auth=False, + ) -> bool: + """Initialize authentication wallet. + + Args: + mint_info (MintInfo, optional): Mint information. If not provided, we load the + info from the database or the mint directly. Defaults to None. + mint_auth_proofs (bool, optional): Whether to mint auth proofs if necessary. + Defaults to True. + force_auth (bool, optional): Whether to force authentication. Defaults to False. + + Returns: + bool: False if the mint does not require clear auth. True otherwise. + """ + if mint_info: + self.mint_info = mint_info + await self.load_mint_info() + if not self.mint_info.requires_clear_auth(): + return False + + # Use the blind auth api_prefix for all following requests + await self.load_mint_keysets() + await self.activate_keyset() + await self.load_proofs() + + self.oidc_discovery_url = self.mint_info.oidc_discovery_url() + self.client_id = self.mint_info.oidc_client_id() + + # Initialize OpenIDClient + self.oidc_client = OpenIDClient( + discovery_url=self.oidc_discovery_url, + client_id=self.client_id, + client_secret=self.client_secret, + auth_flow=self.auth_flow, + username=self.username, + password=self.password, + access_token=self.access_token, + refresh_token=self.refresh_token, + ) + # Authenticate using OpenIDClient + await self.oidc_client.initialize() + await self.oidc_client.authenticate(force_authenticate=force_auth) + + await self.store_username_password() + await self.store_clear_auth_token() + + if mint_auth_proofs: + await self.mint_blind_auth_min_balance() + + return True + + async def mint_blind_auth_min_balance(self) -> None: + """Mint auth tokens if balance is too low.""" + MIN_BALANCE = self.mint_info.bat_max_mint + + if self.available_balance < MIN_BALANCE: + logger.debug( + f"Balance too low. Minting {self.unit.str(MIN_BALANCE)} auth tokens." + ) + try: + await self.mint_blind_auth() + except Exception as e: + logger.error(f"Error minting auth proofs: {str(e)}") + + async def store_username_password(self) -> None: + """Store the username and password in the database.""" + if self.username and self.password: + mint_db = await get_mint_by_url(self.db, self.url) + if not mint_db: + raise Exception("Mint not found.") + if mint_db.username != self.username or mint_db.password != self.password: + mint_db.username = self.username + mint_db.password = self.password + await update_mint(self.db, mint_db) + + async def store_clear_auth_token(self) -> None: + """Store the access and refresh tokens in the database.""" + access_token = self.oidc_client.access_token + refresh_token = self.oidc_client.refresh_token + if not access_token or not refresh_token: + raise Exception("Access or refresh token not available.") + # Store the tokens in the database + mint_db = await get_mint_by_url(self.db, self.url) + if not mint_db: + raise Exception("Mint not found.") + if ( + mint_db.access_token != access_token + or mint_db.refresh_token != refresh_token + ): + mint_db.access_token = access_token + mint_db.refresh_token = refresh_token + await update_mint(self.db, mint_db) + + async def mint_blind_auth(self) -> List[Proof]: + # Ensure access token is valid + if self.oidc_client.is_token_expired(): + await self.oidc_client.refresh_access_token() + await self.store_clear_auth_token() + clear_auth_token = self.oidc_client.access_token + if not clear_auth_token: + raise Exception("No clear auth token available.") + + amounts = self.mint_info.bat_max_mint * [1] # 1 AUTH tokens + secrets = [hashlib.sha256(os.urandom(32)).hexdigest() for _ in amounts] + rs = [PrivateKey(privkey=os.urandom(32), raw=True) for _ in amounts] + derivation_paths = ["" for _ in amounts] + outputs, rs = self._construct_outputs(amounts, secrets, rs) + promises = await self.blind_mint_blind_auth(clear_auth_token, outputs) + new_proofs = await self._construct_proofs( + promises, secrets, rs, derivation_paths + ) + logger.debug( + f"Minted {self.unit.str(sum_proofs(new_proofs))} blind auth proofs." + ) + return new_proofs diff --git a/cashu/wallet/auth/openid_connect/openid_client.py b/cashu/wallet/auth/openid_connect/openid_client.py new file mode 100644 index 0000000..039d3e3 --- /dev/null +++ b/cashu/wallet/auth/openid_connect/openid_client.py @@ -0,0 +1,487 @@ +import argparse +import asyncio +import base64 +import secrets +import webbrowser +from datetime import datetime, timedelta +from enum import Enum +from typing import Any, Dict, Optional +from urllib.parse import urlencode + +import httpx +import jwt +import uvicorn +from fastapi import FastAPI, HTTPException, Request +from fastapi.responses import HTMLResponse +from fastapi.templating import Jinja2Templates +from loguru import logger + + +class AuthorizationFlow(Enum): + AUTHORIZATION_CODE = "authorization_code" + PASSWORD = "password" + DEVICE_CODE = "urn:ietf:params:oauth:grant-type:device_code" + + +class OpenIDClient: + """OpenID Connect client for authentication.""" + + oidc_config: Dict[str, Any] + + def __init__( + self, + discovery_url: str, + client_id: str, + client_secret: str = "", + auth_flow: Optional[AuthorizationFlow] = None, + username: Optional[str] = None, + password: Optional[str] = None, + access_token: Optional[str] = None, + refresh_token: Optional[str] = None, + token_expiration_time: Optional[datetime] = None, + device_code: Optional[str] = None, + ) -> None: + self.discovery_url: str = discovery_url + self.client_id: str = client_id + self.client_secret: str = client_secret + self.auth_flow: Optional[AuthorizationFlow] = auth_flow + self.username: Optional[str] = username + self.password: Optional[str] = password + self.access_token: Optional[str] = access_token + self.refresh_token: Optional[str] = refresh_token + self.token_expiration_time: Optional[datetime] = token_expiration_time + self.device_code: Optional[str] = device_code + + self.redirect_uri: str = "http://localhost:33388/callback" + self.expected_state: str = secrets.token_urlsafe(16) + self.token_response: Dict[str, Any] = {} + self.token_event: asyncio.Event = asyncio.Event() + self.token_endpoint: str = "" + self.authorization_endpoint: str = "" + self.introspection_endpoint: Optional[str] = None + self.revocation_endpoint: Optional[str] = None + self.device_authorization_endpoint: Optional[str] = None + self.templates: Jinja2Templates = Jinja2Templates( + directory="cashu/wallet/auth/openid_connect/templates" + ) + + self.app: FastAPI = FastAPI() + self.app.state.client = self # Store self in app state + + async def initialize(self) -> None: + """Initialize the client asynchronously.""" + await self.fetch_oidc_configuration() + await self.determine_auth_flow() + + async def determine_auth_flow(self) -> AuthorizationFlow: + """Determine the authentication flow to use from the oidc configuration. + Supported flows are chosen in the following order: + - device_code + - authorization_code + - password + """ + if not hasattr(self, "oidc_config"): + raise ValueError( + "OIDC configuration not loaded. Call fetch_oidc_configuration first." + ) + + supported_flows = self.oidc_config.get("grant_types_supported", []) + + # if self.auth_flow is already set, check if it is supported + if self.auth_flow: + if self.auth_flow.value not in supported_flows: + raise ValueError( + f"Authentication flow {self.auth_flow.value} not supported by the OIDC configuration." + ) + return self.auth_flow + + if AuthorizationFlow.DEVICE_CODE.value in supported_flows: + self.auth_flow = AuthorizationFlow.DEVICE_CODE + elif AuthorizationFlow.AUTHORIZATION_CODE.value in supported_flows: + self.auth_flow = AuthorizationFlow.AUTHORIZATION_CODE + elif AuthorizationFlow.PASSWORD.value in supported_flows: + self.auth_flow = AuthorizationFlow.PASSWORD + else: + raise ValueError( + "No supported authentication flows found in the OIDC configuration." + ) + + logger.debug(f"Determined authentication flow: {self.auth_flow.value}") + return self.auth_flow + + async def fetch_oidc_configuration(self) -> None: + """Fetch OIDC configuration from the discovery URL.""" + try: + async with httpx.AsyncClient() as client: + response = await client.get(self.discovery_url) + response.raise_for_status() + self.oidc_config = response.json() + self.authorization_endpoint = self.oidc_config.get( # type: ignore + "authorization_endpoint" + ) + self.token_endpoint = self.oidc_config.get("token_endpoint") # type: ignore + self.introspection_endpoint = self.oidc_config.get( + "introspection_endpoint" + ) + self.revocation_endpoint = self.oidc_config.get("revocation_endpoint") + self.device_authorization_endpoint = self.oidc_config.get( + "device_authorization_endpoint" + ) + except httpx.HTTPError as e: + logger.error(f"Failed to get OpenID configuration: {e}") + raise + + async def handle_callback(self, request: Request) -> HTMLResponse: + """Endpoint to handle the redirect from the OpenID provider.""" + params = request.query_params + if "error" in params: + error_str = params["error"] + if "error_description" in params: + error_str += f": {params['error_description']}" + return self.templates.TemplateResponse( + "error.html", + {"request": request, "error": error_str}, + ) + elif "code" in params and "state" in params: + code: str = params["code"] + state: str = params["state"] + if state != self.expected_state: + raise HTTPException(status_code=400, detail="Invalid state parameter") + token_data: Dict[str, Any] = await self.exchange_code_for_token(code) + self.update_token_data(token_data) + response = self.render_success_page(request, token_data) + self.token_event.set() # Signal that the token has been received + return response + else: + return self.templates.TemplateResponse( + "error.html", + {"request": request, "error": "Missing 'code' or 'state' parameter"}, + ) + + async def exchange_code_for_token(self, code: str) -> Dict[str, Any]: + """Exchange the authorization code for tokens.""" + data: Dict[str, str] = { + "grant_type": "authorization_code", + "code": code, + "redirect_uri": self.redirect_uri, + } + headers: Dict[str, str] = {} + if self.client_secret: + # Use HTTP Basic Auth if client_secret is provided + basic_auth: str = f"{self.client_id}:{self.client_secret}" + basic_auth_bytes: bytes = basic_auth.encode("ascii") + basic_auth_b64: str = base64.b64encode(basic_auth_bytes).decode("ascii") + headers["Authorization"] = f"Basic {basic_auth_b64}" + else: + # Include client_id in the POST body for public clients + data["client_id"] = self.client_id + async with httpx.AsyncClient() as client: + try: + response = await client.post( + self.token_endpoint, data=data, headers=headers + ) + response.raise_for_status() + return response.json() + except httpx.HTTPError as e: + logger.error(f"HTTP error occurred during token exchange: {e}") + self.token_event.set() + return {} + + async def run_server(self) -> None: + """Run the FastAPI server.""" + config = uvicorn.Config( + self.app, host="127.0.0.1", port=33388, log_level="error" + ) + self.server = uvicorn.Server(config) + await self.server.serve() + + async def shutdown_server(self) -> None: + """Shut down the uvicorn server.""" + self.server.should_exit = True + + async def authenticate(self, force_authenticate: bool = False) -> None: + """Start the authentication process.""" + need_authenticate = force_authenticate + if self.access_token and self.refresh_token: + # We have a token and a refresh token, check if token is expired + if self.is_token_expired(): + try: + await self.refresh_access_token() + except httpx.HTTPError: + logger.debug("Failed to refresh token.") + need_authenticate = True + else: + logger.debug("Using existing access token.") + else: + need_authenticate = True + + if need_authenticate: + if self.auth_flow == AuthorizationFlow.AUTHORIZATION_CODE: + await self.authenticate_with_authorization_code() + elif self.auth_flow == AuthorizationFlow.PASSWORD: + await self.authenticate_with_password() + elif self.auth_flow == AuthorizationFlow.DEVICE_CODE: + await self.authenticate_with_device_code() + else: + raise ValueError(f"Unknown authentication flow: {self.auth_flow}") + + def is_token_expired(self) -> bool: + """Check if the access token is expired.""" + if not self.access_token: + raise ValueError("Access token is not set.") + decoded = jwt.decode(self.access_token, options={"verify_signature": False}) + exp = decoded.get("exp") + if not exp: + return False + return datetime.now() >= datetime.fromtimestamp(exp) - timedelta(minutes=1) + + async def refresh_access_token(self) -> None: + """Refresh the access token using the refresh token.""" + data = { + "grant_type": "refresh_token", + "refresh_token": self.refresh_token, + "client_id": self.client_id, + } + if self.client_secret: + data["client_secret"] = self.client_secret + async with httpx.AsyncClient() as client: + try: + response = await client.post(self.token_endpoint, data=data) + response.raise_for_status() + token_data = response.json() + self.update_token_data(token_data) + logger.info("Token refreshed successfully.") + except httpx.HTTPError as e: + logger.debug(f"Failed to refresh token: {e}") + raise + + async def authenticate_with_authorization_code(self) -> None: + """Authenticate using the authorization code flow.""" + + # Set up the route handlers + @self.app.get("/callback", response_class=HTMLResponse) + async def handle_callback(request: Request) -> Any: + print("CALLBACK") + return await self.handle_callback(request) + + # Build the authorization URL + params = { + "response_type": "code", + "client_id": self.client_id, + "redirect_uri": self.redirect_uri, + "scope": "openid", + "state": self.expected_state, + } + auth_url = f"{self.authorization_endpoint}?{urlencode(params)}" + + # Start the web server as an asyncio task + server_task = asyncio.create_task(self.run_server()) + + # Open the browser or print the URL for the user + logger.info("Please open the following URL in your browser to authenticate:") + logger.info(auth_url) + webbrowser.open(auth_url) + + # Wait for the token response + logger.info("Waiting for authentication...") + await self.token_event.wait() + + # Use the retrieved tokens + if self.token_response: + logger.info("Authentication successful!") + # logger.info("Token response:") + # logger.info(self.token_response) + else: + logger.error("Authentication failed.") + + # Signal the server to shut down + await self.shutdown_server() + + # Wait for the server task to finish + await server_task + + def update_token_data(self, token_data: Dict[str, Any]) -> None: + self.token_response.update(token_data) + self.access_token = token_data.get("access_token") + self.refresh_token = token_data.get("refresh_token") + if not self.access_token or not self.refresh_token: + raise ValueError( + "Access token or refresh token not found in token response." + ) + expires_in = token_data.get("expires_in") + if expires_in: + self.token_expiration_time = datetime.utcnow() + timedelta( + seconds=int(expires_in) + ) + refresh_expires_in = token_data.get("refresh_expires_in") + if refresh_expires_in: + logger.debug(f"Refresh token expires in {refresh_expires_in} seconds.") + self.refresh_token_expiration_time = datetime.utcnow() + timedelta( + seconds=int(refresh_expires_in) + ) + + async def authenticate_with_password(self) -> None: + """Authenticate using the resource owner password credentials flow.""" + if not self.username or not self.password: + raise ValueError( + 'Username and password must be provided. To set a password use: "cashu auth -p"' + ) + data = { + "grant_type": "password", + "client_id": self.client_id, + "username": self.username, + "password": self.password, + "scope": "openid", + } + if self.client_secret: + data["client_secret"] = self.client_secret + async with httpx.AsyncClient() as client: + try: + response = await client.post(self.token_endpoint, data=data) + response.raise_for_status() + token_data = response.json() + self.update_token_data(token_data) + logger.info("Authentication successful!") + # logger.info("Token response:") + # logger.info(self.token_response) + except httpx.HTTPError as e: + logger.error(f"Failed to obtain token: {e}") + raise + + def render_success_page( + self, request: Request, token_data: Dict[str, Any] + ) -> HTMLResponse: + """Render an HTML page with a green check mark and user information.""" + context = {"request": request, "token_data": token_data} + return self.templates.TemplateResponse("success.html", context) + + async def authenticate_with_device_code(self) -> None: + """Authenticate using the device code flow.""" + if not self.device_authorization_endpoint: + raise ValueError("Device authorization endpoint not available.") + + data = { + "client_id": self.client_id, + "scope": "openid", + } + if self.client_secret: + data["client_secret"] = self.client_secret + + async with httpx.AsyncClient() as client: + try: + response = await client.post( + self.device_authorization_endpoint, data=data + ) + response.raise_for_status() + device_data = response.json() + except httpx.HTTPError as e: + logger.error(f"Failed to obtain device code: {e}") + raise + + # Extract device code data + device_code = device_data.get("device_code") + user_code = device_data.get("user_code") + verification_uri = device_data.get("verification_uri") + verification_uri_complete = device_data.get("verification_uri_complete") + expires_in = device_data.get("expires_in") + interval = device_data.get("interval", 5) # Default interval is 5 seconds + + if not device_code or not verification_uri: + raise ValueError("Invalid response from device authorization endpoint.") + + # Display instructions to the user and open the browser + if verification_uri_complete: + logger.info("Opening browser to complete authorization...") + logger.info(verification_uri_complete) + webbrowser.open(verification_uri_complete) + else: + logger.info("Please visit the following URL to authorize:") + logger.info(verification_uri) + logger.info(f"Enter the user code: {user_code}") + # Construct the URL for the user to enter the code + full_verification_uri = f"{verification_uri}?user_code={user_code}" + webbrowser.open(full_verification_uri) + + # Start polling the token endpoint + start_time = datetime.now() + expires_at = start_time + timedelta(seconds=expires_in) + token_data = None + while datetime.now() < expires_at: + await asyncio.sleep(interval) + data = { + "grant_type": "urn:ietf:params:oauth:grant-type:device_code", + "device_code": device_code, + "client_id": self.client_id, + } + if self.client_secret: + data["client_secret"] = self.client_secret + async with httpx.AsyncClient() as client: + try: + response = await client.post(self.token_endpoint, data=data) + if response.status_code == 200: + # Successful response + token_data = response.json() + self.update_token_data(token_data) + logger.info("Authentication successful!") + break + else: + error_data = response.json() + error = error_data.get("error") + if error == "authorization_pending": + # Continue polling + pass + elif error == "slow_down": + # Increase interval by 5 seconds + interval += 5 + elif error == "access_denied": + logger.error("Access denied by user.") + break + elif error == "expired_token": + logger.error("Device code has expired.") + break + else: + logger.error(f"Error during polling: {error}") + break + except httpx.HTTPError as e: + logger.error(f"HTTP error during token polling: {e}") + break + else: + logger.error("Device code has expired before authorization.") + raise Exception("Device code expired") + + +async def main() -> None: + parser = argparse.ArgumentParser(description="OpenID Connect Authentication Client") + parser.add_argument("discovery_url", help="OpenID Connect Discovery URL") + parser.add_argument("client_id", help="Client ID") + parser.add_argument("--client_secret", help="Client Secret", default="") + parser.add_argument( + "--auth_flow", + choices=["authorization_code", "password", "device_code"], + default="authorization_code", + help="Authentication flow to use", + ) + parser.add_argument("--username", help="Username for password flow") + parser.add_argument("--password", help="Password for password flow") + parser.add_argument("--access_token", help="Stored access token") + parser.add_argument("--refresh_token", help="Stored refresh token") + parser.add_argument("--device_code", help="Device code for device flow") + args = parser.parse_args() + + client = OpenIDClient( + discovery_url=args.discovery_url, + client_id=args.client_id, + client_secret=args.client_secret, + auth_flow=AuthorizationFlow(args.auth_flow), + username=args.username, + password=args.password, + access_token=args.access_token, + refresh_token=args.refresh_token, + device_code=args.device_code, + ) + await client.initialize() + await client.authenticate() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/cashu/wallet/auth/openid_connect/templates/error.html b/cashu/wallet/auth/openid_connect/templates/error.html new file mode 100644 index 0000000..a713888 --- /dev/null +++ b/cashu/wallet/auth/openid_connect/templates/error.html @@ -0,0 +1,26 @@ + + + + Authentication Error + + + +
+
+

Authentication Error

+

{{ error }}

+
+ + diff --git a/cashu/wallet/auth/openid_connect/templates/success.html b/cashu/wallet/auth/openid_connect/templates/success.html new file mode 100644 index 0000000..03153f3 --- /dev/null +++ b/cashu/wallet/auth/openid_connect/templates/success.html @@ -0,0 +1,33 @@ + + + + Authentication Successful + + + +
+
+

Authentication Successful

+

You can close this window now.

+ +
+ + diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index 00fc883..e22ac4c 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import asyncio +import getpass import os import time from datetime import datetime, timezone @@ -41,6 +42,7 @@ from ...wallet.crud import ( ) from ...wallet.wallet import Wallet as Wallet from ..api.api_server import start_api_server +from ..auth.auth import WalletAuth from ..cli.cli_helpers import ( get_mint_wallet, get_unit_wallet, @@ -84,6 +86,49 @@ def coro(f): return wrapper +def init_auth_wallet(func): + """Decorator to pass auth_db and auth_keyset_id to the Wallet object.""" + + @wraps(func) + async def wrapper(*args, **kwargs): + ctx = args[0] # Assuming the first argument is 'ctx' + wallet: Wallet = ctx.obj["WALLET"] + db_location = wallet.db.db_location + + auth_wallet = await WalletAuth.with_db( + url=ctx.obj["HOST"], + db=db_location, + ) + + requires_auth = await auth_wallet.init_auth_wallet(wallet.mint_info) + + if not requires_auth: + logger.debug("Mint does not require clear auth.") + return await func(*args, **kwargs) + + # Pass auth_db and auth_keyset_id to the wallet object + wallet.auth_db = auth_wallet.db + wallet.auth_keyset_id = auth_wallet.keyset_id + # pass the mint_info so the wallet doesn't need to re-fetch it + wallet.mint_info = auth_wallet.mint_info + + # Pass the auth_wallet to context + args[0].obj["AUTH_WALLET"] = auth_wallet + + # Proceed to the original function + ret = await func(*args, **kwargs) + + if settings.debug: + await auth_wallet.load_proofs(reload=True) + logger.debug( + f"Auth balance: {auth_wallet.unit.str(auth_wallet.available_balance)}" + ) + + return ret + + return wrapper + + @click.group(cls=NaturalOrderGroup) @click.option( "--host", @@ -176,6 +221,10 @@ async def cli(ctx: Context, host: str, walletname: str, unit: str, tests: bool): ctx.obj["HOST"], db_path, name=walletname, unit=unit ) + # if we have never seen this mint before, we load its information + if not wallet.mint_info: + await wallet.load_mint() + assert wallet, "Wallet not found." ctx.obj["WALLET"] = wallet @@ -205,6 +254,7 @@ async def cli(ctx: Context, host: str, walletname: str, unit: str, tests: bool): ) @click.pass_context @coro +@init_auth_wallet async def pay( ctx: Context, invoice: str, amount: Optional[int] = None, yes: bool = False ): @@ -291,6 +341,7 @@ async def pay( ) @click.pass_context @coro +@init_auth_wallet async def invoice( ctx: Context, amount: float, @@ -451,6 +502,7 @@ async def invoice( @cli.command("swap", help="Swap funds between mints.") @click.pass_context @coro +@init_auth_wallet async def swap(ctx: Context): print("Select the mint to swap from:") outgoing_wallet: Wallet = await get_mint_wallet(ctx, force_select=True) @@ -621,8 +673,9 @@ async def balance(ctx: Context, verbose): ) @click.pass_context @coro +@init_auth_wallet async def send_command( - ctx, + ctx: Context, amount: int, memo: str, nostr: str, @@ -668,6 +721,7 @@ async def send_command( ) @click.pass_context @coro +@init_auth_wallet async def receive_cli( ctx: Context, token: str, @@ -685,6 +739,8 @@ async def receive_cli( mint_url, os.path.join(settings.cashu_dir, wallet.name), unit=token_obj.unit, + auth_db=wallet.auth_db.db_location if wallet.auth_db else None, + auth_keyset_id=wallet.auth_keyset_id, ) await verify_mint(mint_wallet, mint_url) receive_wallet = await receive(mint_wallet, token_obj) @@ -830,7 +886,7 @@ async def pending(ctx: Context, legacy, number: int, offset: int): @cli.command("lock", help="Generate receiving lock.") @click.pass_context @coro -async def lock(ctx): +async def lock(ctx: Context): wallet: Wallet = ctx.obj["WALLET"] pubkey = await wallet.create_p2pk_pubkey() @@ -851,7 +907,7 @@ async def lock(ctx): @cli.command("locks", help="Show unused receiving locks.") @click.pass_context @coro -async def locks(ctx): +async def locks(ctx: Context): wallet: Wallet = ctx.obj["WALLET"] # P2PK lock pubkey = await wallet.create_p2pk_pubkey() @@ -899,7 +955,7 @@ async def locks(ctx): ) @click.pass_context @coro -async def invoices(ctx, paid: bool, unpaid: bool, pending: bool, mint: bool): +async def invoices(ctx: Context, paid: bool, unpaid: bool, pending: bool, mint: bool): wallet: Wallet = ctx.obj["WALLET"] if paid and unpaid: @@ -1001,7 +1057,7 @@ async def invoices(ctx, paid: bool, unpaid: bool, pending: bool, mint: bool): @cli.command("wallets", help="List of all available wallets.") @click.pass_context @coro -async def wallets(ctx): +async def wallets(ctx: Context): # list all directories wallets = [ d for d in listdir(settings.cashu_dir) if isdir(join(settings.cashu_dir, d)) @@ -1013,7 +1069,7 @@ async def wallets(ctx): for w in wallets: wallet = Wallet(ctx.obj["HOST"], os.path.join(settings.cashu_dir, w)) try: - await wallet.load_proofs() + await wallet.load_proofs(reload=True, all_keysets=True) if wallet.proofs and len(wallet.proofs): active_wallet = False if w == ctx.obj["WALLET_NAME"]: @@ -1031,9 +1087,10 @@ async def wallets(ctx): @cli.command("info", help="Information about Cashu wallet.") @click.option("--mint", default=False, is_flag=True, help="Fetch mint information.") @click.option("--mnemonic", default=False, is_flag=True, help="Show your mnemonic.") +@click.option("--reload", default=False, is_flag=True, help="Reload mint info.") @click.pass_context @coro -async def info(ctx: Context, mint: bool, mnemonic: bool): +async def info(ctx: Context, mint: bool, mnemonic: bool, reload: bool): wallet: Wallet = ctx.obj["WALLET"] await wallet.load_keysets_from_db(unit=None) @@ -1057,7 +1114,11 @@ async def info(ctx: Context, mint: bool, mnemonic: bool): if mint: wallet.url = mint_url try: - mint_info: dict = (await wallet.load_mint_info()).dict() + mint_info_obj = await wallet.load_mint_info(reload) + if not mint_info_obj: + print(" - Mint information not available.") + continue + mint_info = mint_info_obj.dict() if mint_info: print(f" - Mint name: {mint_info['name']}") if mint_info.get("description"): @@ -1126,6 +1187,7 @@ async def info(ctx: Context, mint: bool, mnemonic: bool): ) @click.pass_context @coro +@init_auth_wallet async def restore(ctx: Context, to: int, batch: int): wallet: Wallet = ctx.obj["WALLET"] # check if there is already a mnemonic in the database @@ -1160,6 +1222,7 @@ async def restore(ctx: Context, to: int, batch: int): # @click.option("--all", default=False, is_flag=True, help="Execute on all available mints.") @click.pass_context @coro +@init_auth_wallet async def selfpay(ctx: Context, all: bool = False): wallet = await get_mint_wallet(ctx, force_select=True) await wallet.load_mint() @@ -1183,3 +1246,46 @@ async def selfpay(ctx: Context, all: bool = False): print(token) token_obj = TokenV4.deserialize(token) await receive(wallet, token_obj) + + +@cli.command("auth", help="Authenticate with mint.") +@click.option("--mint", "-m", default=False, is_flag=True, help="Mint new auth tokens.") +@click.option( + "--force", "-f", default=False, is_flag=True, help="Force authentication." +) +@click.option( + "--password", + "-p", + default=False, + is_flag=True, + help="Use username and password for authentication.", +) +@click.pass_context +@coro +async def auth(ctx: Context, mint: bool, force: bool, password: bool): + # auth_wallet: WalletAuth = ctx.obj["AUTH_WALLET"] + wallet: Wallet = ctx.obj["WALLET"] + username = None + password_str = None + if password: + username = input("Enter username: ") + password_str = getpass.getpass("Enter password: ") + auth_wallet = await WalletAuth.with_db( + url=ctx.obj["HOST"], + db=wallet.db.db_location, + username=username, + password=password_str, + ) + + requires_auth = await auth_wallet.init_auth_wallet( + wallet.mint_info, mint_auth_proofs=False, force_auth=force + ) + if not requires_auth: + print("Mint does not require authentication.") + return + + if mint: + new_proofs = await auth_wallet.mint_blind_auth() + print(f"Minted {auth_wallet.unit.str(sum_proofs(new_proofs))} auth tokens.") + + print(f"Auth balance: {auth_wallet.unit.str(auth_wallet.available_balance)}") diff --git a/cashu/wallet/crud.py b/cashu/wallet/crud.py index 75b42d3..e52941f 100644 --- a/cashu/wallet/crud.py +++ b/cashu/wallet/crud.py @@ -9,6 +9,7 @@ from ..core.base import ( MintQuoteState, Proof, WalletKeyset, + WalletMint, ) from ..core.db import Connection, Database @@ -577,3 +578,59 @@ async def store_seed_and_mnemonic( "mnemonic": mnemonic, }, ) + + +async def store_mint( + db: Database, + mint: WalletMint, + conn: Optional[Connection] = None, +) -> None: + await (conn or db).execute( + """ + INSERT INTO mints + (url, info, updated) + VALUES (:url, :info, :updated) + """, + { + "url": mint.url, + "info": mint.info, + "updated": int(time.time()), + }, + ) + + +async def update_mint( + db: Database, + mint: WalletMint, + conn: Optional[Connection] = None, +) -> None: + await (conn or db).execute( + """ + UPDATE mints + SET info = :info, updated = :updated, access_token = :access_token, refresh_token = :refresh_token, username = :username, password = :password + WHERE url = :url + """, + { + "url": mint.url, + "info": mint.info, + "updated": int(time.time()), + "access_token": mint.access_token, + "refresh_token": mint.refresh_token, + "username": mint.username, + "password": mint.password, + }, + ) + + +async def get_mint_by_url( + db: Database, + url: str, + conn: Optional[Connection] = None, +) -> Optional[WalletMint]: + row = await (conn or db).fetchone( + """ + SELECT * from mints WHERE url = :url + """, + {"url": url}, + ) + return WalletMint.parse_obj(dict(row)) if row else None diff --git a/cashu/wallet/errors.py b/cashu/wallet/errors.py new file mode 100644 index 0000000..d1bf2d6 --- /dev/null +++ b/cashu/wallet/errors.py @@ -0,0 +1,16 @@ +from typing import Optional + + +class WalletError(Exception): + msg: str + + def __init__(self, msg): + super().__init__(msg) + self.msg = msg + + +class BalanceTooLowError(WalletError): + msg = "Balance too low" + + def __init__(self, msg: Optional[str] = None): + super().__init__(msg or self.msg) diff --git a/cashu/wallet/helpers.py b/cashu/wallet/helpers.py index 5dedd6f..1a33049 100644 --- a/cashu/wallet/helpers.py +++ b/cashu/wallet/helpers.py @@ -52,6 +52,8 @@ async def redeem_TokenV3(wallet: Wallet, token: TokenV3) -> Wallet: t.mint, os.path.join(settings.cashu_dir, wallet.name), unit=token.unit or wallet.unit.name, + auth_db=wallet.auth_db.db_location if wallet.auth_db else None, + auth_keyset_id=wallet.auth_keyset_id, ) keyset_ids = mint_wallet._get_proofs_keyset_ids(t.proofs) logger.trace(f"Keysets in tokens: {' '.join(set(keyset_ids))}") diff --git a/cashu/wallet/migrations.py b/cashu/wallet/migrations.py index af9eff1..6b26250 100644 --- a/cashu/wallet/migrations.py +++ b/cashu/wallet/migrations.py @@ -214,7 +214,6 @@ async def m010_add_ids_to_proofs_and_out_to_invoices(db: Database): Columns that store mint and melt id for proofs and invoices. """ async with db.connect() as conn: - print("Running wallet migrations") await conn.execute("ALTER TABLE proofs ADD COLUMN mint_id TEXT") await conn.execute("ALTER TABLE proofs ADD COLUMN melt_id TEXT") @@ -287,11 +286,30 @@ async def m013_add_mint_and_melt_quote_tables(db: Database): """ ) -async def m013_add_key_to_mint_quote_table(db: Database): + +async def m014_add_key_to_mint_quote_table(db: Database): async with db.connect() as conn: await conn.execute( """ ALTER TABLE bolt11_mint_quotes ADD COLUMN privkey TEXT DEFAULT NULL; """ - ) \ No newline at end of file + ) + + +async def m015_add_mints_table(db: Database): + async with db.connect() as conn: + await conn.execute( + f""" + CREATE TABLE IF NOT EXISTS mints ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + url TEXT NOT NULL, + info TEXT NOT NULL, + updated TIMESTAMP DEFAULT {db.timestamp_now}, + access_token TEXT, + refresh_token TEXT, + username TEXT, + password TEXT + ); + """ + ) diff --git a/cashu/wallet/protocols.py b/cashu/wallet/protocols.py index 8b4a2ce..c8a4af8 100644 --- a/cashu/wallet/protocols.py +++ b/cashu/wallet/protocols.py @@ -1,10 +1,11 @@ -from typing import Dict, List, Protocol +from typing import Dict, List, Optional, Protocol import httpx from ..core.base import Proof, Unit, WalletKeyset from ..core.crypto.secp import PrivateKey from ..core.db import Database +from ..core.mint_info import MintInfo class SupportsPrivateKey(Protocol): @@ -28,3 +29,9 @@ class SupportsHttpxClient(Protocol): class SupportsMintURL(Protocol): url: str + + +class SupportsAuth(Protocol): + auth_db: Optional[Database] = None + auth_keyset_id: Optional[str] = None + mint_info: Optional[MintInfo] = None diff --git a/cashu/wallet/subscriptions.py b/cashu/wallet/subscriptions.py index a00aff1..2727832 100644 --- a/cashu/wallet/subscriptions.py +++ b/cashu/wallet/subscriptions.py @@ -47,7 +47,7 @@ class SubscriptionManager: try: msg = JSONRPCNotification.parse_raw(message) - logger.debug(f"Received notification: {msg}") + logger.trace(f"Received notification: {msg}") except Exception as e: logger.error(f"Error parsing notification: {e}") return diff --git a/cashu/wallet/v1_api.py b/cashu/wallet/v1_api.py index 4f4fe61..8297f98 100644 --- a/cashu/wallet/v1_api.py +++ b/cashu/wallet/v1_api.py @@ -12,6 +12,7 @@ from pydantic import ValidationError from cashu.wallet.crud import get_bolt11_melt_quote from ..core.base import ( + AuthProof, BlindedMessage, BlindedSignature, MeltQuoteState, @@ -29,6 +30,8 @@ from ..core.models import ( KeysetsResponse, KeysetsResponseKeyset, KeysResponse, + PostAuthBlindMintRequest, + PostAuthBlindMintResponse, PostCheckStateRequest, PostCheckStateResponse, PostMeltQuoteRequest, @@ -47,8 +50,16 @@ from ..core.models import ( ) from ..core.settings import settings from ..tor.tor import TorProxy +from .crud import ( + get_proofs, + invalidate_proof, +) +from .protocols import SupportsAuth from .wallet_deprecated import LedgerAPIDeprecated +GET = "GET" +POST = "POST" + def async_set_httpx_client(func): """ @@ -78,7 +89,7 @@ def async_set_httpx_client(func): verify=not settings.debug, proxies=proxies_dict, # type: ignore headers=headers_dict, - base_url=self.url, + base_url=self.url.rstrip("/"), timeout=None if settings.debug else 60, ) return await func(self, *args, **kwargs) @@ -99,10 +110,10 @@ def async_ensure_mint_loaded(func): return wrapper -class LedgerAPI(LedgerAPIDeprecated): +class LedgerAPI(LedgerAPIDeprecated, SupportsAuth): tor: TorProxy - db: Database # we need the db for melt_deprecated httpx: httpx.AsyncClient + api_prefix = "v1" def __init__(self, url: str, db: Database): self.url = url @@ -128,7 +139,6 @@ class LedgerAPI(LedgerAPIDeprecated): try: resp_dict = resp.json() except json.JSONDecodeError: - # if we can't decode the response, raise for status resp.raise_for_status() return if "detail" in resp_dict: @@ -137,9 +147,49 @@ class LedgerAPI(LedgerAPIDeprecated): if "code" in resp_dict: error_message += f" (Code: {resp_dict['code']})" raise Exception(error_message) - # raise for status if no error resp.raise_for_status() + async def _request(self, method: str, path: str, noprefix=False, **kwargs): + if not noprefix: + path = join(self.api_prefix, path) + if self.mint_info and self.mint_info.requires_blind_auth_path(method, path): + if not self.auth_db: + raise Exception( + "Mint requires blind auth, but no auth database is set." + ) + if not self.auth_keyset_id: + raise Exception( + "Mint requires blind auth, but no auth keyset id is set." + ) + proofs = await get_proofs(db=self.auth_db, id=self.auth_keyset_id) + if not proofs: + raise Exception( + "Mint requires blind auth, but no blind auth tokens were found." + ) + # select one auth proof + proof = proofs[0] + auth_token = AuthProof.from_proof(proof).to_base64() + kwargs.setdefault("headers", {}).update( + { + "Blind-auth": f"{auth_token}", + } + ) + await invalidate_proof(proof=proof, db=self.auth_db) + if self.mint_info and self.mint_info.requires_clear_auth_path(method, path): + logger.debug(f"Using clear auth token for {path}") + clear_auth_token = kwargs.pop("clear_auth_token") + if not clear_auth_token: + raise Exception( + "Mint requires clear auth, but no clear auth token is set." + ) + kwargs.setdefault("headers", {}).update( + { + "Clear-auth": f"{clear_auth_token}", + } + ) + + return await self.httpx.request(method, path, **kwargs) + """ ENDPOINTS """ @@ -157,9 +207,7 @@ class LedgerAPI(LedgerAPIDeprecated): Raises: Exception: If no keys are received from the mint """ - resp = await self.httpx.get( - join(self.url, "/v1/keys"), - ) + resp = await self._request(GET, "keys") # BEGIN backwards compatibility < 0.15.0 # assume the mint has not upgraded yet if we get a 404 if resp.status_code == 404: @@ -201,9 +249,7 @@ class LedgerAPI(LedgerAPIDeprecated): Exception: If no keys are received from the mint """ keyset_id_urlsafe = keyset_id.replace("+", "-").replace("/", "_") - resp = await self.httpx.get( - join(self.url, f"/v1/keys/{keyset_id_urlsafe}"), - ) + resp = await self._request(GET, f"keys/{keyset_id_urlsafe}") # BEGIN backwards compatibility < 0.15.0 # assume the mint has not upgraded yet if we get a 404 if resp.status_code == 404: @@ -238,9 +284,7 @@ class LedgerAPI(LedgerAPIDeprecated): Raises: Exception: If no keysets are received from the mint """ - resp = await self.httpx.get( - join(self.url, "/v1/keysets"), - ) + resp = await self._request(GET, "keysets") # BEGIN backwards compatibility < 0.15.0 # assume the mint has not upgraded yet if we get a 404 if resp.status_code == 404: @@ -265,9 +309,7 @@ class LedgerAPI(LedgerAPIDeprecated): Raises: Exception: If the mint info request fails """ - resp = await self.httpx.get( - join(self.url, "/v1/info"), - ) + resp = await self._request(GET, "/v1/info", noprefix=True) # BEGIN backwards compatibility < 0.15.0 # assume the mint has not upgraded yet if we get a 404 if resp.status_code == 404: @@ -305,9 +347,12 @@ class LedgerAPI(LedgerAPIDeprecated): payload = PostMintQuoteRequest( unit=unit.name, amount=amount, description=memo, pubkey=pubkey ) - resp = await self.httpx.post( - join(self.url, "/v1/mint/quote/bolt11"), json=payload.dict() + resp = await self._request( + POST, + "mint/quote/bolt11", + json=payload.dict(), ) + # BEGIN backwards compatibility < 0.15.0 # assume the mint has not upgraded yet if we get a 404 if resp.status_code == 404: @@ -329,9 +374,7 @@ class LedgerAPI(LedgerAPIDeprecated): Returns: PostMintQuoteResponse: Mint Quote Response """ - resp = await self.httpx.get( - join(self.url, f"/v1/mint/quote/bolt11/{quote}"), - ) + resp = await self._request(GET, f"mint/quote/bolt11/{quote}") self.raise_on_error_request(resp) return_dict = resp.json() return PostMintQuoteResponse.parse_obj(return_dict) @@ -371,8 +414,9 @@ class LedgerAPI(LedgerAPIDeprecated): return res payload = outputs_payload.dict(include=_mintrequest_include_fields(outputs)) # type: ignore - resp = await self.httpx.post( - join(self.url, "/v1/mint/bolt11"), + resp = await self._request( + POST, + "mint/bolt11", json=payload, # type: ignore ) # BEGIN backwards compatibility < 0.15.0 @@ -383,7 +427,7 @@ class LedgerAPI(LedgerAPIDeprecated): # END backwards compatibility < 0.15.0 self.raise_on_error_request(resp) response_dict = resp.json() - logger.trace("Lightning invoice checked. POST /v1/mint/bolt11") + logger.trace(f"Lightning invoice checked. POST {self.api_prefix}/mint/bolt11") promises = PostMintResponse.parse_obj(response_dict).signatures return promises @@ -406,8 +450,9 @@ class LedgerAPI(LedgerAPIDeprecated): unit=unit.name, request=payment_request, options=melt_options ) - resp = await self.httpx.post( - join(self.url, "/v1/melt/quote/bolt11"), + resp = await self._request( + POST, + "melt/quote/bolt11", json=payload.dict(), ) # BEGIN backwards compatibility < 0.15.0 @@ -441,9 +486,7 @@ class LedgerAPI(LedgerAPIDeprecated): Returns: PostMeltQuoteResponse: Melt Quote Response """ - resp = await self.httpx.get( - join(self.url, f"/v1/melt/quote/bolt11/{quote}"), - ) + resp = await self._request(GET, f"melt/quote/bolt11/{quote}") self.raise_on_error_request(resp) return_dict = resp.json() return PostMeltQuoteResponse.parse_obj(return_dict) @@ -474,8 +517,9 @@ class LedgerAPI(LedgerAPIDeprecated): "outputs": {i: outputs_include for i in range(len(outputs))}, } - resp = await self.httpx.post( - join(self.url, "/v1/melt/bolt11"), + resp = await self._request( + POST, + "melt/bolt11", json=payload.dict(include=_meltrequest_include_fields(proofs, outputs)), # type: ignore timeout=None, ) @@ -523,7 +567,7 @@ class LedgerAPI(LedgerAPIDeprecated): outputs: List[BlindedMessage], ) -> List[BlindedSignature]: """Consume proofs and create new promises based on amount split.""" - logger.debug("Calling split. POST /v1/swap") + logger.debug(f"Calling split. POST {self.api_prefix}/swap") split_payload = PostSwapRequest(inputs=proofs, outputs=outputs) # construct payload @@ -541,8 +585,9 @@ class LedgerAPI(LedgerAPIDeprecated): "inputs": {i: proofs_include for i in range(len(proofs))}, } - resp = await self.httpx.post( - join(self.url, "/v1/swap"), + resp = await self._request( + POST, + "swap", json=split_payload.dict(include=_splitrequest_include_fields(proofs)), # type: ignore ) # BEGIN backwards compatibility < 0.15.0 @@ -568,8 +613,9 @@ class LedgerAPI(LedgerAPIDeprecated): Checks whether the secrets in proofs are already spent or not and returns a list of booleans. """ payload = PostCheckStateRequest(Ys=[p.Y for p in proofs]) - resp = await self.httpx.post( - join(self.url, "/v1/checkstate"), + resp = await self._request( + POST, + "checkstate", json=payload.dict(), ) # BEGIN backwards compatibility < 0.15.0 @@ -595,10 +641,7 @@ class LedgerAPI(LedgerAPIDeprecated): "Received HTTP Error 422. Attempting state check with < 0.16.0 compatibility." ) payload_secrets = {"secrets": [p.secret for p in proofs]} - resp_secrets = await self.httpx.post( - join(self.url, "/v1/checkstate"), - json=payload_secrets, - ) + resp_secrets = await self._request(POST, "checkstate", json=payload_secrets) self.raise_on_error(resp_secrets) states = [ ProofState(Y=p.Y, state=ProofSpentState(s["state"])) @@ -619,7 +662,7 @@ class LedgerAPI(LedgerAPIDeprecated): Asks the mint to restore promises corresponding to outputs. """ payload = PostMintRequest(quote="restore", outputs=outputs) - resp = await self.httpx.post(join(self.url, "/v1/restore"), json=payload.dict()) + resp = await self._request(POST, "restore", json=payload.dict()) # BEGIN backwards compatibility < 0.15.0 # assume the mint has not upgraded yet if we get a 404 if resp.status_code == 404: @@ -637,3 +680,21 @@ class LedgerAPI(LedgerAPIDeprecated): # END backwards compatibility < 0.15.1 return returnObj.outputs, returnObj.signatures + + @async_set_httpx_client + async def blind_mint_blind_auth( + self, clear_auth_token: str, outputs: List[BlindedMessage] + ) -> List[BlindedSignature]: + """ + Asks the mint to mint blind auth tokens. Needs to provide a clear auth token. + """ + payload = PostAuthBlindMintRequest(outputs=outputs) + resp = await self._request( + POST, + "mint", + json=payload.dict(), + clear_auth_token=clear_auth_token, + ) + self.raise_on_error_request(resp) + response_dict = resp.json() + return PostAuthBlindMintResponse.parse_obj(response_dict).signatures diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index ba2020f..886a4cd 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -1,4 +1,5 @@ import copy +import json import threading import time from typing import Callable, Dict, List, Optional, Tuple, Union @@ -17,6 +18,7 @@ from ..core.base import ( Proof, Unit, WalletKeyset, + WalletMint, ) from ..core.crypto import b_dhke from ..core.crypto.secp import PrivateKey, PublicKey @@ -30,6 +32,7 @@ from ..core.helpers import ( ) from ..core.json_rpc.base import JSONRPCSubscriptionKinds from ..core.migrations import migrate_databases +from ..core.mint_info import MintInfo from ..core.models import ( PostCheckStateResponse, PostMeltQuoteResponse, @@ -43,6 +46,7 @@ from .crud import ( bump_secret_derivation, get_bolt11_mint_quote, get_keysets, + get_mint_by_url, get_proofs, invalidate_proof, secret_used, @@ -50,14 +54,16 @@ from .crud import ( store_bolt11_melt_quote, store_bolt11_mint_quote, store_keyset, + store_mint, store_proof, update_bolt11_melt_quote, update_bolt11_mint_quote, update_keyset, + update_mint, update_proof, ) +from .errors import BalanceTooLowError from .htlc import WalletHTLC -from .mint_info import MintInfo from .p2pk import WalletP2PK from .proofs import WalletProofs from .secrets import WalletSecrets @@ -108,21 +114,41 @@ class Wallet( db: Database bip32: BIP32 # private_key: Optional[PrivateKey] = None + auth_db: Optional[Database] = None + auth_keyset_id: Optional[str] = None - def __init__(self, url: str, db: str, name: str = "wallet", unit: str = "sat"): + def __init__( + self, + url: str, + db: str, + name: str = "wallet", + unit: str = "sat", + auth_db: Optional[str] = None, + auth_keyset_id: Optional[str] = None, + ): """A Cashu wallet. Args: url (str): URL of the mint. db (str): Path to the database directory. name (str, optional): Name of the wallet database file. Defaults to "wallet". + unit (str, optional): Unit of the wallet. Defaults to "sat". + auth_db (Optional[str], optional): Path to the auth database directory. Defaults to None. + auth_keyset_id (Optional[str], optional): Keyset ID of the auth keyset. Defaults to None. """ - self.db = Database("wallet", db) + self.db = Database(name, db) self.proofs: List[Proof] = [] self.name = name self.unit = Unit[unit] url = sanitize_url(url) + # if this is an auth wallet + if (auth_db and not auth_keyset_id) or (not auth_db and auth_keyset_id): + raise Exception("Both auth_db and auth_keyset_id must be provided.") + if auth_db and auth_keyset_id: + self.auth_db = Database("auth", auth_db) + self.auth_keyset_id = auth_keyset_id + super().__init__(url=url, db=self.db) logger.debug("Wallet initialized") logger.debug(f"Mint URL: {url}") @@ -137,7 +163,10 @@ class Wallet( name: str = "wallet", skip_db_read: bool = False, unit: str = "sat", + auth_db: Optional[str] = None, + auth_keyset_id: Optional[str] = None, load_all_keysets: bool = False, + **kwargs, ): """Initializes a wallet with a database and initializes the private key. @@ -151,12 +180,22 @@ class Wallet( unit (str, optional): Unit of the wallet. Defaults to "sat". load_all_keysets (bool, optional): If true, all keysets are loaded from the database. Defaults to False. + auth_db (Optional[str], optional): Path to the auth database directory. Defaults to None. + auth_keyset_id (Optional[str], optional): Keyset ID of the auth keyset. Defaults to None. + kwargs: Additional keyword arguments. Returns: Wallet: Initialized wallet. """ logger.trace(f"Initializing wallet with database: {db}") - self = cls(url=url, db=db, name=name, unit=unit) + self = cls( + url=url, + db=db, + name=name, + unit=unit, + auth_db=auth_db, + auth_keyset_id=auth_keyset_id, + ) await self._migrate_database() if skip_db_read: @@ -172,8 +211,13 @@ class Wallet( self.keysets = {k.id: k for k in keysets_active_unit} else: self.keysets = {k.id: k for k in keysets_list} - keysets_str = " ".join([f"{i} {k.unit}" for i, k in self.keysets.items()]) - logger.debug(f"Loaded keysets: {keysets_str}") + + if self.keysets: + keysets_str = " ".join([f"{i} {k.unit}" for i, k in self.keysets.items()]) + logger.debug(f"Loaded keysets: {keysets_str}") + + await self.load_mint_info(offline=True) + return self async def _migrate_database(self): @@ -185,12 +229,63 @@ class Wallet( # ---------- API ---------- - async def load_mint_info(self) -> MintInfo: - """Loads the mint info from the mint.""" - mint_info_resp = await self._get_info() - self.mint_info = MintInfo(**mint_info_resp.dict()) - logger.debug(f"Mint info: {self.mint_info}") - return self.mint_info + async def load_mint_info(self, reload=False, offline=False) -> MintInfo | None: + """Loads the mint info from the mint. + + Args: + reload (bool, optional): If True, the mint info is reloaded from the mint. Defaults to False. + offline (bool, optional): If True, the mint info is not loaded from the mint. Defaults to False. + """ + # if self.mint_info and not reload: + # return self.mint_info + + # read mint info from db + if reload: + if offline: + raise Exception("Cannot reload mint info offline.") + logger.debug("Forcing reload of mint info.") + mint_info_resp = await self._get_info() + self.mint_info = MintInfo(**mint_info_resp.dict()) + + wallet_mint_db = await get_mint_by_url(url=self.url, db=self.db) + if not wallet_mint_db: + if self.mint_info: + logger.debug("Storing mint info in db.") + await store_mint( + db=self.db, + mint=WalletMint( + url=self.url, info=json.dumps(self.mint_info.dict()) + ), + ) + else: + if offline: + return None + logger.debug("Loading mint info from mint.") + mint_info_resp = await self._get_info() + self.mint_info = MintInfo(**mint_info_resp.dict()) + if not wallet_mint_db: + logger.debug("Storing mint info in db.") + await store_mint( + db=self.db, + mint=WalletMint( + url=self.url, info=json.dumps(self.mint_info.dict()) + ), + ) + return self.mint_info + elif ( + self.mint_info + and not json.dumps(self.mint_info.dict()) == wallet_mint_db.info + ): + logger.debug("Updating mint info in db.") + await update_mint( + db=self.db, + mint=WalletMint(url=self.url, info=json.dumps(self.mint_info.dict())), + ) + return self.mint_info + else: + logger.debug("Loading mint info from db.") + self.mint_info = MintInfo.from_json_str(wallet_mint_db.info) + return self.mint_info async def load_mint_keysets(self, force_old_keysets=False): """Loads all keyset of the mint and makes sure we have them all in the database. @@ -304,11 +399,11 @@ class Wallet( force_old_keysets (bool, optional): If true, old deprecated base64 keysets are not ignored. This is necessary for restoring tokens from old base64 keysets. Defaults to False. """ - logger.trace("Loading mint.") + logger.trace(f"Loading mint {self.url}") await self.load_mint_keysets(force_old_keysets) await self.activate_keyset(keyset_id) try: - await self.load_mint_info() + await self.load_mint_info(reload=True) except Exception as e: logger.debug(f"Could not load mint info: {e}") pass @@ -979,7 +1074,7 @@ class Wallet( # select proofs that are not reserved and are in the active keysets of the mint proofs = self.active_proofs(proofs) if sum_proofs(proofs) < amount: - raise Exception("balance too low.") + raise BalanceTooLowError() # coin selection for potentially offline sending send_proofs = self.coinselect(proofs, amount, include_fees=include_fees) @@ -1037,7 +1132,7 @@ class Wallet( # select proofs that are not reserved and are in the active keysets of the mint proofs = self.active_proofs(proofs) if sum_proofs(proofs) < amount: - raise Exception("balance too low.") + raise BalanceTooLowError() # coin selection for swapping, needs to include fees swap_proofs = self.coinselect(proofs, amount, include_fees=True) diff --git a/cashu/wallet/wallet_deprecated.py b/cashu/wallet/wallet_deprecated.py index a612cad..5b1a0b2 100644 --- a/cashu/wallet/wallet_deprecated.py +++ b/cashu/wallet/wallet_deprecated.py @@ -144,6 +144,8 @@ class LedgerAPIDeprecated(SupportsHttpxClient, SupportsMintURL): mint_info = GetInfoResponse( **mint_info_deprecated.dict(exclude={"parameter", "nuts", "contact"}) ) + # monkeypatch nuts + mint_info.nuts = {} return mint_info @async_set_httpx_client @@ -261,7 +263,7 @@ class LedgerAPIDeprecated(SupportsHttpxClient, SupportsMintURL): paid=False, state=MintQuoteState.unpaid.value, expiry=decoded_invoice.date + (decoded_invoice.expiry or 0), - pubkey=None + pubkey=None, ) @async_set_httpx_client diff --git a/keycloak/.env.example b/keycloak/.env.example new file mode 100644 index 0000000..d193ae2 --- /dev/null +++ b/keycloak/.env.example @@ -0,0 +1,7 @@ +POSTGRES_DB=keycloak_db +POSTGRES_USER=keycloak_db_user +POSTGRES_PASSWORD=keycloak_db_user_password +KEYCLOAK_ADMIN=admin +KEYCLOAK_ADMIN_PASSWORD=password +KC_HOSTNAME=localhost +KC_HOSTNAME_PORT=8080 diff --git a/keycloak/README.md b/keycloak/README.md new file mode 100644 index 0000000..03bf94d --- /dev/null +++ b/keycloak/README.md @@ -0,0 +1,129 @@ +## Docker compose + +This docker-compose starts a new keycloak instance. Set up the server as you wish, add realms, users etc. We will then export the data and restore an instance with the exported data. + +We will modify this file later to start the server with the backup data. + +``` +services: + postgres: + image: postgres:16.4 + volumes: + - ./postgres_data:/var/lib/postgresql/data + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + networks: + - keycloak_network + + keycloak: + image: quay.io/keycloak/keycloak:25.0.6 + command: start + environment: + KC_HOSTNAME: localhost + KC_HOSTNAME_PORT: 8080 + KC_HOSTNAME_STRICT_BACKCHANNEL: false + KC_HTTP_ENABLED: true + KC_HOSTNAME_STRICT_HTTPS: false + KC_HEALTH_ENABLED: true + KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN} + KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} + KC_DB: postgres + KC_DB_URL: jdbc:postgresql://postgres/${POSTGRES_DB} + KC_DB_USERNAME: ${POSTGRES_USER} + KC_DB_PASSWORD: ${POSTGRES_PASSWORD} + ports: + - 8080:8080 + restart: always + depends_on: + - postgres + networks: + - keycloak_network + +volumes: + postgres_data: + driver: local + +networks: + keycloak_network: + driver: bridge +``` + +## Backup + +Export realm and users from running container: + +``` +docker exec keycloak-keycloak-1 \ + /opt/keycloak/bin/kc.sh export \ + --dir /opt/keycloak/data/export \ + --users different_files \ + --http-management-port 46566 +``` + +Copy export out of the docker + +``` +docker cp keycloak-keycloak-1:/opt/keycloak/data/export ./keycloak-export +``` + +## Restore + +Use this docker-compose.yml to start keycloak with the exported backup: + +``` +services: + postgres: + image: postgres:16.4 + volumes: + - ./postgres_data:/var/lib/postgresql/data + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + networks: + - keycloak_network + + keycloak: + image: quay.io/keycloak/keycloak:25.0.6 + command: start --import-realm + volumes: + - ./keycloak-export:/opt/keycloak/data/import + environment: + KC_HOSTNAME: localhost + KC_HOSTNAME_PORT: 8080 + KC_HOSTNAME_STRICT_BACKCHANNEL: false + KC_HTTP_ENABLED: true + KC_HOSTNAME_STRICT_HTTPS: false + KC_HEALTH_ENABLED: true + KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN} + KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} + KC_DB: postgres + KC_DB_URL: jdbc:postgresql://postgres/${POSTGRES_DB} + KC_DB_USERNAME: ${POSTGRES_USER} + KC_DB_PASSWORD: ${POSTGRES_PASSWORD} + ports: + - 8080:8080 + restart: always + depends_on: + - postgres + networks: + - keycloak_network + +volumes: + postgres_data: + driver: local + +networks: + keycloak_network: + driver: bridge +``` + +Difference to first docker-compose is only the following part: + +``` + command: start --import-realm + volumes: + - ./keycloak-export:/opt/keycloak/data/import +``` diff --git a/keycloak/docker-compose.yml b/keycloak/docker-compose.yml new file mode 100644 index 0000000..6cf2bcc --- /dev/null +++ b/keycloak/docker-compose.yml @@ -0,0 +1,45 @@ +services: + postgres: + image: postgres:16.4 + volumes: + - ./postgres_data:/var/lib/postgresql/data + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + networks: + - keycloak_network + + keycloak: + image: quay.io/keycloak/keycloak:25.0.6 + command: start --import-realm + volumes: + - ./keycloak-export:/opt/keycloak/data/import + environment: + KC_HOSTNAME: ${KC_HOSTNAME} + KC_HOSTNAME_PORT: ${KC_HOSTNAME_PORT} + KC_HOSTNAME_STRICT_BACKCHANNEL: false + KC_HTTP_ENABLED: true + KC_HOSTNAME_STRICT_HTTPS: true + KC_HEALTH_ENABLED: true + KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN} + KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} + KC_DB: postgres + KC_DB_URL: jdbc:postgresql://postgres/${POSTGRES_DB} + KC_DB_USERNAME: ${POSTGRES_USER} + KC_DB_PASSWORD: ${POSTGRES_PASSWORD} + ports: + - 8080:8080 + restart: always + depends_on: + - postgres + networks: + - keycloak_network + +volumes: + postgres_data: + driver: local + +networks: + keycloak_network: + driver: bridge diff --git a/poetry.lock b/poetry.lock index 1d0da25..2ea7dee 100644 --- a/poetry.lock +++ b/poetry.lock @@ -20,24 +20,24 @@ docs = ["sphinx (==7.2.6)", "sphinx-mdinclude (==0.5.3)"] [[package]] name = "anyio" -version = "4.6.2.post1" +version = "4.8.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" files = [ - {file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"}, - {file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"}, + {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, + {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] +doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] trio = ["trio (>=0.26.1)"] [[package]] @@ -391,13 +391,13 @@ test = ["coverage (>=7)", "hypothesis", "pytest"] [[package]] name = "certifi" -version = "2024.8.30" +version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, + {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, + {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, ] [[package]] @@ -492,13 +492,13 @@ files = [ [[package]] name = "click" -version = "8.1.7" +version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [package.dependencies] @@ -583,73 +583,73 @@ files = [ [[package]] name = "coverage" -version = "7.6.8" +version = "7.6.10" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ - {file = "coverage-7.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50"}, - {file = "coverage-7.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf"}, - {file = "coverage-7.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee"}, - {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6"}, - {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d"}, - {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331"}, - {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638"}, - {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed"}, - {file = "coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e"}, - {file = "coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a"}, - {file = "coverage-7.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4"}, - {file = "coverage-7.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94"}, - {file = "coverage-7.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4"}, - {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1"}, - {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb"}, - {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8"}, - {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a"}, - {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0"}, - {file = "coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801"}, - {file = "coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9"}, - {file = "coverage-7.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee"}, - {file = "coverage-7.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a"}, - {file = "coverage-7.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d"}, - {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb"}, - {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649"}, - {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787"}, - {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c"}, - {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443"}, - {file = "coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad"}, - {file = "coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4"}, - {file = "coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb"}, - {file = "coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63"}, - {file = "coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365"}, - {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002"}, - {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3"}, - {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022"}, - {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e"}, - {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b"}, - {file = "coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146"}, - {file = "coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28"}, - {file = "coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d"}, - {file = "coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451"}, - {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764"}, - {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf"}, - {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5"}, - {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4"}, - {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83"}, - {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b"}, - {file = "coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71"}, - {file = "coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc"}, - {file = "coverage-7.6.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e"}, - {file = "coverage-7.6.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c"}, - {file = "coverage-7.6.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0"}, - {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779"}, - {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92"}, - {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4"}, - {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc"}, - {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea"}, - {file = "coverage-7.6.8-cp39-cp39-win32.whl", hash = "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e"}, - {file = "coverage-7.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076"}, - {file = "coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce"}, - {file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"}, + {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, + {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"}, + {file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"}, + {file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"}, + {file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"}, + {file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"}, + {file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"}, + {file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"}, + {file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"}, + {file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"}, + {file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"}, + {file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"}, + {file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"}, + {file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"}, + {file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"}, + {file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"}, + {file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"}, + {file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"}, + {file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"}, + {file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"}, + {file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"}, + {file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"}, + {file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"}, + {file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"}, + {file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"}, + {file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"}, ] [package.dependencies] @@ -790,13 +790,13 @@ test = ["pytest (>=6)"] [[package]] name = "fastapi" -version = "0.115.5" +version = "0.115.6" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.115.5-py3-none-any.whl", hash = "sha256:596b95adbe1474da47049e802f9a65ab2ffa9c2b07e7efee70eb8a66c9f2f796"}, - {file = "fastapi-0.115.5.tar.gz", hash = "sha256:0e7a4d0dc0d01c68df21887cce0945e72d3c48b9f4f79dfe7a7d53aa08fbb289"}, + {file = "fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305"}, + {file = "fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654"}, ] [package.dependencies] @@ -943,137 +943,137 @@ test = ["objgraph", "psutil"] [[package]] name = "grpcio" -version = "1.68.0" +version = "1.69.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio-1.68.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:619b5d0f29f4f5351440e9343224c3e19912c21aeda44e0c49d0d147a8d01544"}, - {file = "grpcio-1.68.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:a59f5822f9459bed098ffbceb2713abbf7c6fd13f2b9243461da5c338d0cd6c3"}, - {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:c03d89df516128febc5a7e760d675b478ba25802447624edf7aa13b1e7b11e2a"}, - {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44bcbebb24363d587472089b89e2ea0ab2e2b4df0e4856ba4c0b087c82412121"}, - {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79f81b7fbfb136247b70465bd836fa1733043fdee539cd6031cb499e9608a110"}, - {file = "grpcio-1.68.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:88fb2925789cfe6daa20900260ef0a1d0a61283dfb2d2fffe6194396a354c618"}, - {file = "grpcio-1.68.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:99f06232b5c9138593ae6f2e355054318717d32a9c09cdc5a2885540835067a1"}, - {file = "grpcio-1.68.0-cp310-cp310-win32.whl", hash = "sha256:a6213d2f7a22c3c30a479fb5e249b6b7e648e17f364598ff64d08a5136fe488b"}, - {file = "grpcio-1.68.0-cp310-cp310-win_amd64.whl", hash = "sha256:15327ab81131ef9b94cb9f45b5bd98803a179c7c61205c8c0ac9aff9d6c4e82a"}, - {file = "grpcio-1.68.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:3b2b559beb2d433129441783e5f42e3be40a9e1a89ec906efabf26591c5cd415"}, - {file = "grpcio-1.68.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e46541de8425a4d6829ac6c5d9b16c03c292105fe9ebf78cb1c31e8d242f9155"}, - {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c1245651f3c9ea92a2db4f95d37b7597db6b246d5892bca6ee8c0e90d76fb73c"}, - {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f1931c7aa85be0fa6cea6af388e576f3bf6baee9e5d481c586980c774debcb4"}, - {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b0ff09c81e3aded7a183bc6473639b46b6caa9c1901d6f5e2cba24b95e59e30"}, - {file = "grpcio-1.68.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8c73f9fbbaee1a132487e31585aa83987ddf626426d703ebcb9a528cf231c9b1"}, - {file = "grpcio-1.68.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6b2f98165ea2790ea159393a2246b56f580d24d7da0d0342c18a085299c40a75"}, - {file = "grpcio-1.68.0-cp311-cp311-win32.whl", hash = "sha256:e1e7ed311afb351ff0d0e583a66fcb39675be112d61e7cfd6c8269884a98afbc"}, - {file = "grpcio-1.68.0-cp311-cp311-win_amd64.whl", hash = "sha256:e0d2f68eaa0a755edd9a47d40e50dba6df2bceda66960dee1218da81a2834d27"}, - {file = "grpcio-1.68.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8af6137cc4ae8e421690d276e7627cfc726d4293f6607acf9ea7260bd8fc3d7d"}, - {file = "grpcio-1.68.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4028b8e9a3bff6f377698587d642e24bd221810c06579a18420a17688e421af7"}, - {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f60fa2adf281fd73ae3a50677572521edca34ba373a45b457b5ebe87c2d01e1d"}, - {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e18589e747c1e70b60fab6767ff99b2d0c359ea1db8a2cb524477f93cdbedf5b"}, - {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d30f3fee9372796f54d3100b31ee70972eaadcc87314be369360248a3dcffe"}, - {file = "grpcio-1.68.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7e0a3e72c0e9a1acab77bef14a73a416630b7fd2cbd893c0a873edc47c42c8cd"}, - {file = "grpcio-1.68.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a831dcc343440969aaa812004685ed322cdb526cd197112d0db303b0da1e8659"}, - {file = "grpcio-1.68.0-cp312-cp312-win32.whl", hash = "sha256:5a180328e92b9a0050958ced34dddcb86fec5a8b332f5a229e353dafc16cd332"}, - {file = "grpcio-1.68.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bddd04a790b69f7a7385f6a112f46ea0b34c4746f361ebafe9ca0be567c78e9"}, - {file = "grpcio-1.68.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:fc05759ffbd7875e0ff2bd877be1438dfe97c9312bbc558c8284a9afa1d0f40e"}, - {file = "grpcio-1.68.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:15fa1fe25d365a13bc6d52fcac0e3ee1f9baebdde2c9b3b2425f8a4979fccea1"}, - {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:32a9cb4686eb2e89d97022ecb9e1606d132f85c444354c17a7dbde4a455e4a3b"}, - {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dba037ff8d284c8e7ea9a510c8ae0f5b016004f13c3648f72411c464b67ff2fb"}, - {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0efbbd849867e0e569af09e165363ade75cf84f5229b2698d53cf22c7a4f9e21"}, - {file = "grpcio-1.68.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:4e300e6978df0b65cc2d100c54e097c10dfc7018b9bd890bbbf08022d47f766d"}, - {file = "grpcio-1.68.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:6f9c7ad1a23e1047f827385f4713b5b8c6c7d325705be1dd3e31fb00dcb2f665"}, - {file = "grpcio-1.68.0-cp313-cp313-win32.whl", hash = "sha256:3ac7f10850fd0487fcce169c3c55509101c3bde2a3b454869639df2176b60a03"}, - {file = "grpcio-1.68.0-cp313-cp313-win_amd64.whl", hash = "sha256:afbf45a62ba85a720491bfe9b2642f8761ff348006f5ef67e4622621f116b04a"}, - {file = "grpcio-1.68.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:f8f695d9576ce836eab27ba7401c60acaf9ef6cf2f70dfe5462055ba3df02cc3"}, - {file = "grpcio-1.68.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9fe1b141cda52f2ca73e17d2d3c6a9f3f3a0c255c216b50ce616e9dca7e3441d"}, - {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:4df81d78fd1646bf94ced4fb4cd0a7fe2e91608089c522ef17bc7db26e64effd"}, - {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46a2d74d4dd8993151c6cd585594c082abe74112c8e4175ddda4106f2ceb022f"}, - {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17278d977746472698460c63abf333e1d806bd41f2224f90dbe9460101c9796"}, - {file = "grpcio-1.68.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:15377bce516b1c861c35e18eaa1c280692bf563264836cece693c0f169b48829"}, - {file = "grpcio-1.68.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cc5f0a4f5904b8c25729a0498886b797feb817d1fd3812554ffa39551112c161"}, - {file = "grpcio-1.68.0-cp38-cp38-win32.whl", hash = "sha256:def1a60a111d24376e4b753db39705adbe9483ef4ca4761f825639d884d5da78"}, - {file = "grpcio-1.68.0-cp38-cp38-win_amd64.whl", hash = "sha256:55d3b52fd41ec5772a953612db4e70ae741a6d6ed640c4c89a64f017a1ac02b5"}, - {file = "grpcio-1.68.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:0d230852ba97654453d290e98d6aa61cb48fa5fafb474fb4c4298d8721809354"}, - {file = "grpcio-1.68.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:50992f214264e207e07222703c17d9cfdcc2c46ed5a1ea86843d440148ebbe10"}, - {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:14331e5c27ed3545360464a139ed279aa09db088f6e9502e95ad4bfa852bb116"}, - {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f84890b205692ea813653ece4ac9afa2139eae136e419231b0eec7c39fdbe4c2"}, - {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0cf343c6f4f6aa44863e13ec9ddfe299e0be68f87d68e777328bff785897b05"}, - {file = "grpcio-1.68.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fd2c2d47969daa0e27eadaf15c13b5e92605c5e5953d23c06d0b5239a2f176d3"}, - {file = "grpcio-1.68.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:18668e36e7f4045820f069997834e94e8275910b1f03e078a6020bd464cb2363"}, - {file = "grpcio-1.68.0-cp39-cp39-win32.whl", hash = "sha256:2af76ab7c427aaa26aa9187c3e3c42f38d3771f91a20f99657d992afada2294a"}, - {file = "grpcio-1.68.0-cp39-cp39-win_amd64.whl", hash = "sha256:e694b5928b7b33ca2d3b4d5f9bf8b5888906f181daff6b406f4938f3a997a490"}, - {file = "grpcio-1.68.0.tar.gz", hash = "sha256:7e7483d39b4a4fddb9906671e9ea21aaad4f031cdfc349fec76bdfa1e404543a"}, + {file = "grpcio-1.69.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:2060ca95a8db295ae828d0fc1c7f38fb26ccd5edf9aa51a0f44251f5da332e97"}, + {file = "grpcio-1.69.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:2e52e107261fd8fa8fa457fe44bfadb904ae869d87c1280bf60f93ecd3e79278"}, + {file = "grpcio-1.69.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:316463c0832d5fcdb5e35ff2826d9aa3f26758d29cdfb59a368c1d6c39615a11"}, + {file = "grpcio-1.69.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26c9a9c4ac917efab4704b18eed9082ed3b6ad19595f047e8173b5182fec0d5e"}, + {file = "grpcio-1.69.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90b3646ced2eae3a0599658eeccc5ba7f303bf51b82514c50715bdd2b109e5ec"}, + {file = "grpcio-1.69.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3b75aea7c6cb91b341c85e7c1d9db1e09e1dd630b0717f836be94971e015031e"}, + {file = "grpcio-1.69.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5cfd14175f9db33d4b74d63de87c64bb0ee29ce475ce3c00c01ad2a3dc2a9e51"}, + {file = "grpcio-1.69.0-cp310-cp310-win32.whl", hash = "sha256:9031069d36cb949205293cf0e243abd5e64d6c93e01b078c37921493a41b72dc"}, + {file = "grpcio-1.69.0-cp310-cp310-win_amd64.whl", hash = "sha256:cc89b6c29f3dccbe12d7a3b3f1b3999db4882ae076c1c1f6df231d55dbd767a5"}, + {file = "grpcio-1.69.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:8de1b192c29b8ce45ee26a700044717bcbbd21c697fa1124d440548964328561"}, + {file = "grpcio-1.69.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:7e76accf38808f5c5c752b0ab3fd919eb14ff8fafb8db520ad1cc12afff74de6"}, + {file = "grpcio-1.69.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:d5658c3c2660417d82db51e168b277e0ff036d0b0f859fa7576c0ffd2aec1442"}, + {file = "grpcio-1.69.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5494d0e52bf77a2f7eb17c6da662886ca0a731e56c1c85b93505bece8dc6cf4c"}, + {file = "grpcio-1.69.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ed866f9edb574fd9be71bf64c954ce1b88fc93b2a4cbf94af221e9426eb14d6"}, + {file = "grpcio-1.69.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c5ba38aeac7a2fe353615c6b4213d1fbb3a3c34f86b4aaa8be08baaaee8cc56d"}, + {file = "grpcio-1.69.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f79e05f5bbf551c4057c227d1b041ace0e78462ac8128e2ad39ec58a382536d2"}, + {file = "grpcio-1.69.0-cp311-cp311-win32.whl", hash = "sha256:bf1f8be0da3fcdb2c1e9f374f3c2d043d606d69f425cd685110dd6d0d2d61258"}, + {file = "grpcio-1.69.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb9302afc3a0e4ba0b225cd651ef8e478bf0070cf11a529175caecd5ea2474e7"}, + {file = "grpcio-1.69.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:fc18a4de8c33491ad6f70022af5c460b39611e39578a4d84de0fe92f12d5d47b"}, + {file = "grpcio-1.69.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:0f0270bd9ffbff6961fe1da487bdcd594407ad390cc7960e738725d4807b18c4"}, + {file = "grpcio-1.69.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:dc48f99cc05e0698e689b51a05933253c69a8c8559a47f605cff83801b03af0e"}, + {file = "grpcio-1.69.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e925954b18d41aeb5ae250262116d0970893b38232689c4240024e4333ac084"}, + {file = "grpcio-1.69.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d222569273720366f68a99cb62e6194681eb763ee1d3b1005840678d4884f9"}, + {file = "grpcio-1.69.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b62b0f41e6e01a3e5082000b612064c87c93a49b05f7602fe1b7aa9fd5171a1d"}, + {file = "grpcio-1.69.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:db6f9fd2578dbe37db4b2994c94a1d9c93552ed77dca80e1657bb8a05b898b55"}, + {file = "grpcio-1.69.0-cp312-cp312-win32.whl", hash = "sha256:b192b81076073ed46f4b4dd612b8897d9a1e39d4eabd822e5da7b38497ed77e1"}, + {file = "grpcio-1.69.0-cp312-cp312-win_amd64.whl", hash = "sha256:1227ff7836f7b3a4ab04e5754f1d001fa52a730685d3dc894ed8bc262cc96c01"}, + {file = "grpcio-1.69.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:a78a06911d4081a24a1761d16215a08e9b6d4d29cdbb7e427e6c7e17b06bcc5d"}, + {file = "grpcio-1.69.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:dc5a351927d605b2721cbb46158e431dd49ce66ffbacb03e709dc07a491dde35"}, + {file = "grpcio-1.69.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:3629d8a8185f5139869a6a17865d03113a260e311e78fbe313f1a71603617589"}, + {file = "grpcio-1.69.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9a281878feeb9ae26db0622a19add03922a028d4db684658f16d546601a4870"}, + {file = "grpcio-1.69.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cc614e895177ab7e4b70f154d1a7c97e152577ea101d76026d132b7aaba003b"}, + {file = "grpcio-1.69.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:1ee76cd7e2e49cf9264f6812d8c9ac1b85dda0eaea063af07292400f9191750e"}, + {file = "grpcio-1.69.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:0470fa911c503af59ec8bc4c82b371ee4303ececbbdc055f55ce48e38b20fd67"}, + {file = "grpcio-1.69.0-cp313-cp313-win32.whl", hash = "sha256:b650f34aceac8b2d08a4c8d7dc3e8a593f4d9e26d86751ebf74ebf5107d927de"}, + {file = "grpcio-1.69.0-cp313-cp313-win_amd64.whl", hash = "sha256:028337786f11fecb5d7b7fa660475a06aabf7e5e52b5ac2df47414878c0ce7ea"}, + {file = "grpcio-1.69.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:b7f693db593d6bf285e015d5538bf1c86cf9c60ed30b6f7da04a00ed052fe2f3"}, + {file = "grpcio-1.69.0-cp38-cp38-macosx_10_14_universal2.whl", hash = "sha256:8b94e83f66dbf6fd642415faca0608590bc5e8d30e2c012b31d7d1b91b1de2fd"}, + {file = "grpcio-1.69.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:b634851b92c090763dde61df0868c730376cdb73a91bcc821af56ae043b09596"}, + {file = "grpcio-1.69.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bf5f680d3ed08c15330d7830d06bc65f58ca40c9999309517fd62880d70cb06e"}, + {file = "grpcio-1.69.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:200e48a6e7b00f804cf00a1c26292a5baa96507c7749e70a3ec10ca1a288936e"}, + {file = "grpcio-1.69.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:45a4704339b6e5b24b0e136dea9ad3815a94f30eb4f1e1d44c4ac484ef11d8dd"}, + {file = "grpcio-1.69.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85d347cb8237751b23539981dbd2d9d8f6e9ff90082b427b13022b948eb6347a"}, + {file = "grpcio-1.69.0-cp38-cp38-win32.whl", hash = "sha256:60e5de105dc02832dc8f120056306d0ef80932bcf1c0e2b4ca3b676de6dc6505"}, + {file = "grpcio-1.69.0-cp38-cp38-win_amd64.whl", hash = "sha256:282f47d0928e40f25d007f24eb8fa051cb22551e3c74b8248bc9f9bea9c35fe0"}, + {file = "grpcio-1.69.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:dd034d68a2905464c49479b0c209c773737a4245d616234c79c975c7c90eca03"}, + {file = "grpcio-1.69.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:01f834732c22a130bdf3dc154d1053bdbc887eb3ccb7f3e6285cfbfc33d9d5cc"}, + {file = "grpcio-1.69.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:a7f4ed0dcf202a70fe661329f8874bc3775c14bb3911d020d07c82c766ce0eb1"}, + {file = "grpcio-1.69.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd7ea241b10bc5f0bb0f82c0d7896822b7ed122b3ab35c9851b440c1ccf81588"}, + {file = "grpcio-1.69.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f03dc9b4da4c0dc8a1db7a5420f575251d7319b7a839004d8916257ddbe4816"}, + {file = "grpcio-1.69.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ca71d73a270dff052fe4edf74fef142d6ddd1f84175d9ac4a14b7280572ac519"}, + {file = "grpcio-1.69.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ccbed100dc43704e94ccff9e07680b540d64e4cc89213ab2832b51b4f68a520"}, + {file = "grpcio-1.69.0-cp39-cp39-win32.whl", hash = "sha256:1514341def9c6ec4b7f0b9628be95f620f9d4b99331b7ef0a1845fd33d9b579c"}, + {file = "grpcio-1.69.0-cp39-cp39-win_amd64.whl", hash = "sha256:c1fea55d26d647346acb0069b08dca70984101f2dc95066e003019207212e303"}, + {file = "grpcio-1.69.0.tar.gz", hash = "sha256:936fa44241b5379c5afc344e1260d467bee495747eaf478de825bab2791da6f5"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.68.0)"] +protobuf = ["grpcio-tools (>=1.69.0)"] [[package]] name = "grpcio-tools" -version = "1.68.0" +version = "1.69.0" description = "Protobuf code generator for gRPC" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio_tools-1.68.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:9509a5c3ed3d54fa7ac20748d501cb86668f764605a0a68f275339ee0f1dc1a6"}, - {file = "grpcio_tools-1.68.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:59a885091bf29700ba0e14a954d156a18714caaa2006a7f328b18e1ac4b1e721"}, - {file = "grpcio_tools-1.68.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:d3e678162e1d7a8720dc05fdd537fc8df082a50831791f7bb1c6f90095f8368b"}, - {file = "grpcio_tools-1.68.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10d03e3ad4af6284fd27cb14f5a3d52045913c1253e3e24a384ed91bc8adbfcd"}, - {file = "grpcio_tools-1.68.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1769d7f529de1cc102f7fb900611e3c0b69bdb244fca1075b24d6e5b49024586"}, - {file = "grpcio_tools-1.68.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:88640d95ee41921ac7352fa5fadca52a06d7e21fbe53e6a706a9a494f756be7d"}, - {file = "grpcio_tools-1.68.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e903d07bc65232aa9e7704c829aec263e1e139442608e473d7912417a9908e29"}, - {file = "grpcio_tools-1.68.0-cp310-cp310-win32.whl", hash = "sha256:66b70b37184d40806844f51c2757c6b852511d4ea46a3bf2c7e931a47b455bc6"}, - {file = "grpcio_tools-1.68.0-cp310-cp310-win_amd64.whl", hash = "sha256:b47ae076ffb29a68e517bc03552bef0d9c973f8e18adadff180b123e973a26ea"}, - {file = "grpcio_tools-1.68.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:f65942fab440e99113ce14436deace7554d5aa554ea18358e3a5f3fc47efe322"}, - {file = "grpcio_tools-1.68.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8fefc6d000e169a97336feded23ce614df3fb9926fc48c7a9ff8ea459d93b5b0"}, - {file = "grpcio_tools-1.68.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:6dd69c9f3ff85eee8d1f71adf7023c638ca8d465633244ac1b7f19bc3668612d"}, - {file = "grpcio_tools-1.68.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7dc5195dc02057668cc22da1ff1aea1811f6fa0deb801b3194dec1fe0bab1cf0"}, - {file = "grpcio_tools-1.68.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:849b12bec2320e49e988df104c92217d533e01febac172a4495caab36d9f0edc"}, - {file = "grpcio_tools-1.68.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:766c2cd2e365e0fc0e559af56f2c2d144d95fd7cb8668a34d533e66d6435eb34"}, - {file = "grpcio_tools-1.68.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2ec3a2e0afa4866ccc5ba33c071aebaa619245dfdd840cbb74f2b0591868d085"}, - {file = "grpcio_tools-1.68.0-cp311-cp311-win32.whl", hash = "sha256:80b733014eb40d920d836d782e5cdea0dcc90d251a2ffb35ab378ef4f8a42c14"}, - {file = "grpcio_tools-1.68.0-cp311-cp311-win_amd64.whl", hash = "sha256:f95103e3e4e7fee7c6123bc9e4e925e07ad24d8d09d7c1c916fb6c8d1cb9e726"}, - {file = "grpcio_tools-1.68.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:dd9a654af8536b3de8525bff72a245fef62d572eabf96ac946fe850e707cb27d"}, - {file = "grpcio_tools-1.68.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0f77957e3a0916a0dd18d57ce6b49d95fc9a5cfed92310f226339c0fda5394f6"}, - {file = "grpcio_tools-1.68.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:92a09afe64fe26696595de2036e10967876d26b12c894cc9160f00152cacebe7"}, - {file = "grpcio_tools-1.68.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28ebdbad2ef16699d07400b65260240851049a75502eff69a59b127d3ab960f1"}, - {file = "grpcio_tools-1.68.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d3150d784d8050b10dcf5eb06e04fb90747a1547fed3a062a608d940fe57066"}, - {file = "grpcio_tools-1.68.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:261d98fd635595de42aadee848f9af46da6654d63791c888891e94f66c5d0682"}, - {file = "grpcio_tools-1.68.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:061345c0079b9471f32230186ab01acb908ea0e577bc1699a8cf47acef8be4af"}, - {file = "grpcio_tools-1.68.0-cp312-cp312-win32.whl", hash = "sha256:533ce6791a5ba21e35d74c6c25caf4776f5692785a170c01ea1153783ad5af31"}, - {file = "grpcio_tools-1.68.0-cp312-cp312-win_amd64.whl", hash = "sha256:56842a0ce74b4b92eb62cd5ee00181b2d3acc58ba0c4fd20d15a5db51f891ba6"}, - {file = "grpcio_tools-1.68.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:1117a81592542f0c36575082daa6413c57ca39188b18a4c50ec7332616f4b97e"}, - {file = "grpcio_tools-1.68.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:51e5a090849b30c99a2396d42140b8a3e558eff6cdfa12603f9582e2cd07724e"}, - {file = "grpcio_tools-1.68.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:4fe611d89a1836df8936f066d39c7eb03d4241806449ec45d4b8e1c843ae8011"}, - {file = "grpcio_tools-1.68.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c10f3faa0cc4d89eb546f53b623837af23e86dc495d3b89510bcc0e0a6c0b8b2"}, - {file = "grpcio_tools-1.68.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46b537480b8fd2195d988120a28467601a2a3de2e504043b89fb90318e1eb754"}, - {file = "grpcio_tools-1.68.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:17d0c9004ea82b4213955a585401e80c30d4b37a1d4ace32ccdea8db4d3b7d43"}, - {file = "grpcio_tools-1.68.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:2919faae04fe47bad57fc9b578aeaab527da260e851f321a253b6b11862254a8"}, - {file = "grpcio_tools-1.68.0-cp313-cp313-win32.whl", hash = "sha256:ee86157ef899f58ba2fe1055cce0d33bd703e99aa6d5a0895581ac3969f06bfa"}, - {file = "grpcio_tools-1.68.0-cp313-cp313-win_amd64.whl", hash = "sha256:d0470ffc6a93c86cdda48edd428d22e2fef17d854788d60d0d5f291038873157"}, - {file = "grpcio_tools-1.68.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:795f2cd76f68a12b0b5541b98187ba367dd69b49d359cf98b781ead742961370"}, - {file = "grpcio_tools-1.68.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:57e29e78c33fb1b1d557fbe7650d722d1f2b0a9f53ea73beb8ea47e627b6000b"}, - {file = "grpcio_tools-1.68.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:700f171cd3293ee8d50cd43171562ff07b14fa8e49ee471cd91c6924c7da8644"}, - {file = "grpcio_tools-1.68.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:196cd8a3a5963a4c9e424314df9eb573b305e6f958fe6508d26580ce01e7aa56"}, - {file = "grpcio_tools-1.68.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cad40c3164ee9cef62524dea509449ea581b17ea493178beef051bf79b5103ca"}, - {file = "grpcio_tools-1.68.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ab93fab49fa1e699e577ff5fbb99aba660164d710d4c33cfe0aa9d06f585539f"}, - {file = "grpcio_tools-1.68.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:511224a99726eb84db9ddb84dc8a75377c3eae797d835f99e80128ec618376d5"}, - {file = "grpcio_tools-1.68.0-cp38-cp38-win32.whl", hash = "sha256:b4ca81770cd729a9ea536d871aacedbde2b732bb9bb83c9d993d63f58502153d"}, - {file = "grpcio_tools-1.68.0-cp38-cp38-win_amd64.whl", hash = "sha256:6950725bf7a496f81d3ec3324334ffc9dbec743b510dd0e897f51f8627eeb6ac"}, - {file = "grpcio_tools-1.68.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:01ace351a51d7ee120963a4612b1f00e964462ec548db20d17f8902e238592c8"}, - {file = "grpcio_tools-1.68.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5afd2f3f7257b52228a7808a2b4a765893d4d802d7a2377d9284853e67d045c6"}, - {file = "grpcio_tools-1.68.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:453ee3193d59c974c678d91f08786f43c25ef753651b0825dc3d008c31baf68d"}, - {file = "grpcio_tools-1.68.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094b22919b786ad73c20372ef5e546330e7cd2c6dc12293b7ed586975f35d38"}, - {file = "grpcio_tools-1.68.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26335eea976dfc1ff5d90b19c309a9425bd53868112a0507ad20f297f2c21d3e"}, - {file = "grpcio_tools-1.68.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c77ecc5164bb413a613bdac9091dcc29d26834a2ac42fcd1afdfcda9e3003e68"}, - {file = "grpcio_tools-1.68.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e31be6dc61496a59c1079b0a669f93dfcc2cdc4b1dbdc4374247cd09cee1329b"}, - {file = "grpcio_tools-1.68.0-cp39-cp39-win32.whl", hash = "sha256:3aa40958355920ae2846c6fb5cadac4f2c8e33234a2982fef8101da0990e3968"}, - {file = "grpcio_tools-1.68.0-cp39-cp39-win_amd64.whl", hash = "sha256:19bafb80948eda979b1b3a63c1567162d06249f43068a0e46a028a448e6f72d4"}, - {file = "grpcio_tools-1.68.0.tar.gz", hash = "sha256:737804ec2225dd4cc27e633b4ca0e963b0795161bf678285fab6586e917fd867"}, + {file = "grpcio_tools-1.69.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:8c210630faa581c3bd08953dac4ad21a7f49862f3b92d69686e9b436d2f1265d"}, + {file = "grpcio_tools-1.69.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:09b66ea279fcdaebae4ec34b1baf7577af3b14322738aa980c1c33cfea71f7d7"}, + {file = "grpcio_tools-1.69.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:be94a4bfa56d356aae242cc54072c9ccc2704b659eaae2fd599a94afebf791ce"}, + {file = "grpcio_tools-1.69.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28778debad73a8c8e0a0e07e6a2f76eecce43adbc205d17dd244d2d58bb0f0aa"}, + {file = "grpcio_tools-1.69.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:449308d93e4c97ae3a4503510c6d64978748ff5e21429c85da14fdc783c0f498"}, + {file = "grpcio_tools-1.69.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b9343651e73bc6e0df6bb518c2638bf9cc2194b50d060cdbcf1b2121cd4e4ae3"}, + {file = "grpcio_tools-1.69.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2f08b063612553e726e328aef3a27adfaea8d92712b229012afc54d59da88a02"}, + {file = "grpcio_tools-1.69.0-cp310-cp310-win32.whl", hash = "sha256:599ffd39525e7bbb6412a63e56a2e6c1af8f3493fe4305260efd4a11d064cce0"}, + {file = "grpcio_tools-1.69.0-cp310-cp310-win_amd64.whl", hash = "sha256:02f92e3c2bae67ece818787f8d3d89df0fa1e5e6bbb7c1493824fd5dfad886dd"}, + {file = "grpcio_tools-1.69.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:c18df5d1c8e163a29863583ec51237d08d7059ef8d4f7661ee6d6363d3e38fe3"}, + {file = "grpcio_tools-1.69.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:37876ae49235ef2e61e5059faf45dc5e7142ca54ae61aec378bb9483e0cd7e95"}, + {file = "grpcio_tools-1.69.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:33120920e29959eaa37a1268c6a22af243d086b1a5e5222b4203e29560ece9ce"}, + {file = "grpcio_tools-1.69.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:788bb3ecd1b44664d829d319b3c1ebc15c7d7b5e7d1f22706ab57d6acd2c6301"}, + {file = "grpcio_tools-1.69.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f453b11a112e3774c8957ec2570669f3da1f7fbc8ee242482c38981496e88da2"}, + {file = "grpcio_tools-1.69.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7e5c5dc2b656755cb58b11a7e87b65258a4a8eaff01b6c30ffcb230dd447c03d"}, + {file = "grpcio_tools-1.69.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8eabf0a7a98c14322bc74f9910c96f98feebe311e085624b2d022924d4f652ca"}, + {file = "grpcio_tools-1.69.0-cp311-cp311-win32.whl", hash = "sha256:ad567bea43d018c2215e1db10316eda94ca19229a834a3221c15d132d24c1b8a"}, + {file = "grpcio_tools-1.69.0-cp311-cp311-win_amd64.whl", hash = "sha256:3d64e801586dbea3530f245d48b9ed031738cc3eb099d5ce2fdb1b3dc2e1fb20"}, + {file = "grpcio_tools-1.69.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8ef8efe8beac4cc1e30d41893e4096ca2601da61001897bd17441645de2d4d3c"}, + {file = "grpcio_tools-1.69.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:a00e87a0c5a294028115a098819899b08dd18449df5b2aac4a2b87ba865e8681"}, + {file = "grpcio_tools-1.69.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:7722700346d5b223159532e046e51f2ff743ed4342e5fe3e0457120a4199015e"}, + {file = "grpcio_tools-1.69.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a934116fdf202cb675246056ee54645c743e2240632f86a37e52f91a405c7143"}, + {file = "grpcio_tools-1.69.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e6a6d44359ca836acfbc58103daf94b3bb8ac919d659bb348dcd7fbecedc293"}, + {file = "grpcio_tools-1.69.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e27662c0597fd1ab5399a583d358b5203edcb6fc2b29d6245099dfacd51a6ddc"}, + {file = "grpcio_tools-1.69.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7bbb2b2fb81d95bcdd1d8331defb5f5dc256dbe423bb98b682cf129cdd432366"}, + {file = "grpcio_tools-1.69.0-cp312-cp312-win32.whl", hash = "sha256:e11accd10cf4af5031ac86c45f1a13fb08f55e005cea070917c12e78fe6d2aa2"}, + {file = "grpcio_tools-1.69.0-cp312-cp312-win_amd64.whl", hash = "sha256:6df4c6ac109af338a8ccde29d184e0b0bdab13d78490cb360ff9b192a1aec7e2"}, + {file = "grpcio_tools-1.69.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:8c320c4faa1431f2e1252ef2325a970ac23b2fd04ffef6c12f96dd4552c3445c"}, + {file = "grpcio_tools-1.69.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:5f1224596ad74dd14444b20c37122b361c5d203b67e14e018b995f3c5d76eede"}, + {file = "grpcio_tools-1.69.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:965a0cf656a113bc32d15ac92ca51ed702a75d5370ae0afbdd36f818533a708a"}, + {file = "grpcio_tools-1.69.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:978835768c11a7f28778b3b7c40f839d8a57f765c315e80c4246c23900d56149"}, + {file = "grpcio_tools-1.69.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:094c7cec9bd271a32dfb7c620d4a558c63fcb0122fd1651b9ed73d6afd4ae6fe"}, + {file = "grpcio_tools-1.69.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:b51bf4981b3d7e47c2569efadff08284787124eb3dea0f63f491d39703231d3c"}, + {file = "grpcio_tools-1.69.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ea7aaf0dc1a828e2133357a9e9553fd1bb4e766890d52a506cc132e40632acdc"}, + {file = "grpcio_tools-1.69.0-cp313-cp313-win32.whl", hash = "sha256:4320f11b79d3a148cc23bad1b81719ce1197808dc2406caa8a8ba0a5cfb0260d"}, + {file = "grpcio_tools-1.69.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9bae733654e0eb8ca83aa1d0d6b6c2f4a3525ce70d5ffc07df68d28f6520137"}, + {file = "grpcio_tools-1.69.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:c78d3a7d9ba4292ba7abcc43430df426fc805e79a1dcd147509af0668332885b"}, + {file = "grpcio_tools-1.69.0-cp38-cp38-macosx_10_14_universal2.whl", hash = "sha256:497bdaa996a4de70f643c008a08813b4d20e114de50a384ae5e29d849c24c9c8"}, + {file = "grpcio_tools-1.69.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:aea33dd5a07a3b250b02a1b3f435e86d4abc94936b3ce634a2d70bc224189495"}, + {file = "grpcio_tools-1.69.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d3101c8d6f890f9d978e400843cc29992c5e03ae74f359e73dade09f2469a08"}, + {file = "grpcio_tools-1.69.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1163ba3f829141206dce1ceb67cfca73b57d279cd7183f188276443700a4980e"}, + {file = "grpcio_tools-1.69.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a85785058c31bac3d0b26c158b576eed536e4ce1af72c1d05a3518e745d44aac"}, + {file = "grpcio_tools-1.69.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ee934bbe8aa8035eea2711c12a6e537ab4c4a35a6d742ccf34bfa3a0492f412"}, + {file = "grpcio_tools-1.69.0-cp38-cp38-win32.whl", hash = "sha256:808d1b963bda8ca3c9f55cb8aa051ed2f2c98cc1fb89f79b4f67e8218580f8f3"}, + {file = "grpcio_tools-1.69.0-cp38-cp38-win_amd64.whl", hash = "sha256:afa8cd6b93e4f607c3750a976a96f874830ec7dc5f408e0fac270d0464147024"}, + {file = "grpcio_tools-1.69.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:01121b6570932bfb7d8b2ce2c0055dba902a415477079e249d85fe4494f72db2"}, + {file = "grpcio_tools-1.69.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:9861e282aa7b3656c67e84d0c25ee0e9210b955e0ec2c64699b8f80483f90853"}, + {file = "grpcio_tools-1.69.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:00adf628259e8c314a02ca1580d6a8b14eeef266f5dd5e15bf92c1efbbcf63c0"}, + {file = "grpcio_tools-1.69.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:371d03ac31b76ba77d44bdba6a8560f344c6d1ed558babab64760da085e392b7"}, + {file = "grpcio_tools-1.69.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6730414c01fe9027ba12538fd6e192e1bea94d5b819a1e03d15e89aab1b4573"}, + {file = "grpcio_tools-1.69.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5562a1b1b67deffd04fbb1bcf8f1634580538ce35895b77cdfaec1fb115efd95"}, + {file = "grpcio_tools-1.69.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f8996efddc867134f22bbf8a368b1b2a018d0a9b0ac9d3185cfd81d1abd8066"}, + {file = "grpcio_tools-1.69.0-cp39-cp39-win32.whl", hash = "sha256:8f5959d8a453d613e7137831f6885b43b5c378ec317943b4ec599046baa97bfc"}, + {file = "grpcio_tools-1.69.0-cp39-cp39-win_amd64.whl", hash = "sha256:5d47abf7e0662dd5dbb9cc252c3616e5fbc5f71d34e3f6332cd24bcdf2940abd"}, + {file = "grpcio_tools-1.69.0.tar.gz", hash = "sha256:3e1a98f4d9decb84979e1ddd3deb09c0a33a84b6e3c0776d5bde4097e3ab66dd"}, ] [package.dependencies] -grpcio = ">=1.68.0" +grpcio = ">=1.69.0" protobuf = ">=5.26.1,<6.0dev" setuptools = "*" @@ -1136,13 +1136,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "identify" -version = "2.6.2" +version = "2.6.5" description = "File identification library for Python" optional = false python-versions = ">=3.9" files = [ - {file = "identify-2.6.2-py2.py3-none-any.whl", hash = "sha256:c097384259f49e372f4ea00a19719d95ae27dd5ff0fd77ad630aa891306b82f3"}, - {file = "identify-2.6.2.tar.gz", hash = "sha256:fab5c716c24d7a789775228823797296a2994b075fb6080ac83a102772a98cbd"}, + {file = "identify-2.6.5-py2.py3-none-any.whl", hash = "sha256:14181a47091eb75b337af4c23078c9d09225cd4c48929f521f3bf16b09d02566"}, + {file = "identify-2.6.5.tar.gz", hash = "sha256:c10b33f250e5bba374fae86fb57f3adcebf1161bce7cdf92031915fd480c13bc"}, ] [package.extras] @@ -1181,25 +1181,6 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.link perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] -[[package]] -name = "importlib-resources" -version = "6.4.5" -description = "Read resources from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, - {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] -type = ["pytest-mypy"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -1211,20 +1192,36 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "jinja2" +version = "3.1.5" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, + {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + [[package]] name = "limits" -version = "3.13.0" +version = "4.0.1" description = "Rate limiting utilities" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "limits-3.13.0-py3-none-any.whl", hash = "sha256:9767f7233da4255e9904b79908a728e8ec0984c0b086058b4cbbd309aea553f6"}, - {file = "limits-3.13.0.tar.gz", hash = "sha256:6571b0c567bfa175a35fed9f8a954c0c92f1c3200804282f1b8f1de4ad98a953"}, + {file = "limits-4.0.1-py3-none-any.whl", hash = "sha256:67667e669f570cf7be4e2c2bc52f763b3f93bdf66ea945584360bc1a3f251901"}, + {file = "limits-4.0.1.tar.gz", hash = "sha256:a54f5c058dfc965319ae3ee78faf222294659e371b46d22cd7456761f7e46d5a"}, ] [package.dependencies] deprecated = ">=1.2" -importlib-resources = ">=1.3" packaging = ">=21,<25" typing-extensions = "*" @@ -1242,13 +1239,13 @@ rediscluster = ["redis (>=4.2.0,!=4.5.2,!=4.5.3)"] [[package]] name = "loguru" -version = "0.7.2" +version = "0.7.3" description = "Python logging made (stupidly) simple" optional = false -python-versions = ">=3.5" +python-versions = "<4.0,>=3.5" files = [ - {file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"}, - {file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"}, + {file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"}, + {file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"}, ] [package.dependencies] @@ -1256,17 +1253,87 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] -dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"] +dev = ["Sphinx (==8.1.3)", "build (==1.2.2)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.5.0)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.13.0)", "mypy (==v1.4.1)", "myst-parser (==4.0.0)", "pre-commit (==4.0.1)", "pytest (==6.1.2)", "pytest (==8.3.2)", "pytest-cov (==2.12.1)", "pytest-cov (==5.0.0)", "pytest-cov (==6.0.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.1.0)", "sphinx-rtd-theme (==3.0.2)", "tox (==3.27.1)", "tox (==4.23.2)", "twine (==6.0.1)"] + +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] [[package]] name = "marshmallow" -version = "3.23.1" +version = "3.25.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.9" files = [ - {file = "marshmallow-3.23.1-py3-none-any.whl", hash = "sha256:fece2eb2c941180ea1b7fcbd4a83c51bfdd50093fdd3ad2585ee5e1df2508491"}, - {file = "marshmallow-3.23.1.tar.gz", hash = "sha256:3a8dfda6edd8dcdbf216c0ede1d1e78d230a6dc9c5a088f58c4083b974a0d468"}, + {file = "marshmallow-3.25.1-py3-none-any.whl", hash = "sha256:ec5d00d873ce473b7f2ffcb7104286a376c354cab0c2fa12f5573dab03e87210"}, + {file = "marshmallow-3.25.1.tar.gz", hash = "sha256:f4debda3bb11153d81ac34b0d582bf23053055ee11e791b54b4b35493468040a"}, ] [package.dependencies] @@ -1274,7 +1341,7 @@ packaging = ">=17.0" [package.extras] dev = ["marshmallow[tests]", "pre-commit (>=3.5,<5.0)", "tox"] -docs = ["alabaster (==1.0.0)", "autodocsumm (==0.2.14)", "sphinx (==8.1.3)", "sphinx-issues (==5.0.0)", "sphinx-version-warning (==1.1.2)"] +docs = ["autodocsumm (==0.2.14)", "furo (==2024.8.6)", "sphinx (==8.1.3)", "sphinx-copybutton (==0.5.2)", "sphinx-issues (==5.0.0)", "sphinxext-opengraph (==0.9.1)"] tests = ["pytest", "simplejson"] [[package]] @@ -1290,49 +1357,55 @@ files = [ [[package]] name = "mypy" -version = "1.13.0" +version = "1.14.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, - {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, - {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, - {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, - {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, - {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, - {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, - {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, - {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, - {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, - {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, - {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, - {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, - {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, - {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, - {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, - {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, - {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, - {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, - {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, - {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, - {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, + {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, + {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, + {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"}, + {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"}, + {file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"}, + {file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"}, + {file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"}, + {file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"}, + {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"}, + {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"}, + {file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"}, + {file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"}, + {file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"}, + {file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"}, + {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"}, + {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"}, + {file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"}, + {file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"}, + {file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"}, + {file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"}, + {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"}, + {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"}, + {file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"}, + {file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"}, + {file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"}, + {file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"}, + {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"}, + {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"}, + {file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"}, + {file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"}, + {file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"}, + {file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"}, + {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"}, + {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"}, + {file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"}, + {file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"}, + {file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"}, + {file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"}, ] [package.dependencies] -mypy-extensions = ">=1.0.0" +mypy_extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.6.0" +typing_extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] @@ -1440,22 +1513,22 @@ virtualenv = ">=20.10.0" [[package]] name = "protobuf" -version = "5.28.3" +version = "5.29.3" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-5.28.3-cp310-abi3-win32.whl", hash = "sha256:0c4eec6f987338617072592b97943fdbe30d019c56126493111cf24344c1cc24"}, - {file = "protobuf-5.28.3-cp310-abi3-win_amd64.whl", hash = "sha256:91fba8f445723fcf400fdbe9ca796b19d3b1242cd873907979b9ed71e4afe868"}, - {file = "protobuf-5.28.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a3f6857551e53ce35e60b403b8a27b0295f7d6eb63d10484f12bc6879c715687"}, - {file = "protobuf-5.28.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:3fa2de6b8b29d12c61911505d893afe7320ce7ccba4df913e2971461fa36d584"}, - {file = "protobuf-5.28.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:712319fbdddb46f21abb66cd33cb9e491a5763b2febd8f228251add221981135"}, - {file = "protobuf-5.28.3-cp38-cp38-win32.whl", hash = "sha256:3e6101d095dfd119513cde7259aa703d16c6bbdfae2554dfe5cfdbe94e32d548"}, - {file = "protobuf-5.28.3-cp38-cp38-win_amd64.whl", hash = "sha256:27b246b3723692bf1068d5734ddaf2fccc2cdd6e0c9b47fe099244d80200593b"}, - {file = "protobuf-5.28.3-cp39-cp39-win32.whl", hash = "sha256:135658402f71bbd49500322c0f736145731b16fc79dc8f367ab544a17eab4535"}, - {file = "protobuf-5.28.3-cp39-cp39-win_amd64.whl", hash = "sha256:70585a70fc2dd4818c51287ceef5bdba6387f88a578c86d47bb34669b5552c36"}, - {file = "protobuf-5.28.3-py3-none-any.whl", hash = "sha256:cee1757663fa32a1ee673434fcf3bf24dd54763c79690201208bafec62f19eed"}, - {file = "protobuf-5.28.3.tar.gz", hash = "sha256:64badbc49180a5e401f373f9ce7ab1d18b63f7dd4a9cdc43c92b9f0b481cef7b"}, + {file = "protobuf-5.29.3-cp310-abi3-win32.whl", hash = "sha256:3ea51771449e1035f26069c4c7fd51fba990d07bc55ba80701c78f886bf9c888"}, + {file = "protobuf-5.29.3-cp310-abi3-win_amd64.whl", hash = "sha256:a4fa6f80816a9a0678429e84973f2f98cbc218cca434abe8db2ad0bffc98503a"}, + {file = "protobuf-5.29.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8434404bbf139aa9e1300dbf989667a83d42ddda9153d8ab76e0d5dcaca484e"}, + {file = "protobuf-5.29.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:daaf63f70f25e8689c072cfad4334ca0ac1d1e05a92fc15c54eb9cf23c3efd84"}, + {file = "protobuf-5.29.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:c027e08a08be10b67c06bf2370b99c811c466398c357e615ca88c91c07f0910f"}, + {file = "protobuf-5.29.3-cp38-cp38-win32.whl", hash = "sha256:84a57163a0ccef3f96e4b6a20516cedcf5bb3a95a657131c5c3ac62200d23252"}, + {file = "protobuf-5.29.3-cp38-cp38-win_amd64.whl", hash = "sha256:b89c115d877892a512f79a8114564fb435943b59067615894c3b13cd3e1fa107"}, + {file = "protobuf-5.29.3-cp39-cp39-win32.whl", hash = "sha256:0eb32bfa5219fc8d4111803e9a690658aa2e6366384fd0851064b963b6d1f2a7"}, + {file = "protobuf-5.29.3-cp39-cp39-win_amd64.whl", hash = "sha256:6ce8cc3389a20693bfde6c6562e03474c40851b44975c9b2bf6df7d8c4f864da"}, + {file = "protobuf-5.29.3-py3-none-any.whl", hash = "sha256:0a18ed4a24198528f2333802eb075e59dea9d679ab7a6c5efb017a59004d849f"}, + {file = "protobuf-5.29.3.tar.gz", hash = "sha256:5da0f41edaf117bde316404bad1a486cb4ededf8e4a54891296f648e8e076620"}, ] [[package]] @@ -1512,54 +1585,61 @@ files = [ [[package]] name = "pydantic" -version = "1.10.19" +version = "1.10.21" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a415b9e95fa602b10808113967f72b2da8722061265d6af69268c111c254832d"}, - {file = "pydantic-1.10.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:11965f421f7eb026439d4eb7464e9182fe6d69c3d4d416e464a4485d1ba61ab6"}, - {file = "pydantic-1.10.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5bb81fcfc6d5bff62cd786cbd87480a11d23f16d5376ad2e057c02b3b44df96"}, - {file = "pydantic-1.10.19-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83ee8c9916689f8e6e7d90161e6663ac876be2efd32f61fdcfa3a15e87d4e413"}, - {file = "pydantic-1.10.19-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0399094464ae7f28482de22383e667625e38e1516d6b213176df1acdd0c477ea"}, - {file = "pydantic-1.10.19-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8b2cf5e26da84f2d2dee3f60a3f1782adedcee785567a19b68d0af7e1534bd1f"}, - {file = "pydantic-1.10.19-cp310-cp310-win_amd64.whl", hash = "sha256:1fc8cc264afaf47ae6a9bcbd36c018d0c6b89293835d7fb0e5e1a95898062d59"}, - {file = "pydantic-1.10.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d7a8a1dd68bac29f08f0a3147de1885f4dccec35d4ea926e6e637fac03cdb4b3"}, - {file = "pydantic-1.10.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07d00ca5ef0de65dd274005433ce2bb623730271d495a7d190a91c19c5679d34"}, - {file = "pydantic-1.10.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad57004e5d73aee36f1e25e4e73a4bc853b473a1c30f652dc8d86b0a987ffce3"}, - {file = "pydantic-1.10.19-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dce355fe7ae53e3090f7f5fa242423c3a7b53260747aa398b4b3aaf8b25f41c3"}, - {file = "pydantic-1.10.19-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0d32227ea9a3bf537a2273fd2fdb6d64ab4d9b83acd9e4e09310a777baaabb98"}, - {file = "pydantic-1.10.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e351df83d1c9cffa53d4e779009a093be70f1d5c6bb7068584086f6a19042526"}, - {file = "pydantic-1.10.19-cp311-cp311-win_amd64.whl", hash = "sha256:d8d72553d2f3f57ce547de4fa7dc8e3859927784ab2c88343f1fc1360ff17a08"}, - {file = "pydantic-1.10.19-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d5b5b7c6bafaef90cbb7dafcb225b763edd71d9e22489647ee7df49d6d341890"}, - {file = "pydantic-1.10.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:570ad0aeaf98b5e33ff41af75aba2ef6604ee25ce0431ecd734a28e74a208555"}, - {file = "pydantic-1.10.19-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0890fbd7fec9e151c7512941243d830b2d6076d5df159a2030952d480ab80a4e"}, - {file = "pydantic-1.10.19-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec5c44e6e9eac5128a9bfd21610df3b8c6b17343285cc185105686888dc81206"}, - {file = "pydantic-1.10.19-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6eb56074b11a696e0b66c7181da682e88c00e5cebe6570af8013fcae5e63e186"}, - {file = "pydantic-1.10.19-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9d7d48fbc5289efd23982a0d68e973a1f37d49064ccd36d86de4543aff21e086"}, - {file = "pydantic-1.10.19-cp312-cp312-win_amd64.whl", hash = "sha256:fd34012691fbd4e67bdf4accb1f0682342101015b78327eaae3543583fcd451e"}, - {file = "pydantic-1.10.19-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a5d5b877c7d3d9e17399571a8ab042081d22fe6904416a8b20f8af5909e6c8f"}, - {file = "pydantic-1.10.19-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c46f58ef2df958ed2ea7437a8be0897d5efe9ee480818405338c7da88186fb3"}, - {file = "pydantic-1.10.19-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d8a38a44bb6a15810084316ed69c854a7c06e0c99c5429f1d664ad52cec353c"}, - {file = "pydantic-1.10.19-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a82746c6d6e91ca17e75f7f333ed41d70fce93af520a8437821dec3ee52dfb10"}, - {file = "pydantic-1.10.19-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:566bebdbe6bc0ac593fa0f67d62febbad9f8be5433f686dc56401ba4aab034e3"}, - {file = "pydantic-1.10.19-cp37-cp37m-win_amd64.whl", hash = "sha256:22a1794e01591884741be56c6fba157c4e99dcc9244beb5a87bd4aa54b84ea8b"}, - {file = "pydantic-1.10.19-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:076c49e24b73d346c45f9282d00dbfc16eef7ae27c970583d499f11110d9e5b0"}, - {file = "pydantic-1.10.19-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d4320510682d5a6c88766b2a286d03b87bd3562bf8d78c73d63bab04b21e7b4"}, - {file = "pydantic-1.10.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e66aa0fa7f8aa9d0a620361834f6eb60d01d3e9cea23ca1a92cda99e6f61dac"}, - {file = "pydantic-1.10.19-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d216f8d0484d88ab72ab45d699ac669fe031275e3fa6553e3804e69485449fa0"}, - {file = "pydantic-1.10.19-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9f28a81978e936136c44e6a70c65bde7548d87f3807260f73aeffbf76fb94c2f"}, - {file = "pydantic-1.10.19-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d3449633c207ec3d2d672eedb3edbe753e29bd4e22d2e42a37a2c1406564c20f"}, - {file = "pydantic-1.10.19-cp38-cp38-win_amd64.whl", hash = "sha256:7ea24e8614f541d69ea72759ff635df0e612b7dc9d264d43f51364df310081a3"}, - {file = "pydantic-1.10.19-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:573254d844f3e64093f72fcd922561d9c5696821ff0900a0db989d8c06ab0c25"}, - {file = "pydantic-1.10.19-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff09600cebe957ecbb4a27496fe34c1d449e7957ed20a202d5029a71a8af2e35"}, - {file = "pydantic-1.10.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4739c206bfb6bb2bdc78dcd40bfcebb2361add4ceac6d170e741bb914e9eff0f"}, - {file = "pydantic-1.10.19-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bfb5b378b78229119d66ced6adac2e933c67a0aa1d0a7adffbe432f3ec14ce4"}, - {file = "pydantic-1.10.19-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7f31742c95e3f9443b8c6fa07c119623e61d76603be9c0d390bcf7e888acabcb"}, - {file = "pydantic-1.10.19-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c6444368b651a14c2ce2fb22145e1496f7ab23cbdb978590d47c8d34a7bc0289"}, - {file = "pydantic-1.10.19-cp39-cp39-win_amd64.whl", hash = "sha256:945407f4d08cd12485757a281fca0e5b41408606228612f421aa4ea1b63a095d"}, - {file = "pydantic-1.10.19-py3-none-any.whl", hash = "sha256:2206a1752d9fac011e95ca83926a269fb0ef5536f7e053966d058316e24d929f"}, - {file = "pydantic-1.10.19.tar.gz", hash = "sha256:fea36c2065b7a1d28c6819cc2e93387b43dd5d3cf5a1e82d8132ee23f36d1f10"}, + {file = "pydantic-1.10.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:245e486e0fec53ec2366df9cf1cba36e0bbf066af7cd9c974bbbd9ba10e1e586"}, + {file = "pydantic-1.10.21-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c54f8d4c151c1de784c5b93dfbb872067e3414619e10e21e695f7bb84d1d1fd"}, + {file = "pydantic-1.10.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b64708009cfabd9c2211295144ff455ec7ceb4c4fb45a07a804309598f36187"}, + {file = "pydantic-1.10.21-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a148410fa0e971ba333358d11a6dea7b48e063de127c2b09ece9d1c1137dde4"}, + {file = "pydantic-1.10.21-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:36ceadef055af06e7756eb4b871cdc9e5a27bdc06a45c820cd94b443de019bbf"}, + {file = "pydantic-1.10.21-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0501e1d12df6ab1211b8cad52d2f7b2cd81f8e8e776d39aa5e71e2998d0379f"}, + {file = "pydantic-1.10.21-cp310-cp310-win_amd64.whl", hash = "sha256:c261127c275d7bce50b26b26c7d8427dcb5c4803e840e913f8d9df3f99dca55f"}, + {file = "pydantic-1.10.21-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8b6350b68566bb6b164fb06a3772e878887f3c857c46c0c534788081cb48adf4"}, + {file = "pydantic-1.10.21-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:935b19fdcde236f4fbf691959fa5c3e2b6951fff132964e869e57c70f2ad1ba3"}, + {file = "pydantic-1.10.21-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b6a04efdcd25486b27f24c1648d5adc1633ad8b4506d0e96e5367f075ed2e0b"}, + {file = "pydantic-1.10.21-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1ba253eb5af8d89864073e6ce8e6c8dec5f49920cff61f38f5c3383e38b1c9f"}, + {file = "pydantic-1.10.21-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:57f0101e6c97b411f287a0b7cf5ebc4e5d3b18254bf926f45a11615d29475793"}, + {file = "pydantic-1.10.21-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e85834f0370d737c77a386ce505c21b06bfe7086c1c568b70e15a568d9670d"}, + {file = "pydantic-1.10.21-cp311-cp311-win_amd64.whl", hash = "sha256:6a497bc66b3374b7d105763d1d3de76d949287bf28969bff4656206ab8a53aa9"}, + {file = "pydantic-1.10.21-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ed4a5f13cf160d64aa331ab9017af81f3481cd9fd0e49f1d707b57fe1b9f3ae"}, + {file = "pydantic-1.10.21-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b7693bb6ed3fbe250e222f9415abb73111bb09b73ab90d2d4d53f6390e0ccc1"}, + {file = "pydantic-1.10.21-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185d5f1dff1fead51766da9b2de4f3dc3b8fca39e59383c273f34a6ae254e3e2"}, + {file = "pydantic-1.10.21-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38e6d35cf7cd1727822c79e324fa0677e1a08c88a34f56695101f5ad4d5e20e5"}, + {file = "pydantic-1.10.21-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:1d7c332685eafacb64a1a7645b409a166eb7537f23142d26895746f628a3149b"}, + {file = "pydantic-1.10.21-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c9b782db6f993a36092480eeaab8ba0609f786041b01f39c7c52252bda6d85f"}, + {file = "pydantic-1.10.21-cp312-cp312-win_amd64.whl", hash = "sha256:7ce64d23d4e71d9698492479505674c5c5b92cda02b07c91dfc13633b2eef805"}, + {file = "pydantic-1.10.21-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0067935d35044950be781933ab91b9a708eaff124bf860fa2f70aeb1c4be7212"}, + {file = "pydantic-1.10.21-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5e8148c2ce4894ce7e5a4925d9d3fdce429fb0e821b5a8783573f3611933a251"}, + {file = "pydantic-1.10.21-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4973232c98b9b44c78b1233693e5e1938add5af18042f031737e1214455f9b8"}, + {file = "pydantic-1.10.21-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:662bf5ce3c9b1cef32a32a2f4debe00d2f4839fefbebe1d6956e681122a9c839"}, + {file = "pydantic-1.10.21-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:98737c3ab5a2f8a85f2326eebcd214510f898881a290a7939a45ec294743c875"}, + {file = "pydantic-1.10.21-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0bb58bbe65a43483d49f66b6c8474424d551a3fbe8a7796c42da314bac712738"}, + {file = "pydantic-1.10.21-cp313-cp313-win_amd64.whl", hash = "sha256:e622314542fb48542c09c7bd1ac51d71c5632dd3c92dc82ede6da233f55f4848"}, + {file = "pydantic-1.10.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d356aa5b18ef5a24d8081f5c5beb67c0a2a6ff2a953ee38d65a2aa96526b274f"}, + {file = "pydantic-1.10.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08caa8c0468172d27c669abfe9e7d96a8b1655ec0833753e117061febaaadef5"}, + {file = "pydantic-1.10.21-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c677aa39ec737fec932feb68e4a2abe142682f2885558402602cd9746a1c92e8"}, + {file = "pydantic-1.10.21-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:79577cc045d3442c4e845df53df9f9202546e2ba54954c057d253fc17cd16cb1"}, + {file = "pydantic-1.10.21-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:b6b73ab347284719f818acb14f7cd80696c6fdf1bd34feee1955d7a72d2e64ce"}, + {file = "pydantic-1.10.21-cp37-cp37m-win_amd64.whl", hash = "sha256:46cffa24891b06269e12f7e1ec50b73f0c9ab4ce71c2caa4ccf1fb36845e1ff7"}, + {file = "pydantic-1.10.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:298d6f765e3c9825dfa78f24c1efd29af91c3ab1b763e1fd26ae4d9e1749e5c8"}, + {file = "pydantic-1.10.21-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2f4a2305f15eff68f874766d982114ac89468f1c2c0b97640e719cf1a078374"}, + {file = "pydantic-1.10.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35b263b60c519354afb3a60107d20470dd5250b3ce54c08753f6975c406d949b"}, + {file = "pydantic-1.10.21-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e23a97a6c2f2db88995496db9387cd1727acdacc85835ba8619dce826c0b11a6"}, + {file = "pydantic-1.10.21-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:3c96fed246ccc1acb2df032ff642459e4ae18b315ecbab4d95c95cfa292e8517"}, + {file = "pydantic-1.10.21-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b92893ebefc0151474f682e7debb6ab38552ce56a90e39a8834734c81f37c8a9"}, + {file = "pydantic-1.10.21-cp38-cp38-win_amd64.whl", hash = "sha256:b8460bc256bf0de821839aea6794bb38a4c0fbd48f949ea51093f6edce0be459"}, + {file = "pydantic-1.10.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d387940f0f1a0adb3c44481aa379122d06df8486cc8f652a7b3b0caf08435f7"}, + {file = "pydantic-1.10.21-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:266ecfc384861d7b0b9c214788ddff75a2ea123aa756bcca6b2a1175edeca0fe"}, + {file = "pydantic-1.10.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61da798c05a06a362a2f8c5e3ff0341743e2818d0f530eaac0d6898f1b187f1f"}, + {file = "pydantic-1.10.21-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a621742da75ce272d64ea57bd7651ee2a115fa67c0f11d66d9dcfc18c2f1b106"}, + {file = "pydantic-1.10.21-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9e3e4000cd54ef455694b8be9111ea20f66a686fc155feda1ecacf2322b115da"}, + {file = "pydantic-1.10.21-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f198c8206640f4c0ef5a76b779241efb1380a300d88b1bce9bfe95a6362e674d"}, + {file = "pydantic-1.10.21-cp39-cp39-win_amd64.whl", hash = "sha256:e7f0cda108b36a30c8fc882e4fc5b7eec8ef584aa43aa43694c6a7b274fb2b56"}, + {file = "pydantic-1.10.21-py3-none-any.whl", hash = "sha256:db70c920cba9d05c69ad4a9e7f8e9e83011abb2c6490e561de9ae24aee44925c"}, + {file = "pydantic-1.10.21.tar.gz", hash = "sha256:64b48e2b609a6c22178a56c408ee1215a7206077ecb8a193e2fda31858b2362a"}, ] [package.dependencies] @@ -1646,15 +1726,32 @@ examples = ["django", "litestar", "numpy"] test = ["cffi (>=1.17.0)", "flaky", "greenlet (>=3)", "ipython", "pytest", "pytest-asyncio (==0.23.8)", "trio"] types = ["typing-extensions"] +[[package]] +name = "pyjwt" +version = "2.10.1" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, + {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + [[package]] name = "pytest" -version = "8.3.3" +version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, - {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, ] [package.dependencies] @@ -1782,13 +1879,13 @@ files = [ [[package]] name = "redis" -version = "5.2.0" +version = "5.2.1" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.8" files = [ - {file = "redis-5.2.0-py3-none-any.whl", hash = "sha256:ae174f2bb3b1bf2b09d54bf3e51fbc1469cf6c10aa03e21141f51969801a7897"}, - {file = "redis-5.2.0.tar.gz", hash = "sha256:0b1087665a771b1ff2e003aa5bdd354f15a70c9e25d5a7dbf9c722c16528a7b0"}, + {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"}, + {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"}, ] [package.dependencies] @@ -1876,33 +1973,33 @@ cffi = ">=1.3.0" [[package]] name = "setuptools" -version = "75.6.0" +version = "75.8.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" files = [ - {file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"}, - {file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"}, + {file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"}, + {file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "six" -version = "1.16.0" +version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [[package]] @@ -1946,72 +2043,72 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.36" +version = "2.0.37" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa"}, - {file = "SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f"}, - {file = "SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e"}, - {file = "SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436"}, - {file = "SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:be9812b766cad94a25bc63bec11f88c4ad3629a0cec1cd5d4ba48dc23860486b"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aae840ebbd6cdd41af1c14590e5741665e5272d2fee999306673a1bb1fdb4d"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4557e1f11c5f653ebfdd924f3f9d5ebfc718283b0b9beebaa5dd6b77ec290971"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07b441f7d03b9a66299ce7ccf3ef2900abc81c0db434f42a5694a37bd73870f2"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:28120ef39c92c2dd60f2721af9328479516844c6b550b077ca450c7d7dc68575"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-win32.whl", hash = "sha256:b81ee3d84803fd42d0b154cb6892ae57ea6b7c55d8359a02379965706c7efe6c"}, - {file = "SQLAlchemy-2.0.36-cp37-cp37m-win_amd64.whl", hash = "sha256:f942a799516184c855e1a32fbc7b29d7e571b52612647866d4ec1c3242578fcb"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3d6718667da04294d7df1670d70eeddd414f313738d20a6f1d1f379e3139a545"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:72c28b84b174ce8af8504ca28ae9347d317f9dba3999e5981a3cd441f3712e24"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b11d0cfdd2b095e7b0686cf5fabeb9c67fae5b06d265d8180715b8cfa86522e3"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e32092c47011d113dc01ab3e1d3ce9f006a47223b18422c5c0d150af13a00687"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6a440293d802d3011028e14e4226da1434b373cbaf4a4bbb63f845761a708346"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c54a1e53a0c308a8e8a7dffb59097bff7facda27c70c286f005327f21b2bd6b1"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-win32.whl", hash = "sha256:1e0d612a17581b6616ff03c8e3d5eff7452f34655c901f75d62bd86449d9750e"}, - {file = "SQLAlchemy-2.0.36-cp38-cp38-win_amd64.whl", hash = "sha256:8958b10490125124463095bbdadda5aa22ec799f91958e410438ad6c97a7b793"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-win32.whl", hash = "sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28"}, - {file = "SQLAlchemy-2.0.36-cp39-cp39-win_amd64.whl", hash = "sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a"}, - {file = "SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e"}, - {file = "sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da36c3b0e891808a7542c5c89f224520b9a16c7f5e4d6a1156955605e54aef0e"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e7402ff96e2b073a98ef6d6142796426d705addd27b9d26c3b32dbaa06d7d069"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6f5d254a22394847245f411a2956976401e84da4288aa70cbcd5190744062c1"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41296bbcaa55ef5fdd32389a35c710133b097f7b2609d8218c0eabded43a1d84"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bedee60385c1c0411378cbd4dc486362f5ee88deceea50002772912d798bb00f"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6c67415258f9f3c69867ec02fea1bf6508153709ecbd731a982442a590f2b7e4"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-win32.whl", hash = "sha256:650dcb70739957a492ad8acff65d099a9586b9b8920e3507ca61ec3ce650bb72"}, + {file = "SQLAlchemy-2.0.37-cp310-cp310-win_amd64.whl", hash = "sha256:93d1543cd8359040c02b6614421c8e10cd7a788c40047dbc507ed46c29ae5636"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:78361be6dc9073ed17ab380985d1e45e48a642313ab68ab6afa2457354ff692c"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b661b49d0cb0ab311a189b31e25576b7ac3e20783beb1e1817d72d9d02508bf5"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d57bafbab289e147d064ffbd5cca2d7b1394b63417c0636cea1f2e93d16eb9e8"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fa2c0913f02341d25fb858e4fb2031e6b0813494cca1ba07d417674128ce11b"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9df21b8d9e5c136ea6cde1c50d2b1c29a2b5ff2b1d610165c23ff250e0704087"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db18ff6b8c0f1917f8b20f8eca35c28bbccb9f83afa94743e03d40203ed83de9"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-win32.whl", hash = "sha256:46954173612617a99a64aee103bcd3f078901b9a8dcfc6ae80cbf34ba23df989"}, + {file = "SQLAlchemy-2.0.37-cp311-cp311-win_amd64.whl", hash = "sha256:7b7e772dc4bc507fdec4ee20182f15bd60d2a84f1e087a8accf5b5b7a0dcf2ba"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2952748ecd67ed3b56773c185e85fc084f6bdcdec10e5032a7c25a6bc7d682ef"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3151822aa1db0eb5afd65ccfafebe0ef5cda3a7701a279c8d0bf17781a793bb4"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaa8039b6d20137a4e02603aba37d12cd2dde7887500b8855356682fc33933f4"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cdba1f73b64530c47b27118b7053b8447e6d6f3c8104e3ac59f3d40c33aa9fd"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1b2690456528a87234a75d1a1644cdb330a6926f455403c8e4f6cad6921f9098"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf5ae8a9dcf657fd72144a7fd01f243236ea39e7344e579a121c4205aedf07bb"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-win32.whl", hash = "sha256:ea308cec940905ba008291d93619d92edaf83232ec85fbd514dcb329f3192761"}, + {file = "SQLAlchemy-2.0.37-cp312-cp312-win_amd64.whl", hash = "sha256:635d8a21577341dfe4f7fa59ec394b346da12420b86624a69e466d446de16aff"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8c4096727193762e72ce9437e2a86a110cf081241919ce3fab8e89c02f6b6658"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e4fb5ac86d8fe8151966814f6720996430462e633d225497566b3996966b9bdb"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e56a139bfe136a22c438478a86f8204c1eb5eed36f4e15c4224e4b9db01cb3e4"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f95fc8e3f34b5f6b3effb49d10ac97c569ec8e32f985612d9b25dd12d0d2e94"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c505edd429abdfe3643fa3b2e83efb3445a34a9dc49d5f692dd087be966020e0"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:12b0f1ec623cccf058cf21cb544f0e74656618165b083d78145cafde156ea7b6"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-win32.whl", hash = "sha256:293f9ade06b2e68dd03cfb14d49202fac47b7bb94bffcff174568c951fbc7af2"}, + {file = "SQLAlchemy-2.0.37-cp313-cp313-win_amd64.whl", hash = "sha256:d70f53a0646cc418ca4853da57cf3ddddbccb8c98406791f24426f2dd77fd0e2"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:44f569d0b1eb82301b92b72085583277316e7367e038d97c3a1a899d9a05e342"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2eae3423e538c10d93ae3e87788c6a84658c3ed6db62e6a61bb9495b0ad16bb"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfff7be361048244c3aa0f60b5e63221c5e0f0e509f4e47b8910e22b57d10ae7"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:5bc3339db84c5fb9130ac0e2f20347ee77b5dd2596ba327ce0d399752f4fce39"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:84b9f23b0fa98a6a4b99d73989350a94e4a4ec476b9a7dfe9b79ba5939f5e80b"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-win32.whl", hash = "sha256:51bc9cfef83e0ac84f86bf2b10eaccb27c5a3e66a1212bef676f5bee6ef33ebb"}, + {file = "SQLAlchemy-2.0.37-cp37-cp37m-win_amd64.whl", hash = "sha256:8e47f1af09444f87c67b4f1bb6231e12ba6d4d9f03050d7fc88df6d075231a49"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6b788f14c5bb91db7f468dcf76f8b64423660a05e57fe277d3f4fad7b9dcb7ce"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521ef85c04c33009166777c77e76c8a676e2d8528dc83a57836b63ca9c69dcd1"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75311559f5c9881a9808eadbeb20ed8d8ba3f7225bef3afed2000c2a9f4d49b9"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cce918ada64c956b62ca2c2af59b125767097ec1dca89650a6221e887521bfd7"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9d087663b7e1feabea8c578d6887d59bb00388158e8bff3a76be11aa3f748ca2"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cf95a60b36997dad99692314c4713f141b61c5b0b4cc5c3426faad570b31ca01"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-win32.whl", hash = "sha256:d75ead7dd4d255068ea0f21492ee67937bd7c90964c8f3c2bea83c7b7f81b95f"}, + {file = "SQLAlchemy-2.0.37-cp38-cp38-win_amd64.whl", hash = "sha256:74bbd1d0a9bacf34266a7907d43260c8d65d31d691bb2356f41b17c2dca5b1d0"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:648ec5acf95ad59255452ef759054f2176849662af4521db6cb245263ae4aa33"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:35bd2df269de082065d4b23ae08502a47255832cc3f17619a5cea92ce478b02b"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f581d365af9373a738c49e0c51e8b18e08d8a6b1b15cc556773bcd8a192fa8b"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82df02816c14f8dc9f4d74aea4cb84a92f4b0620235daa76dde002409a3fbb5a"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94b564e38b344d3e67d2e224f0aec6ba09a77e4582ced41e7bfd0f757d926ec9"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:955a2a765aa1bd81aafa69ffda179d4fe3e2a3ad462a736ae5b6f387f78bfeb8"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-win32.whl", hash = "sha256:03f0528c53ca0b67094c4764523c1451ea15959bbf0a8a8a3096900014db0278"}, + {file = "SQLAlchemy-2.0.37-cp39-cp39-win_amd64.whl", hash = "sha256:4b12885dc85a2ab2b7d00995bac6d967bffa8594123b02ed21e8eb2205a7584b"}, + {file = "SQLAlchemy-2.0.37-py3-none-any.whl", hash = "sha256:a8998bf9f8658bd3839cbc44ddbe982955641863da0c1efe5b00c1ab4f5c16b1"}, + {file = "sqlalchemy-2.0.37.tar.gz", hash = "sha256:12b28d99a9c14eaf4055810df1001557176716de0167b91026e648e65229bffb"}, ] [package.dependencies] -greenlet = {version = "!=0.4.17", optional = true, markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") or extra == \"asyncio\""} +greenlet = {version = "!=0.4.17", optional = true, markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") or extra == \"asyncio\""} typing-extensions = ">=4.6.0" [package.extras] @@ -2058,24 +2155,54 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7 [[package]] name = "tomli" -version = "2.1.0" +version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ - {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, - {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] name = "types-cffi" -version = "1.16.0.20240331" +version = "1.16.0.20241221" description = "Typing stubs for cffi" optional = false python-versions = ">=3.8" files = [ - {file = "types-cffi-1.16.0.20240331.tar.gz", hash = "sha256:b8b20d23a2b89cfed5f8c5bc53b0cb8677c3aac6d970dbc771e28b9c698f5dee"}, - {file = "types_cffi-1.16.0.20240331-py3-none-any.whl", hash = "sha256:a363e5ea54a4eb6a4a105d800685fde596bc318089b025b27dee09849fe41ff0"}, + {file = "types_cffi-1.16.0.20241221-py3-none-any.whl", hash = "sha256:e5b76b4211d7a9185f6ab8d06a106d56c7eb80af7cdb8bfcb4186ade10fb112f"}, + {file = "types_cffi-1.16.0.20241221.tar.gz", hash = "sha256:1c96649618f4b6145f58231acb976e0b448be6b847f7ab733dabe62dfbff6591"}, ] [package.dependencies] @@ -2083,13 +2210,13 @@ types-setuptools = "*" [[package]] name = "types-protobuf" -version = "5.28.3.20241030" +version = "5.29.1.20241207" description = "Typing stubs for protobuf" optional = false python-versions = ">=3.8" files = [ - {file = "types-protobuf-5.28.3.20241030.tar.gz", hash = "sha256:f7e6b45845d75393fb41c0b3ce82c46d775f9771fae2097414a1dbfe5b51a988"}, - {file = "types_protobuf-5.28.3.20241030-py3-none-any.whl", hash = "sha256:f3dae16adf342d4fb5bb3673cabb22549a6252e5dd66fc52d8310b1a39c64ba9"}, + {file = "types_protobuf-5.29.1.20241207-py3-none-any.whl", hash = "sha256:92893c42083e9b718c678badc0af7a9a1307b92afe1599e5cba5f3d35b668b2f"}, + {file = "types_protobuf-5.29.1.20241207.tar.gz", hash = "sha256:2ebcadb8ab3ef2e3e2f067e0882906d64ba0dc65fc5b0fd7a8b692315b4a0be9"}, ] [[package]] @@ -2124,13 +2251,13 @@ types-pyOpenSSL = "*" [[package]] name = "types-setuptools" -version = "75.5.0.20241122" +version = "75.8.0.20250110" description = "Typing stubs for setuptools" optional = false python-versions = ">=3.8" files = [ - {file = "types_setuptools-75.5.0.20241122-py3-none-any.whl", hash = "sha256:d69c445f7bdd5e49d1b2441aadcee1388febcc9ad9d9d5fd33648b555e0b1c31"}, - {file = "types_setuptools-75.5.0.20241122.tar.gz", hash = "sha256:196aaf1811cbc1c77ac1d4c4879d5308b6fdf426e56b73baadbca2a1827dadef"}, + {file = "types_setuptools-75.8.0.20250110-py3-none-any.whl", hash = "sha256:a9f12980bbf9bcdc23ecd80755789085bad6bfce4060c2275bc2b4ca9f2bc480"}, + {file = "types_setuptools-75.8.0.20250110.tar.gz", hash = "sha256:96f7ec8bbd6e0a54ea180d66ad68ad7a1d7954e7281a710ea2de75e355545271"}, ] [[package]] @@ -2165,13 +2292,13 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [[package]] name = "virtualenv" -version = "20.27.1" +version = "20.29.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" files = [ - {file = "virtualenv-20.27.1-py3-none-any.whl", hash = "sha256:f11f1b8a29525562925f745563bfd48b189450f61fb34c4f9cc79dd5aa32a1f4"}, - {file = "virtualenv-20.27.1.tar.gz", hash = "sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba"}, + {file = "virtualenv-20.29.1-py3-none-any.whl", hash = "sha256:4e4cb403c0b0da39e13b46b1b2476e505cb0046b25f242bee80f62bf990b2779"}, + {file = "virtualenv-20.29.1.tar.gz", hash = "sha256:b8b8970138d32fb606192cb97f6cd4bb644fa486be9308fb9b63f81091b5dc35"}, ] [package.dependencies] @@ -2296,13 +2423,13 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [[package]] name = "win32-setctime" -version = "1.1.0" +version = "1.2.0" description = "A small Python utility to set file creation time on Windows" optional = false python-versions = ">=3.5" files = [ - {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, - {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, + {file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"}, + {file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"}, ] [package.extras] @@ -2310,76 +2437,90 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [[package]] name = "wrapt" -version = "1.17.0" +version = "1.17.2" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" files = [ - {file = "wrapt-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0c23b8319848426f305f9cb0c98a6e32ee68a36264f45948ccf8e7d2b941f8"}, - {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ca5f060e205f72bec57faae5bd817a1560fcfc4af03f414b08fa29106b7e2d"}, - {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e185ec6060e301a7e5f8461c86fb3640a7beb1a0f0208ffde7a65ec4074931df"}, - {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb90765dd91aed05b53cd7a87bd7f5c188fcd95960914bae0d32c5e7f899719d"}, - {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:879591c2b5ab0a7184258274c42a126b74a2c3d5a329df16d69f9cee07bba6ea"}, - {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fce6fee67c318fdfb7f285c29a82d84782ae2579c0e1b385b7f36c6e8074fffb"}, - {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0698d3a86f68abc894d537887b9bbf84d29bcfbc759e23f4644be27acf6da301"}, - {file = "wrapt-1.17.0-cp310-cp310-win32.whl", hash = "sha256:69d093792dc34a9c4c8a70e4973a3361c7a7578e9cd86961b2bbf38ca71e4e22"}, - {file = "wrapt-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:f28b29dc158ca5d6ac396c8e0a2ef45c4e97bb7e65522bfc04c989e6fe814575"}, - {file = "wrapt-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74bf625b1b4caaa7bad51d9003f8b07a468a704e0644a700e936c357c17dd45a"}, - {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f2a28eb35cf99d5f5bd12f5dd44a0f41d206db226535b37b0c60e9da162c3ed"}, - {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81b1289e99cf4bad07c23393ab447e5e96db0ab50974a280f7954b071d41b489"}, - {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2939cd4a2a52ca32bc0b359015718472d7f6de870760342e7ba295be9ebaf9"}, - {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a9653131bda68a1f029c52157fd81e11f07d485df55410401f745007bd6d339"}, - {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4e4b4385363de9052dac1a67bfb535c376f3d19c238b5f36bddc95efae15e12d"}, - {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bdf62d25234290db1837875d4dceb2151e4ea7f9fff2ed41c0fde23ed542eb5b"}, - {file = "wrapt-1.17.0-cp311-cp311-win32.whl", hash = "sha256:5d8fd17635b262448ab8f99230fe4dac991af1dabdbb92f7a70a6afac8a7e346"}, - {file = "wrapt-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:92a3d214d5e53cb1db8b015f30d544bc9d3f7179a05feb8f16df713cecc2620a"}, - {file = "wrapt-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:89fc28495896097622c3fc238915c79365dd0ede02f9a82ce436b13bd0ab7569"}, - {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875d240fdbdbe9e11f9831901fb8719da0bd4e6131f83aa9f69b96d18fae7504"}, - {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ed16d95fd142e9c72b6c10b06514ad30e846a0d0917ab406186541fe68b451"}, - {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18b956061b8db634120b58f668592a772e87e2e78bc1f6a906cfcaa0cc7991c1"}, - {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:daba396199399ccabafbfc509037ac635a6bc18510ad1add8fd16d4739cdd106"}, - {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4d63f4d446e10ad19ed01188d6c1e1bb134cde8c18b0aa2acfd973d41fcc5ada"}, - {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8a5e7cc39a45fc430af1aefc4d77ee6bad72c5bcdb1322cfde852c15192b8bd4"}, - {file = "wrapt-1.17.0-cp312-cp312-win32.whl", hash = "sha256:0a0a1a1ec28b641f2a3a2c35cbe86c00051c04fffcfcc577ffcdd707df3f8635"}, - {file = "wrapt-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:3c34f6896a01b84bab196f7119770fd8466c8ae3dfa73c59c0bb281e7b588ce7"}, - {file = "wrapt-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:714c12485aa52efbc0fc0ade1e9ab3a70343db82627f90f2ecbc898fdf0bb181"}, - {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da427d311782324a376cacb47c1a4adc43f99fd9d996ffc1b3e8529c4074d393"}, - {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba1739fb38441a27a676f4de4123d3e858e494fac05868b7a281c0a383c098f4"}, - {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e711fc1acc7468463bc084d1b68561e40d1eaa135d8c509a65dd534403d83d7b"}, - {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:140ea00c87fafc42739bd74a94a5a9003f8e72c27c47cd4f61d8e05e6dec8721"}, - {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73a96fd11d2b2e77d623a7f26e004cc31f131a365add1ce1ce9a19e55a1eef90"}, - {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0b48554952f0f387984da81ccfa73b62e52817a4386d070c75e4db7d43a28c4a"}, - {file = "wrapt-1.17.0-cp313-cp313-win32.whl", hash = "sha256:498fec8da10e3e62edd1e7368f4b24aa362ac0ad931e678332d1b209aec93045"}, - {file = "wrapt-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd136bb85f4568fffca995bd3c8d52080b1e5b225dbf1c2b17b66b4c5fa02838"}, - {file = "wrapt-1.17.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:17fcf043d0b4724858f25b8826c36e08f9fb2e475410bece0ec44a22d533da9b"}, - {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4a557d97f12813dc5e18dad9fa765ae44ddd56a672bb5de4825527c847d6379"}, - {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0229b247b0fc7dee0d36176cbb79dbaf2a9eb7ecc50ec3121f40ef443155fb1d"}, - {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8425cfce27b8b20c9b89d77fb50e368d8306a90bf2b6eef2cdf5cd5083adf83f"}, - {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9c900108df470060174108012de06d45f514aa4ec21a191e7ab42988ff42a86c"}, - {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4e547b447073fc0dbfcbff15154c1be8823d10dab4ad401bdb1575e3fdedff1b"}, - {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:914f66f3b6fc7b915d46c1cc424bc2441841083de01b90f9e81109c9759e43ab"}, - {file = "wrapt-1.17.0-cp313-cp313t-win32.whl", hash = "sha256:a4192b45dff127c7d69b3bdfb4d3e47b64179a0b9900b6351859f3001397dabf"}, - {file = "wrapt-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:4f643df3d4419ea3f856c5c3f40fec1d65ea2e89ec812c83f7767c8730f9827a"}, - {file = "wrapt-1.17.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:69c40d4655e078ede067a7095544bcec5a963566e17503e75a3a3e0fe2803b13"}, - {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f495b6754358979379f84534f8dd7a43ff8cff2558dcdea4a148a6e713a758f"}, - {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:baa7ef4e0886a6f482e00d1d5bcd37c201b383f1d314643dfb0367169f94f04c"}, - {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fc931382e56627ec4acb01e09ce66e5c03c384ca52606111cee50d931a342d"}, - {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8f8909cdb9f1b237786c09a810e24ee5e15ef17019f7cecb207ce205b9b5fcce"}, - {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ad47b095f0bdc5585bced35bd088cbfe4177236c7df9984b3cc46b391cc60627"}, - {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:948a9bd0fb2c5120457b07e59c8d7210cbc8703243225dbd78f4dfc13c8d2d1f"}, - {file = "wrapt-1.17.0-cp38-cp38-win32.whl", hash = "sha256:5ae271862b2142f4bc687bdbfcc942e2473a89999a54231aa1c2c676e28f29ea"}, - {file = "wrapt-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:f335579a1b485c834849e9075191c9898e0731af45705c2ebf70e0cd5d58beed"}, - {file = "wrapt-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d751300b94e35b6016d4b1e7d0e7bbc3b5e1751e2405ef908316c2a9024008a1"}, - {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7264cbb4a18dc4acfd73b63e4bcfec9c9802614572025bdd44d0721983fc1d9c"}, - {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33539c6f5b96cf0b1105a0ff4cf5db9332e773bb521cc804a90e58dc49b10578"}, - {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c30970bdee1cad6a8da2044febd824ef6dc4cc0b19e39af3085c763fdec7de33"}, - {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bc7f729a72b16ee21795a943f85c6244971724819819a41ddbaeb691b2dd85ad"}, - {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6ff02a91c4fc9b6a94e1c9c20f62ea06a7e375f42fe57587f004d1078ac86ca9"}, - {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dfb7cff84e72e7bf975b06b4989477873dcf160b2fd89959c629535df53d4e0"}, - {file = "wrapt-1.17.0-cp39-cp39-win32.whl", hash = "sha256:2399408ac33ffd5b200480ee858baa58d77dd30e0dd0cab6a8a9547135f30a88"}, - {file = "wrapt-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:4f763a29ee6a20c529496a20a7bcb16a73de27f5da6a843249c7047daf135977"}, - {file = "wrapt-1.17.0-py3-none-any.whl", hash = "sha256:d2c63b93548eda58abf5188e505ffed0229bf675f7c3090f8e36ad55b8cbc371"}, - {file = "wrapt-1.17.0.tar.gz", hash = "sha256:16187aa2317c731170a88ef35e8937ae0f533c402872c1ee5e6d079fcf320801"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62"}, + {file = "wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563"}, + {file = "wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72"}, + {file = "wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317"}, + {file = "wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9"}, + {file = "wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9"}, + {file = "wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504"}, + {file = "wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a"}, + {file = "wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f"}, + {file = "wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555"}, + {file = "wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f"}, + {file = "wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7"}, + {file = "wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9"}, + {file = "wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb"}, + {file = "wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb"}, + {file = "wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8"}, + {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, ] [[package]] @@ -2516,4 +2657,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "803dc065553dcf3cf750ab9b5f9f8ee9145bfbaf54db9e6d6baec6f29e82c908" +content-hash = "5007f3202dedffb266c3bb0ba3101141a6d865e6979185a0ab6ea7d08c13213c" diff --git a/pyproject.toml b/pyproject.toml index 271989b..8a249f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,9 +39,11 @@ googleapis-common-protos = "^1.63.2" mypy-protobuf = "^3.6.0" types-protobuf = "^5.27.0.20240626" grpcio-tools = "^1.65.1" +pyjwt = "^2.9.0" redis = "^5.1.1" brotli = "^1.1.0" zstandard = "^0.23.0" +jinja2 = "^3.1.5" [tool.poetry.group.dev.dependencies] pytest-asyncio = "^0.24.0" diff --git a/tests/conftest.py b/tests/conftest.py index e9ca102..a8f1e16 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -51,6 +51,7 @@ settings.mint_lnd_enable_mpp = True settings.mint_clnrest_enable_mpp = True settings.mint_input_fee_ppk = 0 settings.db_connection_pool = True +# settings.mint_require_auth = False assert "test" in settings.cashu_dir shutil.rmtree(settings.cashu_dir, ignore_errors=True) diff --git a/tests/keycloak_data/docker-compose-restore.yml b/tests/keycloak_data/docker-compose-restore.yml new file mode 100644 index 0000000..eb9e95e --- /dev/null +++ b/tests/keycloak_data/docker-compose-restore.yml @@ -0,0 +1,45 @@ +services: + postgres: + image: postgres:16.4 + volumes: + - ./postgres_data:/var/lib/postgresql/data + environment: + POSTGRES_DB: cashu + POSTGRES_USER: cashu + POSTGRES_PASSWORD: cashu + networks: + - keycloak_network + + keycloak: + image: quay.io/keycloak/keycloak:25.0.6 + command: start --import-realm + volumes: + - ./keycloak-export:/opt/keycloak/data/import + environment: + KC_HOSTNAME: localhost + KC_HOSTNAME_PORT: 8080 + KC_HOSTNAME_STRICT_BACKCHANNEL: false + KC_HTTP_ENABLED: true + KC_HOSTNAME_STRICT_HTTPS: false + KC_HEALTH_ENABLED: true + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: admin + KC_DB: postgres + KC_DB_URL: jdbc:postgresql://postgres/cashu + KC_DB_USERNAME: cashu + KC_DB_PASSWORD: cashu + ports: + - 8080:8080 + restart: always + depends_on: + - postgres + networks: + - keycloak_network + +volumes: + postgres_data: + driver: local + +networks: + keycloak_network: + driver: bridge diff --git a/tests/keycloak_data/keycloak-export/master-realm.json b/tests/keycloak_data/keycloak-export/master-realm.json new file mode 100644 index 0000000..7f65ff7 --- /dev/null +++ b/tests/keycloak_data/keycloak-export/master-realm.json @@ -0,0 +1,2021 @@ +{ + "id" : "8956870d-d9bc-4ffd-bdec-3685db703215", + "realm" : "master", + "displayName" : "Keycloak", + "displayNameHtml" : "
Keycloak
", + "notBefore" : 0, + "defaultSignatureAlgorithm" : "RS256", + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 60, + "accessTokenLifespanForImplicitFlow" : 900, + "ssoSessionIdleTimeout" : 1800, + "ssoSessionMaxLifespan" : 36000, + "ssoSessionIdleTimeoutRememberMe" : 0, + "ssoSessionMaxLifespanRememberMe" : 0, + "offlineSessionIdleTimeout" : 2592000, + "offlineSessionMaxLifespanEnabled" : false, + "offlineSessionMaxLifespan" : 5184000, + "clientSessionIdleTimeout" : 0, + "clientSessionMaxLifespan" : 0, + "clientOfflineSessionIdleTimeout" : 0, + "clientOfflineSessionMaxLifespan" : 0, + "accessCodeLifespan" : 60, + "accessCodeLifespanUserAction" : 300, + "accessCodeLifespanLogin" : 1800, + "actionTokenGeneratedByAdminLifespan" : 43200, + "actionTokenGeneratedByUserLifespan" : 300, + "oauth2DeviceCodeLifespan" : 600, + "oauth2DevicePollingInterval" : 5, + "enabled" : true, + "sslRequired" : "external", + "registrationAllowed" : false, + "registrationEmailAsUsername" : false, + "rememberMe" : false, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : false, + "editUsernameAllowed" : false, + "bruteForceProtected" : false, + "permanentLockout" : false, + "maxTemporaryLockouts" : 0, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "c3b4c96f-6388-46e3-8eb7-9392c7652612", + "name" : "default-roles-master", + "description" : "${role_default-roles}", + "composite" : true, + "composites" : { + "realm" : [ "offline_access", "uma_authorization" ], + "client" : { + "account" : [ "view-profile", "manage-account" ] + } + }, + "clientRole" : false, + "containerId" : "8956870d-d9bc-4ffd-bdec-3685db703215", + "attributes" : { } + }, { + "id" : "81f3c313-ffe4-4b9a-b95f-62210ef4cebb", + "name" : "admin", + "description" : "${role_admin}", + "composite" : true, + "composites" : { + "realm" : [ "create-realm" ], + "client" : { + "nutshell-realm" : [ "query-realms", "query-users", "view-realm", "view-authorization", "manage-realm", "manage-identity-providers", "manage-authorization", "view-identity-providers", "query-clients", "manage-clients", "create-client", "impersonation", "view-events", "manage-events", "manage-users", "view-users", "view-clients", "query-groups" ], + "master-realm" : [ "query-clients", "manage-users", "manage-identity-providers", "manage-authorization", "manage-realm", "view-identity-providers", "create-client", "view-realm", "view-authorization", "manage-events", "query-realms", "query-users", "query-groups", "manage-clients", "view-clients", "impersonation", "view-users", "view-events" ] + } + }, + "clientRole" : false, + "containerId" : "8956870d-d9bc-4ffd-bdec-3685db703215", + "attributes" : { } + }, { + "id" : "9a56b7c9-cd40-4660-8bcc-1e45636f7ef4", + "name" : "create-realm", + "description" : "${role_create-realm}", + "composite" : false, + "clientRole" : false, + "containerId" : "8956870d-d9bc-4ffd-bdec-3685db703215", + "attributes" : { } + }, { + "id" : "60e9bbf2-d65e-4e6e-adff-56abaa59bf94", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "8956870d-d9bc-4ffd-bdec-3685db703215", + "attributes" : { } + }, { + "id" : "c8484e33-5729-4a6a-8ae3-05673d7a68e7", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "8956870d-d9bc-4ffd-bdec-3685db703215", + "attributes" : { } + } ], + "client" : { + "security-admin-console" : [ ], + "admin-cli" : [ ], + "nutshell-realm" : [ { + "id" : "8b1a1634-cbf0-49ba-bd9c-9090fb581ee5", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + }, { + "id" : "2dc5dfc5-c542-450d-9909-b1182734af42", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + }, { + "id" : "7922cd7f-584d-4058-954f-13162c968b9e", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + }, { + "id" : "ade669fc-632d-4df1-8bfb-90393c7f72d6", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + }, { + "id" : "084b2d03-a9bc-496f-8e1b-a36937b37f96", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + }, { + "id" : "62f050e7-bf89-4c29-a67f-8b5dd348314b", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + }, { + "id" : "0f1c9007-80b3-4586-9f31-78cbde902ccf", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + }, { + "id" : "dfafec9f-15be-493a-bd50-12f98608d2a3", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + }, { + "id" : "2f76e0d4-2ff8-49e8-a299-926bccc34621", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + }, { + "id" : "e991ba94-d7ed-4461-891e-388cb6b77979", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + }, { + "id" : "a7437db1-b38c-4270-b999-2e06f78b0748", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + }, { + "id" : "43d4abb1-e359-4f30-9b77-2b3998abf0ce", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + }, { + "id" : "70cdddbc-61d6-4485-a308-0a01b76c69e2", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + }, { + "id" : "da263533-0421-453d-97d2-1c41d5759376", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + }, { + "id" : "07549481-3edd-40ec-b0ca-e97304cff3be", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + }, { + "id" : "22759f97-5a71-437b-a315-711164eb0cdb", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + }, { + "id" : "e3ae21ca-062e-4aee-b82a-9a04f6b6413d", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "nutshell-realm" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + }, { + "id" : "12453bfd-7b41-4366-a463-57be800b65b5", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "nutshell-realm" : [ "query-users", "query-groups" ] + } + }, + "clientRole" : true, + "containerId" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "attributes" : { } + } ], + "account-console" : [ ], + "broker" : [ { + "id" : "d288fe63-2c42-47e1-901e-6b36d660061f", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "52f64ef5-8732-4065-858e-2f2580b7ed9c", + "attributes" : { } + } ], + "master-realm" : [ { + "id" : "f117f84c-45e2-4b68-a652-617da6aa749e", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + }, { + "id" : "a7974424-c166-4653-a486-5cae0713b57c", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + }, { + "id" : "a8bbbc66-c86f-413e-a0d9-e3035d9bd317", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + }, { + "id" : "252481be-b124-4cea-875d-b2431eb73429", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + }, { + "id" : "35d31078-8b0a-482b-b3f9-f8929aa56630", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + }, { + "id" : "c95b6025-b1a9-47bb-9b53-f0e6a625be5f", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + }, { + "id" : "1214b780-d1bf-40c8-b239-50abb734ca51", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + }, { + "id" : "5d1999eb-3a8a-4b83-946d-2eb08cd474b4", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + }, { + "id" : "4280b1a4-da20-4ec8-a0ec-dd271970a537", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + }, { + "id" : "77580d97-d2a0-47c7-9862-9df69a237c68", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + }, { + "id" : "4944013c-27f2-42b7-8d82-fe39089bf23c", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "master-realm" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + }, { + "id" : "31c9f5d6-93ae-4b60-836b-e19a02d342a4", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + }, { + "id" : "1f9a5fe6-f029-4ea2-89ca-e3c3c595aa51", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + }, { + "id" : "9352ff2b-b75b-4cd8-94c2-329847a27126", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + }, { + "id" : "70f6059a-d671-4f01-ad9a-255864d1e8e2", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + }, { + "id" : "0050f16d-b26a-4cca-85b3-b62ff386f36e", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + }, { + "id" : "42bcb56c-8798-4f27-848e-cbe2c14cc5c9", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "master-realm" : [ "query-users", "query-groups" ] + } + }, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + }, { + "id" : "8f366846-0bf9-440d-9eab-5fad3ce0200e", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "attributes" : { } + } ], + "account" : [ { + "id" : "dffdfbeb-a943-4e2b-ad60-017484109595", + "name" : "view-groups", + "description" : "${role_view-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "fd11a567-b5be-4665-9444-4de133068420", + "attributes" : { } + }, { + "id" : "0c86baf9-e9dd-411f-b084-433d9746bcd4", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "fd11a567-b5be-4665-9444-4de133068420", + "attributes" : { } + }, { + "id" : "88c3c74e-4628-4373-8282-68959e5ed34a", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "fd11a567-b5be-4665-9444-4de133068420", + "attributes" : { } + }, { + "id" : "1efff5e8-7324-434d-b010-4fe402400c46", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "fd11a567-b5be-4665-9444-4de133068420", + "attributes" : { } + }, { + "id" : "ddc53580-ac83-49c2-b174-717485c6123f", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "fd11a567-b5be-4665-9444-4de133068420", + "attributes" : { } + }, { + "id" : "837af15c-5688-4a67-afcc-704b08e83230", + "name" : "delete-account", + "description" : "${role_delete-account}", + "composite" : false, + "clientRole" : true, + "containerId" : "fd11a567-b5be-4665-9444-4de133068420", + "attributes" : { } + }, { + "id" : "ce92fd21-5930-4019-893f-07cee7a32202", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "fd11a567-b5be-4665-9444-4de133068420", + "attributes" : { } + }, { + "id" : "4ef61025-a2bb-46c7-a773-45479f94c7ea", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "fd11a567-b5be-4665-9444-4de133068420", + "attributes" : { } + } ] + } + }, + "groups" : [ ], + "defaultRole" : { + "id" : "c3b4c96f-6388-46e3-8eb7-9392c7652612", + "name" : "default-roles-master", + "description" : "${role_default-roles}", + "composite" : true, + "clientRole" : false, + "containerId" : "8956870d-d9bc-4ffd-bdec-3685db703215" + }, + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpPolicyCodeReusable" : false, + "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ], + "localizationTexts" : { }, + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyExtraOrigins" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessExtraOrigins" : [ ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account", "view-groups" ] + } ] + }, + "clients" : [ { + "id" : "fd11a567-b5be-4665-9444-4de133068420", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/master/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/master/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "c38fcb7e-0879-4fe0-9818-01eb6dcc0f17", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/master/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/master/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "1a3217d2-aab7-4749-923c-2b1017709c22", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "92bb82ef-68c9-41f9-ad5e-57e975a4d6ba", + "clientId" : "admin-cli", + "name" : "${client_admin-cli}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : false, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "52f64ef5-8732-4065-858e-2f2580b7ed9c", + "clientId" : "broker", + "name" : "${client_broker}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "357078bd-0421-4b14-a0b9-40c25dde5557", + "clientId" : "master-realm", + "name" : "master Realm", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "6626bbfa-68fc-46ed-8ec0-fb786ab3f2ee", + "clientId" : "nutshell-realm", + "name" : "nutshell Realm", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ ], + "optionalClientScopes" : [ ] + }, { + "id" : "61609810-bd2b-42ac-8168-ab89416b489a", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/master/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/admin/master/console/*" ], + "webOrigins" : [ "+" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "79920355-25fa-423c-a9ec-2f5ca84da358", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "43f68d3e-e543-4684-b324-7d5feda4faec", + "name" : "acr", + "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "d5a92ece-cd72-48dd-b00d-fff7f57a8203", + "name" : "acr loa level", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-acr-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "ba7df914-fb24-40aa-abf7-478c373b5b30", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "consent.screen.text" : "${rolesScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "2bd2c43e-9dfc-43a9-8132-7476e4f0f88d", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + }, { + "id" : "39809465-371a-4b96-9ad9-35248d647a01", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "4468ec06-a16a-44a5-b989-7854bfd53781", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + } ] + }, { + "id" : "6adb47f8-94c7-4d0c-a50b-623c79a5acbc", + "name" : "web-origins", + "description" : "OpenID Connect scope for add allowed web origins to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "consent.screen.text" : "", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "7edaec4b-7cb1-4889-82da-4608f8a1d893", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "fe0b07cd-c0fb-4714-a5e0-950e04e082d7", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "6224866e-e0df-48ef-8ba4-a813df0b7fff", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "66043f0a-23d0-4e6e-b0c0-88b127d0e83b", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${profileScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "b7410ccb-433b-4383-8c84-9f73934aa40b", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "ce4046db-294e-452f-83d7-57c94bf508a1", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + }, { + "id" : "c1c8c377-46fc-4b80-88c3-1c715c522a54", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "f81dc329-9643-47df-a67f-6838aa42c29a", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + }, { + "id" : "78d13844-37f0-47f2-96f7-fc6b04b4c7c6", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + }, { + "id" : "1195910f-7036-4f39-b9aa-4bba3be3ee01", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "19f96415-ee38-4186-b7f4-d7b8fcb24a33", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + }, { + "id" : "26a001b8-0bd6-4631-9174-2522c30c5c88", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "bd5859a9-6581-455f-a284-d35f447eff21", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "long" + } + }, { + "id" : "62e692ce-d578-41f0-bf86-061039fcc555", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "0916a7a3-e8a6-48a7-85eb-be34b9555ac1", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + }, { + "id" : "cd8c2a23-1d0f-4ab1-ba60-88ea8b1396dc", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + }, { + "id" : "6304ea5b-0bd7-439a-974b-aa86ec49d24e", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + }, { + "id" : "85506c77-9b66-40d0-ba4b-a8933a66bd4d", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "01b5238a-8ebf-47ed-a72e-fd28ee8a2025", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${addressScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "a94b76c9-3abe-4c95-9e91-e4a3729e96f0", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "introspection.token.claim" : "true", + "user.attribute.postal_code" : "postal_code", + "userinfo.token.claim" : "true", + "user.attribute.street" : "street", + "id.token.claim" : "true", + "user.attribute.region" : "region", + "access.token.claim" : "true", + "user.attribute.locality" : "locality" + } + } ] + }, { + "id" : "3fa619bd-d3d3-45b4-8898-6657cd1d7801", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${phoneScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "c1f359ff-216c-4d77-8dfb-f997faeee2ad", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "763142bf-b12e-47f0-ad0b-2d87409b7332", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "2c1e962f-4e58-4a42-8e3e-62d392f091b2", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${emailScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "e9a326c5-8a1e-4658-b21a-d98bcfa67177", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + }, { + "id" : "cad78fa0-43ef-498a-b2d2-3c0204aa9792", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + } ] + }, { + "id" : "c9bfad33-ba6a-4803-8734-9f5ae8e97f2d", + "name" : "microprofile-jwt", + "description" : "Microprofile - JWT built-in scope", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "527dfec5-7b13-4d2a-b9d6-1d2a1fbcd4e2", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "multivalued" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + }, { + "id" : "a7a2b0c1-9540-473d-b9d2-9c7420b69e51", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "4476c340-2396-4f85-97c5-1421c5c3d033", + "name" : "basic", + "description" : "OpenID Connect scope for add all basic claims to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "72838032-fffc-4869-aaec-6a10e571b9f5", + "name" : "auth_time", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "AUTH_TIME", + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "auth_time", + "jsonType.label" : "long" + } + }, { + "id" : "faa805a5-17f7-4852-b12a-3e270c2a3a36", + "name" : "sub", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-sub-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "a557e5d5-341a-4ea0-9d6e-b620e55f8ebc", + "name" : "offline_access", + "description" : "OpenID Connect built-in scope: offline_access", + "protocol" : "openid-connect", + "attributes" : { + "consent.screen.text" : "${offlineAccessScopeConsentText}", + "display.on.consent.screen" : "true" + } + } ], + "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins", "acr", "basic" ], + "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], + "browserSecurityHeaders" : { + "contentSecurityPolicyReportOnly" : "", + "xContentTypeOptions" : "nosniff", + "referrerPolicy" : "no-referrer", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection" : "1; mode=block", + "strictTransportSecurity" : "max-age=31536000; includeSubDomains" + }, + "smtpServer" : { }, + "eventsEnabled" : false, + "eventsListeners" : [ "jboss-logging" ], + "enabledEventTypes" : [ ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "identityProviders" : [ ], + "identityProviderMappers" : [ ], + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "66e4f611-e6cc-4b78-ab85-f03a0be1c760", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + }, { + "id" : "12a23404-e173-44c3-99b2-aa744961d0b7", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "oidc-address-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "saml-role-list-mapper" ] + } + }, { + "id" : "0fe538ad-c9f3-4fea-83ec-d1ec834dd030", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "a30be1e3-9b74-40b7-b535-ed8daf324ee4", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + }, { + "id" : "0d41044d-ca32-43ce-8bf9-fdbe2b98fdc6", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-address-mapper", "saml-role-list-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper" ] + } + }, { + "id" : "fe5cbf0b-daf0-48e6-98e4-b54fd7a7dd23", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "c19b8aad-971c-4ea0-8ba1-718db2c5b0b2", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "1d7eddfb-39c1-4aad-b1b2-acc5c645c097", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + } ], + "org.keycloak.userprofile.UserProfileProvider" : [ { + "id" : "86baaf71-87fd-4294-9317-651a6a9527dc", + "providerId" : "declarative-user-profile", + "subComponents" : { }, + "config" : { + "kc.user.profile.config" : [ "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}" ] + } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "5cadce2d-b2da-4b4f-81f7-a950bd0ce156", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEogIBAAKCAQEA0qQDrL3Xf+/4io5uKEfkIT4wpx9iIwqM5oI3jJcm2qbhlvFlDsRv6BG1s4syJvDtEXjXR4GEeY8Lhm1DAW00k5aYzdvm63FaV2zw+XbTxEiaIExFlYWHmZTwvm8DnzfGZSrvr0UScKZkRC52/qaY/ye2txv5YSFsuvGmPVhO8+pUzX4BaJIjoBMDV610ojpcey7xlovV75ZpUHGQQFxheeFeTouduH4CwYJ7tOP8caaU2dnqRHPUknYWxE61TGfD9rW1qjYA9mMPYqly+LAZFIx44wFpoisZlD+Z3ufsPeoAdOIk5q+Rt1sTGxy/B+WWKcRxxutqRjbw2PlUkBf72QIDAQABAoIBAF3aP1rp0UmLUhnmQVXIRnC7ZXEpThAf5MzWC0skk+bLgWtATk9InjXwIh4H8MYiBfcJeR4+qpiA8FkqeLb7gfgamyXqC5cvD9oxS6NTWyKzWJz4bu6iqtr23ldzXlFESC0OdvIInCE+OiGY9GMdNsFFYCdxGum4u1oGTpQ5syABIrRV3IUzC4bPszsVhoNoxaDYWY8fC236XiNd9aPidFgtGlZpXekWqlGMAAPILu/nDF1HxKdD1OeWLJfuisJxC+0xI0b47q9rQBN5ptFK3rSEebzQsycr6sGDWOs7sgMmqsUJpCVqGQxqDggMjb8/VXC/fA+136wku9QQU3Rt1psCgYEA/HnGGbdxca5PzzH0FeaBXyE9eeL73jtviuWyFkZAYukOdtXGB3ELTvXJJkPTIWsSYxzsXaTKjNR9aZnHAq2OfSTFlgteWCe1uLRnBYyZ75AJVCIITom0m+ycoEU5gM4hQYw8rPKxwyhX+yiPOZlAEldSTpVR50xZQahW9INl4h8CgYEA1ZS+EKsPdi1DELRSMNEcjSwpi96+QbtZ7lgyORJzNMwMsin/r/Qc6NfHtybIExpW+x8K1yEnaeCDOCXlQdehNodozRmyUcmkG2V92g8DxgJTVcR0QFxXLQROl+5fqM+qvvAKxhIalbRoHaiCtpy/OPFPhdMqsZgh73rfJK+pkwcCgYA8Tz5yEC7qL/BilxUuUhSfS8pqnjz6FgqMDFhhF8Dzn6ZT3rbiOi+wWegF2vfJKNGImXUg3WeBApU+r3wpeJnr7OfB8s9DkaDIEVf2rGJtJmQEE+kWEbDx+jBj3IVi7lplVQF9cq/h5XY2ybaE1MXIW9GOcf7RmJxNoc+7stOYkwKBgAtoAi6JtC2vhSFjP/Bzen7fmOhrYOXJx6e+9g+uOJDdROBO9eTDuLeGrpfNbmn2wiZvJfkPQDebUeaxv2igx29OE/7AHJHZnvYHmY0HuD/e5+xwrXSyecVhsYDTrjApxwijcS/az6inFdhfo3t1K5Ey8fhHqsQJR+auPTSMXRYJAoGAKwrJj9yju5VpWWlNtrRU2o2DXSLinVcJ96HVJ1WRyS2cnNRgpjEiGqka8andmwfyZujdaVaBHZPZlGiRIirxvSZ6ptC8sox7/a7SfIH7+4uXjz7Gr2r/be3RDWtlsmqo4agFDfyfjzT0fnfGNUMR7hUFgyKj+rTWkC7u3/CkqAo=" ], + "keyUse" : [ "SIG" ], + "certificate" : [ "MIICmzCCAYMCBgGSIN8+lTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjQwOTIzMjE1MDU0WhcNMzQwOTIzMjE1MjM0WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDSpAOsvdd/7/iKjm4oR+QhPjCnH2IjCozmgjeMlybapuGW8WUOxG/oEbWzizIm8O0ReNdHgYR5jwuGbUMBbTSTlpjN2+brcVpXbPD5dtPESJogTEWVhYeZlPC+bwOfN8ZlKu+vRRJwpmRELnb+ppj/J7a3G/lhIWy68aY9WE7z6lTNfgFokiOgEwNXrXSiOlx7LvGWi9XvlmlQcZBAXGF54V5Oi524fgLBgnu04/xxppTZ2epEc9SSdhbETrVMZ8P2tbWqNgD2Yw9iqXL4sBkUjHjjAWmiKxmUP5ne5+w96gB04iTmr5G3WxMbHL8H5ZYpxHHG62pGNvDY+VSQF/vZAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAKW3btD4M04m+qMkD9LGxbHITdft4jbupYudyD+QfYVMusQvnZmSx+A/HFlRI9vS5e5XD4orpS6B7LPOXQ674/uzqG3t008BsWdtiH1uF1kNXZ++34nUlOl3taGzGUAucPripxXl3mOHEia/nb6JyUQ6Nl0aS3aC5iaEKKDPVllbbU+ZeHmIbda35RmGrM9PFvrMYQaZEHn9Bou2DDwo8s2SDQFkBwF3HrBnujwb7gZX/BEybxVMt6JPkqjrMKkBGK/8iMm+ezdk+oaRVI44rnkErxF7YQT+Pji9ENVgxhbnNZ4Jjk1eh9OljuBgMdmWpZCP1UQPBHi8MQ2ADYo8fcI=" ], + "priority" : [ "100" ] + } + }, { + "id" : "0d64cac0-4a44-4eb7-becf-d73c4518f49e", + "name" : "hmac-generated-hs512", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "14061cf3-4756-41b2-b5e5-736210176ad5" ], + "secret" : [ "VG8zu3mIi_W-bbxshUZv4iVhjJF67x3NhA7V7Bs3l-IrF2N1onG35kCVeARF89HxOQsiOp09e3cGbfJ2hKhCFPqR-YJ3nBEY-uoUoNskLsr_OjbytC0n1NOMRRsQQfZ1KCiobvFvv6GMbKQrJUhzKoFpsV-W6O5ElhC6053LTf0" ], + "priority" : [ "100" ], + "algorithm" : [ "HS512" ] + } + }, { + "id" : "a999fc53-cb0c-47c0-b0a4-246cf394b1d7", + "name" : "rsa-enc-generated", + "providerId" : "rsa-enc-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEowIBAAKCAQEAgrItfQbra6avo1hWyiATXPvFJLOwiqmJEtieHdfr4/oRj/kqzALEj8P0zmk7jtNNwJIkJ+/BDh4kSmjmGaMQMxwUnS1UrX9CkB6gpTcZgqjYAQYLZMWYqi6w7PCd4wZc3c5ffS0fyipryK/yvBhNjV3FfdgbHpH7tlglAHzaEQiQKx+boAWidb+84ncN9jov0FyYaPerkFPeamiAIhxHQkHBJi+gNZ33jKFfvlAMQcsxT+zB7WeUt/jq9e+Vhci666BLpZJBFrSmTcBiShf3vJT4rIkJAXMDf7vx84EsttzN7KU6NgYKkclhTXof6Uhoe8HBz1YHFtiGQU2TEHnZpwIDAQABAoIBABTzDJmS51+dG2A6g8bod1IbRVGJwA2p+T/dGxM7jiM/7Vq/nN2QwAMvbOk9Mv/E3Ry2EDealb52lGidAe0wFyOqJ5++KWmaEYhacHOrLlqkR+N6HY9soPj+fPut4hEG7xHax7Gf7w2P2TEVCvqdhqjXVBXE8yvmSIvE+0Flweqi0x0U4kizG3IYVF7srNBf4l05/46ieSUafUnr/LwmQzPilF5vfMd42rgSMLjQ1A5UcTs6m0cgvE/mNe/s50PHLoguDmo89KV+F3Ce0B+cUAbcDTpC+qD/6yZHu+afMB7M54KTEeMqL5ef24wn/z0wcp9BMi9iPnoRTG7iztC2PDkCgYEAt6G4PaIdJvkjD55iBVMSUQBriNdvJkPLytXAqbM75VOoHmAhBmLFXsSPRy2GIt25LQVW53RZBofaH7cveSZXW7Hoikmo1Tl9L19GaVJNUiEH1239NXJBHp4nsKyDg4BM9BfY1US6NhDTVldgC15jqOGf0R1QI1TMg7E0jIIogVMCgYEAtjPeD7QI/M9Z93CnD63JwyZ+I+bGyi1SxWsM59qWzYshOH+iFDUcupVotayz3Ez6/5iYeRM3ju2OC0KUjuRkIcHUpJJYCWBtd/yICjYLbfGfNbT558lTqpW4Xa7siR51xKSCM+fdDY6Y02xaYZ5GWi8hlpy2t5GbKvA6nSVVV90CgYAZMIfzfG9/TyGuHM3ZaSHUFripltPabeZgtp2tKbcHqEghkpI1LAtjCpeU7fu+gKfMMzVOnrkvmicjvp82gTnujCMYBS6xwScY/nrMK8wLfhhzRtU7JaclKhDLvX505X6o5TSLXNgmXnx/FZFJPfNx0TF5IApELne3gPFybNBdnQKBgQCUdFJcjR49jl0JZXpZJgqcvQJOEjyqgp9MR1ruloYE13Wr6SKQQG42AIKedCbgOsDP/O9Oxz+fbyMrPYZ5ntGPR7UQmkSs7yqCdvoJB9vsKtDEG777AmjvNqpPerUzS+Q84qVL0YGlCCixKznBDAradEhzxSCDFVpOdAnt/Hs66QKBgD2R3x/5FGrkRWKyxpofIYojgDJA2dVwET87D59LznBOMm4QCE/PNaK9RzcDD6SIYRP2iSBdjE7NIEKOdVlunzqmTFNpr/2FNVcY7hZFsQP7YwDvqESUCFteVVjIPOYN/e4SMbd39ns9tUAiCTvImyD/3lys5kWxJzKfD+kyKl8x" ], + "keyUse" : [ "ENC" ], + "certificate" : [ "MIICmzCCAYMCBgGSIN8/HjANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjQwOTIzMjE1MDU0WhcNMzQwOTIzMjE1MjM0WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCCsi19Butrpq+jWFbKIBNc+8Uks7CKqYkS2J4d1+vj+hGP+SrMAsSPw/TOaTuO003AkiQn78EOHiRKaOYZoxAzHBSdLVStf0KQHqClNxmCqNgBBgtkxZiqLrDs8J3jBlzdzl99LR/KKmvIr/K8GE2NXcV92Bsekfu2WCUAfNoRCJArH5ugBaJ1v7zidw32Oi/QXJho96uQU95qaIAiHEdCQcEmL6A1nfeMoV++UAxByzFP7MHtZ5S3+Or175WFyLrroEulkkEWtKZNwGJKF/e8lPisiQkBcwN/u/HzgSy23M3spTo2BgqRyWFNeh/pSGh7wcHPVgcW2IZBTZMQedmnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGoW/dE0qJt2H4EjlR4tPt9qF5Mg+ZNBJQBtFDOhqX5i/eQA+LVewWk9OfyRr3IDJeC1yjB3LQY580Hl5Z13vnpt/bJosdEVhFEjfUdiKU5qdCo59+tJdVC6VSH3obuAR6lP4xS/Wm+y5/A+88Pllsuxoa0wFm2aAZjNBLh+hxynegVVD2b5vFM280ECQ1cS2ihcPEZqizn0gQiJEm++2Tf6omdq3KDbWBNBqgKemCUYtPuAg93BxQWW2o6D8nGVXwsf86QKgTPWc+QENX4UvFwMl+w178MRcOl6OC8YGLy5I9FT3Kca8hQn6JNIxJDqaeJpoCQ6l6vVJEkcW5Q5ntA=" ], + "priority" : [ "100" ], + "algorithm" : [ "RSA-OAEP" ] + } + }, { + "id" : "41b42694-9f19-473b-a662-25b2de3e6904", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "5e2674c9-8bb8-4499-a7bc-6b3ed715cf81" ], + "secret" : [ "MYLTsXNl0Bq10linwSJVyg" ], + "priority" : [ "100" ] + } + } ] + }, + "internationalizationEnabled" : false, + "supportedLocales" : [ ], + "authenticationFlows" : [ { + "id" : "66aa2193-9c24-40c2-8afc-19fc1a96e76a", + "alias" : "Account verification options", + "description" : "Method with which to verity the existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-email-verification", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Verify Existing Account by Re-authentication", + "userSetupAllowed" : false + } ] + }, { + "id" : "8d881e24-d189-4c49-8b93-aa83057bf39e", + "alias" : "Browser - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "1ccd8e11-4916-4a8b-bd3d-91eee6ec6de4", + "alias" : "Direct Grant - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "bb2e7d7b-3c08-49c9-ada6-5848243e2766", + "alias" : "First broker login - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "eb418e73-6109-4967-b651-184a95fc81db", + "alias" : "Handle Existing Account", + "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-confirm-link", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Account verification options", + "userSetupAllowed" : false + } ] + }, { + "id" : "714b575a-da59-4664-9055-7292c4b51d35", + "alias" : "Reset - Conditional OTP", + "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "ae0a9437-b908-4554-803f-4de41b6cffb2", + "alias" : "User creation or linking", + "description" : "Flow for the existing/non-existing user alternatives", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "create unique user config", + "authenticator" : "idp-create-user-if-unique", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false + } ] + }, { + "id" : "edb3710b-a6ce-4a5e-a565-554810ab17b8", + "alias" : "Verify Existing Account by Re-authentication", + "description" : "Reauthentication of existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "First broker login - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "506e2c7e-5c64-41b0-a691-fd41a179ec36", + "alias" : "browser", + "description" : "browser based authentication", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-cookie", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-spnego", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "identity-provider-redirector", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 25, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "forms", + "userSetupAllowed" : false + } ] + }, { + "id" : "0840c55c-bfa1-48ee-9f7c-80fc2c21260b", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-secret-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-x509", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 40, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "02e2c0c0-2183-41fb-889d-45b289371216", + "alias" : "direct grant", + "description" : "OpenID Connect Resource Owner Grant", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "direct-grant-validate-username", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "Direct Grant - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "c74e947e-333d-4a99-b011-4d4291e4413e", + "alias" : "docker auth", + "description" : "Used by Docker clients to authenticate against the IDP", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "docker-http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "7ad8c824-b6c2-45ca-97bc-740366e8e6fa", + "alias" : "first broker login", + "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "review profile config", + "authenticator" : "idp-review-profile", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "User creation or linking", + "userSetupAllowed" : false + } ] + }, { + "id" : "29ea962d-32f3-49cf-a009-76116f0a82e1", + "alias" : "forms", + "description" : "Username, password, otp and other auth forms.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Browser - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "2b8859c2-7ed8-4797-997e-a09dd3aac773", + "alias" : "registration", + "description" : "registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : true, + "flowAlias" : "registration form", + "userSetupAllowed" : false + } ] + }, { + "id" : "7dfe64c5-813f-4288-b685-f3ec7845dc22", + "alias" : "registration form", + "description" : "registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-password-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 50, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-recaptcha-action", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 60, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-terms-and-conditions", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 70, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "909ce2fa-c6eb-4c87-8812-ef0d6333af28", + "alias" : "reset credentials", + "description" : "Reset credentials for a user if they forgot their password or something", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "reset-credentials-choose-user", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-credential-email", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 40, + "autheticatorFlow" : true, + "flowAlias" : "Reset - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "d70fe04b-e680-41a8-ba18-6680b48d30a4", + "alias" : "saml ecp", + "description" : "SAML ECP Profile Authentication Flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + } ], + "authenticatorConfig" : [ { + "id" : "98f7f28a-a395-44d6-a2cc-786090c0c607", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "07c6a291-51b4-4984-ac2c-57b5ccc954df", + "alias" : "review profile config", + "config" : { + "update.profile.on.first.login" : "missing" + } + } ], + "requiredActions" : [ { + "alias" : "CONFIGURE_TOTP", + "name" : "Configure OTP", + "providerId" : "CONFIGURE_TOTP", + "enabled" : true, + "defaultAction" : false, + "priority" : 10, + "config" : { } + }, { + "alias" : "TERMS_AND_CONDITIONS", + "name" : "Terms and Conditions", + "providerId" : "TERMS_AND_CONDITIONS", + "enabled" : false, + "defaultAction" : false, + "priority" : 20, + "config" : { } + }, { + "alias" : "UPDATE_PASSWORD", + "name" : "Update Password", + "providerId" : "UPDATE_PASSWORD", + "enabled" : true, + "defaultAction" : false, + "priority" : 30, + "config" : { } + }, { + "alias" : "UPDATE_PROFILE", + "name" : "Update Profile", + "providerId" : "UPDATE_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 40, + "config" : { } + }, { + "alias" : "VERIFY_EMAIL", + "name" : "Verify Email", + "providerId" : "VERIFY_EMAIL", + "enabled" : true, + "defaultAction" : false, + "priority" : 50, + "config" : { } + }, { + "alias" : "delete_account", + "name" : "Delete Account", + "providerId" : "delete_account", + "enabled" : false, + "defaultAction" : false, + "priority" : 60, + "config" : { } + }, { + "alias" : "webauthn-register", + "name" : "Webauthn Register", + "providerId" : "webauthn-register", + "enabled" : true, + "defaultAction" : false, + "priority" : 70, + "config" : { } + }, { + "alias" : "webauthn-register-passwordless", + "name" : "Webauthn Register Passwordless", + "providerId" : "webauthn-register-passwordless", + "enabled" : true, + "defaultAction" : false, + "priority" : 80, + "config" : { } + }, { + "alias" : "VERIFY_PROFILE", + "name" : "Verify Profile", + "providerId" : "VERIFY_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 90, + "config" : { } + }, { + "alias" : "delete_credential", + "name" : "Delete Credential", + "providerId" : "delete_credential", + "enabled" : true, + "defaultAction" : false, + "priority" : 100, + "config" : { } + }, { + "alias" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "firstBrokerLoginFlow" : "first broker login", + "attributes" : { + "cibaBackchannelTokenDeliveryMode" : "poll", + "cibaAuthRequestedUserHint" : "login_hint", + "oauth2DevicePollingInterval" : "5", + "clientOfflineSessionMaxLifespan" : "0", + "clientSessionIdleTimeout" : "0", + "clientOfflineSessionIdleTimeout" : "0", + "cibaInterval" : "5", + "realmReusableOtpCode" : "false", + "cibaExpiresIn" : "120", + "oauth2DeviceCodeLifespan" : "600", + "parRequestUriLifespan" : "60", + "clientSessionMaxLifespan" : "0", + "organizationsEnabled" : "false" + }, + "keycloakVersion" : "25.0.6", + "userManagedAccessAllowed" : false, + "organizationsEnabled" : false, + "clientProfiles" : { + "profiles" : [ ] + }, + "clientPolicies" : { + "policies" : [ ] + } +} diff --git a/tests/keycloak_data/keycloak-export/master-users-0.json b/tests/keycloak_data/keycloak-export/master-users-0.json new file mode 100644 index 0000000..cd1ab2d --- /dev/null +++ b/tests/keycloak_data/keycloak-export/master-users-0.json @@ -0,0 +1,26 @@ +{ + "realm" : "master", + "users" : [ { + "id" : "0ff227f7-c163-4fca-9ae4-c8751c725421", + "username" : "admin", + "emailVerified" : false, + "createdTimestamp" : 1727128354842, + "enabled" : true, + "totp" : false, + "credentials" : [ { + "id" : "11a5f9ed-19c9-4164-be31-28ce6e23955b", + "type" : "password", + "createdDate" : 1727128354904, + "secretData" : "{\"value\":\"s/6M2/FCFd1fOyHJRMvOLvKM7e2JIOC6LZ3ovFVkGi8=\",\"salt\":\"Zjn7ChOL5688O84xf1ElGA==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-master", "admin" ], + "clientRoles" : { + "nutshell-realm" : [ "query-realms", "query-users", "manage-identity-providers", "manage-authorization", "view-identity-providers", "view-realm", "view-authorization", "query-clients", "manage-clients", "create-client", "view-events", "manage-events", "manage-realm", "manage-users", "view-users", "view-clients", "query-groups" ] + }, + "notBefore" : 0, + "groups" : [ ] + } ] +} diff --git a/tests/keycloak_data/keycloak-export/nutshell-realm.json b/tests/keycloak_data/keycloak-export/nutshell-realm.json new file mode 100644 index 0000000..2d3e395 --- /dev/null +++ b/tests/keycloak_data/keycloak-export/nutshell-realm.json @@ -0,0 +1,1902 @@ +{ + "id" : "7ce5df4d-de4c-460c-9623-bf036f5e326d", + "realm" : "nutshell", + "notBefore" : 0, + "defaultSignatureAlgorithm" : "RS256", + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 300, + "accessTokenLifespanForImplicitFlow" : 900, + "ssoSessionIdleTimeout" : 1800, + "ssoSessionMaxLifespan" : 36000, + "ssoSessionIdleTimeoutRememberMe" : 0, + "ssoSessionMaxLifespanRememberMe" : 0, + "offlineSessionIdleTimeout" : 2592000, + "offlineSessionMaxLifespanEnabled" : false, + "offlineSessionMaxLifespan" : 5184000, + "clientSessionIdleTimeout" : 0, + "clientSessionMaxLifespan" : 0, + "clientOfflineSessionIdleTimeout" : 0, + "clientOfflineSessionMaxLifespan" : 0, + "accessCodeLifespan" : 60, + "accessCodeLifespanUserAction" : 300, + "accessCodeLifespanLogin" : 1800, + "actionTokenGeneratedByAdminLifespan" : 43200, + "actionTokenGeneratedByUserLifespan" : 300, + "oauth2DeviceCodeLifespan" : 600, + "oauth2DevicePollingInterval" : 5, + "enabled" : true, + "sslRequired" : "external", + "registrationAllowed" : true, + "registrationEmailAsUsername" : true, + "rememberMe" : true, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : true, + "editUsernameAllowed" : false, + "bruteForceProtected" : false, + "permanentLockout" : false, + "maxTemporaryLockouts" : 0, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "cf15df0b-9cb4-443b-bf6b-ef520ecbac2c", + "name" : "default-roles-nutshell", + "description" : "${role_default-roles}", + "composite" : true, + "composites" : { + "realm" : [ "offline_access", "uma_authorization" ], + "client" : { + "account" : [ "view-profile", "manage-account" ] + } + }, + "clientRole" : false, + "containerId" : "7ce5df4d-de4c-460c-9623-bf036f5e326d", + "attributes" : { } + }, { + "id" : "5f3146f9-69e2-4906-8e7d-200a8cc9cf46", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "7ce5df4d-de4c-460c-9623-bf036f5e326d", + "attributes" : { } + }, { + "id" : "4b4124b9-3023-4557-8fcb-16937aa4da06", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "7ce5df4d-de4c-460c-9623-bf036f5e326d", + "attributes" : { } + } ], + "client" : { + "realm-management" : [ { + "id" : "c44f22a5-1c57-4404-b450-fb22805345ab", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "94ba1856-e4b6-43f7-bc3d-e586a269fa0d", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "101f63ee-c4c2-4bb1-9865-7239199f90ac", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "1888f762-fbe4-4d3e-b62c-c0ce597c432d", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "d9e2c1d1-0704-40d8-b3ed-3bd3bd29605e", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "7c82add7-89ad-47d3-87f1-dd82f48dee9e", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "632cbe8f-d0a6-45b3-913c-76af11603c92", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "82f54d45-4e58-4a61-b0d2-0e0394c83e36", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "63b25859-0fdc-4c59-bae7-967290bed0c9", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "dea2d217-f1c5-43a3-880f-426d3880df78", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "b165f311-5daa-4db3-a294-db3951235c54", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "76241cad-4291-4b4a-83bb-c94334dd54dd", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "f5264002-577b-4ad9-ab9d-a7597709017d", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "b7d712ed-6589-4f8a-af8c-008ef518747f", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "081839a0-4fe3-4b3a-9d00-5dca7061b3d4", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-users", "query-groups" ] + } + }, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "c9c37fa1-ec54-4615-887d-ec6e149e734a", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "aa2a6e1d-2e4b-4a32-a50f-b6d77cca66e6", + "name" : "realm-admin", + "description" : "${role_realm-admin}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "view-events", "manage-clients", "query-users", "view-clients", "query-groups", "create-client", "manage-identity-providers", "view-authorization", "manage-realm", "query-realms", "impersonation", "view-realm", "manage-events", "view-users", "query-clients", "manage-authorization", "view-identity-providers", "manage-users" ] + } + }, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "45bcb41d-d91a-4a91-beaf-16e51f641e76", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + }, { + "id" : "d970c875-f5c8-42c1-9d51-ff08066d2d3f", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "attributes" : { } + } ], + "security-admin-console" : [ ], + "admin-cli" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "b9cd84e5-9fba-4271-833f-b5e255fc94f5", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "4736fd0c-e753-4837-8a6a-74b726caf795", + "attributes" : { } + } ], + "cashu-client" : [ ], + "account" : [ { + "id" : "b752c873-e544-4796-b7ed-9cd59eff5ef8", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "8f251366-4a78-4a67-9e1b-1ce337cc5844", + "attributes" : { } + }, { + "id" : "8c1b6cd8-909c-42d5-9de2-bb8c07bec854", + "name" : "delete-account", + "description" : "${role_delete-account}", + "composite" : false, + "clientRole" : true, + "containerId" : "8f251366-4a78-4a67-9e1b-1ce337cc5844", + "attributes" : { } + }, { + "id" : "16c8767b-3bb6-4f16-9f6c-1179c37a77c8", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "8f251366-4a78-4a67-9e1b-1ce337cc5844", + "attributes" : { } + }, { + "id" : "8bd33ad7-3e3b-48f1-ba3d-4ade3cc6f04c", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "8f251366-4a78-4a67-9e1b-1ce337cc5844", + "attributes" : { } + }, { + "id" : "857a614f-a7ce-49d2-9f2e-6537a9dda21a", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "8f251366-4a78-4a67-9e1b-1ce337cc5844", + "attributes" : { } + }, { + "id" : "062e7546-b48d-41cf-b856-236f70f3cd4f", + "name" : "view-groups", + "description" : "${role_view-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "8f251366-4a78-4a67-9e1b-1ce337cc5844", + "attributes" : { } + }, { + "id" : "d3375327-39f2-4eb9-bb3a-6812c3edf08f", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "8f251366-4a78-4a67-9e1b-1ce337cc5844", + "attributes" : { } + }, { + "id" : "47a8218d-3220-4d4d-a894-55e23d7aad8b", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "8f251366-4a78-4a67-9e1b-1ce337cc5844", + "attributes" : { } + } ] + } + }, + "groups" : [ ], + "defaultRole" : { + "id" : "cf15df0b-9cb4-443b-bf6b-ef520ecbac2c", + "name" : "default-roles-nutshell", + "description" : "${role_default-roles}", + "composite" : true, + "clientRole" : false, + "containerId" : "7ce5df4d-de4c-460c-9623-bf036f5e326d" + }, + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpPolicyCodeReusable" : false, + "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ], + "localizationTexts" : { }, + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyExtraOrigins" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessExtraOrigins" : [ ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account", "view-groups" ] + } ] + }, + "clients" : [ { + "id" : "8f251366-4a78-4a67-9e1b-1ce337cc5844", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/nutshell/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/nutshell/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "fb1aaaf7-f061-4704-b61f-5629a8e17f6a", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/nutshell/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/nutshell/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "e8904c38-e37d-4c34-aa52-878ae2c6621d", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "57f74364-819b-463f-9070-303f665df62c", + "clientId" : "admin-cli", + "name" : "${client_admin-cli}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : false, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "4736fd0c-e753-4837-8a6a-74b726caf795", + "clientId" : "broker", + "name" : "${client_broker}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "a1059e89-c42f-4604-b014-22cf41ae8854", + "clientId" : "cashu-client", + "name" : "", + "description" : "", + "rootUrl" : "", + "adminUrl" : "", + "baseUrl" : "", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "*" ], + "webOrigins" : [ "", "*" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : true, + "protocol" : "openid-connect", + "attributes" : { + "client.introspection.response.allow.jwt.claim.enabled" : "false", + "oauth2.device.authorization.grant.enabled" : "true", + "logoUri" : "https://avatars.githubusercontent.com/u/114246592", + "access.token.signed.response.alg" : "ES256", + "backchannel.logout.revoke.offline.tokens" : "false", + "use.refresh.tokens" : "true", + "oidc.ciba.grant.enabled" : "false", + "client.use.lightweight.access.token.enabled" : "false", + "id.token.signed.response.alg" : "ES256", + "backchannel.logout.session.required" : "true", + "client_credentials.use_refresh_token" : "false", + "acr.loa.map" : "{}", + "require.pushed.authorization.requests" : "false", + "tls.client.certificate.bound.access.tokens" : "false", + "authorization.signed.response.alg" : "ES256", + "display.on.consent.screen" : "false", + "token.response.type.bearer.lower-case" : "false" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "b47dc78e-8815-4103-b644-5c8fdd074aae", + "clientId" : "realm-management", + "name" : "${client_realm-management}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "c189d33f-84f5-4e73-a508-dd174e2f3fde", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/nutshell/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/admin/nutshell/console/*" ], + "webOrigins" : [ "+" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "ca69ba5c-3d32-4c29-882a-c2a4b6b93fdc", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "5006e7c3-113e-4cc2-a4bb-3460c642dd55", + "name" : "web-origins", + "description" : "OpenID Connect scope for add allowed web origins to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "consent.screen.text" : "", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "43abf5a8-3a7d-4865-a8be-135c7d1065b3", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "96e1971c-af62-4292-b3a2-86eb600ba1b4", + "name" : "basic", + "description" : "OpenID Connect scope for add all basic claims to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "de3be070-4e8d-4600-a102-4efe231f026f", + "name" : "auth_time", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "AUTH_TIME", + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "auth_time", + "jsonType.label" : "long" + } + }, { + "id" : "68e1cc26-feba-4309-84a2-7ffd246ff092", + "name" : "sub", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-sub-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "7c739599-c2ff-40a7-97f3-e2364e5500fa", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${emailScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "6b4acd6e-e0cc-438d-ac64-555f8cc27b1b", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "69f98c7a-baf5-4970-a229-1dcb8cdd20f0", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "1d24e6d0-baeb-4eb4-9f52-e0c8e3f3de6a", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "consent.screen.text" : "${rolesScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "d36dafb3-3bc8-4eb3-9908-63ead6b7c6f5", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "89303a1d-2a4f-4179-af11-701218b8d9b4", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + }, { + "id" : "ea0a62ef-7e0e-4f73-90ae-03244954bd58", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + } ] + }, { + "id" : "7c081dbc-5838-4b68-8da3-344d9bb5db29", + "name" : "acr", + "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "48eb8264-69ae-48d1-8e48-aea6a25ffffa", + "name" : "acr loa level", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-acr-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "7e691276-3a80-4721-a975-5c1e9c0f3d4b", + "name" : "offline_access", + "description" : "OpenID Connect built-in scope: offline_access", + "protocol" : "openid-connect", + "attributes" : { + "consent.screen.text" : "${offlineAccessScopeConsentText}", + "display.on.consent.screen" : "true" + } + }, { + "id" : "a02e25ad-763d-4fc7-8d3e-794ef8a93720", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${profileScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "d89569f3-cdcb-4dfa-adee-04f87b286e52", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + }, { + "id" : "dfaa465e-6060-480f-86df-df28b73b121a", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + }, { + "id" : "1ba3807a-7c3f-4cb7-a66d-8c511f8f8225", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + }, { + "id" : "83bf0116-dc01-43d9-aeba-ddd01521eb11", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "long" + } + }, { + "id" : "fdd112c7-13d4-408f-81f0-8b98cfd4e36b", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + }, { + "id" : "4611118f-0308-4bb9-ab80-39ffe25557e5", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "683b369c-53fe-43fd-916d-8897d4d0ec81", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + }, { + "id" : "e520b841-1f22-4d3b-a146-8bcdd1a04d2a", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "0c869236-1de3-495a-835c-c18f2d00f889", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + }, { + "id" : "8bf40003-26d0-4367-987b-7a8eded96141", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + }, { + "id" : "42dbe32f-d15b-4af2-b17e-d0a453a07be2", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "ab590bc9-fb2b-41b1-8a6c-133bd561f839", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "66a50932-4849-40d8-8046-be99ffca998c", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "98f48b49-dd40-438e-910a-d5eebe13c359", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "a7a3a1d9-c6c7-4c94-a592-0a3f2cd80c4b", + "name" : "microprofile-jwt", + "description" : "Microprofile - JWT built-in scope", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "a4ac06bb-75fd-4812-9b18-0224a88b9f73", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + }, { + "id" : "a208bb47-8c4d-4ef3-8e80-a5040e7d4565", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "multivalued" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "ef3ef615-295f-41f3-a5cc-aac78df7ee62", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${addressScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "55ec0ab8-4236-4eb5-acde-cb1a35c740d1", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "introspection.token.claim" : "true", + "user.attribute.postal_code" : "postal_code", + "userinfo.token.claim" : "true", + "user.attribute.street" : "street", + "id.token.claim" : "true", + "user.attribute.region" : "region", + "access.token.claim" : "true", + "user.attribute.locality" : "locality" + } + } ] + }, { + "id" : "9d7c8cf6-9be7-47db-9420-e25040cddc22", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${phoneScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "8f53e3a9-433d-41ce-90b9-c58fa65ae97b", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "29eca6bc-aff7-464b-88e4-af4533f201d3", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "99ac4d72-bdcb-4bd7-9ad7-1fe7813b3c70", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "4edc888c-2930-4a5b-a490-2250dd5c1657", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + } ], + "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins", "acr", "basic" ], + "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], + "browserSecurityHeaders" : { + "contentSecurityPolicyReportOnly" : "", + "xContentTypeOptions" : "nosniff", + "referrerPolicy" : "no-referrer", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection" : "1; mode=block", + "strictTransportSecurity" : "max-age=31536000; includeSubDomains" + }, + "smtpServer" : { }, + "loginTheme" : "keycloak", + "accountTheme" : "", + "adminTheme" : "", + "emailTheme" : "", + "eventsEnabled" : false, + "eventsListeners" : [ "jboss-logging" ], + "enabledEventTypes" : [ ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "identityProviders" : [ { + "alias" : "github", + "internalId" : "cf02af83-93f3-4a76-9613-c10f93e494f4", + "providerId" : "github", + "enabled" : true, + "updateProfileFirstLoginMode" : "on", + "trustEmail" : false, + "storeToken" : false, + "addReadTokenRoleOnCreate" : false, + "authenticateByDefault" : false, + "linkOnly" : false, + "config" : { + "syncMode" : "LEGACY", + "clientSecret" : "3869f5e38ad5f607ba53e598cfb54a8ae0e7b101", + "clientId" : "Ov23lik5qGjCQ2AP5gtD" + } + } ], + "identityProviderMappers" : [ ], + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "3c22dfa8-2c4d-463f-abd2-b29232d769f0", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "f6672321-55e1-419c-820e-fd88dcb350a9", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + }, { + "id" : "a9d0bfa2-89f5-47e5-a5fa-b15465162968", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "d9a7fe92-2b8a-4707-8e78-587f6f912c92", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "oidc-full-name-mapper", "saml-role-list-mapper" ] + } + }, { + "id" : "a4e3bcad-ac4b-4db3-a786-8a8fa175c956", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "saml-role-list-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper" ] + } + }, { + "id" : "ab021a5f-7816-4a4a-83ba-2d22402c7317", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "3aacbf88-0b04-4528-8fbd-15976d4153a1", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "a10a9820-2ef7-4362-b379-c45406886bed", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "f079ce52-e623-409b-bf95-adcd7fc71cdd", + "name" : "rsa-enc-generated", + "providerId" : "rsa-enc-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpAIBAAKCAQEAzPZdUq2F0UP5ehWIssZHG0kDEt7QE5FbJ2iQ3AUfKpVF1hynK0/rLR6cR9jyDzf27YjRMb9iNgzgXqPd2Y23S6vqvnGD8P6WXJvxMZhYkatRvFWPYHCiLiSv++e/1bQNxuAHlP3c9zrba5zzQSyvtVIqLjJyAbwArX/DgetHHYjq9LRw997j/tgRn1EJDp41EogmkKpUsO/uH36yLvoN7ZWko1SytDM/+xQqaxB9vpOMwvq+jHHLnbOCp9y+pOLfAnnRZtJk3UEdktJfP99ganI8ldvvq3rBEfjTqC35STzsOMt17FO6AP4TdisC6WmqazufIMxgLK8xIaiu2pxYEwIDAQABAoIBAAOiGBH41OS14pyDZlmAWSEv1P6wEP8HzSi1GmDVNB+n+SoVUjptLWIKOXCu4qlIHUEAqibvB0TvJqQrZuB7zFSPECme6qYcr3o4NN2/DQUoBNpSXoRERABFZRJaoeIwdlBXmW9g937oIngnllzcQMf1eOaDBmgLv/UKOr6v9rR33LteqPy4jktqPEf+xwILMaEr/AJLg8mSFKtAGiGZ51s8cUMwBNMmFTQYvLDGY9b3zjhyAv/YEhmnEez50Wbum8MIc9PNq5FhtFDNApXzKFkzIjy0A99mPQltwIeWBUYQWCQelUx09Z0ZfIPdZ6NwSGZ9unLe+XhFy35oQg7SzsECgYEA7N45yDqekyEgmxKzz+pxTu6ti3fGvEyNNruQxPF/Sn0LFhPi6iNQixjvA6EKsVHdZ8XvH6lo6LVmhUh0K0dYm0vMoE2z7WeS9tuor4wOqQTeWFlIkRivXtysTfW3z1o+Lzt7rJAOxTu/kA011T/p2XPiOx/blXccYU+672QxqokCgYEA3YRq1xbR2N5a+iZyYKA6MVxqRlpY/+m0EoQ/WIdDJYijAHnBXRX5a772g0K7nutXcN1DBdwwH5mX4WhnzL8gptSZS5yA81MF7D060i61g7CdlfLrIuOs0Haq6QG7/LV4RWMWDbIniakcKJL4b5XfbUEMXvXsb3SiOc1TA0kFFrsCgYEA7M9r82vvt+bJw1/aV4brC2ACL0prbTwXfl3daZiLi3wiRktRdIYj8zzVUMqDdy4CbcpsvDnRwb4CJkR+p/oncvNAPBATT8laG+UV65PZ4E8WwDtbGn0Ub4Gt3i9IOkzdmLkedzJ7IeMPLMSYSoAgmp+J0VSTYwX3YK6mlMmWAgkCgYEA1dnxXwa7vdckE10sjJYCuAaU3qh4RU80NbAQi4HB1Clt25avkxMUwO0RhTTWdpySxPYGr1Cb8NXR551ooCRf/E9AUtubLc0X6bJO5/yJ5cGK0Ok8EWmlO3dklh/DgTscCjiXYM9+Fgr5kT4Zs3gHw6zJqZ9XC16ZAp5zJrfGvuMCgYAQn26aVIDcQT3ijWQ/J4TWRFyy/gjdiOt5lQjMcaB3CT+ZdIKEpj7bCnFeGZ3yai0izCTjverZJ/qEubCIHrmjvmL1/OGwR42DghyIywAQ2R/UFKqawcpHW9RBCw5y2H2+QFi43CKPsik+Vd+lDEj01/sNEOFDtxCSWBHkQ2r9qQ==" ], + "keyUse" : [ "ENC" ], + "certificate" : [ "MIICnzCCAYcCBgGSIOAipDANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAhudXRzaGVsbDAeFw0yNDA5MjMyMTUxNTJaFw0zNDA5MjMyMTUzMzJaMBMxETAPBgNVBAMMCG51dHNoZWxsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzPZdUq2F0UP5ehWIssZHG0kDEt7QE5FbJ2iQ3AUfKpVF1hynK0/rLR6cR9jyDzf27YjRMb9iNgzgXqPd2Y23S6vqvnGD8P6WXJvxMZhYkatRvFWPYHCiLiSv++e/1bQNxuAHlP3c9zrba5zzQSyvtVIqLjJyAbwArX/DgetHHYjq9LRw997j/tgRn1EJDp41EogmkKpUsO/uH36yLvoN7ZWko1SytDM/+xQqaxB9vpOMwvq+jHHLnbOCp9y+pOLfAnnRZtJk3UEdktJfP99ganI8ldvvq3rBEfjTqC35STzsOMt17FO6AP4TdisC6WmqazufIMxgLK8xIaiu2pxYEwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQDDrlkC4E1kfzeczBnsiECC1CVxPfT5D9CNdXXAA1OgbaKLe2FvKgfK80Y2wmEWV18QjLBpTlp/Aa3KnYfwzxwigHVBA0i5Z9nHdMw3a68YMtQ4drCrp/XBvS+jT2L/HZGlTH3MNRvMHKcuc78FX/vtd9CZGcA52BUtC0o4vwyOl31Jf117Dc5pxRiBg8WItw2WPpYVnU7x+PRTfeVyRGu28k6saKzoFjMCFroyAUEhwxfEuxt+75ar8vmEkZzodiJU4DYfgzWLUGkxkbFRokwAw/KFMozjgaAR5OUqZcn29yezBMqJ65Bpms6IoNXpiEo4hLwoMmYY4lB7ZxnppxWf" ], + "priority" : [ "100" ], + "algorithm" : [ "RSA-OAEP" ] + } + }, { + "id" : "4a630ed9-db18-45c5-b285-c550fb1c6606", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "da49cf04-046f-4721-bd10-bfdceac9cbbd" ], + "secret" : [ "IwAmNIEnrifbve0ZXJzdKg" ], + "priority" : [ "100" ] + } + }, { + "id" : "fd258d82-129a-4d78-a93b-52cc37ae77db", + "name" : "fallback-ES256", + "providerId" : "ecdsa-generated", + "subComponents" : { }, + "config" : { + "ecdsaPublicKey" : [ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEChyakaf24ospBi9idCQTo2MsSg/oJsiqs3NlJ76SkK2ZVQytSxGKuim3uc70AJvja6qYIE8FkXlQJR3GPr4nMw==" ], + "ecdsaEllipticCurveKey" : [ "P-256" ], + "ecdsaPrivateKey" : [ "MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCCqvLfC1oqsIBBfkI4ySBhR7itPS4MajczCKhnSGn1fkQ==" ], + "priority" : [ "-100" ] + } + }, { + "id" : "bcceacf9-8b3a-4b61-abba-9741cf2589dc", + "name" : "hmac-generated-hs512", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "5009ff4c-6866-4e0d-bd04-d6e6e40fdc15" ], + "secret" : [ "mo8Ctme-HaJwsFx5u2iwopw38pFMnObXrP8Ac4puRzsjH14Myn5Ea-DZeNGkzd5nkj-bxGugqNfHfS7QfzS_8iTPzTkg8rfChH673PMdaS7J5iK1p_nxfhKOpyAW1RMr5JOtVRgxihmiut9ee_UfDvx7zSlSH8K5reS1AvVy1Mk" ], + "priority" : [ "100" ], + "algorithm" : [ "HS512" ] + } + }, { + "id" : "144e7278-2b96-4f75-b79d-e9eb63aa43c0", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpAIBAAKCAQEA4uJjlBCZIKX6wWKnOsppXDgfIc4LZCFg5G3tIzXWAfCj23EGkb03f+AWBTXbBDrWQysxC5pWB/0L7w2/OpIWXajf7bTIvYA32AURhW7Z+CtWLI7mUkUI5E4rAMeS48aRdRlXnuHfyCrDvqdXIal93HM1dmfBzh8eQDjUfn+ooxss1TNvsnj6S5bJtPIp/ikAArLXwBkCjQy2aSqokH66DJgJHOhJMR+xLfQ8obfpimj+Ez1nLxnlhvyIbtltWa0vQEYkjK1/CIfya+8DqOrFm0uIOjWhWmQVXZTn12B8FaP4mGjeO4/liAtW7XHQo7WFjB9boyKI2GwXYHX9CamViQIDAQABAoIBABl/lrAhaOo4ySySdyel768h7grFLrXNRAfMHGRyhJeReULcah/3L113XFYv0ipgp+ui1ydeDCjn7R9L0Nfm6u1SAT+ka5KTjkMgd7KCravEyBGoKjWpBUmuSMo0w7sGWhraUyT7ruQevULIKQRTGX5s8r9YxBjTmblfQa9pTrUszp7dSfD31gpFtnjTOPI21xlRhFnNJvUCSsAz2VVBu99KjCv6uOHD/pMgxO+zp0A84BO0KHMEcpB/C0/CvOSSFsGqOAPGMsPbm1Q4/tAKnMp87ENHWubFv3MYccj7hYPiOuxj5eBGlUEkNRlKdjaaeNRjEdT0OM8QnIvCD2tjCnECgYEA9oUYW+9eqjEtRvx+LYEwOzjCiaG+RltwVUkyEA3NIBOW8BV2ZI0alC8ZYoyciEGV/WNaOt0z4xiH1l+n7xHyz7TCh2yC3bhuH999aw130E6k2I6yx5kYyEOrRGQhsi5auJmjnrGIwtkB4bOZvUbn6P5Obc2vsCp5DYqWVwN3M5kCgYEA65v9AGbwTvp97AiZz2JZzelTJPxAzlKhWFGG7gA9Js3BjMCYkdhWlvnhn3p6RfKFoX9PcxjPGOiHM6ZWdx0jVUCaviiFx5MFjFPbPDH2poTH7DtVGpTfogkz09xgDkdP02e0VXpcyyp6gq5wfGWGb3c5HE2pzkpG8EI3hqhbp3ECgYEAhO7Ib+roVUYncDv/nnInnAfDf4wkmrP8I0FRKa8HieCGZ/hq21XrmzS6r5W7Yw5a30SQB2X48ODtfwAeAqDfGnoS3Av7law6Vh2h9/RPQ5jk85IffdpkrrkuxbZpJTgx25Gd1ZlOciOrDBZZNOPjcpSPnk5oCsscc9zjrRBFWyECgYEAzGPMzk1+iLUrCdjIPa0PRN71Xu9p9NKf3zMSf2M7qW7zSfxGHrdzHpP1k9i3O/jQzjHYJXvPJMeLilXxSnG/lhRuaSpUK7ayKHMSjBy34OrtWFR8VovxmOqsyEy0E47vg/DB8yksWJ8oGjfjozabshTWOWRyO2AaPBDlhG6G3DECgYBnQPQLpK4Oz+SwQWNctkdc5sNgaJcgoaNX8lC19+1w8oMPYHQDNph1wTTGJAivF165L95wzfxJvf1Qj5o7FdodmB6YJpMb5U7webDtEygwzSBxce6tkTFD6A6IC/WHilerQ7PbBSamSPFg0BapUEzSO1FxCU69GFe/EQZX91F5wQ==" ], + "keyUse" : [ "SIG" ], + "certificate" : [ "MIICnzCCAYcCBgGSIOAhwjANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAhudXRzaGVsbDAeFw0yNDA5MjMyMTUxNTJaFw0zNDA5MjMyMTUzMzJaMBMxETAPBgNVBAMMCG51dHNoZWxsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4uJjlBCZIKX6wWKnOsppXDgfIc4LZCFg5G3tIzXWAfCj23EGkb03f+AWBTXbBDrWQysxC5pWB/0L7w2/OpIWXajf7bTIvYA32AURhW7Z+CtWLI7mUkUI5E4rAMeS48aRdRlXnuHfyCrDvqdXIal93HM1dmfBzh8eQDjUfn+ooxss1TNvsnj6S5bJtPIp/ikAArLXwBkCjQy2aSqokH66DJgJHOhJMR+xLfQ8obfpimj+Ez1nLxnlhvyIbtltWa0vQEYkjK1/CIfya+8DqOrFm0uIOjWhWmQVXZTn12B8FaP4mGjeO4/liAtW7XHQo7WFjB9boyKI2GwXYHX9CamViQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCBNGmP+KzmSjrbXGbEwrOjQjirqi+lxbwBlxKDShpXZMx9e82XJPHwjUWJeBtNnMj8twXYOOGCTvNLGO6/ELTPzmKh5uJq/NAQYPhiieCf6H4dIf4jykMEmC0S2RsJpQCTTz1L+z+9GToTxLB6pkUPnz6rvqvyDtYBgz7EJOHBhwBbP3OTIUPVtXAFj48hXLw4FK7oUn0tSc378Nvtuj3enE/8DZ5EFgHhw9PZjyljLyNSJyf/ihZlVIiy+jxm4yU9mshqF5n1orqpflJpCnIWVPt7//9AdQ120Y/0YnwpGjsTxg39a9grKzsVosrRPY9MsoGnuwOlUKcZi4jj9Ox4" ], + "priority" : [ "100" ] + } + } ] + }, + "internationalizationEnabled" : false, + "supportedLocales" : [ ], + "authenticationFlows" : [ { + "id" : "c552fea2-2cd3-400e-912e-e61b4a759a33", + "alias" : "Account verification options", + "description" : "Method with which to verity the existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-email-verification", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Verify Existing Account by Re-authentication", + "userSetupAllowed" : false + } ] + }, { + "id" : "76c9cb0e-6247-431e-b75d-2569c2c8906e", + "alias" : "Browser - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "a13cd0d1-cddd-440c-beeb-c2e36763f0b6", + "alias" : "Direct Grant - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "c26dc239-0ee9-4324-bd17-f407b53079e5", + "alias" : "First broker login - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "a3fb308b-3159-46c8-be96-91e0c687c2e4", + "alias" : "Handle Existing Account", + "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-confirm-link", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Account verification options", + "userSetupAllowed" : false + } ] + }, { + "id" : "c13b5a6b-01c7-4154-80da-d84ad57ced5a", + "alias" : "Reset - Conditional OTP", + "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "04ac72d4-c0a0-429c-bf2a-a26531ebf039", + "alias" : "User creation or linking", + "description" : "Flow for the existing/non-existing user alternatives", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "create unique user config", + "authenticator" : "idp-create-user-if-unique", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false + } ] + }, { + "id" : "ba4af5e1-c841-4313-98c3-747d097929eb", + "alias" : "Verify Existing Account by Re-authentication", + "description" : "Reauthentication of existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "First broker login - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "238a1e24-e7eb-4195-9001-27a3d6116ff0", + "alias" : "browser", + "description" : "browser based authentication", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-cookie", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-spnego", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "identity-provider-redirector", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 25, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "forms", + "userSetupAllowed" : false + } ] + }, { + "id" : "59afc123-384a-44ca-859a-73c6bb1208e3", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-secret-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-x509", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 40, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "fcf8c8d1-00fe-4d7a-b003-0f97913bd670", + "alias" : "direct grant", + "description" : "OpenID Connect Resource Owner Grant", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "direct-grant-validate-username", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "Direct Grant - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "00521719-1597-454f-a32d-22ab105b6011", + "alias" : "docker auth", + "description" : "Used by Docker clients to authenticate against the IDP", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "docker-http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "15800909-918c-4243-ba50-2e1f2229ea09", + "alias" : "first broker login", + "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "review profile config", + "authenticator" : "idp-review-profile", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "User creation or linking", + "userSetupAllowed" : false + } ] + }, { + "id" : "01616a3f-57bd-4ed7-afc5-f5eae75dac81", + "alias" : "forms", + "description" : "Username, password, otp and other auth forms.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Browser - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "e9bed59b-3077-455f-9f37-abfc17b28e7e", + "alias" : "registration", + "description" : "registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : true, + "flowAlias" : "registration form", + "userSetupAllowed" : false + } ] + }, { + "id" : "dcb5a291-7fff-450f-8495-80535f958bcb", + "alias" : "registration form", + "description" : "registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-password-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 50, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-recaptcha-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 60, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-terms-and-conditions", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 70, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "bd4b9d12-2a46-4f13-913c-2b045ebe2b7c", + "alias" : "reset credentials", + "description" : "Reset credentials for a user if they forgot their password or something", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "reset-credentials-choose-user", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-credential-email", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 40, + "autheticatorFlow" : true, + "flowAlias" : "Reset - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "98e4b385-f5df-45c4-bd46-e585700ec414", + "alias" : "saml ecp", + "description" : "SAML ECP Profile Authentication Flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + } ], + "authenticatorConfig" : [ { + "id" : "13f71db7-a606-4ed2-be30-68ee880e05dd", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "3399153b-e46b-4315-83ef-f669688b38ff", + "alias" : "review profile config", + "config" : { + "update.profile.on.first.login" : "missing" + } + } ], + "requiredActions" : [ { + "alias" : "TERMS_AND_CONDITIONS", + "name" : "Terms and Conditions", + "providerId" : "TERMS_AND_CONDITIONS", + "enabled" : true, + "defaultAction" : false, + "priority" : 10, + "config" : { } + }, { + "alias" : "CONFIGURE_TOTP", + "name" : "Configure OTP", + "providerId" : "CONFIGURE_TOTP", + "enabled" : true, + "defaultAction" : false, + "priority" : 20, + "config" : { } + }, { + "alias" : "UPDATE_PASSWORD", + "name" : "Update Password", + "providerId" : "UPDATE_PASSWORD", + "enabled" : true, + "defaultAction" : false, + "priority" : 30, + "config" : { } + }, { + "alias" : "UPDATE_PROFILE", + "name" : "Update Profile", + "providerId" : "UPDATE_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 40, + "config" : { } + }, { + "alias" : "VERIFY_EMAIL", + "name" : "Verify Email", + "providerId" : "VERIFY_EMAIL", + "enabled" : true, + "defaultAction" : false, + "priority" : 50, + "config" : { } + }, { + "alias" : "delete_account", + "name" : "Delete Account", + "providerId" : "delete_account", + "enabled" : true, + "defaultAction" : false, + "priority" : 60, + "config" : { } + }, { + "alias" : "webauthn-register", + "name" : "Webauthn Register", + "providerId" : "webauthn-register", + "enabled" : true, + "defaultAction" : false, + "priority" : 70, + "config" : { } + }, { + "alias" : "webauthn-register-passwordless", + "name" : "Webauthn Register Passwordless", + "providerId" : "webauthn-register-passwordless", + "enabled" : true, + "defaultAction" : false, + "priority" : 80, + "config" : { } + }, { + "alias" : "VERIFY_PROFILE", + "name" : "Verify Profile", + "providerId" : "VERIFY_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 90, + "config" : { } + }, { + "alias" : "delete_credential", + "name" : "Delete Credential", + "providerId" : "delete_credential", + "enabled" : true, + "defaultAction" : false, + "priority" : 100, + "config" : { } + }, { + "alias" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "firstBrokerLoginFlow" : "first broker login", + "attributes" : { + "cibaBackchannelTokenDeliveryMode" : "poll", + "cibaAuthRequestedUserHint" : "login_hint", + "oauth2DevicePollingInterval" : "5", + "clientOfflineSessionMaxLifespan" : "0", + "clientSessionIdleTimeout" : "0", + "clientOfflineSessionIdleTimeout" : "0", + "cibaInterval" : "5", + "realmReusableOtpCode" : "false", + "cibaExpiresIn" : "120", + "oauth2DeviceCodeLifespan" : "600", + "parRequestUriLifespan" : "60", + "clientSessionMaxLifespan" : "0", + "organizationsEnabled" : "false" + }, + "keycloakVersion" : "25.0.6", + "userManagedAccessAllowed" : false, + "organizationsEnabled" : false, + "clientProfiles" : { + "profiles" : [ ] + }, + "clientPolicies" : { + "policies" : [ ] + } +} diff --git a/tests/keycloak_data/keycloak-export/nutshell-users-0.json b/tests/keycloak_data/keycloak-export/nutshell-users-0.json new file mode 100644 index 0000000..9b9183e --- /dev/null +++ b/tests/keycloak_data/keycloak-export/nutshell-users-0.json @@ -0,0 +1,53 @@ +{ + "realm" : "nutshell", + "users" : [ { + "id" : "c4fc742a-700f-4c83-96f2-8777c8bb56d1", + "username" : "asd@asd.com", + "firstName" : "asd", + "lastName" : "asd", + "email" : "asd@asd.com", + "emailVerified" : false, + "createdTimestamp" : 1727128876722, + "enabled" : true, + "totp" : false, + "credentials" : [ { + "id" : "23ea2b79-9c09-4133-b53b-2708258da890", + "type" : "password", + "createdDate" : 1727128876754, + "secretData" : "{\"value\":\"fDXqE3IjxS5uIYfn9eYgW5GwokWvGsg2wWY0lOgeYyE=\",\"salt\":\"Wlb5f8yPTh4QreuC99b7Zg==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-nutshell" ], + "clientConsents" : [ { + "clientId" : "cashu-client", + "grantedClientScopes" : [ "email", "roles", "profile" ], + "createdDate" : 1732651444894, + "lastUpdatedDate" : 1732651444908 + } ], + "notBefore" : 0, + "groups" : [ ] + }, { + "id" : "43a16bd6-f5c5-4dfa-bcd4-6a5540564797", + "username" : "callebtc@protonmail.com", + "firstName" : "asdasd", + "lastName" : "asdasdasdasd", + "email" : "callebtc@protonmail.com", + "emailVerified" : false, + "createdTimestamp" : 1732639511706, + "enabled" : true, + "totp" : false, + "credentials" : [ ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "federatedIdentities" : [ { + "identityProvider" : "github", + "userId" : "93376500", + "userName" : "callebtc" + } ], + "realmRoles" : [ "default-roles-nutshell" ], + "notBefore" : 0, + "groups" : [ ] + } ] +} diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 886ce73..9135ace 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -183,14 +183,14 @@ async def test_mint(wallet1: Wallet): assert wallet1.balance == 64 # verify that proofs in proofs_used db have the same mint_id as the invoice in the db - mint_quote = await get_bolt11_mint_quote(db=wallet1.db, quote=mint_quote.quote) - assert mint_quote + mint_quote_2 = await get_bolt11_mint_quote(db=wallet1.db, quote=mint_quote.quote) + assert mint_quote_2 proofs_minted = await get_proofs( - db=wallet1.db, mint_id=mint_quote.quote, table="proofs" + db=wallet1.db, mint_id=mint_quote_2.quote, table="proofs" ) assert len(proofs_minted) == len(expected_proof_amounts) assert all([p.amount in expected_proof_amounts for p in proofs_minted]) - assert all([p.mint_id == mint_quote.quote for p in proofs_minted]) + assert all([p.mint_id == mint_quote_2.quote for p in proofs_minted]) @pytest.mark.asyncio @@ -356,7 +356,7 @@ async def test_swap_to_send_more_than_balance(wallet1: Wallet): await wallet1.mint(64, quote_id=mint_quote.quote) await assert_err( wallet1.swap_to_send(wallet1.proofs, 128, set_reserved=True), - "balance too low.", + "Balance too low", ) assert wallet1.balance == 64 assert wallet1.available_balance == 64 diff --git a/tests/test_wallet_auth.py b/tests/test_wallet_auth.py new file mode 100644 index 0000000..baf62a6 --- /dev/null +++ b/tests/test_wallet_auth.py @@ -0,0 +1,251 @@ +import hashlib +import os +import shutil +from pathlib import Path + +import pytest +import pytest_asyncio + +from cashu.core.base import Unit +from cashu.core.crypto.keys import random_hash +from cashu.core.crypto.secp import PrivateKey +from cashu.core.errors import ( + BlindAuthFailedError, + BlindAuthRateLimitExceededError, + ClearAuthFailedError, +) +from cashu.core.settings import settings +from cashu.wallet.auth.auth import WalletAuth +from cashu.wallet.wallet import Wallet +from tests.conftest import SERVER_ENDPOINT +from tests.helpers import assert_err + + +@pytest_asyncio.fixture(scope="function") +async def wallet(): + dirpath = Path("test_data/wallet") + if dirpath.exists() and dirpath.is_dir(): + shutil.rmtree(dirpath) + wallet = await Wallet.with_db( + url=SERVER_ENDPOINT, + db="test_data/wallet", + name="wallet", + ) + await wallet.load_mint() + yield wallet + + +@pytest.mark.skipif( + not settings.mint_require_auth, + reason="settings.mint_require_auth is False", +) +@pytest.mark.asyncio +async def test_wallet_auth_password(wallet: Wallet): + auth_wallet = await WalletAuth.with_db( + url=wallet.url, + db=wallet.db.db_location, + username="asd@asd.com", + password="asdasd", + ) + + requires_auth = await auth_wallet.init_auth_wallet( + wallet.mint_info, mint_auth_proofs=False + ) + assert requires_auth + + # expect JWT (CAT) with format ey*.ey* + assert auth_wallet.oidc_client.access_token + assert auth_wallet.oidc_client.access_token.split(".")[0].startswith("ey") + assert auth_wallet.oidc_client.access_token.split(".")[1].startswith("ey") + + +@pytest.mark.skipif( + not settings.mint_require_auth, + reason="settings.mint_require_auth is False", +) +@pytest.mark.asyncio +async def test_wallet_auth_wrong_password(wallet: Wallet): + auth_wallet = await WalletAuth.with_db( + url=wallet.url, + db=wallet.db.db_location, + username="asd@asd.com", + password="wrong_password", + ) + + await assert_err(auth_wallet.init_auth_wallet(wallet.mint_info), "401 Unauthorized") + + +@pytest.mark.skipif( + not settings.mint_require_auth, + reason="settings.mint_require_auth is False", +) +@pytest.mark.asyncio +async def test_wallet_auth_mint(wallet: Wallet): + auth_wallet = await WalletAuth.with_db( + url=wallet.url, + db=wallet.db.db_location, + username="asd@asd.com", + password="asdasd", + ) + + requires_auth = await auth_wallet.init_auth_wallet(wallet.mint_info) + assert requires_auth + + await auth_wallet.load_proofs() + assert len(auth_wallet.proofs) == auth_wallet.mint_info.bat_max_mint + + +@pytest.mark.skipif( + not settings.mint_require_auth, + reason="settings.mint_require_auth is False", +) +@pytest.mark.asyncio +async def test_wallet_auth_mint_manually(wallet: Wallet): + auth_wallet = await WalletAuth.with_db( + url=wallet.url, + db=wallet.db.db_location, + username="asd@asd.com", + password="asdasd", + ) + + requires_auth = await auth_wallet.init_auth_wallet( + wallet.mint_info, mint_auth_proofs=False + ) + assert requires_auth + assert len(auth_wallet.proofs) == 0 + + await auth_wallet.mint_blind_auth() + assert len(auth_wallet.proofs) == auth_wallet.mint_info.bat_max_mint + + +@pytest.mark.skipif( + not settings.mint_require_auth, + reason="settings.mint_require_auth is False", +) +@pytest.mark.asyncio +async def test_wallet_auth_mint_manually_invalid_cat(wallet: Wallet): + auth_wallet = await WalletAuth.with_db( + url=wallet.url, + db=wallet.db.db_location, + username="asd@asd.com", + password="asdasd", + ) + + requires_auth = await auth_wallet.init_auth_wallet( + wallet.mint_info, mint_auth_proofs=False + ) + assert requires_auth + assert len(auth_wallet.proofs) == 0 + + # invalidate CAT in the database + auth_wallet.oidc_client.access_token = random_hash() + + # this is the code executed in auth_wallet.mint_blind_auth(): + clear_auth_token = auth_wallet.oidc_client.access_token + if not clear_auth_token: + raise Exception("No clear auth token available.") + + amounts = auth_wallet.mint_info.bat_max_mint * [1] # 1 AUTH tokens + secrets = [hashlib.sha256(os.urandom(32)).hexdigest() for _ in amounts] + rs = [PrivateKey(privkey=os.urandom(32), raw=True) for _ in amounts] + outputs, rs = auth_wallet._construct_outputs(amounts, secrets, rs) + + # should fail because of invalid CAT + await assert_err( + auth_wallet.blind_mint_blind_auth(clear_auth_token, outputs), + ClearAuthFailedError.detail, + ) + + +@pytest.mark.skipif( + not settings.mint_require_auth, + reason="settings.mint_require_auth is False", +) +@pytest.mark.asyncio +async def test_wallet_auth_invoice(wallet: Wallet): + # should fail, wallet error + await assert_err(wallet.mint_quote(10, Unit.sat), "Mint requires blind auth") + + auth_wallet = await WalletAuth.with_db( + url=wallet.url, + db=wallet.db.db_location, + username="asd@asd.com", + password="asdasd", + ) + requires_auth = await auth_wallet.init_auth_wallet(wallet.mint_info) + assert requires_auth + + await auth_wallet.load_proofs() + assert len(auth_wallet.proofs) == auth_wallet.mint_info.bat_max_mint + + wallet.auth_db = auth_wallet.db + wallet.auth_keyset_id = auth_wallet.keyset_id + + # should succeed + await wallet.mint_quote(10, Unit.sat) + + +@pytest.mark.skipif( + not settings.mint_require_auth, + reason="settings.mint_require_auth is False", +) +@pytest.mark.asyncio +async def test_wallet_auth_invoice_invalid_bat(wallet: Wallet): + # should fail, wallet error + await assert_err(wallet.mint_quote(10, Unit.sat), "Mint requires blind auth") + + auth_wallet = await WalletAuth.with_db( + url=wallet.url, + db=wallet.db.db_location, + username="asd@asd.com", + password="asdasd", + ) + requires_auth = await auth_wallet.init_auth_wallet(wallet.mint_info) + assert requires_auth + + await auth_wallet.load_proofs() + assert len(auth_wallet.proofs) == auth_wallet.mint_info.bat_max_mint + + # invalidate blind auth proofs + for p in auth_wallet.proofs: + await auth_wallet.db.execute( + f"UPDATE proofs SET secret = '{random_hash()}' WHERE secret = '{p.secret}'" + ) + + wallet.auth_db = auth_wallet.db + wallet.auth_keyset_id = auth_wallet.keyset_id + + # blind auth failed + await assert_err(wallet.mint_quote(10, Unit.sat), BlindAuthFailedError.detail) + + +@pytest.mark.skipif( + not settings.mint_require_auth, + reason="settings.mint_require_auth is False", +) +@pytest.mark.asyncio +async def test_wallet_auth_rate_limit(wallet: Wallet): + auth_wallet = await WalletAuth.with_db( + url=wallet.url, + db=wallet.db.db_location, + username="asd@asd.com", + password="asdasd", + ) + requires_auth = await auth_wallet.init_auth_wallet( + wallet.mint_info, mint_auth_proofs=False + ) + assert requires_auth + + errored = False + for _ in range(100): + try: + await auth_wallet.mint_blind_auth() + except Exception as e: + assert BlindAuthRateLimitExceededError.detail in str(e) + errored = True + break + + assert errored + + # should have minted at least twice + assert len(auth_wallet.proofs) > auth_wallet.mint_info.bat_max_mint diff --git a/tests/test_wallet_cli.py b/tests/test_wallet_cli.py index fe6f06f..94cce01 100644 --- a/tests/test_wallet_cli.py +++ b/tests/test_wallet_cli.py @@ -54,7 +54,7 @@ async def init_wallet(): wallet = await Wallet.with_db( url=settings.mint_url, db="test_data/test_cli_wallet", - name="wallet", + name="test_cli_wallet", ) await wallet.load_proofs() return wallet @@ -411,7 +411,7 @@ def test_wallets(cli_prefix): print("WALLETS") # on github this is empty if len(result.output): - assert "test_cli_wallet" in result.output + assert "wallet" in result.output assert result.exit_code == 0 @@ -474,7 +474,7 @@ def test_send_too_much(mint, cli_prefix): cli, [*cli_prefix, "send", "100000"], ) - assert "balance too low" in str(result.exception) + assert "Balance too low" in str(result.exception) def test_receive_tokenv3(mint, cli_prefix):