From 12d79792d9aa9ae619539ee4e741d30f2045fdde Mon Sep 17 00:00:00 2001 From: yggverse Date: Thu, 23 Jan 2025 10:57:12 +0200 Subject: [PATCH] make multi-protocol identity feature --- .../window/tab/item/client/driver/gemini.rs | 1 - .../window/tab/item/identity/gemini.rs | 23 +-- .../tab/item/identity/gemini/widget/form.rs | 7 +- .../item/identity/gemini/widget/form/drop.rs | 10 +- .../item/identity/gemini/widget/form/exit.rs | 9 +- .../item/identity/gemini/widget/form/list.rs | 21 +-- .../identity/gemini/widget/form/list/item.rs | 61 +++---- .../gemini/widget/form/list/item/is_active.rs | 7 +- .../gemini/widget/form/list/item/subtitle.rs | 5 +- .../gemini/widget/form/list/item/title.rs | 2 +- .../gemini/widget/form/list/item/tooltip.rs | 5 +- .../gemini/widget/form/list/item/value.rs | 2 +- .../item/identity/gemini/widget/form/save.rs | 4 +- .../gemini/widget/form/save/certificate.rs | 11 +- .../widget/form/save/certificate/error.rs | 4 +- .../window/tab/item/page/navigation.rs | 1 - src/profile/identity.rs | 149 +++++++++++++---- src/profile/identity/{gemini => }/auth.rs | 30 ++-- .../identity/{gemini => }/auth/database.rs | 57 +++---- .../identity/{gemini => }/auth/error.rs | 0 .../identity/{gemini => }/auth/memory.rs | 21 +-- .../identity/{gemini => }/auth/memory/auth.rs | 2 +- .../{gemini => }/auth/memory/error.rs | 0 .../identity/{gemini => }/certificate.rs | 0 src/profile/identity/database.rs | 118 +++++++------ src/profile/identity/error.rs | 12 +- src/profile/identity/gemini.rs | 158 ------------------ src/profile/identity/gemini/database.rs | 146 ---------------- src/profile/identity/gemini/error.rs | 24 --- .../identity/{gemini/identity.rs => item.rs} | 4 +- .../{gemini/identity => item}/error.rs | 0 src/profile/identity/{gemini => }/memory.rs | 8 +- .../identity/{gemini => }/memory/error.rs | 0 33 files changed, 309 insertions(+), 593 deletions(-) rename src/profile/identity/{gemini => }/auth.rs (73%) rename src/profile/identity/{gemini => }/auth/database.rs (64%) rename src/profile/identity/{gemini => }/auth/error.rs (100%) rename src/profile/identity/{gemini => }/auth/memory.rs (78%) rename src/profile/identity/{gemini => }/auth/memory/auth.rs (59%) rename src/profile/identity/{gemini => }/auth/memory/error.rs (100%) rename src/profile/identity/{gemini => }/certificate.rs (100%) delete mode 100644 src/profile/identity/gemini.rs delete mode 100644 src/profile/identity/gemini/database.rs delete mode 100644 src/profile/identity/gemini/error.rs rename src/profile/identity/{gemini/identity.rs => item.rs} (93%) rename src/profile/identity/{gemini/identity => item}/error.rs (100%) rename src/profile/identity/{gemini => }/memory.rs (82%) rename src/profile/identity/{gemini => }/memory/error.rs (100%) diff --git a/src/app/browser/window/tab/item/client/driver/gemini.rs b/src/app/browser/window/tab/item/client/driver/gemini.rs index 9c50f5e7..a9b76442 100644 --- a/src/app/browser/window/tab/item/client/driver/gemini.rs +++ b/src/app/browser/window/tab/item/client/driver/gemini.rs @@ -164,7 +164,6 @@ fn handle( .page .profile .identity - .gemini .match_scope(&uri.to_string()) { Some(identity) => match identity.to_tls_certificate() { diff --git a/src/app/browser/window/tab/item/identity/gemini.rs b/src/app/browser/window/tab/item/identity/gemini.rs index c154f291..6fe65e4a 100644 --- a/src/app/browser/window/tab/item/identity/gemini.rs +++ b/src/app/browser/window/tab/item/identity/gemini.rs @@ -42,25 +42,20 @@ impl Gemini { move |response| { // Get option match user choice let option = match response { - Value::ProfileIdentityGeminiId(value) => Some(value), + Value::ProfileIdentityId(value) => Some(value), Value::GuestSession => None, Value::GeneratePem => Some( match profile .identity - .gemini .make(None, &widget.form.name.value().unwrap()) { - Ok(profile_identity_gemini_id) => profile_identity_gemini_id, + Ok(profile_identity_id) => profile_identity_id, Err(e) => todo!("{}", e.to_string()), }, ), Value::ImportPem => Some( - match profile - .identity - .gemini - .add(&widget.form.file.pem.take().unwrap()) - { - Ok(profile_identity_gemini_id) => profile_identity_gemini_id, + match profile.identity.add(&widget.form.file.pem.take().unwrap()) { + Ok(profile_identity_id) => profile_identity_id, Err(e) => todo!("{}", e.to_string()), }, ), @@ -69,19 +64,15 @@ impl Gemini { // Apply auth match option { // Activate identity for `auth_uri` - Some(profile_identity_gemini_id) => { - if let Err(e) = profile - .identity - .gemini - .auth - .apply(profile_identity_gemini_id, &auth_url) + Some(profile_identity_id) => { + if let Err(e) = profile.identity.auth.apply(profile_identity_id, &auth_url) { todo!("{}", e.to_string()) }; } // Remove all identity auths for `auth_uri` None => { - if let Err(e) = profile.identity.gemini.auth.remove_scope(&auth_url) { + if let Err(e) = profile.identity.auth.remove_scope(&auth_url) { todo!("{}", e.to_string()) }; } diff --git a/src/app/browser/window/tab/item/identity/gemini/widget/form.rs b/src/app/browser/window/tab/item/identity/gemini/widget/form.rs index b0926bb8..2f00ac10 100644 --- a/src/app/browser/window/tab/item/identity/gemini/widget/form.rs +++ b/src/app/browser/window/tab/item/identity/gemini/widget/form.rs @@ -104,19 +104,16 @@ impl Form { self.file.update(matches!(value, Value::ImportPem)); match value { - Value::ProfileIdentityGeminiId(profile_identity_gemini_id) => { + Value::ProfileIdentityId(profile_identity_id) => { self.drop.update(true); self.exit.update( true, self.profile .identity - .gemini .auth .memory .match_scope(&self.auth_uri.to_string()) - .is_some_and(|auth| { - auth.profile_identity_gemini_id == profile_identity_gemini_id - }), + .is_some_and(|auth| auth.profile_identity_id == profile_identity_id), ); self.save.update(true); } diff --git a/src/app/browser/window/tab/item/identity/gemini/widget/form/drop.rs b/src/app/browser/window/tab/item/identity/gemini/widget/form/drop.rs index 3e729ecc..34f44898 100644 --- a/src/app/browser/window/tab/item/identity/gemini/widget/form/drop.rs +++ b/src/app/browser/window/tab/item/identity/gemini/widget/form/drop.rs @@ -45,7 +45,7 @@ impl Drop { let profile = profile.clone(); move |_| { match list.selected().value_enum() { - Value::ProfileIdentityGeminiId(profile_identity_gemini_id) => { + Value::ProfileIdentityId(profile_identity_id) => { // Init sub-widget let alert_dialog = AlertDialog::builder() .heading(HEADING) @@ -74,13 +74,9 @@ impl Drop { let button = button.clone(); let list = list.clone(); let profile = profile.clone(); - move |_, _| match profile - .identity - .gemini - .delete(profile_identity_gemini_id) - { + move |_, _| match profile.identity.delete(profile_identity_id) { Ok(_) => { - if list.remove(profile_identity_gemini_id).is_some() { + if list.remove(profile_identity_id).is_some() { button.set_css_classes(&["success"]); button.set_label("Identity successfully deleted") } else { diff --git a/src/app/browser/window/tab/item/identity/gemini/widget/form/exit.rs b/src/app/browser/window/tab/item/identity/gemini/widget/form/exit.rs index 89226377..19be9ae5 100644 --- a/src/app/browser/window/tab/item/identity/gemini/widget/form/exit.rs +++ b/src/app/browser/window/tab/item/identity/gemini/widget/form/exit.rs @@ -58,7 +58,7 @@ impl Exit { move |_| { // Get selected identity from holder match list.selected().value_enum() { - Value::ProfileIdentityGeminiId(profile_identity_gemini_id) => { + Value::ProfileIdentityId(profile_identity_id) => { // Init sub-widget let alert_dialog = AlertDialog::builder() .heading(HEADING) @@ -91,12 +91,7 @@ impl Exit { let browser_action = browser_action.clone(); let widget_action = widget_action.clone(); move |_, _| { - match profile - .identity - .gemini - .auth - .remove_ref(profile_identity_gemini_id) - { + match profile.identity.auth.remove_ref(profile_identity_id) { Ok(_) => match list .selected() .update(&profile, &auth_uri.to_string()) diff --git a/src/app/browser/window/tab/item/identity/gemini/widget/form/list.rs b/src/app/browser/window/tab/item/identity/gemini/widget/form/list.rs index e850026e..1748a098 100644 --- a/src/app/browser/window/tab/item/identity/gemini/widget/form/list.rs +++ b/src/app/browser/window/tab/item/identity/gemini/widget/form/list.rs @@ -38,15 +38,12 @@ impl List { list_store.append(&generate_pem); list_store.append(&import_pem); - match profile.identity.gemini.database.records() { + match profile.identity.database.records() { Ok(identities) => { let mut is_guest_session = true; for identity in identities { - match Item::new_profile_identity_gemini_id( - profile, - identity.id, - &auth_uri.to_string(), - ) { + match Item::new_profile_identity_id(profile, identity.id, &auth_uri.to_string()) + { Ok(item) => { if item.is_active() { is_guest_session = false; @@ -178,18 +175,18 @@ impl List { // Actions - /// Find list item by `profile_identity_gemini_id` + /// Find list item by `profile_identity_id` /// * return `position` found - pub fn find(&self, profile_identity_gemini_id: i64) -> Option { + pub fn find(&self, profile_identity_id: i64) -> Option { self.list_store.find_with_equal_func(|this| { - profile_identity_gemini_id == this.downcast_ref::().unwrap().value() + profile_identity_id == this.downcast_ref::().unwrap().value() }) } - /// Remove list item by `profile_identity_gemini_id` + /// Remove list item by `profile_identity_id` /// * return `position` of removed list item - pub fn remove(&self, profile_identity_gemini_id: i64) -> Option { - match self.find(profile_identity_gemini_id) { + pub fn remove(&self, profile_identity_id: i64) -> Option { + match self.find(profile_identity_id) { Some(position) => { self.list_store.remove(position); Some(position) diff --git a/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item.rs b/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item.rs index 10fcbf18..cc8611d8 100644 --- a/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item.rs +++ b/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item.rs @@ -21,7 +21,7 @@ glib::wrapper! { } // C-type property `value` conversion for `Item` -// * values > 0 reserved for `profile_identity_gemini_id` +// * values > 0 reserved for `profile_identity_id` const G_VALUE_GENERATE_PEM: i64 = 0; const G_VALUE_IMPORT_PEM: i64 = -1; const G_VALUE_GUEST_SESSION: i64 = -2; @@ -53,42 +53,34 @@ impl Item { .build() } - pub fn new_profile_identity_gemini_id( + pub fn new_profile_identity_id( profile: &Rc, - profile_identity_gemini_id: i64, + profile_identity_id: i64, auth_url: &str, ) -> Result { // Get PEM by ID - match profile - .identity - .gemini - .memory - .get(profile_identity_gemini_id) - { + match profile.identity.memory.get(profile_identity_id) { // Extract certificate details from PEM string Ok(ref pem) => match TlsCertificate::from_pem(pem) { // Collect certificate scopes for item - Ok(ref certificate) => match scope(profile, profile_identity_gemini_id) { + Ok(ref certificate) => match scope(profile, profile_identity_id) { // Ready to build `Item` GObject Ok(ref scope) => Ok(Object::builder() - .property("value", profile_identity_gemini_id) - .property( - "title", - title::new_for_profile_identity_gemini_id(certificate), - ) + .property("value", profile_identity_id) + .property("title", title::new_for_profile_identity_id(certificate)) .property( "subtitle", - subtitle::new_for_profile_identity_gemini_id(certificate, scope), + subtitle::new_for_profile_identity_id(certificate, scope), ) .property( "tooltip", - tooltip::new_for_profile_identity_gemini_id(certificate, scope), + tooltip::new_for_profile_identity_id(certificate, scope), ) .property( "is-active", - is_active::new_for_profile_identity_gemini_id( + is_active::new_for_profile_identity_id( profile, - profile_identity_gemini_id, + profile_identity_id, auth_url, ), ) @@ -107,36 +99,31 @@ impl Item { pub fn update(&self, profile: &Rc, auth_url: &str) -> Result<(), Error> { // Update item depending on value type match self.value_enum() { - Value::ProfileIdentityGeminiId(profile_identity_gemini_id) => { + Value::ProfileIdentityId(profile_identity_id) => { // Get PEM by ID - match profile - .identity - .gemini - .memory - .get(profile_identity_gemini_id) - { + match profile.identity.memory.get(profile_identity_id) { // Extract certificate details from PEM string Ok(ref pem) => match TlsCertificate::from_pem(pem) { Ok(ref certificate) => { // Get current scope - let scope = &scope(profile, profile_identity_gemini_id)?; + let scope = &scope(profile, profile_identity_id)?; // Update properties - self.set_title(title::new_for_profile_identity_gemini_id(certificate)); + self.set_title(title::new_for_profile_identity_id(certificate)); - self.set_subtitle(subtitle::new_for_profile_identity_gemini_id( + self.set_subtitle(subtitle::new_for_profile_identity_id( certificate, scope, )); - self.set_tooltip(tooltip::new_for_profile_identity_gemini_id( + self.set_tooltip(tooltip::new_for_profile_identity_id( certificate, scope, )); - self.set_is_active(is_active::new_for_profile_identity_gemini_id( + self.set_is_active(is_active::new_for_profile_identity_id( profile, - profile_identity_gemini_id, + profile_identity_id, auth_url, )); @@ -162,21 +149,21 @@ impl Item { G_VALUE_GENERATE_PEM => Value::GeneratePem, G_VALUE_GUEST_SESSION => Value::GuestSession, G_VALUE_IMPORT_PEM => Value::ImportPem, - value => Value::ProfileIdentityGeminiId(value), + value => Value::ProfileIdentityId(value), } } } // Tools -/// Collect certificate scope vector from `Profile` database for `profile_identity_gemini_id` -fn scope(profile: &Rc, profile_identity_gemini_id: i64) -> Result, Error> { - match profile.identity.gemini.auth.database.records_scope(None) { +/// Collect certificate scope vector from `Profile` database for `profile_identity_id` +fn scope(profile: &Rc, profile_identity_id: i64) -> Result, Error> { + match profile.identity.auth.database.records_scope(None) { Ok(result) => { let mut scope = Vec::new(); for auth in result .iter() - .filter(|this| this.profile_identity_gemini_id == profile_identity_gemini_id) + .filter(|this| this.profile_identity_id == profile_identity_id) { scope.push(auth.scope.clone()) } diff --git a/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/is_active.rs b/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/is_active.rs index 32964601..ac62095c 100644 --- a/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/is_active.rs +++ b/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/is_active.rs @@ -1,16 +1,15 @@ use crate::profile::Profile; use std::rc::Rc; -pub fn new_for_profile_identity_gemini_id( +pub fn new_for_profile_identity_id( profile: &Rc, - profile_identity_gemini_id: i64, + profile_identity_id: i64, auth_url: &str, ) -> bool { profile .identity - .gemini .auth .memory .match_scope(auth_url) - .is_some_and(|auth| auth.profile_identity_gemini_id == profile_identity_gemini_id) + .is_some_and(|auth| auth.profile_identity_id == profile_identity_id) } diff --git a/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/subtitle.rs b/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/subtitle.rs index 8fb6b741..d0c7029c 100644 --- a/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/subtitle.rs +++ b/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/subtitle.rs @@ -2,10 +2,7 @@ use gtk::{gio::TlsCertificate, prelude::TlsCertificateExt}; const DATE_FORMAT: &str = "%Y.%m.%d"; -pub fn new_for_profile_identity_gemini_id( - certificate: &TlsCertificate, - scope: &[String], -) -> String { +pub fn new_for_profile_identity_id(certificate: &TlsCertificate, scope: &[String]) -> String { format!( "{} - {} | scope: {}", certificate diff --git a/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/title.rs b/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/title.rs index 3ad0d8e8..8dcf7a9a 100644 --- a/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/title.rs +++ b/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/title.rs @@ -1,6 +1,6 @@ use gtk::{gio::TlsCertificate, glib::gformat, prelude::TlsCertificateExt}; -pub fn new_for_profile_identity_gemini_id(certificate: &TlsCertificate) -> String { +pub fn new_for_profile_identity_id(certificate: &TlsCertificate) -> String { certificate .subject_name() .unwrap_or(gformat!("Unknown")) diff --git a/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/tooltip.rs b/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/tooltip.rs index e2426958..e35fb43e 100644 --- a/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/tooltip.rs +++ b/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/tooltip.rs @@ -1,9 +1,6 @@ use gtk::{gio::TlsCertificate, prelude::TlsCertificateExt}; -pub fn new_for_profile_identity_gemini_id( - certificate: &TlsCertificate, - scope: &[String], -) -> String { +pub fn new_for_profile_identity_id(certificate: &TlsCertificate, scope: &[String]) -> String { let mut tooltip = "Certificate\n".to_string(); if let Some(subject_name) = certificate.subject_name() { diff --git a/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/value.rs b/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/value.rs index 9740d638..4c47e517 100644 --- a/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/value.rs +++ b/src/app/browser/window/tab/item/identity/gemini/widget/form/list/item/value.rs @@ -3,5 +3,5 @@ pub enum Value { GeneratePem, GuestSession, ImportPem, - ProfileIdentityGeminiId(i64), + ProfileIdentityId(i64), } diff --git a/src/app/browser/window/tab/item/identity/gemini/widget/form/save.rs b/src/app/browser/window/tab/item/identity/gemini/widget/form/save.rs index 9ee5cad0..f093d167 100644 --- a/src/app/browser/window/tab/item/identity/gemini/widget/form/save.rs +++ b/src/app/browser/window/tab/item/identity/gemini/widget/form/save.rs @@ -40,12 +40,12 @@ impl Save { move |_| { // Get selected identity from holder match list.selected().value_enum() { - Value::ProfileIdentityGeminiId(profile_identity_gemini_id) => { + Value::ProfileIdentityId(profile_identity_id) => { // Lock open button (prevent double click) button.set_sensitive(false); // Create PEM file based on option ID selected - match Certificate::new(profile.clone(), profile_identity_gemini_id) { + match Certificate::new(profile.clone(), profile_identity_id) { Ok(certificate) => { // Init file filters related with PEM extension let filters = ListStore::new::(); diff --git a/src/app/browser/window/tab/item/identity/gemini/widget/form/save/certificate.rs b/src/app/browser/window/tab/item/identity/gemini/widget/form/save/certificate.rs index 5621e0df..22cdd6e3 100644 --- a/src/app/browser/window/tab/item/identity/gemini/widget/form/save/certificate.rs +++ b/src/app/browser/window/tab/item/identity/gemini/widget/form/save/certificate.rs @@ -15,13 +15,8 @@ impl Certificate { // Constructors /// Create new `Self` - pub fn new(profile: Rc, profile_identity_gemini_id: i64) -> Result { - match profile - .identity - .gemini - .database - .record(profile_identity_gemini_id) - { + pub fn new(profile: Rc, profile_identity_id: i64) -> Result { + match profile.identity.database.record(profile_identity_id) { Ok(record) => match record { Some(identity) => match TlsCertificate::from_pem(&identity.pem) { Ok(certificate) => Ok(Self { @@ -30,7 +25,7 @@ impl Certificate { }), Err(e) => Err(Error::TlsCertificate(e)), }, - None => Err(Error::NotFound(profile_identity_gemini_id)), + None => Err(Error::NotFound(profile_identity_id)), }, Err(e) => Err(Error::Database(e)), } diff --git a/src/app/browser/window/tab/item/identity/gemini/widget/form/save/certificate/error.rs b/src/app/browser/window/tab/item/identity/gemini/widget/form/save/certificate/error.rs index 0aa6bcec..118b9c41 100644 --- a/src/app/browser/window/tab/item/identity/gemini/widget/form/save/certificate/error.rs +++ b/src/app/browser/window/tab/item/identity/gemini/widget/form/save/certificate/error.rs @@ -14,8 +14,8 @@ impl Display for Error { Self::Database(e) => { write!(f, "Database error: {e}") } - Self::NotFound(profile_identity_gemini_id) => { - write!(f, "Record for `{profile_identity_gemini_id}` not found") + Self::NotFound(profile_identity_id) => { + write!(f, "Record for `{profile_identity_id}` not found") } Self::TlsCertificate(e) => { write!(f, "TLS certificate error: {e}") diff --git a/src/app/browser/window/tab/item/page/navigation.rs b/src/app/browser/window/tab/item/page/navigation.rs index 7f6a1ecc..31741142 100644 --- a/src/app/browser/window/tab/item/page/navigation.rs +++ b/src/app/browser/window/tab/item/page/navigation.rs @@ -79,7 +79,6 @@ impl Navigation { self.request.update( self.profile .identity - .gemini .auth .memory .match_scope(&request) diff --git a/src/profile/identity.rs b/src/profile/identity.rs index d17bc921..4b7c53b6 100644 --- a/src/profile/identity.rs +++ b/src/profile/identity.rs @@ -1,51 +1,144 @@ +mod auth; +mod certificate; mod database; mod error; -mod gemini; +mod item; +mod memory; +use auth::Auth; use database::Database; pub use error::Error; -use gemini::Gemini; +use item::Item; +use memory::Memory; + +use gtk::glib::DateTime; use sqlite::{Connection, Transaction}; use std::{rc::Rc, sync::RwLock}; -/// Authorization wrapper for different protocols +/// Authorization wrapper for Gemini protocol +/// +/// https://geminiprotocol.net/docs/protocol-specification.gmi#client-certificates pub struct Identity { - // database: Rc, - pub gemini: Rc, + pub auth: Rc, + pub database: Rc, + pub memory: Rc, } impl Identity { // Constructors /// Create new `Self` - pub fn build(connection: &Rc>, profile_id: &Rc) -> Result { - // Init identity database - let database = Rc::new(Database::build(connection)); + pub fn build( + connection: &Rc>, + profile_identity_id: &Rc, + ) -> Result { + // Init components + let auth = match Auth::new(connection) { + Ok(auth) => Rc::new(auth), + Err(e) => return Err(Error::Auth(e)), + }; + let database = Rc::new(Database::build(connection, profile_identity_id)); + let memory = Rc::new(Memory::new()); - // Get active identity set for profile or create new one - let profile_identity_id = Rc::new(match database.active() { - Ok(result) => match result { - Some(identity) => identity.id, - None => match database.add(profile_id, true) { - Ok(id) => id, - Err(e) => return Err(Error::Database(e)), - }, + // Init `Self` + let this = Self { + auth, + database, + memory, + }; + + // Build initial index + Self::index(&this)?; + + Ok(this) + } + + // Actions + + /// Add new record to database, update memory index + /// * return new `profile_identity_id` on success + pub fn add(&self, pem: &str) -> Result { + match self.database.add(pem) { + Ok(profile_identity_id) => { + self.index()?; + Ok(profile_identity_id) + } + Err(e) => Err(Error::Database(e)), + } + } + + /// Delete record from database including children dependencies, update memory index + pub fn delete(&self, profile_identity_id: i64) -> Result<(), Error> { + match self.auth.remove_ref(profile_identity_id) { + Ok(_) => match self.database.delete(profile_identity_id) { + Ok(_) => { + self.index()?; + Ok(()) + } + Err(e) => Err(Error::Database(e)), }, + Err(e) => Err(Error::Auth(e)), + } + } + + /// Generate new certificate and insert record to DB, update memory index + /// * return new `profile_identity_id` on success + pub fn make(&self, time: Option<(DateTime, DateTime)>, name: &str) -> Result { + // Generate new certificate + match certificate::generate( + match time { + Some(value) => value, + None => ( + DateTime::now_local().unwrap(), + DateTime::from_local(9999, 12, 31, 23, 59, 59.9).unwrap(), // max @TODO + ), + }, + name, + ) { + Ok(pem) => self.add(&pem), + Err(e) => Err(Error::Certificate(e)), + } + } + + /// Create new `Memory` index from `Database` for `Self` + pub fn index(&self) -> Result<(), Error> { + // Clear previous records + if let Err(e) = self.memory.clear() { + return Err(Error::Memory(e)); + } + + // Build new index + match self.database.records() { + Ok(records) => { + for record in records { + if let Err(e) = self.memory.add(record.id, record.pem) { + return Err(Error::Memory(e)); + } + } + } Err(e) => return Err(Error::Database(e)), - }); + }; - // Init gemini component - let gemini = Rc::new(match Gemini::build(connection, &profile_identity_id) { - Ok(result) => result, - Err(e) => return Err(Error::Gemini(e)), - }); + Ok(()) + } - // Done - Ok(Self { - // database, - gemini, - }) + /// Get `Identity` match `request` + /// * [Client certificates specification](https://geminiprotocol.net/docs/protocol-specification.gmi#client-certificates) + /// * this function work with memory cache (not database) + pub fn match_scope(&self, request: &str) -> Option { + if let Some(auth) = self.auth.memory.match_scope(request) { + match self.memory.get(auth.profile_identity_id) { + Ok(pem) => { + return Some(Item { + // scope: auth.scope, + pem, + }); + } + Err(e) => todo!("{:?}", e.to_string()), + } + } + None } } @@ -58,7 +151,7 @@ pub fn migrate(tx: &Transaction) -> Result<(), String> { } // Delegate migration to childs - gemini::migrate(tx)?; + auth::migrate(tx)?; // Success Ok(()) diff --git a/src/profile/identity/gemini/auth.rs b/src/profile/identity/auth.rs similarity index 73% rename from src/profile/identity/gemini/auth.rs rename to src/profile/identity/auth.rs index 1479d68d..ce3dda01 100644 --- a/src/profile/identity/gemini/auth.rs +++ b/src/profile/identity/auth.rs @@ -11,7 +11,7 @@ use memory::Memory; use sqlite::{Connection, Transaction}; use std::{rc::Rc, sync::RwLock}; -/// API for `profile_identity_gemini_id` + `scope` auth pairs operations +/// API for `profile_identity_id` + `scope` auth pairs operations pub struct Auth { pub database: Rc, pub memory: Rc, @@ -37,26 +37,25 @@ impl Auth { // Actions - /// Apply `profile_identity_gemini_id` certificate as the auth for `scope` + /// Apply `profile_identity_id` certificate as the auth for `scope` /// * deactivate active auth by remove previous records from `Self` database /// * reindex `Self` memory index on success - /// * return last insert `profile_identity_gemini_auth_id` on success - pub fn apply(&self, profile_identity_gemini_id: i64, scope: &str) -> Result { + /// * return last insert `profile_identity_auth_id` on success + pub fn apply(&self, profile_identity_id: i64, scope: &str) -> Result { // Cleanup records match `scope` (unauthorize) self.remove_scope(scope)?; // Create new record (auth) - let profile_identity_gemini_auth_id = - match self.database.add(profile_identity_gemini_id, scope) { - Ok(id) => id, - Err(e) => return Err(Error::Database(e)), - }; + let profile_identity_auth_id = match self.database.add(profile_identity_id, scope) { + Ok(id) => id, + Err(e) => return Err(Error::Database(e)), + }; // Reindex self.index()?; // Done - Ok(profile_identity_gemini_auth_id) + Ok(profile_identity_auth_id) } /// Remove all records match request (unauthorize) @@ -75,9 +74,9 @@ impl Auth { Ok(()) } - /// Remove all records match `profile_identity_gemini_id` foreign reference key - pub fn remove_ref(&self, profile_identity_gemini_id: i64) -> Result<(), Error> { - match self.database.records_ref(profile_identity_gemini_id) { + /// Remove all records match `profile_identity_id` foreign reference key + pub fn remove_ref(&self, profile_identity_id: i64) -> Result<(), Error> { + match self.database.records_ref(profile_identity_id) { Ok(records) => { for record in records { if let Err(e) = self.database.delete(record.id) { @@ -102,10 +101,7 @@ impl Auth { match self.database.records_scope(None) { Ok(records) => { for record in records { - if let Err(e) = self - .memory - .add(record.scope, record.profile_identity_gemini_id) - { + if let Err(e) = self.memory.add(record.scope, record.profile_identity_id) { return Err(Error::Memory(e)); } } diff --git a/src/profile/identity/gemini/auth/database.rs b/src/profile/identity/auth/database.rs similarity index 64% rename from src/profile/identity/gemini/auth/database.rs rename to src/profile/identity/auth/database.rs index 47190a5a..ca6416a7 100644 --- a/src/profile/identity/gemini/auth/database.rs +++ b/src/profile/identity/auth/database.rs @@ -3,11 +3,11 @@ use std::{rc::Rc, sync::RwLock}; pub struct Table { pub id: i64, - pub profile_identity_gemini_id: i64, + pub profile_identity_id: i64, pub scope: String, } -/// Storage for `profile_identity_gemini_id` + `scope` auth pairs +/// Storage for `profile_identity_id` + `scope` auth pairs pub struct Database { connection: Rc>, } @@ -25,13 +25,13 @@ impl Database { // Actions /// Create new record in database - pub fn add(&self, profile_identity_gemini_id: i64, scope: &str) -> Result { + pub fn add(&self, profile_identity_id: i64, scope: &str) -> Result { // Begin new transaction let mut writable = self.connection.write().unwrap(); // @TODO let tx = writable.transaction()?; // Create new record - insert(&tx, profile_identity_gemini_id, scope)?; + insert(&tx, profile_identity_id, scope)?; // Hold insert ID for result let id = last_insert_id(&tx); @@ -69,10 +69,10 @@ impl Database { } /// Get records from database match current `profile_id` optionally filtered by `scope` - pub fn records_ref(&self, profile_identity_gemini_id: i64) -> Result, Error> { + pub fn records_ref(&self, profile_identity_id: i64) -> Result, Error> { let readable = self.connection.read().unwrap(); // @TODO let tx = readable.unchecked_transaction()?; - select_ref(&tx, profile_identity_gemini_id) + select_ref(&tx, profile_identity_id) } } @@ -80,54 +80,47 @@ impl Database { pub fn init(tx: &Transaction) -> Result { tx.execute( - "CREATE TABLE IF NOT EXISTS `profile_identity_gemini_auth` + "CREATE TABLE IF NOT EXISTS `profile_identity_auth` ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - `profile_identity_gemini_id` INTEGER NOT NULL, - `scope` VARCHAR(1024) NOT NULL, + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `profile_identity_id` INTEGER NOT NULL, + `scope` VARCHAR(1024) NOT NULL, - FOREIGN KEY (`profile_identity_gemini_id`) REFERENCES `profile_identity_gemini`(`id`), + FOREIGN KEY (`profile_identity_id`) REFERENCES `profile_identity`(`id`), UNIQUE (`scope`) )", [], ) } -pub fn insert( - tx: &Transaction, - profile_identity_gemini_id: i64, - scope: &str, -) -> Result { +pub fn insert(tx: &Transaction, profile_identity_id: i64, scope: &str) -> Result { tx.execute( - "INSERT INTO `profile_identity_gemini_auth` ( - `profile_identity_gemini_id`, + "INSERT INTO `profile_identity_auth` ( + `profile_identity_id`, `scope` ) VALUES (?, ?)", - (profile_identity_gemini_id, scope), + (profile_identity_id, scope), ) } pub fn delete(tx: &Transaction, id: i64) -> Result { - tx.execute( - "DELETE FROM `profile_identity_gemini_auth` WHERE `id` = ?", - [id], - ) + tx.execute("DELETE FROM `profile_identity_auth` WHERE `id` = ?", [id]) } pub fn select_scope(tx: &Transaction, scope: Option<&str>) -> Result, Error> { let mut stmt = tx.prepare( "SELECT `id`, - `profile_identity_gemini_id`, + `profile_identity_id`, `scope` - FROM `profile_identity_gemini_auth` + FROM `profile_identity_auth` WHERE `scope` LIKE ?", )?; let result = stmt.query_map([scope.unwrap_or("%")], |row| { Ok(Table { id: row.get(0)?, - profile_identity_gemini_id: row.get(1)?, + profile_identity_id: row.get(1)?, scope: row.get(2)?, }) })?; @@ -142,20 +135,20 @@ pub fn select_scope(tx: &Transaction, scope: Option<&str>) -> Result, Ok(records) } -pub fn select_ref(tx: &Transaction, profile_identity_gemini_id: i64) -> Result, Error> { +pub fn select_ref(tx: &Transaction, profile_identity_id: i64) -> Result, Error> { let mut stmt = tx.prepare( "SELECT `id`, - `profile_identity_gemini_id`, + `profile_identity_id`, `scope` - FROM `profile_identity_gemini_auth` - WHERE `profile_identity_gemini_id` = ?", + FROM `profile_identity_auth` + WHERE `profile_identity_id` = ?", )?; - let result = stmt.query_map([profile_identity_gemini_id], |row| { + let result = stmt.query_map([profile_identity_id], |row| { Ok(Table { id: row.get(0)?, - profile_identity_gemini_id: row.get(1)?, + profile_identity_id: row.get(1)?, scope: row.get(2)?, }) })?; diff --git a/src/profile/identity/gemini/auth/error.rs b/src/profile/identity/auth/error.rs similarity index 100% rename from src/profile/identity/gemini/auth/error.rs rename to src/profile/identity/auth/error.rs diff --git a/src/profile/identity/gemini/auth/memory.rs b/src/profile/identity/auth/memory.rs similarity index 78% rename from src/profile/identity/gemini/auth/memory.rs rename to src/profile/identity/auth/memory.rs index 7288fa2a..7483adc0 100644 --- a/src/profile/identity/gemini/auth/memory.rs +++ b/src/profile/identity/auth/memory.rs @@ -29,9 +29,9 @@ impl Memory { // Actions - /// Add new record with `scope` as key and `profile_identity_gemini_id` as value + /// Add new record with `scope` as key and `profile_identity_id` as value /// * validate record with same key does not exist yet - pub fn add(&self, scope: String, profile_identity_gemini_id: i64) -> Result<(), Error> { + pub fn add(&self, scope: String, profile_identity_id: i64) -> Result<(), Error> { // Borrow shared index access let mut index = self.index.borrow_mut(); @@ -41,7 +41,7 @@ impl Memory { } // Slot should be free, let check it twice - match index.insert(scope, profile_identity_gemini_id) { + match index.insert(scope, profile_identity_id) { Some(_) => Err(Error::Unexpected), None => Ok(()), } @@ -65,10 +65,10 @@ impl Memory { let mut result = Vec::new(); // Get all records starts with `scope` - for (scope, &profile_identity_gemini_id) in self.index.borrow().iter() { - if alias(request).starts_with(scope) { + for (scope, &profile_identity_id) in self.index.borrow().iter() { + if request.starts_with(scope) { result.push(Auth { - profile_identity_gemini_id, + profile_identity_id, scope: scope.clone(), }) } @@ -81,12 +81,3 @@ impl Memory { result.first().cloned() } } - -// Tools - -// @TODO optional -fn alias(request: &str) -> String { - request - .replace("gemini://", "titan://") - .replace("titan://", "gemini://") -} diff --git a/src/profile/identity/gemini/auth/memory/auth.rs b/src/profile/identity/auth/memory/auth.rs similarity index 59% rename from src/profile/identity/gemini/auth/memory/auth.rs rename to src/profile/identity/auth/memory/auth.rs index 39dfc848..33bdf1f8 100644 --- a/src/profile/identity/gemini/auth/memory/auth.rs +++ b/src/profile/identity/auth/memory/auth.rs @@ -1,5 +1,5 @@ #[derive(Clone)] pub struct Auth { - pub profile_identity_gemini_id: i64, + pub profile_identity_id: i64, pub scope: String, } diff --git a/src/profile/identity/gemini/auth/memory/error.rs b/src/profile/identity/auth/memory/error.rs similarity index 100% rename from src/profile/identity/gemini/auth/memory/error.rs rename to src/profile/identity/auth/memory/error.rs diff --git a/src/profile/identity/gemini/certificate.rs b/src/profile/identity/certificate.rs similarity index 100% rename from src/profile/identity/gemini/certificate.rs rename to src/profile/identity/certificate.rs diff --git a/src/profile/identity/database.rs b/src/profile/identity/database.rs index 3c85081a..9833d95c 100644 --- a/src/profile/identity/database.rs +++ b/src/profile/identity/database.rs @@ -3,57 +3,37 @@ use std::{rc::Rc, sync::RwLock}; pub struct Table { pub id: i64, - pub profile_id: i64, - pub is_active: bool, + //pub profile_id: i64, + pub pem: String, } +/// Storage for Gemini auth certificates pub struct Database { - pub connection: Rc>, + connection: Rc>, + profile_id: Rc, // multi-profile relationship } impl Database { // Constructors /// Create new `Self` - pub fn build(connection: &Rc>) -> Self { + pub fn build(connection: &Rc>, profile_id: &Rc) -> Self { Self { connection: connection.clone(), + profile_id: profile_id.clone(), } } - // Getters + // Actions - /// Get all records - pub fn records(&self) -> Result, Error> { - let readable = self.connection.read().unwrap(); - let tx = readable.unchecked_transaction()?; - select(&tx) - } - - /// Get active identity record if exist - pub fn active(&self) -> Result, Error> { - let records = self.records()?; - Ok(records.into_iter().find(|record| record.is_active)) - } - - // Setters - - /// Create new record in `Self` database connected - pub fn add(&self, profile_id: &Rc, is_active: bool) -> Result { + /// Create new record in database + pub fn add(&self, pem: &str) -> Result { // Begin new transaction - let mut writable = self.connection.write().unwrap(); + let mut writable = self.connection.write().unwrap(); // @TODO let tx = writable.transaction()?; - // New record has active status - if is_active { - // Deactivate other records as only one profile should be active - for record in select(&tx)? { - update(&tx, record.profile_id, record.id, false)?; - } - } - // Create new record - insert(&tx, profile_id, is_active)?; + insert(&tx, *self.profile_id, pem)?; // Hold insert ID for result let id = last_insert_id(&tx); @@ -64,6 +44,44 @@ impl Database { Err(e) => Err(e), } } + + /// Delete record with given `id` from database + pub fn delete(&self, id: i64) -> Result<(), Error> { + // Begin new transaction + let mut writable = self.connection.write().unwrap(); // @TODO + let tx = writable.transaction()?; + + // Create new record + delete(&tx, id)?; + + // Done + match tx.commit() { + Ok(_) => Ok(()), + Err(e) => Err(e), + } + } + + /// Get single record match `id` + pub fn record(&self, id: i64) -> Result, Error> { + let readable = self.connection.read().unwrap(); + let tx = readable.unchecked_transaction()?; + let records = select(&tx, *self.profile_id)?; // @TODO single record query + + for record in records { + if record.id == id { + return Ok(Some(record)); + } + } + + Ok(None) + } + + /// Get all records match current `profile_id` + pub fn records(&self) -> Result, Error> { + let readable = self.connection.read().unwrap(); // @TODO + let tx = readable.unchecked_transaction()?; + select(&tx, *self.profile_id) + } } // Low-level DB API @@ -74,7 +92,7 @@ pub fn init(tx: &Transaction) -> Result { ( `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `profile_id` INTEGER NOT NULL, - `is_active` INTEGER NOT NULL, + `pem` TEXT NOT NULL, FOREIGN KEY (`profile_id`) REFERENCES `profile`(`id`) )", @@ -82,34 +100,34 @@ pub fn init(tx: &Transaction) -> Result { ) } -pub fn insert(tx: &Transaction, profile_id: &Rc, is_active: bool) -> Result { +pub fn insert(tx: &Transaction, profile_id: i64, pem: &str) -> Result { tx.execute( "INSERT INTO `profile_identity` ( `profile_id`, - `is_active` + `pem` ) VALUES (?, ?)", - (profile_id, is_active), + (profile_id, pem), ) } -pub fn update(tx: &Transaction, id: i64, profile_id: i64, is_active: bool) -> Result { - tx.execute( - "UPDATE `profile_identity` - SET `profile_id` = ?, - `is_active` = ? - WHERE - `id` = ?", - (profile_id, is_active, id), - ) +pub fn delete(tx: &Transaction, id: i64) -> Result { + tx.execute("DELETE FROM `profile_identity` WHERE `id` = ?", [id]) } -pub fn select(tx: &Transaction) -> Result, Error> { - let mut stmt = tx.prepare("SELECT `id`, `profile_id`, `is_active` FROM `profile_identity`")?; - let result = stmt.query_map([], |row| { +pub fn select(tx: &Transaction, profile_id: i64) -> Result, Error> { + let mut stmt = tx.prepare( + "SELECT `id`, + `profile_id`, + `pem` + + FROM `profile_identity` WHERE `profile_id` = ?", + )?; + + let result = stmt.query_map([profile_id], |row| { Ok(Table { id: row.get(0)?, - profile_id: row.get(1)?, - is_active: row.get(2)?, + //profile_id: row.get(1)?, + pem: row.get(2)?, }) })?; diff --git a/src/profile/identity/error.rs b/src/profile/identity/error.rs index 4a183c65..8097a694 100644 --- a/src/profile/identity/error.rs +++ b/src/profile/identity/error.rs @@ -2,19 +2,23 @@ use std::fmt::{Display, Formatter, Result}; #[derive(Debug)] pub enum Error { + Auth(super::auth::Error), + Certificate(Box), Database(sqlite::Error), - Gemini(super::gemini::Error), + Memory(super::memory::Error), } impl Display for Error { fn fmt(&self, f: &mut Formatter) -> Result { match self { + Self::Auth(e) => write!(f, "Could not create auth: {e}"), + Self::Certificate(e) => { + write!(f, "Could not create certificate: {e}") + } Self::Database(e) => { write!(f, "Database error: {e}") } - Self::Gemini(e) => { - write!(f, "Could not init Gemini identity: {e}") - } + Self::Memory(e) => write!(f, "Memory error: {e}"), } } } diff --git a/src/profile/identity/gemini.rs b/src/profile/identity/gemini.rs deleted file mode 100644 index 73b153e5..00000000 --- a/src/profile/identity/gemini.rs +++ /dev/null @@ -1,158 +0,0 @@ -mod auth; -mod certificate; -mod database; -mod error; -mod identity; -mod memory; - -use auth::Auth; -use database::Database; -pub use error::Error; -use identity::Identity; - -use memory::Memory; - -use gtk::glib::DateTime; -use sqlite::{Connection, Transaction}; -use std::{rc::Rc, sync::RwLock}; - -/// Authorization wrapper for Gemini protocol -/// -/// https://geminiprotocol.net/docs/protocol-specification.gmi#client-certificates -pub struct Gemini { - pub auth: Rc, - pub database: Rc, - pub memory: Rc, -} - -impl Gemini { - // Constructors - - /// Create new `Self` - pub fn build( - connection: &Rc>, - profile_identity_id: &Rc, - ) -> Result { - // Init components - let auth = match Auth::new(connection) { - Ok(auth) => Rc::new(auth), - Err(e) => return Err(Error::Auth(e)), - }; - let database = Rc::new(Database::build(connection, profile_identity_id)); - let memory = Rc::new(Memory::new()); - - // Init `Self` - let this = Self { - auth, - database, - memory, - }; - - // Build initial index - Self::index(&this)?; - - Ok(this) - } - - // Actions - - /// Add new record to database, update memory index - /// * return new `profile_identity_gemini_id` on success - pub fn add(&self, pem: &str) -> Result { - match self.database.add(pem) { - Ok(profile_identity_gemini_id) => { - self.index()?; - Ok(profile_identity_gemini_id) - } - Err(e) => Err(Error::Database(e)), - } - } - - /// Delete record from database including children dependencies, update memory index - pub fn delete(&self, profile_identity_gemini_id: i64) -> Result<(), Error> { - match self.auth.remove_ref(profile_identity_gemini_id) { - Ok(_) => match self.database.delete(profile_identity_gemini_id) { - Ok(_) => { - self.index()?; - Ok(()) - } - Err(e) => Err(Error::Database(e)), - }, - Err(e) => Err(Error::Auth(e)), - } - } - - /// Generate new certificate and insert record to DB, update memory index - /// * return new `profile_identity_gemini_id` on success - pub fn make(&self, time: Option<(DateTime, DateTime)>, name: &str) -> Result { - // Generate new certificate - match certificate::generate( - match time { - Some(value) => value, - None => ( - DateTime::now_local().unwrap(), - DateTime::from_local(9999, 12, 31, 23, 59, 59.9).unwrap(), // max @TODO - ), - }, - name, - ) { - Ok(pem) => self.add(&pem), - Err(e) => Err(Error::Certificate(e)), - } - } - - /// Create new `Memory` index from `Database` for `Self` - pub fn index(&self) -> Result<(), Error> { - // Clear previous records - if let Err(e) = self.memory.clear() { - return Err(Error::Memory(e)); - } - - // Build new index - match self.database.records() { - Ok(records) => { - for record in records { - if let Err(e) = self.memory.add(record.id, record.pem) { - return Err(Error::Memory(e)); - } - } - } - Err(e) => return Err(Error::Database(e)), - }; - - Ok(()) - } - - /// Get `Identity` match `request` - /// * [Client certificates specification](https://geminiprotocol.net/docs/protocol-specification.gmi#client-certificates) - /// * this function work with memory cache (not database) - pub fn match_scope(&self, request: &str) -> Option { - if let Some(auth) = self.auth.memory.match_scope(request) { - match self.memory.get(auth.profile_identity_gemini_id) { - Ok(pem) => { - return Some(Identity { - // scope: auth.scope, - pem, - }); - } - Err(e) => todo!("{:?}", e.to_string()), - } - } - None - } -} - -// Tools - -pub fn migrate(tx: &Transaction) -> Result<(), String> { - // Migrate self components - if let Err(e) = database::init(tx) { - return Err(e.to_string()); - } - - // Delegate migration to childs - auth::migrate(tx)?; - - // Success - Ok(()) -} diff --git a/src/profile/identity/gemini/database.rs b/src/profile/identity/gemini/database.rs deleted file mode 100644 index 08e44c35..00000000 --- a/src/profile/identity/gemini/database.rs +++ /dev/null @@ -1,146 +0,0 @@ -use sqlite::{Connection, Error, Transaction}; -use std::{rc::Rc, sync::RwLock}; - -pub struct Table { - pub id: i64, - //pub profile_identity_id: i64, - pub pem: String, -} - -/// Storage for Gemini auth certificates -pub struct Database { - connection: Rc>, - profile_identity_id: Rc, // multi-profile relationship -} - -impl Database { - // Constructors - - /// Create new `Self` - pub fn build(connection: &Rc>, profile_identity_id: &Rc) -> Self { - Self { - connection: connection.clone(), - profile_identity_id: profile_identity_id.clone(), - } - } - - // Actions - - /// Create new record in database - pub fn add(&self, pem: &str) -> Result { - // Begin new transaction - let mut writable = self.connection.write().unwrap(); // @TODO - let tx = writable.transaction()?; - - // Create new record - insert(&tx, *self.profile_identity_id, pem)?; - - // Hold insert ID for result - let id = last_insert_id(&tx); - - // Done - match tx.commit() { - Ok(_) => Ok(id), - Err(e) => Err(e), - } - } - - /// Delete record with given `id` from database - pub fn delete(&self, id: i64) -> Result<(), Error> { - // Begin new transaction - let mut writable = self.connection.write().unwrap(); // @TODO - let tx = writable.transaction()?; - - // Create new record - delete(&tx, id)?; - - // Done - match tx.commit() { - Ok(_) => Ok(()), - Err(e) => Err(e), - } - } - - /// Get single record match `id` - pub fn record(&self, id: i64) -> Result, Error> { - let readable = self.connection.read().unwrap(); - let tx = readable.unchecked_transaction()?; - let records = select(&tx, *self.profile_identity_id)?; // @TODO single record query - - for record in records { - if record.id == id { - return Ok(Some(record)); - } - } - - Ok(None) - } - - /// Get all records match current `profile_identity_id` - pub fn records(&self) -> Result, Error> { - let readable = self.connection.read().unwrap(); // @TODO - let tx = readable.unchecked_transaction()?; - select(&tx, *self.profile_identity_id) - } -} - -// Low-level DB API - -pub fn init(tx: &Transaction) -> Result { - tx.execute( - "CREATE TABLE IF NOT EXISTS `profile_identity_gemini` - ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - `profile_identity_id` INTEGER NOT NULL, - `pem` TEXT NOT NULL, - - FOREIGN KEY (`profile_identity_id`) REFERENCES `profile_identity`(`id`) - )", - [], - ) -} - -pub fn insert(tx: &Transaction, profile_identity_id: i64, pem: &str) -> Result { - tx.execute( - "INSERT INTO `profile_identity_gemini` ( - `profile_identity_id`, - `pem` - ) VALUES (?, ?)", - (profile_identity_id, pem), - ) -} - -pub fn delete(tx: &Transaction, id: i64) -> Result { - tx.execute("DELETE FROM `profile_identity_gemini` WHERE `id` = ?", [id]) -} - -pub fn select(tx: &Transaction, profile_identity_id: i64) -> Result, Error> { - let mut stmt = tx.prepare( - "SELECT `id`, - `profile_identity_id`, - `pem` - - FROM `profile_identity_gemini` WHERE `profile_identity_id` = ?", - )?; - - let result = stmt.query_map([profile_identity_id], |row| { - Ok(Table { - id: row.get(0)?, - //profile_identity_id: row.get(1)?, - pem: row.get(2)?, - }) - })?; - - let mut records = Vec::new(); - - for record in result { - let table = record?; - records.push(table); - } - - Ok(records) -} - -pub fn last_insert_id(tx: &Transaction) -> i64 { - tx.last_insert_rowid() -} diff --git a/src/profile/identity/gemini/error.rs b/src/profile/identity/gemini/error.rs deleted file mode 100644 index 8097a694..00000000 --- a/src/profile/identity/gemini/error.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::fmt::{Display, Formatter, Result}; - -#[derive(Debug)] -pub enum Error { - Auth(super::auth::Error), - Certificate(Box), - Database(sqlite::Error), - Memory(super::memory::Error), -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter) -> Result { - match self { - Self::Auth(e) => write!(f, "Could not create auth: {e}"), - Self::Certificate(e) => { - write!(f, "Could not create certificate: {e}") - } - Self::Database(e) => { - write!(f, "Database error: {e}") - } - Self::Memory(e) => write!(f, "Memory error: {e}"), - } - } -} diff --git a/src/profile/identity/gemini/identity.rs b/src/profile/identity/item.rs similarity index 93% rename from src/profile/identity/gemini/identity.rs rename to src/profile/identity/item.rs index ec34fb29..9743250a 100644 --- a/src/profile/identity/gemini/identity.rs +++ b/src/profile/identity/item.rs @@ -5,12 +5,12 @@ use gtk::gio::TlsCertificate; /// Gemini identity holder for cached record in application-wide struct format. /// Implements also additional conversion methods. -pub struct Identity { +pub struct Item { pub pem: String, // pub scope: String, } -impl Identity { +impl Item { /// Convert `Self` to [TlsCertificate](https://docs.gtk.org/gio/class.TlsCertificate.html) pub fn to_tls_certificate(&self) -> Result { match TlsCertificate::from_pem(&self.pem) { diff --git a/src/profile/identity/gemini/identity/error.rs b/src/profile/identity/item/error.rs similarity index 100% rename from src/profile/identity/gemini/identity/error.rs rename to src/profile/identity/item/error.rs diff --git a/src/profile/identity/gemini/memory.rs b/src/profile/identity/memory.rs similarity index 82% rename from src/profile/identity/gemini/memory.rs rename to src/profile/identity/memory.rs index e0f9ec81..969762ef 100644 --- a/src/profile/identity/gemini/memory.rs +++ b/src/profile/identity/memory.rs @@ -28,17 +28,17 @@ impl Memory { /// Add new record with `id` as key and `pem` as value /// * validate record with same key does not exist yet - pub fn add(&self, profile_identity_gemini_id: i64, pem: String) -> Result<(), Error> { + pub fn add(&self, profile_identity_id: i64, pem: String) -> Result<(), Error> { // Borrow shared index access let mut index = self.index.borrow_mut(); // Prevent existing key overwrite - if index.contains_key(&profile_identity_gemini_id) { - return Err(Error::Overwrite(profile_identity_gemini_id)); + if index.contains_key(&profile_identity_id) { + return Err(Error::Overwrite(profile_identity_id)); } // Slot should be free, let check it twice - match index.insert(profile_identity_gemini_id, pem) { + match index.insert(profile_identity_id, pem) { Some(_) => Err(Error::Unexpected), None => Ok(()), } diff --git a/src/profile/identity/gemini/memory/error.rs b/src/profile/identity/memory/error.rs similarity index 100% rename from src/profile/identity/gemini/memory/error.rs rename to src/profile/identity/memory/error.rs