mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 02:24:20 +01:00
* auth server * cleaning up * auth ledger class * class variables -> instance variables * annotations * add models and api route * custom amount and api prefix * add auth db * blind auth token working * jwt working * clean up * JWT works * using openid connect server * use oauth server with password flow * new realm * add keycloak docker * hopefully not garbage * auth works * auth kinda working * fix cli * auth works for send and receive * pass auth_db to Wallet * auth in info * refactor * fix supported * cache mint info * fix settings and endpoints * add description to .env.example * track changes for openid connect client * store mint in db * store credentials * clean up v1_api.py * load mint info into auth wallet * fix first login * authenticate if refresh token fails * clear auth also middleware * use regex * add cli command * pw works * persist keyset amounts * add errors.py * do not start auth server if disabled in config * upadte poetry * disvoery url * fix test * support device code flow * adopt latest spec changes * fix code flow * mint max bat dynamic * mypy ignore * fix test * do not serialize amount in authproof * all auth flows working * fix tests * submodule * refactor * test * dont sleep * test * add wallet auth tests * test differently * test only keycloak for now * fix creds * daemon * fix test * install everything * install jinja * delete wallet for every test * auth: use global rate limiter * test auth rate limit * keycloak hostname * move keycloak test data * reactivate all tests * add readme * load proofs * remove unused code * remove unused code * implement change suggestions by ok300 * add error codes * test errors
227 lines
8.4 KiB
Python
227 lines
8.4 KiB
Python
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,
|
|
MELT_NUT,
|
|
MINT_NUT,
|
|
MINT_QUOTE_SIGNATURE_NUT,
|
|
MPP_NUT,
|
|
P2PK_NUT,
|
|
RESTORE_NUT,
|
|
SCRIPT_NUT,
|
|
STATE_NUT,
|
|
WEBSOCKETS_NUT,
|
|
)
|
|
from ..core.settings import settings
|
|
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, 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)
|
|
mint_features = self.add_mpp_features(mint_features)
|
|
mint_features = self.add_websocket_features(mint_features)
|
|
mint_features = self.add_cache_features(mint_features)
|
|
|
|
return mint_features
|
|
|
|
def create_mint_features(self) -> Dict[int, Union[List[Any], Dict[str, Any]]]:
|
|
mint_method_settings: List[MintMethodSetting] = []
|
|
for method, unit_dict in self.backends.items():
|
|
for unit in unit_dict.keys():
|
|
mint_setting = MintMethodSetting(method=method.name, unit=unit.name)
|
|
if settings.mint_max_peg_in:
|
|
mint_setting.max_amount = settings.mint_max_peg_in
|
|
mint_setting.min_amount = 0
|
|
mint_method_settings.append(mint_setting)
|
|
mint_setting.description = unit_dict[unit].supports_description
|
|
melt_method_settings: List[MeltMethodSetting] = []
|
|
for method, unit_dict in self.backends.items():
|
|
for unit in unit_dict.keys():
|
|
melt_setting = MeltMethodSetting(method=method.name, unit=unit.name)
|
|
if settings.mint_max_peg_out:
|
|
melt_setting.max_amount = settings.mint_max_peg_out
|
|
melt_setting.min_amount = 0
|
|
melt_method_settings.append(melt_setting)
|
|
|
|
mint_features: Dict[int, Union[List[Any], Dict[str, Any]]] = {
|
|
MINT_NUT: dict(
|
|
methods=mint_method_settings,
|
|
disabled=settings.mint_peg_out_only,
|
|
),
|
|
MELT_NUT: dict(
|
|
methods=melt_method_settings,
|
|
disabled=False,
|
|
),
|
|
}
|
|
return mint_features
|
|
|
|
def add_supported_features(
|
|
self, mint_features: Dict[int, Union[List[Any], Dict[str, Any]]]
|
|
):
|
|
supported_dict = dict(supported=True)
|
|
mint_features[STATE_NUT] = supported_dict
|
|
mint_features[FEE_RETURN_NUT] = supported_dict
|
|
mint_features[RESTORE_NUT] = supported_dict
|
|
mint_features[SCRIPT_NUT] = supported_dict
|
|
mint_features[P2PK_NUT] = supported_dict
|
|
mint_features[DLEQ_NUT] = supported_dict
|
|
mint_features[HTLC_NUT] = supported_dict
|
|
mint_features[MINT_QUOTE_SIGNATURE_NUT] = supported_dict
|
|
return mint_features
|
|
|
|
def add_mpp_features(
|
|
self, mint_features: Dict[int, Union[List[Any], Dict[str, Any]]]
|
|
):
|
|
# signal which method-unit pairs support MPP
|
|
mpp_features = []
|
|
for method, unit_dict in self.backends.items():
|
|
for unit in unit_dict.keys():
|
|
if unit_dict[unit].supports_mpp:
|
|
mpp_features.append({"method": method.name, "unit": unit.name})
|
|
|
|
if mpp_features:
|
|
mint_features[MPP_NUT] = dict(methods=mpp_features)
|
|
|
|
return mint_features
|
|
|
|
def add_websocket_features(
|
|
self, mint_features: Dict[int, Union[List[Any], Dict[str, Any]]]
|
|
):
|
|
# specify which websocket features are supported
|
|
# these two are supported by default
|
|
websocket_features: Dict[str, List[Dict[str, Union[str, List[str]]]]] = {
|
|
_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]:
|
|
for unit in unit_dict.keys():
|
|
websocket_features[_SUPPORTED].append(
|
|
{
|
|
_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] = (
|
|
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(
|
|
self, mint_features: Dict[int, Union[List[Any], Dict[str, Any]]]
|
|
):
|
|
if settings.mint_redis_cache_enabled:
|
|
cache_features: dict[str, list[dict[str, str]] | int] = {
|
|
"cached_endpoints": [
|
|
{
|
|
"method": "POST",
|
|
"path": "/v1/mint/bolt11",
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/v1/melt/bolt11",
|
|
},
|
|
{
|
|
"method": "POST",
|
|
"path": "/v1/swap",
|
|
},
|
|
]
|
|
}
|
|
if settings.mint_redis_cache_ttl:
|
|
cache_features["ttl"] = settings.mint_redis_cache_ttl
|
|
|
|
mint_features[CACHE_NUT] = cache_features
|
|
return mint_features
|