mirror of
https://codeberg.org/tyy/aspm
synced 2024-12-23 00:09:28 -07:00
Migrate to sqlite+sea_orm
This commit is contained in:
parent
5390a9389a
commit
19c42f6138
14 changed files with 1423 additions and 246 deletions
1252
Cargo.lock
generated
1252
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -18,7 +18,10 @@ thiserror = "1.0.40"
|
||||||
asp = { path = "crates/asp" }
|
asp = { path = "crates/asp" }
|
||||||
indoc = "2.0.1"
|
indoc = "2.0.1"
|
||||||
anstyle = "1.0.1"
|
anstyle = "1.0.1"
|
||||||
redb = "1.0.3"
|
|
||||||
dialoguer = { version = "0.10.4", features = ["password"] }
|
dialoguer = { version = "0.10.4", features = ["password"] }
|
||||||
argon2 = { version = "0.5.0", features = ["std"] }
|
argon2 = { version = "0.5.0", features = ["std"] }
|
||||||
data-encoding = "2.4.0"
|
data-encoding = "2.4.0"
|
||||||
|
sea-orm = { version = "0.11.3", features = ["sqlx-sqlite", "runtime-tokio-native-tls"] }
|
||||||
|
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"] }
|
||||||
|
sea-orm-migration = "0.11.3"
|
||||||
|
async-trait = "0.1.68"
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use anyhow::bail;
|
||||||
use josekit::{
|
use josekit::{
|
||||||
jwk::{
|
jwk::{
|
||||||
alg::{ec::EcCurve, ed::EdCurve},
|
alg::{ec::EcCurve, ed::EdCurve},
|
||||||
|
@ -19,6 +20,26 @@ pub enum AspKeyType {
|
||||||
ES256,
|
ES256,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Into<i32> for AspKeyType {
|
||||||
|
fn into(self) -> i32 {
|
||||||
|
match self {
|
||||||
|
Self::Ed25519 => 0,
|
||||||
|
Self::ES256 => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<i32> for AspKeyType {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
fn try_from(value: i32) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
0 => Ok(Self::Ed25519),
|
||||||
|
1 => Ok(Self::ES256),
|
||||||
|
_ => bail!("Invalid index for AspKeyType"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A struct representing a key that can be used to create profiles or ASPE requests
|
/// A struct representing a key that can be used to create profiles or ASPE requests
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AspKey {
|
pub struct AspKey {
|
||||||
|
|
|
@ -4,9 +4,9 @@ use asp::keys::AspKey;
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
use data_encoding::BASE64_NOPAD;
|
use data_encoding::BASE64_NOPAD;
|
||||||
use dialoguer::{theme::ColorfulTheme, Password};
|
use dialoguer::{theme::ColorfulTheme, Password};
|
||||||
use redb::ReadableTable;
|
use sea_orm::EntityTrait;
|
||||||
|
|
||||||
use crate::{commands::AspmSubcommand, db::KEYS_TABLE};
|
use crate::{commands::AspmSubcommand, entities::keys};
|
||||||
|
|
||||||
#[derive(ValueEnum, Debug, Clone)]
|
#[derive(ValueEnum, Debug, Clone)]
|
||||||
pub enum KeyExportFormat {
|
pub enum KeyExportFormat {
|
||||||
|
@ -29,20 +29,16 @@ pub struct KeysExportCommand {
|
||||||
fingerprint: String,
|
fingerprint: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl AspmSubcommand for KeysExportCommand {
|
impl AspmSubcommand for KeysExportCommand {
|
||||||
fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||||
let txn = state
|
// Fetch key from db
|
||||||
.db
|
let entry = keys::Entity::find_by_id(&self.fingerprint)
|
||||||
.begin_read()
|
.one(&state.db)
|
||||||
.context("Unable to start db read transaction")?;
|
.await
|
||||||
let table = txn.open_table(KEYS_TABLE)?;
|
|
||||||
let entry = table
|
|
||||||
.get(&*self.fingerprint)
|
|
||||||
.context("Unable to fetch key from database")?;
|
.context("Unable to fetch key from database")?;
|
||||||
|
|
||||||
if let Some(entry) = entry {
|
if let Some(entry) = entry {
|
||||||
let value = entry.value();
|
|
||||||
|
|
||||||
let key_password = std::env::var("KEY_PASSWORD").or_else(|_| {
|
let key_password = std::env::var("KEY_PASSWORD").or_else(|_| {
|
||||||
Password::with_theme(&ColorfulTheme::default())
|
Password::with_theme(&ColorfulTheme::default())
|
||||||
.with_prompt("Please enter a password to decrypt the key with")
|
.with_prompt("Please enter a password to decrypt the key with")
|
||||||
|
@ -61,7 +57,7 @@ impl AspmSubcommand for KeysExportCommand {
|
||||||
let aes_key = hash.hash.context("Unable to derive encryption key")?;
|
let aes_key = hash.hash.context("Unable to derive encryption key")?;
|
||||||
let aes_key = &aes_key.as_bytes()[0..32];
|
let aes_key = &aes_key.as_bytes()[0..32];
|
||||||
|
|
||||||
if let Ok(decrypted) = AspKey::from_encrypted(aes_key, &value.key) {
|
if let Ok(decrypted) = AspKey::from_encrypted(aes_key, &entry.encrypted) {
|
||||||
let export = match self.format {
|
let export = match self.format {
|
||||||
KeyExportFormat::Encrypted => decrypted
|
KeyExportFormat::Encrypted => decrypted
|
||||||
.export_encrypted(aes_key)
|
.export_encrypted(aes_key)
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, bail, Context};
|
||||||
use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
|
use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
|
||||||
use asp::keys::{AspKey, AspKeyType};
|
use asp::keys::{AspKey, AspKeyType};
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
use data_encoding::BASE64_NOPAD;
|
use data_encoding::BASE64_NOPAD;
|
||||||
use dialoguer::{theme::ColorfulTheme, Input, Password};
|
use dialoguer::{theme::ColorfulTheme, Input, Password};
|
||||||
use indoc::printdoc;
|
use indoc::printdoc;
|
||||||
|
use sea_orm::{ActiveValue, EntityTrait};
|
||||||
|
|
||||||
use crate::{
|
use crate::{commands::AspmSubcommand, entities::keys};
|
||||||
commands::AspmSubcommand,
|
|
||||||
db::{KeysTableValue, KEYS_TABLE},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
|
||||||
pub enum KeyGenerationType {
|
pub enum KeyGenerationType {
|
||||||
|
@ -25,19 +23,23 @@ pub struct KeysGenerateCommand {
|
||||||
/// It doesn't really matter that much which one is used, as they both work fine, but Ed25519 is used as a safe default.
|
/// It doesn't really matter that much which one is used, as they both work fine, but Ed25519 is used as a safe default.
|
||||||
#[clap(value_enum, default_value_t = KeyGenerationType::Ed25519, long_about, ignore_case = true)]
|
#[clap(value_enum, default_value_t = KeyGenerationType::Ed25519, long_about, ignore_case = true)]
|
||||||
key_type: KeyGenerationType,
|
key_type: KeyGenerationType,
|
||||||
|
/// Tha alias of the key to generate. This can be anything, and it can also be omitted to prompt interactively. This has no purpose other than providing a way to nicely name keys, rather than having to remember a fingerprint.
|
||||||
|
#[arg(short = 'n', long)]
|
||||||
|
key_alias: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl AspmSubcommand for KeysGenerateCommand {
|
impl AspmSubcommand for KeysGenerateCommand {
|
||||||
fn execute(&self, config: crate::AspmState) -> Result<(), anyhow::Error> {
|
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||||
let alias = Input::with_theme(&ColorfulTheme::default())
|
let alias = if let Some(alias) = &self.key_alias {
|
||||||
.with_prompt("Please enter an alias to give to this key")
|
alias.clone()
|
||||||
.allow_empty(false)
|
} else {
|
||||||
.validate_with(|input: &String| match input.as_bytes().len() <= 255 {
|
Input::with_theme(&ColorfulTheme::default())
|
||||||
true => Ok(()),
|
.with_prompt("Please enter an alias to give to this key")
|
||||||
false => Err("Alias must not be longer than 255 characters!"),
|
.allow_empty(false)
|
||||||
})
|
.interact()
|
||||||
.interact()
|
.context("Unable to prompt on stderr")?
|
||||||
.context("Unable to prompt on stderr")?;
|
};
|
||||||
|
|
||||||
let key_password = std::env::var("KEY_PASSWORD").or_else(|_| {
|
let key_password = std::env::var("KEY_PASSWORD").or_else(|_| {
|
||||||
Password::with_theme(&ColorfulTheme::default())
|
Password::with_theme(&ColorfulTheme::default())
|
||||||
|
@ -70,26 +72,20 @@ impl AspmSubcommand for KeysGenerateCommand {
|
||||||
.export_encrypted(aes_key)
|
.export_encrypted(aes_key)
|
||||||
.context("Unable to derive the encryption key")?;
|
.context("Unable to derive the encryption key")?;
|
||||||
|
|
||||||
let txn = config
|
// Write to db
|
||||||
.db
|
let entry = keys::ActiveModel {
|
||||||
.begin_write()
|
fingerprint: ActiveValue::Set(key.fingerprint.clone()),
|
||||||
.context("Unable to open the database for writing")?;
|
key_type: ActiveValue::Set(key.key_type.into()),
|
||||||
{
|
alias: ActiveValue::Set(alias),
|
||||||
let mut table = txn.open_table(KEYS_TABLE)?;
|
encrypted: ActiveValue::Set(encrypted),
|
||||||
table
|
};
|
||||||
.insert(
|
let res = keys::Entity::insert(entry)
|
||||||
key.fingerprint.as_str(),
|
.exec(&state.db)
|
||||||
KeysTableValue {
|
.await
|
||||||
key: encrypted,
|
.context("Unable to add key to database")?;
|
||||||
alias: alias
|
if res.last_insert_id != key.fingerprint {
|
||||||
.try_into()
|
bail!("The key was unable to be saved to the database")
|
||||||
.context("alias must be less than or equal to 255 characters")?,
|
|
||||||
key_type: key.key_type,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.context("Unable to write to database")?;
|
|
||||||
}
|
}
|
||||||
txn.commit().context("Unable to write to database")?;
|
|
||||||
|
|
||||||
printdoc! {
|
printdoc! {
|
||||||
"
|
"
|
||||||
|
|
|
@ -1,24 +1,23 @@
|
||||||
use anstyle::{AnsiColor, Reset, Style as Anstyle};
|
use anstyle::{AnsiColor, Reset, Style as Anstyle};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use asp::keys::AspKeyType;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use indoc::printdoc;
|
use indoc::printdoc;
|
||||||
use redb::ReadableTable;
|
use sea_orm::EntityTrait;
|
||||||
|
|
||||||
use crate::{commands::AspmSubcommand, db::KEYS_TABLE};
|
use crate::{commands::AspmSubcommand, entities::keys};
|
||||||
|
|
||||||
/// A command to list all saved keys, along with their fingerprints and types
|
/// A command to list all saved keys, along with their fingerprints and types
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
pub struct KeysListCommand;
|
pub struct KeysListCommand;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl AspmSubcommand for KeysListCommand {
|
impl AspmSubcommand for KeysListCommand {
|
||||||
fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||||
let txn = state
|
let entries = keys::Entity::find()
|
||||||
.db
|
.all(&state.db)
|
||||||
.begin_read()
|
.await
|
||||||
.context("Unable to start db read transaction")?;
|
.context("Unable to read keys from database")?;
|
||||||
let table = txn.open_table(KEYS_TABLE)?;
|
|
||||||
let iter = table.iter().context("Unable to read table entries")?;
|
|
||||||
let entries: Vec<_> = iter.collect();
|
|
||||||
|
|
||||||
// Construct styles
|
// Construct styles
|
||||||
let reset = Reset::default().render();
|
let reset = Reset::default().render();
|
||||||
|
@ -44,18 +43,15 @@ impl AspmSubcommand for KeysListCommand {
|
||||||
n = entries.len(),
|
n = entries.len(),
|
||||||
);
|
);
|
||||||
for entry in entries.iter() {
|
for entry in entries.iter() {
|
||||||
if let Ok((fingerprint, value)) = entry {
|
printdoc! {
|
||||||
let value = value.value();
|
"
|
||||||
printdoc! {
|
{alias_style}{alias}:{reset}
|
||||||
"
|
{key_style}Fingerprint{reset} {value_style}{fingerprint}{reset}
|
||||||
{alias_style}{alias}:{reset}
|
{key_style}Key Type{reset} {value_style}{key_type:?}{reset}
|
||||||
{key_style}Fingerprint{reset} {value_style}{fingerprint}{reset}
|
",
|
||||||
{key_style}Key Type{reset} {value_style}{key_type:?}{reset}
|
fingerprint = entry.fingerprint,
|
||||||
",
|
key_type = TryInto::<AspKeyType>::try_into(entry.key_type).context("Unable to get key type from database")?,
|
||||||
fingerprint = fingerprint.value(),
|
alias = entry.alias
|
||||||
key_type = value.key_type,
|
|
||||||
alias = value.alias
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ use clap::Parser;
|
||||||
|
|
||||||
use crate::AspmState;
|
use crate::AspmState;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
pub trait AspmSubcommand: Parser {
|
pub trait AspmSubcommand: Parser {
|
||||||
fn execute(&self, state: AspmState) -> Result<(), anyhow::Error>;
|
async fn execute(&self, state: AspmState) -> Result<(), anyhow::Error>;
|
||||||
}
|
}
|
||||||
|
|
128
src/db.rs
128
src/db.rs
|
@ -1,128 +0,0 @@
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use anyhow::bail;
|
|
||||||
use asp::keys::AspKeyType;
|
|
||||||
use redb::{RedbValue, TableDefinition};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct KeysTableValue {
|
|
||||||
pub alias: BoundedString,
|
|
||||||
pub key: String,
|
|
||||||
pub key_type: AspKeyType,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct BoundedString {
|
|
||||||
inner: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for BoundedString {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.inner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<String> for BoundedString {
|
|
||||||
type Error = anyhow::Error;
|
|
||||||
|
|
||||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
|
||||||
if value.as_bytes().len() > 255 {
|
|
||||||
bail!("Value was too long");
|
|
||||||
}
|
|
||||||
Ok(Self { inner: value })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<Vec<u8>> for BoundedString {
|
|
||||||
fn into(self) -> Vec<u8> {
|
|
||||||
self.inner.as_bytes().to_vec()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RedbValue for KeysTableValue {
|
|
||||||
type SelfType<'a> = KeysTableValue;
|
|
||||||
|
|
||||||
/// The first u8 is a length, specifying how long the alias is (therefore the alias can only be a max of 255 bytes)
|
|
||||||
/// The next bytes (bounded by the length of the first u8) is the alias
|
|
||||||
/// The next byte is a u8 representing the type of key
|
|
||||||
/// The rest of the bytes are the key
|
|
||||||
type AsBytes<'a> = Vec<u8>;
|
|
||||||
|
|
||||||
fn fixed_width() -> Option<usize> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
// This does panic, but I don't know if there is a better way to do it. If you manage to insert bad data into the table, good job, that is your problem
|
|
||||||
fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
|
|
||||||
where
|
|
||||||
Self: 'a,
|
|
||||||
{
|
|
||||||
let mut iter = data.iter().map(|b| *b);
|
|
||||||
|
|
||||||
let alias_length: usize = iter
|
|
||||||
.next()
|
|
||||||
.expect("parsing key table value failed: unable to get first byte")
|
|
||||||
.into();
|
|
||||||
// Get alias
|
|
||||||
let alias_bytes = {
|
|
||||||
let mut vec = Vec::new();
|
|
||||||
for _ in 0..alias_length {
|
|
||||||
vec.push(
|
|
||||||
iter.next()
|
|
||||||
.expect("parsing key table value failed: ran out of bytes on alias"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
vec
|
|
||||||
};
|
|
||||||
if alias_bytes.len() != alias_length {
|
|
||||||
panic!("parsing key table value failed: unable to get full alias");
|
|
||||||
};
|
|
||||||
// Get the type of key
|
|
||||||
let key_type_byte = iter
|
|
||||||
.next()
|
|
||||||
.expect("parsing key table value failed: unable to get the key type");
|
|
||||||
// Get key
|
|
||||||
let key_bytes = iter.collect::<Vec<u8>>();
|
|
||||||
// Assemble bytes into strings and struct
|
|
||||||
Self {
|
|
||||||
key: String::from_utf8(key_bytes)
|
|
||||||
.expect("parsing key table value failed: unable to decode key into string"),
|
|
||||||
alias: String::from_utf8(alias_bytes)
|
|
||||||
.expect("parsing key table value failed: unable to decode alias into string")
|
|
||||||
.try_into()
|
|
||||||
.unwrap(),
|
|
||||||
key_type: match key_type_byte {
|
|
||||||
0 => AspKeyType::Ed25519,
|
|
||||||
1 => AspKeyType::ES256,
|
|
||||||
_ => panic!("parsing key table value failed: unknown key type byte found"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
|
|
||||||
where
|
|
||||||
Self: 'a,
|
|
||||||
Self: 'b,
|
|
||||||
{
|
|
||||||
let key_bytes = value.key.as_bytes();
|
|
||||||
let alias_bytes: Vec<u8> = value.alias.clone().into();
|
|
||||||
|
|
||||||
let mut serialized: Vec<u8> = vec![];
|
|
||||||
serialized.push(alias_bytes.len().try_into().unwrap()); // Add the first byte (alias length)
|
|
||||||
serialized.extend_from_slice(alias_bytes.as_slice()); // Add the alias bytes
|
|
||||||
serialized.push(match value.key_type {
|
|
||||||
AspKeyType::Ed25519 => 0,
|
|
||||||
AspKeyType::ES256 => 1,
|
|
||||||
}); // Add the key type byte
|
|
||||||
serialized.extend_from_slice(key_bytes); // Add the rest of the bytes, all of which are the key
|
|
||||||
|
|
||||||
serialized
|
|
||||||
}
|
|
||||||
|
|
||||||
fn type_name() -> redb::TypeName {
|
|
||||||
redb::TypeName::new("aspm::KeysTableValue")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A table to contain saved keys
|
|
||||||
pub const KEYS_TABLE: TableDefinition<&str, KeysTableValue> = TableDefinition::new("claims");
|
|
18
src/entities/keys.rs
Normal file
18
src/entities/keys.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "keys")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub fingerprint: String,
|
||||||
|
pub key_type: i32,
|
||||||
|
pub alias: String,
|
||||||
|
pub encrypted: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
5
src/entities/mod.rs
Normal file
5
src/entities/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
|
||||||
|
|
||||||
|
pub mod prelude;
|
||||||
|
|
||||||
|
pub mod keys;
|
3
src/entities/prelude.rs
Normal file
3
src/entities/prelude.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
|
||||||
|
|
||||||
|
pub use super::keys::Entity as Keys;
|
49
src/main.rs
49
src/main.rs
|
@ -1,12 +1,15 @@
|
||||||
mod commands;
|
mod commands;
|
||||||
mod db;
|
mod entities;
|
||||||
|
mod migrations;
|
||||||
|
|
||||||
use anstyle::{AnsiColor, Color as AnstyleColor, Style as Anstyle};
|
use anstyle::{AnsiColor, Color as AnstyleColor, Style as Anstyle};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use app_dirs2::{AppDataType, AppInfo};
|
use app_dirs2::{AppDataType, AppInfo};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use commands::{keys::KeysSubcommands, AspmSubcommand};
|
use commands::{keys::KeysSubcommands, AspmSubcommand};
|
||||||
use redb::Database;
|
use migrations::Migrator;
|
||||||
|
use sea_orm::{Database, DatabaseConnection};
|
||||||
|
use sea_orm_migration::{MigratorTrait, SchemaManager};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -15,11 +18,12 @@ const APP_INFO: AppInfo = AppInfo {
|
||||||
name: env!("CARGO_CRATE_NAME"),
|
name: env!("CARGO_CRATE_NAME"),
|
||||||
author: "Ty",
|
author: "Ty",
|
||||||
};
|
};
|
||||||
|
const DATABASE_URL: &str = "sqlite://DB_PATH?mode=rwc";
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AspmState {
|
pub struct AspmState {
|
||||||
pub data_dir: PathBuf,
|
pub data_dir: PathBuf,
|
||||||
pub db: Database,
|
pub db: DatabaseConnection,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
@ -73,8 +77,9 @@ pub enum AspmSubcommands {
|
||||||
Keys(commands::keys::KeysSubcommand),
|
Keys(commands::keys::KeysSubcommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
#[tokio::main]
|
||||||
match cli() {
|
async fn main() {
|
||||||
|
match cli().await {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("An error occurred while running that command:\n{e}");
|
eprintln!("An error occurred while running that command:\n{e}");
|
||||||
}
|
}
|
||||||
|
@ -82,7 +87,7 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cli() -> Result<(), anyhow::Error> {
|
async fn cli() -> Result<(), anyhow::Error> {
|
||||||
let parsed = AspmCommand::parse();
|
let parsed = AspmCommand::parse();
|
||||||
|
|
||||||
// Check the data dir (read and write)
|
// Check the data dir (read and write)
|
||||||
|
@ -96,14 +101,24 @@ fn cli() -> Result<(), anyhow::Error> {
|
||||||
std::fs::read_dir(&data_dir).or(Err(AspmError::DataDirReadError))?;
|
std::fs::read_dir(&data_dir).or(Err(AspmError::DataDirReadError))?;
|
||||||
|
|
||||||
// Construct the database in the dir
|
// Construct the database in the dir
|
||||||
let mut db = Database::create({
|
let db = Database::connect(DATABASE_URL.replace("DB_PATH", &{
|
||||||
let mut new = data_dir.clone();
|
let mut new = data_dir.clone();
|
||||||
new.push("db.redb");
|
new.push("db.sqlite");
|
||||||
new
|
new.to_str()
|
||||||
})
|
.context("Unable to parse database path into string")
|
||||||
.or(Err(AspmError::DatabaseCreateError))?;
|
.map(|s| s.to_string())
|
||||||
db.check_integrity()
|
}?))
|
||||||
.context("Unable to check database integrity")?;
|
.await
|
||||||
|
.context("Unable to open database")?;
|
||||||
|
|
||||||
|
let schema_manager = SchemaManager::new(&db);
|
||||||
|
Migrator::up(&db, None)
|
||||||
|
.await
|
||||||
|
.context("Unable to migrate database")?;
|
||||||
|
assert!(schema_manager
|
||||||
|
.has_table("keys")
|
||||||
|
.await
|
||||||
|
.context("Unable to check database for keys table")?);
|
||||||
|
|
||||||
// Make the state
|
// Make the state
|
||||||
let state = AspmState { data_dir, db };
|
let state = AspmState { data_dir, db };
|
||||||
|
@ -111,9 +126,9 @@ fn cli() -> Result<(), anyhow::Error> {
|
||||||
// Call the subcommand
|
// Call the subcommand
|
||||||
match &parsed.subcommand {
|
match &parsed.subcommand {
|
||||||
AspmSubcommands::Keys(subcommand) => match &subcommand.subcommand {
|
AspmSubcommands::Keys(subcommand) => match &subcommand.subcommand {
|
||||||
KeysSubcommands::Generate(subcommand) => subcommand.execute(state),
|
KeysSubcommands::Generate(subcommand) => subcommand.execute(state).await,
|
||||||
KeysSubcommands::List(subcommand) => subcommand.execute(state),
|
KeysSubcommands::List(subcommand) => subcommand.execute(state).await,
|
||||||
KeysSubcommands::Export(subcommand) => subcommand.execute(state),
|
KeysSubcommands::Export(subcommand) => subcommand.execute(state).await,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,8 +137,6 @@ fn cli() -> Result<(), anyhow::Error> {
|
||||||
enum AspmError {
|
enum AspmError {
|
||||||
#[error("The data directory was unable to be created, is it correct, and does the current user have permission to create it?")]
|
#[error("The data directory was unable to be created, is it correct, and does the current user have permission to create it?")]
|
||||||
DataDirCreateError,
|
DataDirCreateError,
|
||||||
#[error("The database was unable to be created, is the data dir correct, and does the current user have permission to modify it?")]
|
|
||||||
DatabaseCreateError,
|
|
||||||
#[error("The data directory was unable to be read, is it correct, and does the current user have permission to read it?")]
|
#[error("The data directory was unable to be read, is it correct, and does the current user have permission to read it?")]
|
||||||
DataDirReadError,
|
DataDirReadError,
|
||||||
#[error("An unknown internal error occurred, please report this to the developer")]
|
#[error("An unknown internal error occurred, please report this to the developer")]
|
||||||
|
|
48
src/migrations/m_20230701_000001_create_keys_table.rs
Normal file
48
src/migrations/m_20230701_000001_create_keys_table.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
impl MigrationName for Migration {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"m_20230701_000001_create_keys_table"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
// Define how to apply this migration: Create the Bakery table.
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.create_table(
|
||||||
|
Table::create()
|
||||||
|
.table(Keys::Table)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(Keys::Fingerprint)
|
||||||
|
.string_len(26)
|
||||||
|
.not_null()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(Keys::KeyType).integer().not_null())
|
||||||
|
.col(ColumnDef::new(Keys::Alias).string().not_null())
|
||||||
|
.col(ColumnDef::new(Keys::Encrypted).string().not_null())
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define how to rollback this migration: Drop the Bakery table.
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.drop_table(Table::drop().table(Keys::Table).to_owned())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Iden)]
|
||||||
|
enum Keys {
|
||||||
|
Table,
|
||||||
|
Fingerprint,
|
||||||
|
KeyType,
|
||||||
|
Alias,
|
||||||
|
Encrypted,
|
||||||
|
}
|
11
src/migrations/mod.rs
Normal file
11
src/migrations/mod.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
mod m_20230701_000001_create_keys_table;
|
||||||
|
|
||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
pub struct Migrator;
|
||||||
|
|
||||||
|
impl MigratorTrait for Migrator {
|
||||||
|
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||||
|
vec![Box::new(m_20230701_000001_create_keys_table::Migration)]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue