1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//! Telegram's inbound webhooks: verify the delivery, then parse it. The
//! inbound sibling of [`HttpTelegramBot`](crate::HttpTelegramBot), which
//! sends.
//!
//! **Verify first, parse second — always.** [`parse_verified`] compares the
//! `X-Telegram-Bot-Api-Secret-Token` header against the configured secret
//! over the **raw body bytes** before any JSON is read, and returns
//! [`WebhookError::Unverified`] without touching them when it does not
//! match. Parsing first and verifying after is the vulnerability this shape
//! exists to prevent. The check is core's [`WebhookVerifier`] under the
//! [`SharedTokenScheme`] — the header carries the secret itself, so the
//! comparison is constant-time and nothing is hashed.
//!
//! **Deduplicate on [`Update::update_id`](crate::Update::update_id).** A
//! shared token proves the sender, but it signs nothing: a captured
//! delivery replays forever. Telegram redelivers, so claim each
//! `update_id` through a dedup ledger ([`Inbox`](cratefield_core::Inbox))
//! before acting on it.
//!
//! ```
//! use cratefield_adapter_telegram::webhook::{parse_verified, SECRET_HEADER};
//! use http::HeaderMap;
//!
//! let mut headers = HeaderMap::new();
//! headers.insert(SECRET_HEADER, "cratefield-secret-1".parse().unwrap());
//! // The right token but a body no update could ever parse: verification
//! // passes, parsing fails — which is why the two errors are distinct.
//! let parsed = parse_verified("cratefield-secret-1", &headers, b"not json", 0);
//! assert!(matches!(parsed, Err(cratefield_adapter_telegram::webhook::WebhookError::Malformed)));
//! ```
use ;
use HeaderMap;
use crate;
/// The header Telegram stamps every webhook delivery with, carrying the
/// secret `set_webhook` registered: `X-Telegram-Bot-Api-Secret-Token`.
pub const SECRET_HEADER: &str = "X-Telegram-Bot-Api-Secret-Token";
/// The webhook verifier: a [`SharedTokenScheme`] reading
/// [`SECRET_HEADER`]. Built fresh per check — it holds only the scheme,
/// and the scheme is a const-compatible struct.
/// Whether `secret` is a secret Telegram would have accepted at
/// `setWebhook`: 1–256 characters of `A-Z a-z 0-9 _ -`. A configuration
/// check — run it where the secret is set, so a bad one fails there
/// instead of silently refusing every delivery later.
/// Why a delivery was refused.
/// Verifies `body` against `headers`, then parses it. `now_unix` is the
/// current Unix time, which a shared-token scheme does not use but the
/// verifier's signature asks for — pass `clock.now().unix_timestamp()`.
///
/// # Errors
///
/// [`WebhookError::Unverified`] if verification fails — returned before
/// the body is read, so an unverified delivery is never parsed. Otherwise
/// [`WebhookError::Malformed`] if the verified body is not a well-formed
/// update.