1
0
Fork 0
forked from 2sys/shoutdotdev

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 chrono::{DateTime, Utc};
use deadpool_diesel::postgres::Connection; use deadpool_diesel::postgres::Connection;
use diesel::{ use diesel::{
@ -53,3 +55,28 @@ impl ApiKey {
api_keys::team_id.eq(team_id) 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 .await
.unwrap()?; .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)] #[derive(Template)]
#[template(path = "projects.html")] #[template(path = "projects.html")]
struct ResponseTemplate { struct ResponseTemplate {

View file

@ -16,7 +16,7 @@ use uuid::Uuid;
use validator::Validate; use validator::Validate;
use crate::{ use crate::{
api_keys::ApiKey, api_keys::{try_parse_as_uuid, ApiKey},
app_error::AppError, app_error::AppError,
app_state::{AppState, DbConn}, app_state::{AppState, DbConn},
channels::Channel, channels::Channel,
@ -38,7 +38,7 @@ pub fn new_router(state: AppState) -> Router<AppState> {
#[derive(Deserialize, Validate)] #[derive(Deserialize, Validate)]
struct SayQuery { struct SayQuery {
#[serde(alias = "k")] #[serde(alias = "k")]
key: Uuid, key: String,
#[serde(alias = "p")] #[serde(alias = "p")]
#[serde(default = "default_project")] #[serde(default = "default_project")]
#[validate(regex( #[validate(regex(
@ -67,7 +67,9 @@ async fn say_get(
query.validate().map_err(AppError::from_validation_errors)?; query.validate().map_err(AppError::from_validation_errors)?;
let api_key = { 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 db_conn
.interact::<_, Result<ApiKey, AppError>>(move |conn| { .interact::<_, Result<ApiKey, AppError>>(move |conn| {
update(api_keys::table.filter(ApiKey::with_id(query_key))) update(api_keys::table.filter(ApiKey::with_id(query_key)))
@ -76,7 +78,7 @@ async fn say_get(
.get_result(conn) .get_result(conn)
.optional() .optional()
.context("failed to get API key")? .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 .await
.unwrap()? .unwrap()?

View file

@ -78,7 +78,7 @@
<tr> <tr>
<td> <td>
<code> <code>
********{{ key.id.simple().to_string()[key.id.simple().to_string().char_indices().nth_back(3).unwrap().0..] }} {{ key.id|compact_uuid|redact }}
</code> </code>
</td> </td>
<td> <td>
@ -94,7 +94,7 @@
class="btn btn-outline-light" class="btn btn-outline-light"
type="button" type="button"
name="api-key-copy-button" name="api-key-copy-button"
data-copy="{{ key.id.simple() }}" data-copy="{{ key.id|compact_uuid }}"
> >
Copy Copy
</button> </button>