mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-04 11:03:27 +00:00
Integrate KeyManager with library creation flow (#491)
* integrate keymanager with library creation flow * final fixes * fix clippy recommendations * minor fixes on library create dialog * reenable `panic!` for no key + fix secret handling code * prevent user setting secret , instead hardcode it * clean library manager default key selection * bring back wrongly removed `keys.onboarding` resolver * fix types in `CreateLibraryDialog`
This commit is contained in:
parent
a309d15a8d
commit
3964d44ce5
|
@ -43,7 +43,13 @@ const CreateLibraryDialog = ({ children, onSubmit, disableBackdropClose }: Props
|
|||
title="Create New Library"
|
||||
description="Choose a name for your new library, you can configure this and more settings from the library settings later on."
|
||||
ctaLabel="Create"
|
||||
ctaAction={() => createLibrary(libName)}
|
||||
ctaAction={() =>
|
||||
createLibrary({
|
||||
name: libName,
|
||||
// TODO: Support password and secret on mobile
|
||||
password: undefined
|
||||
})
|
||||
}
|
||||
loading={createLibLoading}
|
||||
ctaDisabled={libName.length === 0}
|
||||
trigger={children}
|
||||
|
|
|
@ -12,17 +12,12 @@ import { useAutoForm } from '~/hooks/useAutoForm';
|
|||
import tw from '~/lib/tailwind';
|
||||
import { SettingsStackScreenProps } from '~/navigation/SettingsNavigator';
|
||||
|
||||
type LibraryFormData = {
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
const LibraryGeneralSettingsScreen = ({
|
||||
navigation
|
||||
}: SettingsStackScreenProps<'LibraryGeneralSettings'>) => {
|
||||
const { library } = useCurrentLibrary();
|
||||
|
||||
const form = useForm<LibraryFormData>({
|
||||
const form = useForm({
|
||||
defaultValues: { name: library.config.name, description: library.config.description }
|
||||
});
|
||||
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
use std::io::{Read, Write};
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
use sd_crypto::keys::keymanager::StoredKey;
|
||||
use sd_crypto::{
|
||||
crypto::stream::Algorithm,
|
||||
keys::{hashing::HashingAlgorithm, keymanager::KeyManager},
|
||||
Protected,
|
||||
};
|
||||
use sd_crypto::keys::keymanager::{KeyManager, StoredKey};
|
||||
use sd_crypto::{crypto::stream::Algorithm, keys::hashing::HashingAlgorithm, Protected};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specta::Type;
|
||||
use uuid::Uuid;
|
||||
|
@ -48,6 +44,7 @@ pub struct RestoreBackupArgs {
|
|||
pub struct OnboardingArgs {
|
||||
algorithm: Algorithm,
|
||||
hashing_algorithm: HashingAlgorithm,
|
||||
password: Protected<String>,
|
||||
}
|
||||
|
||||
#[derive(Type, Deserialize)]
|
||||
|
@ -141,7 +138,7 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
let key = library.key_manager.save_to_database(key_uuid)?;
|
||||
|
||||
// does not check that the key doesn't exist before writing
|
||||
write_storedkey_to_db(library.db.clone(), &key).await?;
|
||||
write_storedkey_to_db(&library.db, &key).await?;
|
||||
|
||||
invalidate_query!(library, "keys.list");
|
||||
Ok(())
|
||||
|
@ -192,7 +189,8 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
})
|
||||
.library_mutation("onboarding", |t| {
|
||||
t(|_, args: OnboardingArgs, library| async move {
|
||||
let bundle = KeyManager::onboarding(args.algorithm, args.hashing_algorithm)?;
|
||||
let bundle =
|
||||
KeyManager::onboarding(args.algorithm, args.hashing_algorithm, args.password)?;
|
||||
|
||||
let verification_key = bundle.verification_key;
|
||||
|
||||
|
@ -205,7 +203,7 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
.exec()
|
||||
.await?;
|
||||
|
||||
write_storedkey_to_db(library.db.clone(), &verification_key).await?;
|
||||
write_storedkey_to_db(&library.db, &verification_key).await?;
|
||||
|
||||
let keys = OnboardingKeys {
|
||||
master_password: bundle.master_password.expose().clone(),
|
||||
|
@ -307,7 +305,7 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
let stored_key = library.key_manager.access_keystore(uuid)?;
|
||||
|
||||
if args.library_sync {
|
||||
write_storedkey_to_db(library.db.clone(), &stored_key).await?;
|
||||
write_storedkey_to_db(&library.db, &stored_key).await?;
|
||||
|
||||
if args.automount {
|
||||
library
|
||||
|
@ -393,7 +391,7 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
)?;
|
||||
|
||||
for key in &updated_keys {
|
||||
write_storedkey_to_db(library.db.clone(), key).await?;
|
||||
write_storedkey_to_db(&library.db, key).await?;
|
||||
}
|
||||
|
||||
invalidate_query!(library, "keys.list");
|
||||
|
@ -419,7 +417,7 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
.await?;
|
||||
|
||||
// write the new verification key
|
||||
write_storedkey_to_db(library.db.clone(), &bundle.verification_key).await?;
|
||||
write_storedkey_to_db(&library.db, &bundle.verification_key).await?;
|
||||
|
||||
Ok(bundle.secret_key.expose().clone())
|
||||
})
|
||||
|
|
|
@ -8,6 +8,7 @@ use super::{utils::LibraryRequest, RouterBuilder};
|
|||
use chrono::Utc;
|
||||
use fs_extra::dir::get_size; // TODO: Remove this dependency as it is sync instead of async
|
||||
use rspc::Type;
|
||||
use sd_crypto::Protected;
|
||||
use serde::Deserialize;
|
||||
use tokio::fs;
|
||||
use uuid::Uuid;
|
||||
|
@ -73,13 +74,22 @@ pub(crate) fn mount() -> RouterBuilder {
|
|||
})
|
||||
})
|
||||
.mutation("create", |t| {
|
||||
t(|ctx, name: String| async move {
|
||||
#[derive(Deserialize, Type)]
|
||||
pub struct CreateLibraryArgs {
|
||||
name: String,
|
||||
password: Option<Protected<String>>,
|
||||
}
|
||||
|
||||
t(|ctx, args: CreateLibraryArgs| async move {
|
||||
Ok(ctx
|
||||
.library_manager
|
||||
.create(LibraryConfig {
|
||||
name: name.to_string(),
|
||||
..Default::default()
|
||||
})
|
||||
.create(
|
||||
LibraryConfig {
|
||||
name: args.name.to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
args.password,
|
||||
)
|
||||
.await?)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -22,6 +22,9 @@ pub struct LibraryConfig {
|
|||
pub name: String,
|
||||
/// description is a user set description of the library. This is used in the UI and is set by the user.
|
||||
pub description: String,
|
||||
// /// is_encrypted is a flag that is set to true if the library is encrypted.
|
||||
// #[serde(default)]
|
||||
// pub is_encrypted: bool,
|
||||
}
|
||||
|
||||
impl LibraryConfig {
|
||||
|
|
|
@ -19,6 +19,8 @@ use super::LibraryConfig;
|
|||
pub struct LibraryContext {
|
||||
/// id holds the ID of the current library.
|
||||
pub id: Uuid,
|
||||
/// local_id holds the local ID of the current library.
|
||||
pub local_id: i32,
|
||||
/// config holds the configuration of the current library.
|
||||
pub config: LibraryConfig,
|
||||
/// db holds the database client for the current library.
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::{
|
||||
invalidate_query,
|
||||
node::Platform,
|
||||
prisma::{key, node, PrismaClient},
|
||||
prisma::{node, PrismaClient},
|
||||
util::{
|
||||
db::load_and_migrate,
|
||||
db::{load_and_migrate, write_storedkey_to_db},
|
||||
seeder::{indexer_rules_seeder, SeederError},
|
||||
},
|
||||
NodeContext,
|
||||
|
@ -16,6 +16,7 @@ use sd_crypto::{
|
|||
keymanager::{KeyManager, StoredKey},
|
||||
},
|
||||
primitives::to_array,
|
||||
Protected,
|
||||
};
|
||||
use std::{
|
||||
env, fs, io,
|
||||
|
@ -71,58 +72,19 @@ impl From<LibraryManagerError> for rspc::Error {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn create_keymanager(client: &PrismaClient) -> Result<KeyManager, LibraryManagerError> {
|
||||
// retrieve all stored keys from the DB
|
||||
pub async fn create_keymanager(
|
||||
client: &PrismaClient,
|
||||
) -> Result<Arc<KeyManager>, LibraryManagerError> {
|
||||
let key_manager = KeyManager::new(vec![])?;
|
||||
|
||||
// BRXKEN128: REMOVE THIS ONCE ONBOARDING HAS BEEN DONE
|
||||
// this is so if there's no verification key set, we set one so users can use the key manager
|
||||
// it will be done during onboarding, but for now things are statically set (unless they were changed)
|
||||
if client
|
||||
.key()
|
||||
.find_many(vec![key::uuid::equals(uuid::Uuid::nil().to_string())])
|
||||
.exec()
|
||||
.await?
|
||||
.is_empty()
|
||||
{
|
||||
client
|
||||
.key()
|
||||
.delete_many(vec![key::uuid::equals(uuid::Uuid::nil().to_string())])
|
||||
.exec()
|
||||
.await?;
|
||||
// BRXKEN128: REMOVE THIS ONCE ONBOARDING HAS BEEN DONE
|
||||
let verification_key = KeyManager::onboarding(
|
||||
Algorithm::XChaCha20Poly1305,
|
||||
HashingAlgorithm::Argon2id(Params::Standard),
|
||||
)?
|
||||
.verification_key;
|
||||
|
||||
// BRXKEN128: REMOVE THIS ONCE ONBOARDING HAS BEEN DONE
|
||||
client
|
||||
.key()
|
||||
.create(
|
||||
verification_key.uuid.to_string(),
|
||||
verification_key.algorithm.serialize().to_vec(),
|
||||
verification_key.hashing_algorithm.serialize().to_vec(),
|
||||
verification_key.content_salt.to_vec(),
|
||||
verification_key.master_key.to_vec(),
|
||||
verification_key.master_key_nonce.to_vec(),
|
||||
verification_key.key_nonce.to_vec(),
|
||||
verification_key.key.to_vec(),
|
||||
verification_key.salt.to_vec(),
|
||||
vec![],
|
||||
)
|
||||
.exec()
|
||||
.await?;
|
||||
}
|
||||
|
||||
let db_stored_keys = client.key().find_many(vec![]).exec().await?;
|
||||
|
||||
let mut default = Uuid::nil();
|
||||
let mut default: Option<Uuid> = None;
|
||||
|
||||
// collect and serialize the stored keys
|
||||
// shouldn't call unwrap so much here
|
||||
let stored_keys: Vec<StoredKey> = db_stored_keys
|
||||
let stored_keys: Vec<StoredKey> = client
|
||||
.key()
|
||||
.find_many(vec![])
|
||||
.exec()
|
||||
.await?
|
||||
.iter()
|
||||
.map(|key| {
|
||||
let key = key.clone();
|
||||
|
@ -130,7 +92,7 @@ pub async fn create_keymanager(client: &PrismaClient) -> Result<KeyManager, Libr
|
|||
let uuid = uuid::Uuid::from_str(&key.uuid).unwrap();
|
||||
|
||||
if key.default {
|
||||
default = uuid;
|
||||
default = Some(uuid);
|
||||
}
|
||||
|
||||
let stored_key = StoredKey {
|
||||
|
@ -156,11 +118,11 @@ pub async fn create_keymanager(client: &PrismaClient) -> Result<KeyManager, Libr
|
|||
key_manager.populate_keystore(stored_keys)?;
|
||||
|
||||
// if any key had an associated default tag
|
||||
if !default.is_nil() {
|
||||
if let Some(default) = default {
|
||||
key_manager.set_default(default)?;
|
||||
}
|
||||
|
||||
Ok(key_manager)
|
||||
Ok(Arc::new(key_manager))
|
||||
}
|
||||
|
||||
impl LibraryManager {
|
||||
|
@ -220,6 +182,7 @@ impl LibraryManager {
|
|||
pub(crate) async fn create(
|
||||
&self,
|
||||
config: LibraryConfig,
|
||||
password: Option<Protected<String>>,
|
||||
) -> Result<LibraryConfigWrapped, LibraryManagerError> {
|
||||
let id = Uuid::new_v4();
|
||||
LibraryConfig::save(
|
||||
|
@ -236,6 +199,23 @@ impl LibraryManager {
|
|||
)
|
||||
.await?;
|
||||
|
||||
// Run seeders
|
||||
indexer_rules_seeder(&library.db).await?;
|
||||
|
||||
// Setup default key
|
||||
if let Some(password) = password {
|
||||
let verification_key = KeyManager::onboarding(
|
||||
Algorithm::XChaCha20Poly1305,
|
||||
HashingAlgorithm::Argon2id(Params::Standard),
|
||||
password,
|
||||
)?
|
||||
.verification_key;
|
||||
|
||||
write_storedkey_to_db(&library.db, &verification_key).await?;
|
||||
} else {
|
||||
// TODO: Make setting up keys optional with rest of system before removing this.
|
||||
todo!();
|
||||
}
|
||||
invalidate_query!(library, "library.list");
|
||||
|
||||
self.libraries.write().await.push(library);
|
||||
|
@ -347,7 +327,6 @@ impl LibraryManager {
|
|||
};
|
||||
|
||||
let uuid_vec = id.as_bytes().to_vec();
|
||||
|
||||
let node_data = db
|
||||
.node()
|
||||
.upsert(
|
||||
|
@ -362,16 +341,12 @@ impl LibraryManager {
|
|||
.exec()
|
||||
.await?;
|
||||
|
||||
// Run seeders
|
||||
indexer_rules_seeder(&db).await?;
|
||||
|
||||
let key_manager = Arc::new(create_keymanager(&db).await?);
|
||||
|
||||
Ok(LibraryContext {
|
||||
id,
|
||||
local_id: node_data.id,
|
||||
config,
|
||||
key_manager: create_keymanager(&db).await?,
|
||||
db,
|
||||
key_manager,
|
||||
node_local_id: node_data.id,
|
||||
node_context,
|
||||
})
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::{
|
||||
job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext},
|
||||
location::indexer::rules::RuleKind,
|
||||
prisma::{file_path, location},
|
||||
};
|
||||
|
||||
|
@ -22,7 +23,7 @@ use super::{
|
|||
create_many_file_paths, get_max_file_path_id, set_max_file_path_id,
|
||||
FilePathBatchCreateEntry,
|
||||
},
|
||||
rules::{IndexerRule, RuleKind},
|
||||
rules::IndexerRule,
|
||||
walk::{walk, WalkEntry},
|
||||
};
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ use crate::prisma::{self, PrismaClient};
|
|||
use prisma_client_rust::QueryError;
|
||||
use prisma_client_rust::{migrations::*, NewClientError};
|
||||
use sd_crypto::keys::keymanager::StoredKey;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
|
||||
/// MigrationError represents an error that occurring while opening a initialising and running migrations on the database.
|
||||
|
@ -46,10 +45,7 @@ pub async fn load_and_migrate(db_url: &str) -> Result<PrismaClient, MigrationErr
|
|||
|
||||
/// This writes a `StoredKey` to prisma
|
||||
/// If the key is marked as memory-only, it is skipped
|
||||
pub async fn write_storedkey_to_db(
|
||||
db: Arc<PrismaClient>,
|
||||
key: &StoredKey,
|
||||
) -> Result<(), QueryError> {
|
||||
pub async fn write_storedkey_to_db(db: &PrismaClient, key: &StoredKey) -> Result<(), QueryError> {
|
||||
if !key.memory_only {
|
||||
db.key()
|
||||
.create(
|
||||
|
|
|
@ -39,8 +39,8 @@ use std::sync::Mutex;
|
|||
|
||||
use crate::crypto::stream::{StreamDecryption, StreamEncryption};
|
||||
use crate::primitives::{
|
||||
derive_key, generate_master_key, generate_nonce, generate_passphrase, generate_salt, to_array,
|
||||
KEY_LEN, MASTER_PASSWORD_CONTEXT, ROOT_KEY_CONTEXT,
|
||||
derive_key, generate_master_key, generate_nonce, generate_salt, to_array, KEY_LEN,
|
||||
MASTER_PASSWORD_CONTEXT, ROOT_KEY_CONTEXT,
|
||||
};
|
||||
use crate::{
|
||||
crypto::stream::Algorithm,
|
||||
|
@ -154,23 +154,18 @@ impl KeyManager {
|
|||
pub fn onboarding(
|
||||
algorithm: Algorithm,
|
||||
hashing_algorithm: HashingAlgorithm,
|
||||
password: Protected<String>,
|
||||
) -> Result<OnboardingBundle> {
|
||||
let _master_password = generate_passphrase();
|
||||
let _content_salt = generate_salt(); // secret key
|
||||
|
||||
// BRXKEN128: REMOVE THIS ONCE ONBOARDING HAS BEEN DONE
|
||||
let master_password = Protected::new("password".to_string());
|
||||
let content_salt = *b"0000000000000000"; // secret key
|
||||
let content_salt = *b"0000000000000000"; // secret key // TODO: Don't hardcode this
|
||||
|
||||
// Hash the master password
|
||||
let hashed_password = hashing_algorithm.hash(
|
||||
Protected::new(master_password.expose().as_bytes().to_vec()),
|
||||
Protected::new(password.expose().as_bytes().to_vec()),
|
||||
content_salt,
|
||||
)?;
|
||||
|
||||
let salt = generate_salt();
|
||||
let derived_key = derive_key(hashed_password, salt, MASTER_PASSWORD_CONTEXT);
|
||||
|
||||
let uuid = uuid::Uuid::nil();
|
||||
|
||||
// Generate items we'll need for encryption
|
||||
|
@ -215,7 +210,7 @@ impl KeyManager {
|
|||
|
||||
let onboarding_bundle = OnboardingBundle {
|
||||
verification_key,
|
||||
master_password,
|
||||
master_password: password,
|
||||
secret_key,
|
||||
};
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//!
|
||||
//! This includes things such as cryptographically-secure random salt/master key/nonce generation,
|
||||
//! lengths for master keys and even the streaming block size.
|
||||
use rand::{seq::SliceRandom, RngCore, SeedableRng};
|
||||
use rand::{RngCore, SeedableRng};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use crate::{
|
||||
|
@ -107,33 +107,33 @@ pub fn to_array<const I: usize>(bytes: Vec<u8>) -> Result<[u8; I]> {
|
|||
})
|
||||
}
|
||||
|
||||
/// This generates a 7 word diceware passphrase, separated with `-`
|
||||
#[must_use]
|
||||
pub fn generate_passphrase() -> Protected<String> {
|
||||
let wordlist = include_str!("../assets/eff_large_wordlist.txt")
|
||||
.lines()
|
||||
.collect::<Vec<&str>>();
|
||||
// /// This generates a 7 word diceware passphrase, separated with `-`
|
||||
// #[must_use]
|
||||
// pub fn generate_passphrase() -> Protected<String> {
|
||||
// let wordlist = include_str!("../assets/eff_large_wordlist.txt")
|
||||
// .lines()
|
||||
// .collect::<Vec<&str>>();
|
||||
|
||||
let words: Vec<String> = wordlist
|
||||
.choose_multiple(
|
||||
&mut rand_chacha::ChaCha20Rng::from_entropy(),
|
||||
PASSPHRASE_LEN,
|
||||
)
|
||||
.map(ToString::to_string)
|
||||
.collect();
|
||||
// let words: Vec<String> = wordlist
|
||||
// .choose_multiple(
|
||||
// &mut rand_chacha::ChaCha20Rng::from_entropy(),
|
||||
// PASSPHRASE_LEN,
|
||||
// )
|
||||
// .map(ToString::to_string)
|
||||
// .collect();
|
||||
|
||||
let passphrase = words
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, word)| {
|
||||
if i < PASSPHRASE_LEN - 1 {
|
||||
word.clone() + "-"
|
||||
} else {
|
||||
word.clone()
|
||||
}
|
||||
})
|
||||
.into_iter()
|
||||
.collect();
|
||||
// let passphrase = words
|
||||
// .iter()
|
||||
// .enumerate()
|
||||
// .map(|(i, word)| {
|
||||
// if i < PASSPHRASE_LEN - 1 {
|
||||
// word.clone() + "-"
|
||||
// } else {
|
||||
// word.clone()
|
||||
// }
|
||||
// })
|
||||
// .into_iter()
|
||||
// .collect();
|
||||
|
||||
Protected::new(passphrase)
|
||||
}
|
||||
// Protected::new(passphrase)
|
||||
// }
|
||||
|
|
|
@ -84,3 +84,36 @@ where
|
|||
f.write_str("[REDACTED]")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de, T> serde::Deserialize<'de> for Protected<T>
|
||||
where
|
||||
T: serde::Deserialize<'de> + Zeroize,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
Ok(Self::new(T::deserialize(deserializer)?))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rspc")]
|
||||
impl<T> specta::Type for Protected<T>
|
||||
where
|
||||
T: specta::Type + Zeroize,
|
||||
{
|
||||
const NAME: &'static str = T::NAME;
|
||||
|
||||
fn inline(opts: specta::DefOpts, generics: &[specta::DataType]) -> specta::DataType {
|
||||
T::inline(opts, generics)
|
||||
}
|
||||
|
||||
fn reference(opts: specta::DefOpts, generics: &[specta::DataType]) -> specta::DataType {
|
||||
T::reference(opts, generics)
|
||||
}
|
||||
|
||||
fn definition(opts: specta::DefOpts) -> specta::DataType {
|
||||
T::definition(opts)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ export type Procedures = {
|
|||
{ key: "keys.unmountAll", input: LibraryArgs<null>, result: null } |
|
||||
{ key: "keys.updateAutomountStatus", input: LibraryArgs<AutomountUpdateArgs>, result: null } |
|
||||
{ key: "keys.updateKeyName", input: LibraryArgs<KeyNameUpdateArgs>, result: null } |
|
||||
{ key: "library.create", input: string, result: LibraryConfigWrapped } |
|
||||
{ key: "library.create", input: CreateLibraryArgs, result: LibraryConfigWrapped } |
|
||||
{ key: "library.delete", input: string, result: null } |
|
||||
{ key: "library.edit", input: EditLibraryArgs, result: null } |
|
||||
{ key: "locations.addLibrary", input: LibraryArgs<LocationCreateArgs>, result: null } |
|
||||
|
@ -85,6 +85,8 @@ export interface BuildInfo { version: string, commit: string }
|
|||
|
||||
export interface ConfigMetadata { version: string | null }
|
||||
|
||||
export interface CreateLibraryArgs { name: string, password: string | null }
|
||||
|
||||
export interface EditLibraryArgs { id: string, name: string | null, description: string | null }
|
||||
|
||||
export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" } & Tag
|
||||
|
@ -157,7 +159,7 @@ export interface Object { id: number, cas_id: string, integrity_checksum: string
|
|||
|
||||
export interface ObjectValidatorArgs { id: number, path: string }
|
||||
|
||||
export interface OnboardingArgs { algorithm: Algorithm, hashing_algorithm: HashingAlgorithm }
|
||||
export interface OnboardingArgs { algorithm: Algorithm, hashing_algorithm: HashingAlgorithm, password: string }
|
||||
|
||||
export interface OnboardingKeys { master_password: string, secret_key: string }
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@ import { useBridgeMutation } from '@sd/client';
|
|||
import { Input } from '@sd/ui';
|
||||
import { Dialog } from '@sd/ui';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { PropsWithChildren, useState } from 'react';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
export default function CreateLibraryDialog({
|
||||
children,
|
||||
|
@ -10,27 +11,38 @@ export default function CreateLibraryDialog({
|
|||
open,
|
||||
setOpen
|
||||
}: PropsWithChildren<{ onSubmit?: () => void; open: boolean; setOpen: (state: boolean) => void }>) {
|
||||
const [newLibName, setNewLibName] = useState('');
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate: createLibrary, isLoading: createLibLoading } = useBridgeMutation(
|
||||
'library.create',
|
||||
{
|
||||
onSuccess: (library: any) => {
|
||||
setOpen(false);
|
||||
|
||||
queryClient.setQueryData(['library.list'], (libraries: any) => [
|
||||
...(libraries || []),
|
||||
library
|
||||
]);
|
||||
|
||||
if (onSubmit) onSubmit();
|
||||
},
|
||||
onError: (err: any) => {
|
||||
console.error(err);
|
||||
}
|
||||
const form = useForm({
|
||||
defaultValues: {
|
||||
name: '',
|
||||
// TODO: Remove these default values once we go to prod
|
||||
password: 'password' as string | null
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const createLibrary = useBridgeMutation('library.create', {
|
||||
onSuccess: (library) => {
|
||||
queryClient.setQueryData(['library.list'], (libraries: any) => [
|
||||
...(libraries || []),
|
||||
library
|
||||
]);
|
||||
|
||||
if (onSubmit) onSubmit();
|
||||
setOpen(false);
|
||||
form.reset();
|
||||
},
|
||||
onError: (err: any) => {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
const doSubmit = form.handleSubmit((data) => {
|
||||
// TODO: This is skechy, but will work for now.
|
||||
if (data.password === '') {
|
||||
data.password = null;
|
||||
}
|
||||
|
||||
return createLibrary.mutateAsync(data);
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
|
@ -38,19 +50,44 @@ export default function CreateLibraryDialog({
|
|||
setOpen={setOpen}
|
||||
title="Create New Library"
|
||||
description="Choose a name for your new library, you can configure this and more settings from the library settings later on."
|
||||
ctaAction={() => createLibrary(newLibName)}
|
||||
loading={createLibLoading}
|
||||
submitDisabled={!newLibName}
|
||||
ctaAction={doSubmit}
|
||||
loading={form.formState.isSubmitting}
|
||||
submitDisabled={!form.formState.isValid}
|
||||
ctaLabel="Create"
|
||||
trigger={children}
|
||||
>
|
||||
<Input
|
||||
className="flex-grow w-full mt-3"
|
||||
value={newLibName}
|
||||
placeholder="My Cool Library"
|
||||
onChange={(e) => setNewLibName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<form onSubmit={doSubmit}>
|
||||
<div className="relative flex flex-col">
|
||||
<p className="text-sm mt-3">Name:</p>
|
||||
<Input
|
||||
className="flex-grow w-full"
|
||||
placeholder="My Cool Library"
|
||||
disabled={form.formState.isSubmitting}
|
||||
{...form.register('name', { required: true })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* TODO: Proper UI for this. Maybe checkbox for encrypted or not and then reveal these fields. Select encrypted by default. */}
|
||||
<span className="text-sm">Make password field empty to skip key setup.</span>
|
||||
|
||||
<div className="relative flex flex-col">
|
||||
<p className="text-sm mt-2">Password:</p>
|
||||
<Input
|
||||
className="flex-grow !py-0.5"
|
||||
disabled={form.formState.isSubmitting}
|
||||
{...form.register('password')}
|
||||
placeholder="password"
|
||||
/>
|
||||
</div>
|
||||
<div className="relative flex flex-col">
|
||||
<p className="text-sm mt-2">Secret Key:</p>
|
||||
<Input
|
||||
className="flex-grow !py-0.5"
|
||||
placeholder="00000000-00000000-00000000-00000000"
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -20,28 +20,6 @@ export default function OnboardingPage() {
|
|||
)}
|
||||
>
|
||||
<h1 className="text-red-500">Welcome to Spacedrive</h1>
|
||||
<div className="text-white mt-2 mb-4">
|
||||
<p className="text-sm mb-1">
|
||||
The default keymanager details are below. This is only for development, and will be
|
||||
completely random once onboarding has completed. The secret key is just 16x zeroes encoded
|
||||
in hex.
|
||||
</p>
|
||||
<div className="flex space-x-2">
|
||||
<div className="relative flex">
|
||||
<p className="mr-2 text-sm mt-2">Password:</p>
|
||||
<Input value="password" className="flex-grow !py-0.5" disabled />
|
||||
</div>
|
||||
<div className="relative flex w-[375px]">
|
||||
<p className="mr-2 text-sm mt-2">Secret Key:</p>
|
||||
<Input
|
||||
value="30303030-30303030-30303030-30303030"
|
||||
className="flex-grow !py-0.5"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateLibraryDialog open={open} setOpen={setOpen} onSubmit={() => navigate('/overview')}>
|
||||
<Button variant="accent" size="sm">
|
||||
Create your library
|
||||
|
|
Loading…
Reference in a new issue