Blind authentication (#675)

* 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
This commit is contained in:
callebtc
2025-01-29 22:48:51 -06:00
committed by GitHub
parent b67ffd8705
commit a0ef44dba0
58 changed files with 8188 additions and 701 deletions

View File

@@ -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(