"""Генерация пары ключей VAPID для Web Push (ТЗ 3.21).
Запуск: `uv run python -m app.vapid` — печатает готовые строки для `.env`.
Формат ключей не произвольный, а тот, что ждёт push-сервис браузера:
- **публичный** — сырая несжатая точка P-256 (`X962`/`UncompressedPoint`, 65 байт)
в base64url без выравнивания; ровно её браузер принимает как
`applicationServerKey` при подписке, и её же мы отдаём фронту через
`GET /api/push/config`;
- **приватный** — PKCS8 DER в base64url: так его читает `pywebpush`
(`Vapid.from_string`), когда мы подписываем запрос к push-сервису.
PEM здесь не годится: он был бы понятен человеку, но не подписи запроса.
"""
import base64
from cryptography.hazmat.primitives import serialization
from py_vapid import Vapid01
def _b64(raw: bytes) -> str:
"""base64url без `=` — формат ключей Web Push (выравнивание не допускается)."""
return base64.urlsafe_b64encode(raw).rstrip(b"=").decode()
def generate_keys() -> tuple[str, str]:
"""Вернуть пару (публичный, приватный) в формате, который понимают браузер и pywebpush."""
vapid = Vapid01()
vapid.generate_keys()
public_raw = vapid.public_key.public_bytes(
serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint
)
private_der = vapid.private_key.private_bytes(
serialization.Encoding.DER,
serialization.PrivateFormat.PKCS8,
serialization.NoEncryption(),
)
return _b64(public_raw), _b64(private_der)
def main() -> None:
public, private = generate_keys()
print("# Web Push (ТЗ 3.21): вставьте в .env и перезапустите API")
print(f"VAPID_PUBLIC_KEY={public}")
print(f"VAPID_PRIVATE_KEY={private}")
print("VAPID_SUBJECT=mailto:you@example.com")
if __name__ == "__main__":
main()