make keys shorter by encoding bytes as base64

This commit is contained in:
Brent Schroeter 2025-03-13 00:09:19 -07:00
parent b3870cd48a
commit c9912ff332
4 changed files with 49 additions and 6 deletions

View file

@ -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<Uuid> {
if value.len() < 32 {
let bytes: Vec<u8> = 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")))
}
}

View file

@ -248,6 +248,20 @@ async fn projects_page(
.await
.unwrap()?;
mod filters {
use uuid::Uuid;
pub fn compact_uuid(id: &Uuid) -> askama::Result<String> {
Ok(crate::api_keys::compact_uuid(id))
}
pub fn redact(value: &str) -> askama::Result<String> {
Ok(format!(
"********{}",
value[value.char_indices().nth_back(3).unwrap().0..].to_string()
))
}
}
#[derive(Template)]
#[template(path = "projects.html")]
struct ResponseTemplate {

View file

@ -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<AppState> {
#[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<ApiKey, AppError>>(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()?

View file

@ -78,7 +78,7 @@
<tr>
<td>
<code>
********{{ key.id.simple().to_string()[key.id.simple().to_string().char_indices().nth_back(3).unwrap().0..] }}
{{ key.id|compact_uuid|redact }}
</code>
</td>
<td>
@ -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
</button>