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: 200:
content: content:
application/json: application/json:
type: integer type: string
description: Created chat id (cid). description: Created chat id (cid).
400: 400:
description: Body is invalid or fails the verification. 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 INDEX IF NOT EXISTS `member_room` ON `room_member` (`uid` ASC, `rid` ASC);
CREATE TABLE IF NOT EXISTS `room_item` ( 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, `rid` INTEGER NOT NULL REFERENCES `room` ON DELETE CASCADE,
`uid` INTEGER NOT NULL REFERENCES `user` ON DELETE RESTRICT, `uid` INTEGER NOT NULL REFERENCES `user` ON DELETE RESTRICT,
`timestamp` INTEGER NOT NULL, `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. // Simple and stupid version check for now.
// `echo -n 'blahd-database-0' | sha256sum | head -c5` || version // `echo -n 'blahd-database-0' | sha256sum | head -c5` || version
const APPLICATION_ID: i32 = 0xd9e_8401; const APPLICATION_ID: i32 = 0xd9e_8402;
#[derive(Debug)] #[derive(Debug)]
pub struct Database { 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 config::Config;
use database::Database; use database::Database;
use ed25519_dalek::SIGNATURE_LENGTH; use ed25519_dalek::SIGNATURE_LENGTH;
use id::IdExt;
use middleware::{ApiError, MaybeAuth, ResultExt as _, SignedJson}; use middleware::{ApiError, MaybeAuth, ResultExt as _, SignedJson};
use parking_lot::Mutex; use parking_lot::Mutex;
use rusqlite::{named_params, params, Connection, OptionalExtension, Row, ToSql}; use rusqlite::{named_params, params, Connection, OptionalExtension, Row, ToSql};
@ -30,6 +31,7 @@ mod middleware;
mod config; mod config;
mod database; mod database;
mod event; mod event;
mod id;
mod utils; mod utils;
/// Blah Chat Server /// Blah Chat Server
@ -356,17 +358,18 @@ async fn room_create(
}; };
let txn = conn.transaction()?; let txn = conn.transaction()?;
let rid = Id::gen();
txn.execute( txn.execute(
r" r"
INSERT INTO `room` (`title`, `attrs`) INSERT INTO `room` (`rid`, `title`, `attrs`)
VALUES (:title, :attrs) VALUES (:rid, :title, :attrs)
", ",
named_params! { named_params! {
":rid": rid,
":title": params.signee.payload.title, ":title": params.signee.payload.title,
":attrs": params.signee.payload.attrs, ":attrs": params.signee.payload.attrs,
}, },
)?; )?;
let rid = Id(txn.last_insert_rowid());
let mut insert_user = txn.prepare( let mut insert_user = txn.prepare(
r" r"
INSERT INTO `user` (`userkey`) INSERT INTO `user` (`userkey`)
@ -664,7 +667,7 @@ async fn room_post_item(
st: ArcState, st: ArcState,
R(Path(rid), _): RE<Path<Id>>, R(Path(rid), _): RE<Path<Id>>,
SignedJson(chat): SignedJson<ChatPayload>, SignedJson(chat): SignedJson<ChatPayload>,
) -> Result<Json<u64>, ApiError> { ) -> Result<Json<Id>, ApiError> {
if rid != chat.signee.payload.room { if rid != chat.signee.payload.room {
return Err(error_response!( return Err(error_response!(
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
@ -705,13 +708,14 @@ async fn room_post_item(
)); ));
}; };
let cid = conn.query_row( let cid = Id::gen();
conn.execute(
r" r"
INSERT INTO `room_item` (`rid`, `uid`, `timestamp`, `nonce`, `sig`, `rich_text`) INSERT INTO `room_item` (`cid`, `rid`, `uid`, `timestamp`, `nonce`, `sig`, `rich_text`)
VALUES (:rid, :uid, :timestamp, :nonce, :sig, :rich_text) VALUES (:cid, :rid, :uid, :timestamp, :nonce, :sig, :rich_text)
RETURNING `cid`
", ",
named_params! { named_params! {
":cid": cid,
":rid": rid, ":rid": rid,
":uid": uid, ":uid": uid,
":timestamp": chat.signee.timestamp, ":timestamp": chat.signee.timestamp,
@ -719,7 +723,6 @@ async fn room_post_item(
":rich_text": &chat.signee.payload.rich_text, ":rich_text": &chat.signee.payload.rich_text,
":sig": chat.sig, ":sig": chat.sig,
}, },
|row| row.get::<_, u64>(0),
)?; )?;
// FIXME: Optimize this to not traverses over all members. // FIXME: Optimize this to not traverses over all members.