Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions cashu/core/mint_info.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import json
import re
from typing import Any, Dict, List, Optional

from pydantic import BaseModel
Expand All @@ -9,6 +8,25 @@
from .nuts.nuts import BLIND_AUTH_NUT, CLEAR_AUTH_NUT, MPP_NUT, WEBSOCKETS_NUT


def _match_protected_endpoint(endpoint_path: str, request_path: str) -> bool:
"""
Match a request path against a protected endpoint path using wildcard rules.

Rules (per NUT-21/NUT-22):
1. Exact match: no trailing '*' -> request path MUST equal endpoint_path
2. Prefix match: ends with '*' -> request path MUST start with the prefix ('*' removed)

The '*' wildcard, if present, MUST be the final character only.
"""
if endpoint_path.endswith("*"):
# Prefix match: path must start with the prefix (excluding the trailing '*')
prefix = endpoint_path[:-1] # Remove the trailing '*'
return request_path.startswith(prefix)
else:
# Exact match
return request_path == endpoint_path


class MintInfo(BaseModel):
name: Optional[str]
pubkey: Optional[str]
Expand Down Expand Up @@ -92,7 +110,9 @@ def requires_clear_auth_path(self, method: str, path: str) -> bool:
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):
if method == endpoint.method and _match_protected_endpoint(
endpoint.path, path
):
return True
return False

Expand Down Expand Up @@ -122,6 +142,8 @@ def requires_blind_auth_path(self, method: str, path: str) -> bool:
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):
if method == endpoint.method and _match_protected_endpoint(
endpoint.path, path
):
return True
return False