fix(push): fix VAPID key format and add regenerate endpoint + UI button

This commit is contained in:
2026-04-07 06:59:06 -04:00
parent 1d0cf4828b
commit 814f44c3fb
3 changed files with 83 additions and 7 deletions
+33 -5
View File
@@ -62,11 +62,14 @@ def ensure_vapid_keys() -> None:
v = Vapid01()
v.generate_keys()
private_pem = v.private_key.private_bytes(
serialization.Encoding.PEM,
# pywebpush expects the private key as a base64url-encoded DER blob
# (passed to Vapid.from_string → from_der), NOT a PEM string.
private_der = v.private_key.private_bytes(
serialization.Encoding.DER,
serialization.PrivateFormat.TraditionalOpenSSL,
serialization.NoEncryption(),
).decode()
)
private_b64 = base64.urlsafe_b64encode(private_der).rstrip(b"=").decode()
pub_bytes = v.public_key.public_bytes(
serialization.Encoding.X962,
@@ -76,15 +79,40 @@ def ensure_vapid_keys() -> None:
# Persist so they survive container restarts.
_VAPID_KEYS_FILE.parent.mkdir(parents=True, exist_ok=True)
_VAPID_KEYS_FILE.write_text(json.dumps({"private_key": private_pem, "public_key": public_b64}))
_VAPID_KEYS_FILE.write_text(json.dumps({"private_key": private_b64, "public_key": public_b64}))
Config.VAPID_PRIVATE_KEY = private_pem
Config.VAPID_PRIVATE_KEY = private_b64
Config.VAPID_PUBLIC_KEY = public_b64
logger.info("Generated new VAPID keys and saved to %s", _VAPID_KEYS_FILE)
except Exception:
logger.warning("Failed to generate VAPID keys — push notifications will be unavailable", exc_info=True)
async def regenerate_vapid_keys() -> bool:
"""Delete existing VAPID keys, clear all push subscriptions, and generate a fresh pair.
All existing browser subscriptions are invalidated when keys rotate, so they
must be cleared — users will need to re-enable notifications.
"""
if _VAPID_KEYS_FILE.exists():
_VAPID_KEYS_FILE.unlink()
Config.VAPID_PRIVATE_KEY = ""
Config.VAPID_PUBLIC_KEY = ""
# Clear all push subscriptions — they are bound to the old public key.
async with async_session() as session:
await session.execute(delete(PushSubscription))
await session.commit()
ensure_vapid_keys()
enabled = vapid_enabled()
if enabled:
logger.info("VAPID keys regenerated successfully")
else:
logger.error("VAPID key regeneration failed")
return enabled
async def save_subscription(user_id: int, subscription_json: dict, user_agent: str | None = None) -> PushSubscription:
"""Upsert a push subscription by endpoint."""
endpoint = subscription_json.get("endpoint", "")