Проверка подписи
Как убедиться, что уведомление пришло от нас.
Секрет уведомлений (whsec_…) показывается один раз — при создании ключа или когда вы просите новый.
Подпись — HMAC-SHA256 от строки <t>.<тело запроса как есть> вашим секретом, в шестнадцатеричном виде:
- возьмите из заголовка
Golosar-Signatureзначенияtиv1; - посчитайте HMAC-SHA256 от
t + "." + телосекретом; - сравните с
v1безопасным сравнением; - проверьте, что
tне старше 30 минут — так старое уведомление не повторят.
Подпись считается от тела как оно пришло — не разбирайте JSON и не собирайте его заново перед проверкой.
import hashlib, hmac, os, time
def verify(secret: str, body: bytes, header: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts["t"])
if abs(time.time() - t) > 1800:
return False
mac = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(mac, parts["v1"])
# Flask
from flask import Flask, request, abort
app = Flask(__name__)
@app.post("/golosar/webhook")
def hook():
if not verify(os.environ["GOLOSAR_WHSEC"], request.get_data(), request.headers["Golosar-Signature"]):
abort(400)
event = request.get_json()
print(event["type"], event["data"]["job"]["id"])
return "", 204
import crypto from "node:crypto";
import express from "express";
export function verify(secret, rawBody, header) {
const p = Object.fromEntries(header.split(",").map((x) => x.split("=")));
if (Math.abs(Date.now() / 1000 - Number(p.t)) > 1800) return false;
const mac = crypto.createHmac("sha256", secret).update(`${p.t}.`).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(p.v1));
}
const app = express();
app.post("/golosar/webhook", express.raw({ type: "application/json" }), (req, res) => {
if (!verify(process.env.GOLOSAR_WHSEC, req.body, req.get("Golosar-Signature"))) return res.sendStatus(400);
const event = JSON.parse(req.body);
console.log(event.type, event.data.job.id);
res.sendStatus(204);
});