Implement AuthorizationRequestRecords

This commit is contained in:
fluo10 2025-09-18 08:14:57 +09:00
parent 88d87bd25d
commit 9ee5156dfc
7 changed files with 187 additions and 215 deletions

View file

@ -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<Local>,
closed_at: Option<DateTime<Local>>,
}
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<Self, rusqlite::Error> {
let created_at: NaiveDateTime = row.get(3)?;
let closed_at: Option<NaiveDateTime> = 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())
})
}
}

View file

@ -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<Local>,
responded_at: Option<DateTime<Local>>,
pub struct ReceivedAuthorizationRequestRecord {
id: u32,
authorization_request_id: u32,
peer_note: String,
}
impl ReceivedAuthorizationRequest {
pub fn get_by_request_id(request_id: SingleId) -> Result<Self, rusqlite::Error> {
todo!()
}
pub fn get_by_public_key(public_key: PublicKey) -> Result<Self, rusqlite::Error> {
todo!()
}
pub fn get_by_local_peer_id(local_peer_id: DoubleId) -> Result<Self, rusqlite::Error> {
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",
const SELECT_COLUMNS: &[&str] = &[
"id",
"authorization_request_id",
"peer_note"
];
type DefaultParams<'a> = (&'a SingleId, &'a [u8;32], &'a str, NaiveDateTime, Option<NaiveDateTime>)
where
Self: 'a;
const INSERT_COLUMNS: &[&str] = &[
"authorization_request_id",
"peer_note"
];
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<Self, rusqlite::Error> {
let created_at: NaiveDateTime = row.get(3)?;
let responded_at: Option<NaiveDateTime> = row.get(4)?;
type InsertParams<'a> = (&'a u32, &'a str);
fn from_row(row: &rusqlite::Row<'_>) -> Result<Self, rusqlite::Error> {
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<Vec<Self>, 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)
}
}

View file

@ -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<Local>,
responded_at: Option<DateTime<Local>>,
}
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<NaiveDateTime>)
where
Self: 'a;
fn from_default_row(row: &rusqlite::Row<'_>) -> Result<Self, rusqlite::Error> {
let created_at: NaiveDateTime = row.get(2)?;
let responded_at: Option<NaiveDateTime> = row.get(3)?;
const INSERT_COLUMNS: &[&str] = &[
"authorization_request_id",
"passcode"
];
type InsertParams<'a> = (&'a u32, &'a str);
fn from_row(row: &rusqlite::Row<'_>) -> Result<Self, rusqlite::Error> {
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())),
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<Vec<Self>, 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()))
}
}

View file

@ -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(())

View file

@ -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,
uid INTEGER NOT NULL UNIQUE,
public_key BLOB UNIQUE NOT NULL
);
CREATE TABLE received_authorization_request (
CREATE TABLE authorization_request (
id INTEGER PRIMARY KEY,
request_id INTEGER NOT NULL UNIQUE,
public_key BLOB NOT NULL UNIQUE,
node_info TEXT,
uid INTEGER NOT NULL UNIQUE,
peer_id INTEGER NOT NULL UNIQUE,
created_at TEXT NOT NULL,
responded_at TEXT
closed_at TEXT,
FOREIGN KEY(peer_id) REFERENCES peer(id)
);
CREATE TABLE received_authorization_request (
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,
authorization_request_id INTEGER NOT NULL UNIQUE,
passcode TEXT NOT NULL,
created_at TEXT NOT NULL,
responded_at TEXT
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(())
}

View file

@ -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<String> = LazyLock::new(|| {
String::from("SELECT ") + &Self::DEFAULT_COLUMNS.join(", ") + " FROM " + Self::TABLE_NAME
const SELECT_STATEMENT: LazyLock<String> = LazyLock::new(|| {
String::from("SELECT ") + &Self::SELECT_COLUMNS.join(", ") + " FROM " + Self::TABLE_NAME
});
const DEFAULT_PLACEHOLDER: LazyLock<String> = LazyLock::new(|| {
const SELECT_PLACEHOLDER: LazyLock<String> = LazyLock::new(|| {
let mut result : Vec<String> = 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<String> = LazyLock::new(|| {
let mut result : Vec<String> = 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<Self, rusqlite::Error>
{
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<P>(where_statement: &str, params: P) -> Result<Option<Self>, 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<T>(field_name: &str, field_value: T) -> Result<Option<Self>, rusqlite::Error>
fn get_one_by_field<T>(field_name: &str, field_value: T) -> Result<Option<Self>, 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<Option<Self>, rusqlite::Error> {
Self::get_one_by("id", id )
Self::get_one_by_field("id", id )
}
fn from_row(row: &Row<'_>) -> Result<Self, rusqlite::Error>;
fn get_all() -> Result<Vec<Self>, 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<Self, rusqlite::Error>;
fn get_all() -> Result<Vec<Self>, rusqlite::Error>;
}

View file

@ -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<Option<Self>, rusqlite::Error> {
pub fn get_by_uid(local_id: &DoubleId) -> Result<Option<Self>, rusqlite::Error> {
Self::get_one_where("WHERE local_peer_id = ?1", (local_id,))
}
pub fn get_by_public_key(public_key: &PublicKey) -> Result<Option<Self>, 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<Self, rusqlite::Error> {
fn from_row(row: &rusqlite::Row<'_>) -> Result<Self, rusqlite::Error> {
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<Vec<Self>, 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());
}
}