From c9912ff3329d11e856dae07a9780c04e736e767d Mon Sep 17 00:00:00 2001 From: Brent Schroeter Date: Thu, 13 Mar 2025 00:09:19 -0700 Subject: [PATCH] make keys shorter by encoding bytes as base64 --- src/api_keys.rs | 27 +++++++++++++++++++++++++++ src/router.rs | 14 ++++++++++++++ src/v0_router.rs | 10 ++++++---- templates/projects.html | 4 ++-- 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/api_keys.rs b/src/api_keys.rs index 8d3e5de..a08e451 100644 --- a/src/api_keys.rs +++ b/src/api_keys.rs @@ -1,3 +1,5 @@ +use anyhow::Result; +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; use chrono::{DateTime, Utc}; use deadpool_diesel::postgres::Connection; use diesel::{ @@ -53,3 +55,28 @@ impl ApiKey { api_keys::team_id.eq(team_id) } } + +/** + * Encode big-endian bytes of a UUID as URL-safe base64. + */ +pub fn compact_uuid(id: &Uuid) -> String { + URL_SAFE_NO_PAD.encode(id.as_bytes()) +} + +/** + * Attempt to parse a string as either a standard formatted UUID or a big-endian + * base64 encoding of one. + */ +pub fn try_parse_as_uuid(value: &str) -> Result { + if value.len() < 32 { + let bytes: Vec = URL_SAFE_NO_PAD + .decode(value) + .or(Err(anyhow::anyhow!("failed to parse")))?; + let bytes: [u8; 16] = bytes + .try_into() + .or(Err(anyhow::anyhow!("failed to parse")))?; + Ok(Uuid::from_bytes(bytes)) + } else { + Uuid::try_parse(value).or(Err(anyhow::anyhow!("failed to parse"))) + } +} diff --git a/src/router.rs b/src/router.rs index 9a26fb0..65000b0 100644 --- a/src/router.rs +++ b/src/router.rs @@ -248,6 +248,20 @@ async fn projects_page( .await .unwrap()?; + mod filters { + use uuid::Uuid; + + pub fn compact_uuid(id: &Uuid) -> askama::Result { + Ok(crate::api_keys::compact_uuid(id)) + } + + pub fn redact(value: &str) -> askama::Result { + Ok(format!( + "********{}", + value[value.char_indices().nth_back(3).unwrap().0..].to_string() + )) + } + } #[derive(Template)] #[template(path = "projects.html")] struct ResponseTemplate { diff --git a/src/v0_router.rs b/src/v0_router.rs index faf47f1..a1efcaf 100644 --- a/src/v0_router.rs +++ b/src/v0_router.rs @@ -16,7 +16,7 @@ use uuid::Uuid; use validator::Validate; use crate::{ - api_keys::ApiKey, + api_keys::{try_parse_as_uuid, ApiKey}, app_error::AppError, app_state::{AppState, DbConn}, channels::Channel, @@ -38,7 +38,7 @@ pub fn new_router(state: AppState) -> Router { #[derive(Deserialize, Validate)] struct SayQuery { #[serde(alias = "k")] - key: Uuid, + key: String, #[serde(alias = "p")] #[serde(default = "default_project")] #[validate(regex( @@ -67,7 +67,9 @@ async fn say_get( query.validate().map_err(AppError::from_validation_errors)?; let api_key = { - let query_key = query.key.clone(); + let query_key = try_parse_as_uuid(&query.key).or(Err(AppError::ForbiddenError( + "key not accepted".to_string(), + )))?; db_conn .interact::<_, Result>(move |conn| { update(api_keys::table.filter(ApiKey::with_id(query_key))) @@ -76,7 +78,7 @@ async fn say_get( .get_result(conn) .optional() .context("failed to get API key")? - .ok_or(AppError::ForbiddenError("Key not accepted.".to_string())) + .ok_or(AppError::ForbiddenError("key not accepted.".to_string())) }) .await .unwrap()? diff --git a/templates/projects.html b/templates/projects.html index 89f9f54..8097b63 100644 --- a/templates/projects.html +++ b/templates/projects.html @@ -78,7 +78,7 @@ - ********{{ key.id.simple().to_string()[key.id.simple().to_string().char_indices().nth_back(3).unwrap().0..] }} + {{ key.id|compact_uuid|redact }} @@ -94,7 +94,7 @@ class="btn btn-outline-light" type="button" name="api-key-copy-button" - data-copy="{{ key.id.simple() }}" + data-copy="{{ key.id|compact_uuid }}" > Copy