Port Brave Sync server to Rust

Use SQLite for persistence and generate Rust protobuf bindings at build time.

Add Docker image support for containerized deployment.

Co-Authored-By: codex <[email protected]>
This commit is contained in:
janisslsmandcodex committed 2026-06-04 23:57:44 +03:00
commit 28826af88d
93 files changed
+11666

No files matched your search

+131
View File
@@ -0,0 +1,131 @@
use axum::http::HeaderMap;
use base64::{Engine, engine::general_purpose::URL_SAFE};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use regex::Regex;
use std::{env, sync::LazyLock};
use thiserror::Error;
use crate::util::now_millis;
pub const TOKEN_MAX_DURATION: i64 = 86_400 * 1_000;
const BEARER_PREFIX: &str = "Bearer ";
static TOKEN_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^([a-fA-F0-9]+)\|([a-fA-F0-9]+)\|([a-fA-F0-9]+)$").unwrap());
#[derive(Debug, Error)]
pub enum AuthError {
#[error("not a valid token")]
InvalidToken,
#[error("invalid token format")]
InvalidTokenFormat,
#[error("token is expired")]
Expired,
#[error("signature verification failed")]
Signature,
#[error("this client ID is blocked")]
Blocked,
#[error("{0}")]
Decode(String),
}
pub fn authenticate(token: &str) -> Result<String, AuthError> {
let decoded = URL_SAFE
.decode(token)
.map_err(|err| AuthError::Decode(format!("error doing base64 decoding: {err}")))?;
let decoded = std::str::from_utf8(&decoded)
.map_err(|err| AuthError::Decode(format!("error decoding token as utf8: {err}")))?;
let captures = TOKEN_RE
.captures(decoded)
.ok_or(AuthError::InvalidTokenFormat)?;
let timestamp_hex = &captures[1];
let signed_timestamp_hex = &captures[2];
let public_key_hex = &captures[3];
let public_key = hex::decode(public_key_hex)
.map_err(|err| AuthError::Decode(format!("error decoding hex string: {err}")))?;
let timestamp_bytes = hex::decode(timestamp_hex)
.map_err(|err| AuthError::Decode(format!("error decoding hex string: {err}")))?;
let signature_bytes = hex::decode(signed_timestamp_hex)
.map_err(|err| AuthError::Decode(format!("error decoding hex string: {err}")))?;
let key = VerifyingKey::from_bytes(
public_key
.as_slice()
.try_into()
.map_err(|_| AuthError::InvalidTokenFormat)?,
)
.map_err(|_| AuthError::InvalidTokenFormat)?;
let signature =
Signature::from_slice(&signature_bytes).map_err(|_| AuthError::InvalidTokenFormat)?;
key.verify(&timestamp_bytes, &signature)
.map_err(|_| AuthError::Signature)?;
let timestamp = std::str::from_utf8(&timestamp_bytes)
.map_err(|err| AuthError::Decode(format!("error parsing timestamp: {err}")))?
.parse::<i64>()
.map_err(|err| AuthError::Decode(format!("error parsing timestamp: {err}")))?;
if (now_millis() - timestamp).abs() > TOKEN_MAX_DURATION {
return Err(AuthError::Expired);
}
let public_key_lc = public_key_hex.to_ascii_lowercase();
let blocked = env::var("BLOCKED_CLIENT_IDS").unwrap_or_default();
if blocked
.split(',')
.any(|id| id.trim().eq_ignore_ascii_case(&public_key_lc))
{
return Err(AuthError::Blocked);
}
Ok(public_key_lc)
}
pub fn authorize(headers: &HeaderMap) -> Result<String, AuthError> {
let token = headers
.get("authorization")
.and_then(|value| value.to_str().ok())
.ok_or(AuthError::InvalidToken)?;
let token = token
.strip_prefix(BEARER_PREFIX)
.ok_or(AuthError::InvalidToken)?;
if token.is_empty() {
return Err(AuthError::InvalidToken);
}
authenticate(token).map_err(|err| AuthError::Decode(format!("error authorizing: {err}")))
}
#[cfg(test)]
mod tests {
use super::*;
use base64::engine::general_purpose::URL_SAFE;
use ed25519_dalek::{Signer, SigningKey};
fn generate_token(timestamp: i64) -> (String, String) {
let key = SigningKey::from_bytes(&[7u8; 32]);
let timestamp_bytes = timestamp.to_string().into_bytes();
let signature = key.sign(&timestamp_bytes);
let public_key_hex = hex::encode(key.verifying_key().to_bytes());
let token_hex = format!(
"{}|{}|{}",
hex::encode(timestamp_bytes),
hex::encode(signature.to_bytes()),
public_key_hex
);
(URL_SAFE.encode(token_hex), public_key_hex)
}
#[test]
fn authenticate_accepts_valid_token() {
let (token, public_key_hex) = generate_token(now_millis());
let client_id = authenticate(&token).expect("token should authenticate");
assert_eq!(client_id, public_key_hex);
}
#[test]
fn authenticate_rejects_expired_token() {
let (token, _) = generate_token(now_millis() - TOKEN_MAX_DURATION - 1);
assert!(matches!(authenticate(&token), Err(AuthError::Expired)));
}
}