Use simplified Snowflake ID generation

This prevents leaking server information to client with little effort.
This commit is contained in:
oxalica 2024-09-06 01:51:27 -04:00
parent 59d51937da
commit a7f260027d
5 changed files with 36 additions and 12 deletions

View file

@ -179,7 +179,7 @@ paths:
200:
content:
application/json:
type: integer
type: string
description: Created chat id (cid).
400:
description: Body is invalid or fails the verification.

View file

@ -23,7 +23,7 @@ CREATE TABLE IF NOT EXISTS `room_member` (
CREATE INDEX IF NOT EXISTS `member_room` ON `room_member` (`uid` ASC, `rid` ASC);
CREATE TABLE IF NOT EXISTS `room_item` (
`cid` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`cid` INTEGER NOT NULL PRIMARY KEY,
`rid` INTEGER NOT NULL REFERENCES `room` ON DELETE CASCADE,
`uid` INTEGER NOT NULL REFERENCES `user` ON DELETE RESTRICT,
`timestamp` INTEGER NOT NULL,

View file

@ -10,7 +10,7 @@ static INIT_SQL: &str = include_str!("../schema.sql");
// Simple and stupid version check for now.
// `echo -n 'blahd-database-0' | sha256sum | head -c5` || version
const APPLICATION_ID: i32 = 0xd9e_8401;
const APPLICATION_ID: i32 = 0xd9e_8402;
#[derive(Debug)]
pub struct Database {

21
blahd/src/id.rs Normal file
View file

@ -0,0 +1,21 @@
/// Id generation.
/// Ref: https://en.wikipedia.org/wiki/Snowflake_ID
/// FIXME: Currently we assume no more than one request in a single millisecond.
use std::time::SystemTime;
use blah::types::Id;
pub trait IdExt {
fn gen() -> Self;
}
impl IdExt for Id {
fn gen() -> Self {
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("after UNIX epoch");
let timestamp_ms = timestamp.as_millis() as i64;
assert!(timestamp_ms > 0);
Id(timestamp_ms << 16)
}
}

View file

@ -18,6 +18,7 @@ use blah::types::{
use config::Config;
use database::Database;
use ed25519_dalek::SIGNATURE_LENGTH;
use id::IdExt;
use middleware::{ApiError, MaybeAuth, ResultExt as _, SignedJson};
use parking_lot::Mutex;
use rusqlite::{named_params, params, Connection, OptionalExtension, Row, ToSql};
@ -30,6 +31,7 @@ mod middleware;
mod config;
mod database;
mod event;
mod id;
mod utils;
/// Blah Chat Server
@ -356,17 +358,18 @@ async fn room_create(
};
let txn = conn.transaction()?;
let rid = Id::gen();
txn.execute(
r"
INSERT INTO `room` (`title`, `attrs`)
VALUES (:title, :attrs)
INSERT INTO `room` (`rid`, `title`, `attrs`)
VALUES (:rid, :title, :attrs)
",
named_params! {
":rid": rid,
":title": params.signee.payload.title,
":attrs": params.signee.payload.attrs,
},
)?;
let rid = Id(txn.last_insert_rowid());
let mut insert_user = txn.prepare(
r"
INSERT INTO `user` (`userkey`)
@ -664,7 +667,7 @@ async fn room_post_item(
st: ArcState,
R(Path(rid), _): RE<Path<Id>>,
SignedJson(chat): SignedJson<ChatPayload>,
) -> Result<Json<u64>, ApiError> {
) -> Result<Json<Id>, ApiError> {
if rid != chat.signee.payload.room {
return Err(error_response!(
StatusCode::BAD_REQUEST,
@ -705,13 +708,14 @@ async fn room_post_item(
));
};
let cid = conn.query_row(
let cid = Id::gen();
conn.execute(
r"
INSERT INTO `room_item` (`rid`, `uid`, `timestamp`, `nonce`, `sig`, `rich_text`)
VALUES (:rid, :uid, :timestamp, :nonce, :sig, :rich_text)
RETURNING `cid`
INSERT INTO `room_item` (`cid`, `rid`, `uid`, `timestamp`, `nonce`, `sig`, `rich_text`)
VALUES (:cid, :rid, :uid, :timestamp, :nonce, :sig, :rich_text)
",
named_params! {
":cid": cid,
":rid": rid,
":uid": uid,
":timestamp": chat.signee.timestamp,
@ -719,7 +723,6 @@ async fn room_post_item(
":rich_text": &chat.signee.payload.rich_text,
":sig": chat.sig,
},
|row| row.get::<_, u64>(0),
)?;
// FIXME: Optimize this to not traverses over all members.