From c129b6a53dff921adcc8cc3d60eb15032980624a Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 1 Jul 2022 23:10:00 +0400 Subject: [PATCH 1/9] Update teloxide-core to master --- Cargo.toml | 3 ++- src/utils/html.rs | 4 ++++ src/utils/markdown.rs | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a4ece2c9..2ae8da8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,8 @@ full = [ ] [dependencies] -teloxide-core = { version = "0.6.0", default-features = false } +#teloxide-core = { version = "0.6.0", default-features = false } +teloxide-core = { git = "https://github.com/teloxide/teloxide-core", rev = "b13393d", default-features = false } teloxide-macros = { version = "0.6.2", optional = true } serde_json = "1.0" diff --git a/src/utils/html.rs b/src/utils/html.rs index 11ae475d..dc655fbf 100644 --- a/src/utils/html.rs +++ b/src/utils/html.rs @@ -191,6 +191,8 @@ mod tests { last_name: None, username: Some("abcd".to_string()), language_code: None, + is_premium: false, + added_to_attachment_menu: false, }; assert_eq!(user_mention_or_link(&user_with_username), "@abcd"); let user_without_username = User { @@ -200,6 +202,8 @@ mod tests { last_name: None, username: None, language_code: None, + is_premium: false, + added_to_attachment_menu: false, }; assert_eq!( user_mention_or_link(&user_without_username), diff --git a/src/utils/markdown.rs b/src/utils/markdown.rs index c07d53d9..4419feb9 100644 --- a/src/utils/markdown.rs +++ b/src/utils/markdown.rs @@ -240,6 +240,8 @@ mod tests { last_name: None, username: Some("abcd".to_string()), language_code: None, + is_premium: false, + added_to_attachment_menu: false, }; assert_eq!(user_mention_or_link(&user_with_username), "@abcd"); let user_without_username = User { @@ -249,6 +251,8 @@ mod tests { last_name: None, username: None, language_code: None, + is_premium: false, + added_to_attachment_menu: false, }; assert_eq!( user_mention_or_link(&user_without_username), From 8806cb9d78b37a22c34031d9d98410596c7695c1 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 1 Jul 2022 23:21:39 +0400 Subject: [PATCH 2/9] Add support for secret_token in built-in webhooks --- Cargo.toml | 4 +- src/dispatching/update_listeners.rs | 2 +- src/dispatching/update_listeners/webhooks.rs | 81 ++++++++++++++++++- .../update_listeners/webhooks/axum.rs | 65 ++++++++++++--- 4 files changed, 136 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2ae8da8b..5dfc708b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,8 @@ exclude = ["media"] [features] default = ["native-tls", "ctrlc_handler", "teloxide-core/default", "auto-send"] -webhooks-axum = ["axum", "tower", "tower-http"] +webhooks = ["rand"] +webhooks-axum = ["webhooks", "axum", "tower", "tower-http"] sqlite-storage = ["sqlx"] redis-storage = ["redis"] @@ -92,6 +93,7 @@ bincode = { version = "1.3", optional = true } axum = { version = "0.4.8", optional = true } tower = { version = "0.4.12", optional = true } tower-http = { version = "0.2.5", features = ["trace"], optional = true } +rand = { version = "0.8.5", optional = true } [dev-dependencies] rand = "0.8.3" diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 4c01d174..f3e358c5 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -27,7 +27,7 @@ /// Implementations of webhook update listeners - an alternative (to /// [`fn@polling`]) way of receiving updates from telegram. -#[cfg(any(feature = "webhooks-axum"))] +#[cfg(feature = "webhooks")] pub mod webhooks; use futures::Stream; diff --git a/src/dispatching/update_listeners/webhooks.rs b/src/dispatching/update_listeners/webhooks.rs index b20afd86..828f2683 100644 --- a/src/dispatching/update_listeners/webhooks.rs +++ b/src/dispatching/update_listeners/webhooks.rs @@ -42,13 +42,28 @@ pub struct Options { /// /// Default - false. pub drop_pending_updates: bool, + + /// A secret token to be sent in a header “X-Telegram-Bot-Api-Secret-Token” + /// in every webhook request, 1-256 characters. Only characters `A-Z`, + /// `a-z`, `0-9`, `_` and `-` are allowed. The header is useful to ensure + /// that the request comes from a webhook set by you. + /// + /// Default - teloxide will generate a random token. + pub secret_token: Option, } impl Options { /// Construct a new webhook options, see [`Options::address`] and /// [`Options::url`] for details. pub fn new(address: SocketAddr, url: url::Url) -> Self { - Self { address, url, certificate: None, max_connections: None, drop_pending_updates: false } + Self { + address, + url, + certificate: None, + max_connections: None, + drop_pending_updates: false, + secret_token: None, + } } /// Upload your public key certificate so that the root certificate in use @@ -71,6 +86,32 @@ impl Options { pub fn drop_pending_updates(self) -> Self { Self { drop_pending_updates: true, ..self } } + + /// A secret token to be sent in a header “X-Telegram-Bot-Api-Secret-Token” + /// in every webhook request, 1-256 characters. Only characters `A-Z`, + /// `a-z`, `0-9`, `_` and `-` are allowed. The header is useful to ensure + /// that the request comes from a webhook set by you. + /// + /// ## Panics + /// + /// If the token is invalid. + #[track_caller] + pub fn secret_token(self, token: String) -> Self { + check_secret(token.as_bytes()).expect("Invalid secret token"); + + Self { secret_token: Some(token), ..self } + } + + /// Returns `self.secret_token`, generating a new one if it's `None`. + /// + /// After a call to this function `self.secret_token` is always `Some(_)`. + /// + /// **Note**: if you leave webhook setup to teloxide, it will automatically + /// generate a secret token. Call this function only if you need to know the + /// secret (for example because you are calling `set_webhook` by yourself). + pub fn get_or_gen_secret_token(&mut self) -> &str { + self.secret_token.get_or_insert_with(gen_secret_token) + } } #[cfg(feature = "webhooks-axum")] @@ -91,6 +132,7 @@ where use crate::requests::Request; use teloxide_core::requests::HasPayload; + let secret = options.get_or_gen_secret_token().to_owned(); let &mut Options { ref url, ref mut certificate, max_connections, drop_pending_updates, .. } = options; @@ -99,12 +141,49 @@ where req.payload_mut().certificate = certificate.take(); req.payload_mut().max_connections = max_connections; req.payload_mut().drop_pending_updates = Some(drop_pending_updates); + req.payload_mut().secret_token = Some(secret); req.send().await?; Ok(()) } +/// Generates a random string consisting of 32 characters (`a-z`, `A-Z`, `0-9`, +/// `_` and `-`). +fn gen_secret_token() -> String { + use rand::{distributions::Uniform, Rng}; + const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"; + const SECRET_LENGTH: usize = 32; + + let random = rand::thread_rng() + .sample_iter(Uniform::new(0, CHARSET.len())) + .map(|idx| CHARSET[idx] as char) + .take(SECRET_LENGTH); + + let mut secret = String::with_capacity(SECRET_LENGTH); + secret.extend(random); + + secret +} + +fn check_secret(bytes: &[u8]) -> Result<&[u8], &'static str> { + let len = bytes.len(); + + // Check that length is in bounds + if !(1 <= len && len <= 256) { + return Err("secret token length must be in range 1..=256"); + } + + // Check that all characters of the secret are supported by telegram + let is_not_supported = + |c: &_| !matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'-'); + if bytes.iter().any(is_not_supported) { + return Err("secret token must only contain of `a-z`, `A-Z`, `0-9`, `_` and `-` characters"); + } + + Ok(bytes) +} + /// Returns first (`.0`) field from a tuple as a `&mut` reference. /// /// This hack is needed because there isn't currently a way to easily force a diff --git a/src/dispatching/update_listeners/webhooks/axum.rs b/src/dispatching/update_listeners/webhooks/axum.rs index e145c4b2..ab08cd92 100644 --- a/src/dispatching/update_listeners/webhooks/axum.rs +++ b/src/dispatching/update_listeners/webhooks/axum.rs @@ -1,12 +1,14 @@ -use std::convert::Infallible; +use std::{convert::Infallible, future::Future, pin::Pin}; + +use axum::{ + extract::{FromRequest, RequestParts}, + http::status::StatusCode, +}; use crate::{ dispatching::{ stop_token::{AsyncStopFlag, StopToken}, - update_listeners::{ - webhooks::{setup_webhook, tuple_first_mut, Options}, - UpdateListener, - }, + update_listeners::{webhooks::Options, UpdateListener}, }, requests::Requester, }; @@ -105,15 +107,12 @@ where pub async fn axum_to_router( bot: R, mut options: Options, -) -> Result< - (impl UpdateListener, impl std::future::Future + Send, axum::Router), - R::Err, -> +) -> Result<(impl UpdateListener, impl Future + Send, axum::Router), R::Err> where R: Requester + Send, ::DeleteWebhook: Send, { - use crate::requests::Request; + use crate::{dispatching::update_listeners::webhooks::setup_webhook, requests::Request}; use futures::FutureExt; setup_webhook(&bot, &mut options).await?; @@ -149,12 +148,15 @@ where /// function. pub fn axum_no_setup( options: Options, -) -> (impl UpdateListener, impl std::future::Future, axum::Router) { +) -> (impl UpdateListener, impl Future, axum::Router) { use crate::{ - dispatching::{stop_token::AsyncStopToken, update_listeners}, + dispatching::{ + stop_token::AsyncStopToken, + update_listeners::{self, webhooks::tuple_first_mut}, + }, types::Update, }; - use axum::{extract::Extension, http::StatusCode, response::IntoResponse, routing::post}; + use axum::{extract::Extension, response::IntoResponse, routing::post}; use tokio::sync::mpsc; use tokio_stream::wrappers::UnboundedReceiverStream; use tower::ServiceBuilder; @@ -167,9 +169,16 @@ pub fn axum_no_setup( async fn telegram_request( input: String, + secret_header: XTelegramBotApiSecretToken, + secret: Extension>, tx: Extension, flag: Extension, ) -> impl IntoResponse { + // FIXME: use constant time comparison here + if secret_header.0.as_deref() != secret.as_deref().map(str::as_bytes) { + return StatusCode::UNAUTHORIZED; + } + let tx = match tx.get() { None => return StatusCode::SERVICE_UNAVAILABLE, // Do not process updates after `.stop()` is called even if the server is still @@ -206,6 +215,7 @@ pub fn axum_no_setup( .layer(TraceLayer::new_for_http()) .layer(Extension(ClosableSender::new(tx))) .layer(Extension(stop_flag.clone())) + .layer(Extension(options.secret_token)) .into_inner(), ); @@ -245,3 +255,32 @@ impl ClosableSender { self.origin.write().unwrap().take(); } } + +struct XTelegramBotApiSecretToken(Option>); + +impl FromRequest for XTelegramBotApiSecretToken { + type Rejection = StatusCode; + + fn from_request<'l0, 'at>( + req: &'l0 mut RequestParts, + ) -> Pin> + Send + 'at>> + where + 'l0: 'at, + Self: 'at, + { + use crate::dispatching::update_listeners::webhooks::check_secret; + + let res = req + .headers_mut() + .and_then(|map| map.remove("x-telegram-bot-api-secret-token")) + .map(|header| { + check_secret(header.as_bytes()) + .map(<_>::to_owned) + .map_err(|_| StatusCode::BAD_REQUEST) + }) + .transpose() + .map(Self); + + Box::pin(async { res }) as _ + } +} From 4edd41fd58431cbba20915838ad0aa73d3cbaec2 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 1 Jul 2022 23:27:46 +0400 Subject: [PATCH 3/9] Reformat features file --- src/features.md | 31 +++++++++++++++++++++++++++++++ src/features.txt | 29 ----------------------------- src/lib.rs | 2 +- 3 files changed, 32 insertions(+), 30 deletions(-) create mode 100644 src/features.md delete mode 100644 src/features.txt diff --git a/src/features.md b/src/features.md new file mode 100644 index 00000000..b7ef133b --- /dev/null +++ b/src/features.md @@ -0,0 +1,31 @@ +## Cargo features + +| Feature | Description | +|----------------------|------------------------------------------------------------------------------------| +| `redis-storage` | Enables the [Redis] storage support for dialogues. | +| `sqlite-storage` | Enables the [Sqlite] storage support for dialogues. | +| `cbor-serializer` | Enables the [CBOR] serializer for dialogues. | +| `bincode-serializer` | Enables the [Bincode] serializer for dialogues. | +| `macros` | Re-exports macros from [`teloxide-macros`]. | +| `native-tls` | Enables the [`native-tls`] TLS implementation (**enabled by default**). | +| `rustls` | Enables the [`rustls`] TLS implementation. | +| `ctrlc_handler` | Enables the [`Dispatcher::setup_ctrlc_handler`] function (**enabled by default**). | +| `auto-send` | Enables the [`AutoSend`](adaptors::AutoSend) bot adaptor (**enabled by default**). | +| `throttle` | Enables the [`Throttle`](adaptors::Throttle) bot adaptor. | +| `cache-me` | Enables the [`CacheMe`](adaptors::CacheMe) bot adaptor. | +| `trace-adaptor` | Enables the [`Trace`](adaptors::Trace) bot adaptor. | +| `erased` | Enables the [`ErasedRequester`](adaptors::ErasedRequester) bot adaptor. | +| `full` | Enables all the features except `nightly`. | +| `nightly` | Enables nightly-only features (see the [teloxide-core features]). | + +[Redis]: https://redis.io/ +[Sqlite]: https://www.sqlite.org/ +[CBOR]: https://en.wikipedia.org/wiki/CBOR +[Bincode]: https://github.com/servo/bincode +[`teloxide-macros`]: https://github.com/teloxide/teloxide-macros +[`native-tls`]: https://docs.rs/native-tls +[`rustls`]: https://docs.rs/rustls +[`teloxide::utils::UpState`]: utils::UpState +[teloxide-core features]: https://docs.rs/teloxide-core/latest/teloxide_core/#cargo-features + +[`Dispatcher::setup_ctrlc_handler`]: dispatching::Dispatcher::setup_ctrlc_handler \ No newline at end of file diff --git a/src/features.txt b/src/features.txt deleted file mode 100644 index 32d737b3..00000000 --- a/src/features.txt +++ /dev/null @@ -1,29 +0,0 @@ -## Cargo features - -| Feature | Description | -|----------|----------| -| `redis-storage` | Enables the [Redis] storage support for dialogues.| -| `sqlite-storage` | Enables the [Sqlite] storage support for dialogues. | -| `cbor-serializer` | Enables the [CBOR] serializer for dialogues. | -| `bincode-serializer` | Enables the [Bincode] serializer for dialogues. | -| `macros` | Re-exports macros from [`teloxide-macros`]. | -| `native-tls` | Enables the [`native-tls`] TLS implementation (enabled by default). | -| `rustls` | Enables the [`rustls`] TLS implementation. | -| `ctrlc_handler` | Enables the [`Dispatcher::setup_ctrlc_handler`](dispatching::Dispatcher::setup_ctrlc_handler) function. | -| `auto-send` | Enables the [`AutoSend`](adaptors::AutoSend) bot adaptor. | -| `throttle` | Enables the [`Throttle`](adaptors::Throttle) bot adaptor. | -| `cache-me` | Enables the [`CacheMe`](adaptors::CacheMe) bot adaptor. | -| `trace-adaptor` | Enables the [`Trace`](adaptors::Trace) bot adaptor. | -| `erased` | Enables the [`ErasedRequester`](adaptors::ErasedRequester) bot adaptor. | -| `full` | Enables all the features except `nightly`. | -| `nightly` | Enables nightly-only features (see the [teloxide-core features]). | - -[Redis]: https://redis.io/ -[Sqlite]: https://www.sqlite.org/ -[CBOR]: https://en.wikipedia.org/wiki/CBOR -[Bincode]: https://github.com/servo/bincode -[`teloxide-macros`]: https://github.com/teloxide/teloxide-macros -[`native-tls`]: https://docs.rs/native-tls -[`rustls`]: https://docs.rs/rustls -[`teloxide::utils::UpState`]: utils::UpState -[teloxide-core features]: https://docs.rs/teloxide-core/latest/teloxide_core/#cargo-features diff --git a/src/lib.rs b/src/lib.rs index e43af26c..2964cd74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,7 @@ // [1]: https://github.com/rust-lang/rustfmt/issues/4210 // [2]: https://github.com/rust-lang/rustfmt/issues/4787 // [3]: https://github.com/rust-lang/rust/issues/82768#issuecomment-803935643 -#![cfg_attr(feature = "nightly", cfg_attr(feature = "nightly", doc = include_str!("features.txt")))] +#![cfg_attr(feature = "nightly", cfg_attr(feature = "nightly", doc = include_str!("features.md")))] // https://github.com/teloxide/teloxide/raw/master/logo.svg doesn't work in html_logo_url, I don't know why. #![doc( html_logo_url = "https://github.com/teloxide/teloxide/raw/master/ICON.png", From dc9dc8cd13f21e22734e8df8daaeabddeed73e3c Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 1 Jul 2022 23:29:39 +0400 Subject: [PATCH 4/9] Reorder features in features.md --- src/features.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/features.md b/src/features.md index b7ef133b..c0e8b4eb 100644 --- a/src/features.md +++ b/src/features.md @@ -2,13 +2,7 @@ | Feature | Description | |----------------------|------------------------------------------------------------------------------------| -| `redis-storage` | Enables the [Redis] storage support for dialogues. | -| `sqlite-storage` | Enables the [Sqlite] storage support for dialogues. | -| `cbor-serializer` | Enables the [CBOR] serializer for dialogues. | -| `bincode-serializer` | Enables the [Bincode] serializer for dialogues. | | `macros` | Re-exports macros from [`teloxide-macros`]. | -| `native-tls` | Enables the [`native-tls`] TLS implementation (**enabled by default**). | -| `rustls` | Enables the [`rustls`] TLS implementation. | | `ctrlc_handler` | Enables the [`Dispatcher::setup_ctrlc_handler`] function (**enabled by default**). | | `auto-send` | Enables the [`AutoSend`](adaptors::AutoSend) bot adaptor (**enabled by default**). | | `throttle` | Enables the [`Throttle`](adaptors::Throttle) bot adaptor. | @@ -17,6 +11,13 @@ | `erased` | Enables the [`ErasedRequester`](adaptors::ErasedRequester) bot adaptor. | | `full` | Enables all the features except `nightly`. | | `nightly` | Enables nightly-only features (see the [teloxide-core features]). | +| `native-tls` | Enables the [`native-tls`] TLS implementation (**enabled by default**). | +| `rustls` | Enables the [`rustls`] TLS implementation. | +| `redis-storage` | Enables the [Redis] storage support for dialogues. | +| `sqlite-storage` | Enables the [Sqlite] storage support for dialogues. | +| `cbor-serializer` | Enables the [CBOR] serializer for dialogues. | +| `bincode-serializer` | Enables the [Bincode] serializer for dialogues. | + [Redis]: https://redis.io/ [Sqlite]: https://www.sqlite.org/ From 1458e40b45fb64c648b9f457a664a4355af4c7e1 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 1 Jul 2022 23:31:28 +0400 Subject: [PATCH 5/9] Document webhook* features --- src/features.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/features.md b/src/features.md index c0e8b4eb..1d199a80 100644 --- a/src/features.md +++ b/src/features.md @@ -2,6 +2,8 @@ | Feature | Description | |----------------------|------------------------------------------------------------------------------------| +| `webhooks` | Enables general webhook utilities (almost useless on its own) | +| `webhooks-axum` | Enables webhook implementation based on axum framework | | `macros` | Re-exports macros from [`teloxide-macros`]. | | `ctrlc_handler` | Enables the [`Dispatcher::setup_ctrlc_handler`] function (**enabled by default**). | | `auto-send` | Enables the [`AutoSend`](adaptors::AutoSend) bot adaptor (**enabled by default**). | From 88ddf7f7a296b17e5ee431d44904df01a917e166 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 1 Jul 2022 23:36:39 +0400 Subject: [PATCH 6/9] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53ff1e5e..5b0647a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## unreleased +### Added + +- Security checks based on `secret_token` param of `set_webhook` to built-in webhooks + ### Fixed - `Dispatcher` no longer "leaks" memory for every inactive user ([PR 657](https://github.com/teloxide/teloxide/pull/657)). From e8ce86df8b206c47ee170e122c900f7d64fbf9ac Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 3 Jul 2022 13:13:34 +0400 Subject: [PATCH 7/9] Clippy :| --- src/dispatching/update_listeners/webhooks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatching/update_listeners/webhooks.rs b/src/dispatching/update_listeners/webhooks.rs index 828f2683..e68cdd90 100644 --- a/src/dispatching/update_listeners/webhooks.rs +++ b/src/dispatching/update_listeners/webhooks.rs @@ -170,7 +170,7 @@ fn check_secret(bytes: &[u8]) -> Result<&[u8], &'static str> { let len = bytes.len(); // Check that length is in bounds - if !(1 <= len && len <= 256) { + if !(1..=256).contains(&len) { return Err("secret token length must be in range 1..=256"); } From db59b476749d9a8922c4a0618aa38ffab5f020d9 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 4 Jul 2022 23:52:59 +0400 Subject: [PATCH 8/9] Mark `webhooks::Options` as `#[must_use]` --- src/dispatching/update_listeners/webhooks.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dispatching/update_listeners/webhooks.rs b/src/dispatching/update_listeners/webhooks.rs index e68cdd90..3cd3d503 100644 --- a/src/dispatching/update_listeners/webhooks.rs +++ b/src/dispatching/update_listeners/webhooks.rs @@ -4,6 +4,7 @@ use std::net::SocketAddr; use crate::{requests::Requester, types::InputFile}; /// Options related to setting up webhooks. +#[must_use] pub struct Options { /// Local address to listen to. pub address: SocketAddr, From b849067378853f654fc04bbdc00cb8ed1001a0ae Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 4 Jul 2022 23:58:46 +0400 Subject: [PATCH 9/9] Remove useless comments --- src/dispatching/update_listeners/webhooks.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/dispatching/update_listeners/webhooks.rs b/src/dispatching/update_listeners/webhooks.rs index 3cd3d503..ef94fec4 100644 --- a/src/dispatching/update_listeners/webhooks.rs +++ b/src/dispatching/update_listeners/webhooks.rs @@ -170,12 +170,10 @@ fn gen_secret_token() -> String { fn check_secret(bytes: &[u8]) -> Result<&[u8], &'static str> { let len = bytes.len(); - // Check that length is in bounds if !(1..=256).contains(&len) { return Err("secret token length must be in range 1..=256"); } - // Check that all characters of the secret are supported by telegram let is_not_supported = |c: &_| !matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'-'); if bytes.iter().any(is_not_supported) {