From 9ee5156dfc042a25c3d3405972592a8ad520d026 Mon Sep 17 00:00:00 2001 From: fluo10 Date: Thu, 18 Sep 2025 08:14:57 +0900 Subject: [PATCH] Implement AuthorizationRequestRecords --- .../data/local/authorization_request/mod.rs | 47 ++++++++- .../local/authorization_request/received.rs | 95 +++++-------------- .../data/local/authorization_request/sent.rs | 75 ++++----------- core/src/data/local/migration/mod.rs | 5 +- core/src/data/local/migration/v1.rs | 53 +++++------ core/src/data/local/mod.rs | 68 ++++++++----- core/src/data/local/peer.rs | 59 +++++------- 7 files changed, 187 insertions(+), 215 deletions(-) diff --git a/core/src/data/local/authorization_request/mod.rs b/core/src/data/local/authorization_request/mod.rs index eba1521..70c68e9 100644 --- a/core/src/data/local/authorization_request/mod.rs +++ b/core/src/data/local/authorization_request/mod.rs @@ -5,9 +5,54 @@ mod received; use std::os::unix::raw::time_t; +use caretta_id::DoubleId; use chrono::{DateTime, Local, NaiveDateTime}; -use iroh::NodeId; +use iroh::{NodeId, PublicKey}; pub use sent::*; pub use received::*; use rusqlite::{params, types::FromSqlError, Connection}; use uuid::Uuid; + +use crate::data::local::LocalRecord; + + +/// Request of node authentication. +#[derive(Debug, Clone)] +pub struct AuthorizationRequestRecord { + id: u32, + uid: DoubleId, + peer_id: u32, + created_at: DateTime, + closed_at: Option>, +} + +impl LocalRecord for AuthorizationRequestRecord { + + const TABLE_NAME: &str = "authorization_request"; + const SELECT_COLUMNS: &[&str] = &[ + "id", + "uid", + "peer_id", + "created_at", + "closed_at" + ]; + const INSERT_COLUMNS: &[&str] = &[ + "uid", + "peer_id", + "created_at" + ]; + + type InsertParams<'a> = (&'a DoubleId, &'a [u8;32], &'a NaiveDateTime); + + fn from_row(row: &rusqlite::Row<'_>) -> Result { + let created_at: NaiveDateTime = row.get(3)?; + let closed_at: Option = row.get(4)?; + Ok(Self { + id: row.get(0)?, + uid: row.get(1)?, + peer_id: row.get(2)?, + created_at: created_at.and_utc().into(), + closed_at: closed_at.map(|x| x.and_utc().into()) + }) + } +} \ No newline at end of file diff --git a/core/src/data/local/authorization_request/received.rs b/core/src/data/local/authorization_request/received.rs index 18cd36e..fcd9d23 100644 --- a/core/src/data/local/authorization_request/received.rs +++ b/core/src/data/local/authorization_request/received.rs @@ -6,85 +6,36 @@ use crate::{data::local::LocalRecord, global::LOCAL_DATABASE_CONNECTION}; /// Response of node authentication. #[derive(Debug, Clone)] -pub struct ReceivedAuthorizationRequest { - request_id: SingleId, - public_key: PublicKey, - node_info: String, - created_at: DateTime, - responded_at: Option>, +pub struct ReceivedAuthorizationRequestRecord { + id: u32, + authorization_request_id: u32, + peer_note: String, } - - -impl ReceivedAuthorizationRequest { - pub fn get_by_request_id(request_id: SingleId) -> Result { - todo!() - } - pub fn get_by_public_key(public_key: PublicKey) -> Result { - todo!() - } - pub fn get_by_local_peer_id(local_peer_id: DoubleId) -> Result { - todo!() - } -} - -impl LocalRecord for ReceivedAuthorizationRequest { +impl LocalRecord for ReceivedAuthorizationRequestRecord { const TABLE_NAME: &str = "received_authorization_request"; - const DEFAULT_COLUMNS: &[&str] = &[ - "request_id", - "public_key", - "node_info", - "created_at", - "responded_at", - ]; - - type DefaultParams<'a> = (&'a SingleId, &'a [u8;32], &'a str, NaiveDateTime, Option) - where - Self: 'a; - fn as_default_params<'a>(&'a self) -> Self::DefaultParams<'a> { - (&self.request_id,&self.public_key.as_bytes(), &self.node_info, self.created_at.naive_utc(), self.responded_at.map(|x| x.naive_utc())) - } - fn from_default_row(row: &rusqlite::Row<'_>) -> Result { - let created_at: NaiveDateTime = row.get(3)?; - let responded_at: Option = row.get(4)?; + const SELECT_COLUMNS: &[&str] = &[ + "id", + "authorization_request_id", + "peer_note" + ]; + + const INSERT_COLUMNS: &[&str] = &[ + "authorization_request_id", + "peer_note" + ]; + + type InsertParams<'a> = (&'a u32, &'a str); + + fn from_row(row: &rusqlite::Row<'_>) -> Result { Ok(Self { - request_id: row.get(0)?, - public_key: PublicKey::from_bytes(&row.get(1)?).map_err(|e| rusqlite::types::FromSqlError::Other(Box::new(e)))?, - node_info: row.get(2)?, - created_at: DateTime::from(created_at.and_utc()), - responded_at: responded_at.map(|x| DateTime::from(x.and_utc())), + id: row.get(0)?, + authorization_request_id: row.get(1)?, + peer_note: row.get(2)? }) } - fn insert(&self) -> Result<(), rusqlite::Error> { - let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); - - connection.execute( - &("INSERT INTO ".to_string() + Self::TABLE_NAME + " (" + &Self::DEFAULT_COLUMNS.join(", ") + ") VALUES (?1, ?2, ?3, ?4, ?5)"), - ( - &self.request_id, - &self.public_key.as_bytes(), - &self.node_info, - &self.created_at.naive_utc(), - &self.responded_at.map(|x| x.naive_utc()), - ) - )?; - Ok(()) - } - fn get_all() -> Result, rusqlite::Error> { - let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); - - let mut stmt = connection.prepare(&(String::from("SELECT ") + &Self::DEFAULT_COLUMNS.join(", ") + " FROM " + Self::TABLE_NAME))?; - let rows = stmt.query_map( - [], - Self::from_default_row - )?; - let mut result= Vec::new(); - for row in rows { - result.push(row?); - } - Ok(result) - } + } \ No newline at end of file diff --git a/core/src/data/local/authorization_request/sent.rs b/core/src/data/local/authorization_request/sent.rs index e5cdf77..d7bdec6 100644 --- a/core/src/data/local/authorization_request/sent.rs +++ b/core/src/data/local/authorization_request/sent.rs @@ -7,68 +7,33 @@ use crate::{data::local::LocalRecord, global::LOCAL_DATABASE_CONNECTION}; /// Request of node authentication. #[derive(Debug, Clone)] -pub struct SentAuthorizationRequest { - request_id: SingleId, - public_key: PublicKey, +pub struct SentAuthorizationRequestRecord { + id: u32, + authorization_request_id: u32, passcode: String, - created_at: DateTime, - responded_at: Option>, } -impl LocalRecord for SentAuthorizationRequest { +impl LocalRecord for SentAuthorizationRequestRecord { - const TABLE_NAME: &str = "sent_authorization"; - const DEFAULT_COLUMNS: &[&str] = &[ - "request_id", - "public_key", + const TABLE_NAME: &str = "sent_authorization_request"; + const SELECT_COLUMNS: &[&str] = &[ + "id", + "authorization_request_id", "passcode", - "created_at", - "responded_at" ]; - type DefaultParams<'a> = (&'a SingleId, &'a [u8;32], &'a str, NaiveDateTime, Option) - where - Self: 'a; - fn from_default_row(row: &rusqlite::Row<'_>) -> Result { - let created_at: NaiveDateTime = row.get(2)?; - let responded_at: Option = row.get(3)?; - Ok(Self { - request_id: row.get(0)?, - public_key: PublicKey::from_bytes(&row.get(1)?).map_err(|e| FromSqlError::Other(Box::new(e)))?, - passcode: row.get(2)?, - created_at: DateTime::from(created_at.and_utc()), - responded_at: responded_at.map(|x| DateTime::from(x.and_utc())), + const INSERT_COLUMNS: &[&str] = &[ + "authorization_request_id", + "passcode" + ]; + + type InsertParams<'a> = (&'a u32, &'a str); + + fn from_row(row: &rusqlite::Row<'_>) -> Result { + Ok(Self{ + id: row.get(0)?, + authorization_request_id: row.get(0)?, + passcode: row.get(2)? }) } - fn insert(&self) -> Result<(), rusqlite::Error> { - let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); - connection.execute( - &(String::from("INSERT INTO ") + Self::TABLE_NAME + " (" + &Self::DEFAULT_COLUMNS.join(", ") + ") VALUES (?1, ?2, ?3, ?4, ?5)"), - ( - &self.request_id, - &self.public_key.as_bytes(), - &self.passcode, - &self.created_at.naive_utc(), - &self.responded_at.map(|x| x.naive_utc()) - ), - )?; - Ok(()) - } - fn get_all() -> Result, rusqlite::Error> { - let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); - - let mut stmt = connection.prepare(&(String::from("SELECT ") + &Self::DEFAULT_COLUMNS.join(", ") + " FROM " + Self::TABLE_NAME))?; - let rows = stmt.query_map( - [], - Self::from_default_row - )?; - let mut result= Vec::new(); - for row in rows { - result.push(row?); - } - Ok(result) - } - fn as_default_params<'a>(&'a self) -> Self::DefaultParams<'a> { - (&self.request_id, &self.public_key.as_bytes(), &self.passcode, self.created_at.naive_utc(), self.responded_at.map(|x| x.naive_utc())) - } } \ No newline at end of file diff --git a/core/src/data/local/migration/mod.rs b/core/src/data/local/migration/mod.rs index b8aa28b..a2c3971 100644 --- a/core/src/data/local/migration/mod.rs +++ b/core/src/data/local/migration/mod.rs @@ -6,8 +6,11 @@ use tracing::{event, Level}; pub fn migrate(con: &mut Connection) -> Result<(), Error>{ let version: u32 = con.pragma_query_value(None,"user_version", |row| row.get(0)).expect("Failed to get user_version"); if version < 1 { + let tx = con.transaction()?; event!(Level::INFO, "Migrate local db to version 1"); - v1::migrate(con)?; + v1::migrate(&tx)?; + tx.pragma_update(None, "user_version", 1)?; + tx.commit()?; event!(Level::INFO, "Migration done."); } Ok(()) diff --git a/core/src/data/local/migration/v1.rs b/core/src/data/local/migration/v1.rs index 11fed69..cd4ee63 100644 --- a/core/src/data/local/migration/v1.rs +++ b/core/src/data/local/migration/v1.rs @@ -1,46 +1,43 @@ -use rusqlite::{Error, Connection}; +use rusqlite::{Connection, Error, Transaction}; -pub fn migrate(con: &mut Connection) -> Result<(), Error>{ - let tx = con.transaction()?; +pub fn migrate(tx: &Transaction) -> Result<(), Error>{ tx.execute_batch( "CREATE TABLE peer ( - id INTEGER PRIMARY KEY, - local_peer_id INTEGER NOT NULL UNIQUE, - public_key BLOB UNIQUE NOT NULL + id INTEGER PRIMARY KEY, + uid INTEGER NOT NULL UNIQUE, + public_key BLOB UNIQUE NOT NULL + ); + CREATE TABLE authorization_request ( + id INTEGER PRIMARY KEY, + uid INTEGER NOT NULL UNIQUE, + peer_id INTEGER NOT NULL UNIQUE, + created_at TEXT NOT NULL, + closed_at TEXT, + FOREIGN KEY(peer_id) REFERENCES peer(id) ); CREATE TABLE received_authorization_request ( - id INTEGER PRIMARY KEY, - request_id INTEGER NOT NULL UNIQUE, - public_key BLOB NOT NULL UNIQUE, - node_info TEXT, - created_at TEXT NOT NULL, - responded_at TEXT + id INTEGER PRIMARY KEY + authorization_request_id INTEGER NOT NULL UNIQUE + peer_note TEXT, + FOREIGN KEY(authorization_request_id) REFERENCES authorization_request(id) ); CREATE TABLE sent_authorization_request ( - id INTEGER PRIMARY KEY, - request_id INTEGER NOT NULL UNIQUE, - public_key BLOB NOT NULL UNIQUE, - passcode TEXT NOT NULL, - created_at TEXT NOT NULL, - responded_at TEXT + id INTEGER PRIMARY KEY, + authorization_request_id INTEGER NOT NULL UNIQUE, + passcode TEXT NOT NULL, + FOREIGN KEY(authorization_request_id) REFERENCES authorization_request(id) ); CREATE TABLE authorized_peer ( id INTEGER PRIMARY KEY, - node_id BLOB NOT NULL UNIQUE, + uid INTEGER NOT NULL UNIQUE, + public_key BLOB NOT NULL UNIQUE, + note TEXT NOT NULL, last_synced_at TEXT, last_sent_version_vector BLOB, created_at TEXT NOT NULL, updated_at TEXT NOT NULL - ); - CREATE TABLE authorization ( - id INTEGER PRIMARY KEY, - node_id BLOB UNIQUE NOT NULL, - passcode TEXT NOT NULL, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL );", )?; - tx.pragma_update(None, "user_version", 1)?; - tx.commit()?; + Ok(()) } \ No newline at end of file diff --git a/core/src/data/local/mod.rs b/core/src/data/local/mod.rs index 69c0dce..9f073ea 100644 --- a/core/src/data/local/mod.rs +++ b/core/src/data/local/mod.rs @@ -15,34 +15,45 @@ pub use authorization_request::*; /// use LOCAL_DATABASE_CONNECTION for database connection. pub trait LocalRecord: Sized { const TABLE_NAME: &str; - const DEFAULT_COLUMNS: &[&str]; + const SELECT_COLUMNS: &[&str]; + const INSERT_COLUMNS: &[&str]; - const DEFAULT_SELECT_STATEMENT: LazyLock = LazyLock::new(|| { - String::from("SELECT ") + &Self::DEFAULT_COLUMNS.join(", ") + " FROM " + Self::TABLE_NAME + const SELECT_STATEMENT: LazyLock = LazyLock::new(|| { + String::from("SELECT ") + &Self::SELECT_COLUMNS.join(", ") + " FROM " + Self::TABLE_NAME }); - const DEFAULT_PLACEHOLDER: LazyLock = LazyLock::new(|| { + const SELECT_PLACEHOLDER: LazyLock = LazyLock::new(|| { let mut result : Vec = Vec::new(); - for i in 0..Self::DEFAULT_COLUMNS.len() { + for i in 0..Self::SELECT_COLUMNS.len() { + result.push(String::from("?") + &(i+1).to_string()); + } + result.join(", ") + }); + const INSERT_PLACEHOLDER: LazyLock = LazyLock::new(|| { + let mut result : Vec = Vec::new(); + for i in 0..Self::INSERT_COLUMNS.len() { result.push(String::from("?") + &(i+1).to_string()); } result.join(", ") }); - type DefaultParams<'a>: Params + type InsertParams<'a>: Params where Self: 'a; - fn as_default_params<'a>(&'a self) -> Self::DefaultParams<'a>; - - fn insert(&self) -> Result<(), rusqlite::Error> { + fn insert(params: Self::InsertParams<'_>) -> Result + { let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); - connection.execute( - &("INSERT INTO ".to_owned() + Self::TABLE_NAME + " (" + &Self::DEFAULT_COLUMNS.join(", ") + ") VALUES (" + &*Self::DEFAULT_PLACEHOLDER + ")"), - self.as_default_params() - )?; - Ok(()) + Ok(connection.query_row( + &[ + "INSERT INTO ", Self::TABLE_NAME, "(" , &Self::INSERT_COLUMNS.join(", "), ")", + "VALUES (" , &*Self::INSERT_PLACEHOLDER , ")", + "RETURNING", &Self::SELECT_COLUMNS.join(", ") + ].join(" "), + params, + Self::from_row + )?) } fn get_one_where

(where_statement: &str, params: P) -> Result, rusqlite::Error> @@ -50,26 +61,39 @@ pub trait LocalRecord: Sized { { let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); Ok(connection.query_row( - &(String::new() + &Self::DEFAULT_SELECT_STATEMENT + " " + where_statement), + &(String::new() + &Self::SELECT_STATEMENT + " " + where_statement), params, - Self::from_default_row + Self::from_row ).optional()?) } - fn get_one_by(field_name: &str, field_value: T) -> Result, rusqlite::Error> + fn get_one_by_field(field_name: &str, field_value: T) -> Result, rusqlite::Error> where T: ToSql { let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); Ok(Some(connection.query_row( - &("SELECT ".to_string() + &Self::DEFAULT_COLUMNS.join(", ") + " FROM " + Self::TABLE_NAME + " WHERE " + field_name + "=(?1)"), + &("SELECT ".to_string() + &Self::SELECT_COLUMNS.join(", ") + " FROM " + Self::TABLE_NAME + " WHERE " + field_name + "= ?1"), params![field_value], - Self::from_default_row + Self::from_row )?)) } fn get_one_by_id(id: u32) -> Result, rusqlite::Error> { - Self::get_one_by("id", id ) + Self::get_one_by_field("id", id ) + } + fn from_row(row: &Row<'_>) -> Result; + fn get_all() -> Result, rusqlite::Error> { + let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); + let mut stmt = connection.prepare(&("SELECT ".to_string() + &Self::SELECT_COLUMNS.join(", ") + " FROM " + Self::TABLE_NAME))?; + let rows = stmt.query_map( + [], + Self::from_row + )?; + let mut result= Vec::new(); + for row in rows { + result.push(row?); + } + Ok(result) + } - fn from_default_row(row: &Row<'_>) -> Result; - fn get_all() -> Result, rusqlite::Error>; } \ No newline at end of file diff --git a/core/src/data/local/peer.rs b/core/src/data/local/peer.rs index 53c39ab..f98c459 100644 --- a/core/src/data/local/peer.rs +++ b/core/src/data/local/peer.rs @@ -5,14 +5,14 @@ use std::os::unix::raw::time_t; use caretta_id::DoubleId; use chrono::{DateTime, Local, NaiveDateTime}; use iroh::{NodeId, PublicKey}; -use rusqlite::{params, types::FromSqlError, Connection}; +use rusqlite::{params, types::{FromSqlError, Null}, Connection}; use uuid::Uuid; use crate::{data::local::{self, LocalRecord}, global::LOCAL_DATABASE_CONNECTION}; /// Peer information cached in local database. /// -/// - Currently this only contain local id and public key (=node id) of iroh. +/// - Currently this only contain local uid and public key (=node id) of iroh. /// - This is a junction table enable to use caretta-id to specify items in the UI, especially on the CLI. /// - Actual peer information is managed by iroh endpoint and not contained in this model. /// - Once a peer is authorized, it is assigned a global (=synced) ID as authorized_peer so essentially this local id targets unauthorized peers. @@ -20,9 +20,12 @@ use crate::{data::local::{self, LocalRecord}, global::LOCAL_DATABASE_CONNECTION} #[derive(Clone, Debug, PartialEq)] pub struct PeerRecord { - /// local id of peer. + /// primary key. + pub id: u32, + + /// uid of peer. /// this id is use only the node itself and not synced so another node has different local_peer_id even if its public_key is same. - pub local_peer_id: DoubleId, + pub uid: DoubleId, pub public_key: PublicKey, } @@ -31,17 +34,13 @@ impl PeerRecord { match Self::get_by_public_key(public_key)? { Some(x) => Ok(x), None => { - let new = Self{ - local_peer_id: rand::random(), - public_key: public_key.clone(), - }; - new.insert()?; - Ok(new) + let new_uid: DoubleId = rand::random(); + Ok(Self::insert((&new_uid, public_key.as_bytes()))?) } } } - pub fn get_by_local_id(local_id: &DoubleId) -> Result, rusqlite::Error> { + pub fn get_by_uid(local_id: &DoubleId) -> Result, rusqlite::Error> { Self::get_one_where("WHERE local_peer_id = ?1", (local_id,)) } pub fn get_by_public_key(public_key: &PublicKey) -> Result, rusqlite::Error> { @@ -51,36 +50,25 @@ impl PeerRecord { impl LocalRecord for PeerRecord { const TABLE_NAME: &str = "peer"; - const DEFAULT_COLUMNS: &[&str] = &[ - "local_peer_id", + const SELECT_COLUMNS: &[&str] = &[ + "id", + "uid", "public_key" ]; - type DefaultParams<'a> = (&'a DoubleId, &'a [u8;32]); - fn as_default_params<'a>(&'a self) -> Self::DefaultParams<'a> - { - (&self.local_peer_id, &self.public_key.as_bytes()) - } + const INSERT_COLUMNS: &[&str] = &[ + "uid", + "public_key" + ]; + type InsertParams<'a> = (&'a DoubleId, &'a [u8;32]); - fn from_default_row(row: &rusqlite::Row<'_>) -> Result { + fn from_row(row: &rusqlite::Row<'_>) -> Result { Ok(Self { - local_peer_id: row.get(0)?, - public_key: PublicKey::from_bytes(&row.get(1)?).map_err(|e| FromSqlError::Other(Box::new(e)))? + id: row.get(0)?, + uid: row.get(1)?, + public_key: PublicKey::from_bytes(&row.get(2)?).map_err(|e| FromSqlError::Other(Box::new(e)))? }) } - fn get_all() -> Result, rusqlite::Error> { - let connection = LOCAL_DATABASE_CONNECTION.get_unchecked(); - let mut stmt = connection.prepare(&("SELECT ".to_string() + &Self::DEFAULT_COLUMNS.join(", ") + " FROM " + Self::TABLE_NAME))?; - let rows = stmt.query_map( - [], - Self::from_default_row - )?; - let mut result= Vec::new(); - for row in rows { - result.push(row?); - } - Ok(result) - } } #[cfg(test)] @@ -97,8 +85,7 @@ mod tests { let key = SecretKey::generate(&mut rand::rngs::OsRng); let pubkey = key.public(); let record = PeerRecord::get_or_insert_by_public_key(&pubkey).unwrap(); - assert_eq!(record, PeerRecord::get_by_local_id(&record.local_peer_id).unwrap().unwrap()); + assert_eq!(record, PeerRecord::get_by_uid(&record.uid).unwrap().unwrap()); assert_eq!(record, PeerRecord::get_by_public_key(&record.public_key).unwrap().unwrap()); - } } \ No newline at end of file