fix: Address review comments

This commit is contained in:
Louis Escher
2026-08-13 16:09:03 +00:00
committed by Tangled
parent 434079a732
commit b3c314ce66
8 changed files with 329 additions and 49 deletions
+37 -11
View File
@@ -7,7 +7,7 @@ const COMPRESSED_PREFIX: &str = "$br$";
const QUALITY: u32 = 9;
const WINDOW_BITS: u32 = 16;
const BUFFER_SIZE: usize = 4096;
const MAX_DECOMPRESSED_LEN: u64 = 64 * 1024;
const MAX_SCOPE_LEN: u64 = 64 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScopeDecodeError {
@@ -28,6 +28,21 @@ impl fmt::Display for ScopeDecodeError {
impl std::error::Error for ScopeDecodeError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScopeEncodeError {
TooLarge,
}
impl fmt::Display for ScopeEncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TooLarge => write!(f, "Scope exceeds maximum length"),
}
}
}
impl std::error::Error for ScopeEncodeError {}
fn brotli_compress(input: &str) -> Vec<u8> {
let mut writer = CompressorWriter::new(Vec::new(), BUFFER_SIZE, QUALITY, WINDOW_BITS);
@@ -42,24 +57,28 @@ fn brotli_decompress(input: &[u8]) -> Result<String, ScopeDecodeError> {
let mut output = String::new();
Decompressor::new(input, BUFFER_SIZE)
.take(MAX_DECOMPRESSED_LEN + 1)
.take(MAX_SCOPE_LEN + 1)
.read_to_string(&mut output)
.map_err(|_| ScopeDecodeError::DecompressFailed)?;
if output.len() as u64 > MAX_DECOMPRESSED_LEN {
if output.len() as u64 > MAX_SCOPE_LEN {
return Err(ScopeDecodeError::TooLarge);
}
Ok(output)
}
pub fn encode_scope(scope: &str) -> String {
pub fn encode_scope(scope: &str) -> Result<String, ScopeEncodeError> {
if scope.len() as u64 > MAX_SCOPE_LEN {
return Err(ScopeEncodeError::TooLarge);
}
let encoded = URL_SAFE_NO_PAD.encode(brotli_compress(scope));
if COMPRESSED_PREFIX.len() + encoded.len() < scope.len() {
format!("{COMPRESSED_PREFIX}{encoded}")
Ok(format!("{COMPRESSED_PREFIX}{encoded}"))
} else {
scope.to_owned()
Ok(scope.to_owned())
}
}
@@ -96,7 +115,7 @@ mod tests {
#[test]
fn long_scope_roundtrips_through_compression() {
let scope = long_scope();
let encoded = encode_scope(&scope);
let encoded = encode_scope(&scope).unwrap();
assert!(encoded.starts_with(COMPRESSED_PREFIX));
assert!(encoded.len() < scope.len());
@@ -105,7 +124,7 @@ mod tests {
#[test]
fn short_scope_stays_plaintext() {
let encoded = encode_scope("com.atproto.access");
let encoded = encode_scope("com.atproto.access").unwrap();
assert_eq!(encoded, "com.atproto.access");
assert_eq!(decode_scope(&encoded).unwrap(), "com.atproto.access");
@@ -134,12 +153,19 @@ mod tests {
#[test]
fn compression_bomb_is_rejected() {
let bomb = encode_scope(&"a".repeat(MAX_DECOMPRESSED_LEN as usize * 2));
let encoded = bomb.strip_prefix(COMPRESSED_PREFIX).unwrap_or(&bomb);
let bomb = URL_SAFE_NO_PAD.encode(brotli_compress(&"a".repeat(MAX_SCOPE_LEN as usize * 2)));
assert_eq!(
decode_scope(&format!("{COMPRESSED_PREFIX}{encoded}")),
decode_scope(&format!("{COMPRESSED_PREFIX}{bomb}")),
Err(ScopeDecodeError::TooLarge)
);
}
#[test]
fn encode_rejects_oversized_scope() {
let oversized = "a".repeat(MAX_SCOPE_LEN as usize + 1);
assert_eq!(encode_scope(&oversized), Err(ScopeEncodeError::TooLarge));
assert!(encode_scope(&"a".repeat(MAX_SCOPE_LEN as usize)).is_ok());
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ pub use token::{
create_service_token_hs256,
};
pub use compress::{ScopeDecodeError, decode_scope, encode_scope};
pub use compress::{ScopeDecodeError, ScopeEncodeError, decode_scope, encode_scope};
pub use totp::{
TotpError, decrypt_totp_secret, encrypt_totp_secret, generate_backup_codes,
+3 -3
View File
@@ -3,7 +3,7 @@ use crate::compress::encode_scope;
use super::types::{
ActClaim, Claims, Header, SigningAlgorithm, TokenScope, TokenType, TokenWithMetadata,
};
use anyhow::Result;
use anyhow::{Context, Result};
use base64::Engine as _;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use chrono::{DateTime, Duration, Utc};
@@ -207,7 +207,7 @@ fn create_signed_token_pinned(
aud: format!("did:web:{}", aud_hostname),
exp: expiration,
iat: Utc::now().timestamp(),
scope: Some(encode_scope(scope)),
scope: Some(encode_scope(scope).context("Scope too large to encode")?),
lxm: None,
jti: jti.clone(),
act,
@@ -330,7 +330,7 @@ fn create_hs256_token_with_metadata(
),
exp: expiration,
iat: Utc::now().timestamp(),
scope: Some(encode_scope(scope)),
scope: Some(encode_scope(scope).context("Scope too large to encode")?),
lxm: None,
jti: jti.clone(),
act: None,