fix: use mTLS client cert to bypass PoW on flaskpaste

When secrets/flaskpaste/derp.crt and derp.key are present, load them
into the SSL context for mutual TLS auth and skip the PoW challenge
entirely. Fall back to PoW only when no client cert is available.
This commit is contained in:
user
2026-02-16 23:13:09 +01:00
parent 3cdc00c285
commit ffa75670e2
2 changed files with 39 additions and 44 deletions

View File

@@ -29,13 +29,18 @@ def _get_base_url(bot) -> str:
return url.rstrip("/")
def _has_client_cert() -> bool:
"""Check if mTLS client cert and key are available."""
return (_CERT_DIR / "derp.crt").exists() and (_CERT_DIR / "derp.key").exists()
def _ssl_context() -> ssl.SSLContext:
"""Build SSL context with custom CA cert if available."""
"""Build SSL context, loading client cert for mTLS if available."""
ctx = ssl.create_default_context()
cert_path = _CERT_DIR / "derp.crt"
if cert_path.exists():
ctx = ssl.create_default_context(cafile=str(cert_path))
else:
ctx = ssl.create_default_context()
key_path = _CERT_DIR / "derp.key"
if cert_path.exists() and key_path.exists():
ctx.load_cert_chain(str(cert_path), str(key_path))
return ctx
@@ -74,55 +79,45 @@ def _get_challenge(base_url: str) -> dict:
return json.loads(resp.read())
def _create_paste(base_url: str, content: str) -> str:
"""Challenge + solve + POST / to create a paste. Returns paste URL."""
def _pow_headers(base_url: str) -> dict:
"""Solve PoW challenge and return auth headers. Empty dict if mTLS."""
if _has_client_cert():
return {}
ch = _get_challenge(base_url)
nonce = ch["nonce"]
difficulty = ch["difficulty"]
token = ch["token"]
solution = _solve_pow(nonce, difficulty)
solution = _solve_pow(ch["nonce"], ch["difficulty"])
return {
"X-PoW-Token": ch["token"],
"X-PoW-Solution": str(solution),
}
def _create_paste(base_url: str, content: str) -> str:
"""POST / to create a paste. Uses mTLS or PoW. Returns paste URL."""
headers = {
"Content-Type": "application/json",
"User-Agent": "derp-bot",
**_pow_headers(base_url),
}
data = json.dumps({"content": content}).encode()
req = urllib.request.Request(
base_url,
data=data,
headers={
"Content-Type": "application/json",
"X-PoW-Token": token,
"X-PoW-Solution": str(solution),
"User-Agent": "derp-bot",
},
)
req = urllib.request.Request(base_url, data=data, headers=headers)
ctx = _ssl_context()
with urllib.request.urlopen(req, timeout=_TIMEOUT, context=ctx) as resp:
body = json.loads(resp.read())
# Response should contain the paste URL or ID
paste_id = body.get("id", "")
if paste_id:
return f"{base_url}/{paste_id}"
# Fallback: check for url field
return body.get("url", "")
def _shorten_url(base_url: str, url: str) -> str:
"""Challenge + solve + POST /s to shorten a URL. Returns short URL."""
ch = _get_challenge(base_url)
nonce = ch["nonce"]
difficulty = ch["difficulty"]
token = ch["token"]
solution = _solve_pow(nonce, difficulty)
"""POST /s to shorten a URL. Uses mTLS or PoW. Returns short URL."""
headers = {
"Content-Type": "application/json",
"User-Agent": "derp-bot",
**_pow_headers(base_url),
}
data = json.dumps({"url": url}).encode()
req = urllib.request.Request(
f"{base_url}/s",
data=data,
headers={
"Content-Type": "application/json",
"X-PoW-Token": token,
"X-PoW-Solution": str(solution),
"User-Agent": "derp-bot",
},
)
req = urllib.request.Request(f"{base_url}/s", data=data, headers=headers)
ctx = _ssl_context()
with urllib.request.urlopen(req, timeout=_TIMEOUT, context=ctx) as resp:
body = json.loads(resp.read())