diff --git a/crates/sp_client/src/aesgcm.rs b/crates/sp_client/src/aesgcm.rs deleted file mode 100644 index 59ab295..0000000 --- a/crates/sp_client/src/aesgcm.rs +++ /dev/null @@ -1,447 +0,0 @@ -use std::collections::HashMap; - -use anyhow::{Error, Result}; -use sp_backend::{ - bitcoin::{ - consensus::serde::hex, - hex::DisplayHex, - key::constants::SECRET_KEY_SIZE, - secp256k1::{ecdh::SharedSecret, SecretKey}, - Txid, - }, - silentpayments::sending::SilentPaymentAddress, -}; -use wasm_bindgen::JsValue; - -use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; - -use aes::cipher::generic_array::GenericArray; -use aes::{ - cipher::consts::{U32, U8}, - Aes256, -}; -use aes_gcm::{ - aead::{Aead, AeadInPlace, KeyInit, Nonce}, - AeadCore, Aes256Gcm, AesGcm, Key, TagSize, -}; -use rand::{thread_rng, RngCore}; - -const HALFKEYSIZE: usize = SECRET_KEY_SIZE / 2; - -const THIRTYTWO: usize = 32; - -pub struct HalfKey([u8; HALFKEYSIZE]); - -impl TryFrom> for HalfKey { - type Error = anyhow::Error; - fn try_from(value: Vec) -> std::prelude::v1::Result { - if value.len() == HALFKEYSIZE { - let mut buf = [0u8; HALFKEYSIZE]; - buf.copy_from_slice(&value); - Ok(HalfKey(buf)) - } else { - Err(Error::msg("Invalid length for HalfKey")) - } - } -} - -impl HalfKey { - pub fn as_slice(&self) -> &[u8] { - &self.0 - } - - pub fn to_inner(&self) -> Vec { - self.0.to_vec() - } -} - -pub enum Purpose { - Login, - ThirtyTwoBytes, -} - -pub type CipherText = Vec; - -pub type EncryptedKey = Vec; - -pub struct Aes256Decryption { - pub purpose: Purpose, - cipher_text: CipherText, - aes_key: [u8; 32], - nonce: [u8; 12], -} - -impl Aes256Decryption { - pub fn new( - purpose: Purpose, - cipher_text: CipherText, - encrypted_aes_key: Vec, // If shared_secret is none this is actually the aes_key - shared_secret: Option, // We don't need that for certain purpose, like Login - ) -> Result { - let mut aes_key = [0u8; 32]; - if let Some(shared_secret) = shared_secret { - if encrypted_aes_key.len() <= 12 { - return Err(Error::msg("encrypted_aes_key is shorter than nonce length")); - } // Actually we could probably test that if the remnant is not a multiple of 32, something's wrong - // take the first 12 bytes form encrypted_aes_key as nonce - let (decrypt_key_nonce, encrypted_key) = encrypted_aes_key.split_at(12); - // decrypt key with shared_secret obtained from transaction - let decrypt_key_cipher = Aes256Gcm::new_from_slice(shared_secret.as_ref()) - .map_err(|e| Error::msg(format!("{}", e)))?; - let aes_key_plain = decrypt_key_cipher - .decrypt(decrypt_key_nonce.into(), encrypted_key) - .map_err(|e| Error::msg(format!("{}", e)))?; - if aes_key_plain.len() != 32 { - return Err(Error::msg("Invalid length for decrypted key")); - } - aes_key.copy_from_slice(&aes_key_plain); - } else { - if encrypted_aes_key.len() != 32 { - return Err(Error::msg("Invalid length for decrypted key")); - } - aes_key.copy_from_slice(&encrypted_aes_key); - } - if cipher_text.len() <= 12 { - return Err(Error::msg("cipher_text is shorter than nonce length")); - } - let (message_nonce, message_cipher) = cipher_text.split_at(12); - let mut nonce = [0u8; 12]; - nonce.copy_from_slice(message_nonce); - Ok(Self { - purpose, - cipher_text: message_cipher.to_vec(), - aes_key, - nonce, - }) - } - - pub fn decrypt_with_key(&self) -> Result> { - match self.purpose { - Purpose::Login => { - let half_key = self.decrypt_login()?; - Ok(half_key.to_inner()) - } - Purpose::ThirtyTwoBytes => { - let thirty_two_buf = self.decrypt_thirty_two()?; - Ok(thirty_two_buf.to_vec()) - } - } - } - - fn decrypt_login(&self) -> Result { - let cipher = Aes256Gcm::new(&self.aes_key.into()); - let plain = cipher - .decrypt(&self.nonce.into(), &*self.cipher_text) - .map_err(|e| Error::msg(format!("{}", e)))?; - if plain.len() != SECRET_KEY_SIZE / 2 { - return Err(Error::msg("Plain text of invalid lenght for a login")); - } - let mut key_half = [0u8; SECRET_KEY_SIZE / 2]; - key_half.copy_from_slice(&plain); - Ok(HalfKey(key_half)) - } - - fn decrypt_thirty_two(&self) -> Result<[u8; THIRTYTWO]> { - let cipher = Aes256Gcm::new(&self.aes_key.into()); - let plain = cipher - .decrypt(&self.nonce.into(), &*self.cipher_text) - .map_err(|e| Error::msg(format!("{}", e)))?; - if plain.len() != THIRTYTWO { - return Err(Error::msg("Plain text of invalid length, should be 32")); - } - let mut thirty_two = [0u8; THIRTYTWO]; - thirty_two.copy_from_slice(&plain); - Ok(thirty_two) - } -} - -pub struct Aes256Encryption { - pub purpose: Purpose, - plaintext: Vec, - aes_key: [u8; 32], - nonce: [u8; 12], - shared_secrets: HashMap>, -} - -impl Aes256Encryption { - pub fn new(purpose: Purpose, plaintext: Vec) -> Result { - let mut rng = thread_rng(); - let aes_key: [u8; 32] = Aes256Gcm::generate_key(&mut rng).into(); - let nonce: [u8; 12] = Aes256Gcm::generate_nonce(&mut rng).into(); - Self::import_key(purpose, plaintext, aes_key, nonce) - } - - pub fn set_shared_secret( - &mut self, - shared_secrets: HashMap>, - ) { - self.shared_secrets = shared_secrets; - } - - pub fn encrypt_keys_with_shared_secrets( - &self, - ) -> Result> { - let mut res = HashMap::new(); - let mut rng = thread_rng(); - - for (_, sp_address2shared_secret) in self.shared_secrets.iter() { - for (sp_address, shared_secret) in sp_address2shared_secret { - let cipher = Aes256Gcm::new_from_slice(shared_secret.as_ref()) - .map_err(|e| Error::msg(format!("{}", e)))?; - let nonce = Aes256Gcm::generate_nonce(&mut rng); - let encrypted_key = cipher - .encrypt(&nonce, self.aes_key.as_slice()) - .map_err(|e| Error::msg(format!("{}", e)))?; - - let mut ciphertext = Vec::::with_capacity(nonce.len() + encrypted_key.len()); - ciphertext.extend(nonce); - ciphertext.extend(encrypted_key); - - res.insert(sp_address.to_owned(), ciphertext); - } - } - Ok(res) - } - - pub fn import_key( - purpose: Purpose, - plaintext: Vec, - aes_key: [u8; 32], - nonce: [u8; 12], - ) -> Result { - if plaintext.len() == 0 { - return Err(Error::msg("Can't create encryption for an empty message")); - } - Ok(Self { - purpose, - plaintext, - aes_key, - nonce, - shared_secrets: HashMap::new(), - }) - } - - pub fn encrypt_with_aes_key(&self) -> Result { - match self.purpose { - Purpose::Login => self.encrypt_login(), - Purpose::ThirtyTwoBytes => self.encrypt_thirty_two(), - } - } - - fn encrypt_login(&self) -> Result { - let half_key: HalfKey = self.plaintext.clone().try_into()?; - let cipher = Aes256Gcm::new(&self.aes_key.into()); - let cipher_text = cipher - .encrypt(&self.nonce.into(), half_key.as_slice()) - .map_err(|e| Error::msg(format!("{}", e)))?; - let mut res = Vec::with_capacity(self.nonce.len() + cipher_text.len()); - res.extend_from_slice(&self.nonce); - res.extend_from_slice(&cipher_text); - Ok(res) - } - - fn encrypt_thirty_two(&self) -> Result { - if self.plaintext.len() != 32 { - return Err(Error::msg("Invalid length, should be 32")); - } - let mut thirty_two = [0u8; 32]; - thirty_two.copy_from_slice(&self.plaintext); - let cipher = Aes256Gcm::new(&self.aes_key.into()); - let cipher_text = cipher - .encrypt(&self.nonce.into(), thirty_two.as_slice()) - .map_err(|e| Error::msg(format!("{}", e)))?; - let mut res = Vec::with_capacity(self.nonce.len() + cipher_text.len()); - log::info!("{}", cipher_text.len()); - res.extend_from_slice(&self.nonce); - res.extend_from_slice(&cipher_text); - Ok(res) - } -} - -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use super::*; - - const ALICE_SP_ADDRESS: &str = "tsp1qqw3lqr6xravz9nf8ntazgwwl0fqv47kfjdxsnxs6eutavqfwyv5q6qk97mmyf6dtkdyzqlu2zv6h9j2ggclk7vn705q5u2phglpq7yw3dg5rwpdz"; - const BOB_SP_ADDRESS: &str = "tsp1qq2hlsgrj0gz8kcfkf9flqw5llz0u2vr04telqndku9mcqm6dl4fhvq60t8r78srrf56w9yr7w9e9dusc2wjqc30up6fjwnh9mw3e3veqegdmtf08"; - const TRANSACTION: &str = "4e6d03dec558e1b6624f813bf2da7cd8d8fb1c2296684c08cf38724dcfd8d10b"; - const ALICE_SHARED_SECRET: &str = - "ccf02d364c2641ca129a3fdf49de57b705896e233f7ba6d738991993ea7e2106"; - const BOB_SHARED_SECRET: &str = - "15ef3e377fb842e81de52dbaaea8ba30aeb051a81043ee19264afd27353da521"; - - #[test] - fn new_aes_empty_plaintext() { - let plaintext = Vec::new(); - let aes_enc = Aes256Encryption::new(Purpose::Login, plaintext); - - assert!(aes_enc.is_err()); - } - - #[test] - fn aes_encrypt_login_invalid_length() { - let plaintext = "example"; - let aes_enc_short = Aes256Encryption::new(Purpose::Login, plaintext.as_bytes().to_vec()); - - assert!(aes_enc_short.is_ok()); - - let cipher = aes_enc_short.unwrap().encrypt_with_aes_key(); - - assert!(cipher.is_err()); - - let plaintext = [1u8; 64]; - let aes_enc_long = Aes256Encryption::new(Purpose::Login, plaintext.to_vec()); - - assert!(aes_enc_long.is_ok()); - - let cipher = aes_enc_long.unwrap().encrypt_with_aes_key(); - - assert!(cipher.is_err()); - } - - #[test] - fn aes_encrypt_login() { - let plaintext = [1u8; HALFKEYSIZE]; - let aes_key = Aes256Gcm::generate_key(&mut thread_rng()); - let nonce = Aes256Gcm::generate_nonce(&mut thread_rng()); - let aes_enc = Aes256Encryption::import_key( - Purpose::Login, - plaintext.to_vec(), - aes_key.into(), - nonce.into(), - ); - - assert!(aes_enc.is_ok()); - - let cipher = aes_enc.unwrap().encrypt_with_aes_key(); - - assert!(cipher.is_ok()); - - let mut plain_key = [0u8; 32]; - plain_key.copy_from_slice(&aes_key.to_vec()); - - let aes_dec = - Aes256Decryption::new(Purpose::Login, cipher.unwrap(), plain_key.to_vec(), None); - - assert!(aes_dec.is_ok()); - } - - #[test] - fn aes_encrypt_key() { - let plaintext = [1u8; HALFKEYSIZE]; - let mut aes_enc = Aes256Encryption::new(Purpose::Login, plaintext.to_vec()).unwrap(); - - let mut shared_secrets: HashMap = HashMap::new(); - let mut sp_address2shared_secrets: HashMap = - HashMap::new(); - sp_address2shared_secrets.insert( - ALICE_SP_ADDRESS.try_into().unwrap(), - SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap(), - ); - shared_secrets.insert( - Txid::from_str(TRANSACTION).unwrap(), - sp_address2shared_secrets, - ); - - aes_enc.set_shared_secret(shared_secrets); - - let sp_address2encrypted_keys = aes_enc.encrypt_keys_with_shared_secrets(); - - assert!(sp_address2encrypted_keys.is_ok()); - - let encrypted_key = sp_address2encrypted_keys - .unwrap() - .get(&ALICE_SP_ADDRESS.try_into().unwrap()) - .cloned(); - - let ciphertext = aes_enc.encrypt_with_aes_key(); - - assert!(ciphertext.is_ok()); - - let aes_dec = Aes256Decryption::new( - Purpose::Login, - ciphertext.unwrap(), - encrypted_key.unwrap(), - Some(SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap()), - ); - - assert!(aes_dec.is_ok()); - - let retrieved_plain = aes_dec.unwrap().decrypt_with_key(); - - assert!(retrieved_plain.is_ok()); - - assert!(retrieved_plain.unwrap() == plaintext); - } - - #[test] - fn aes_encrypt_key_many() { - let plaintext = [1u8; THIRTYTWO]; - let mut aes_enc = - Aes256Encryption::new(Purpose::ThirtyTwoBytes, plaintext.to_vec()).unwrap(); - - let mut shared_secrets: HashMap = HashMap::new(); - let mut sp_address2shared_secrets: HashMap = - HashMap::new(); - sp_address2shared_secrets.insert( - ALICE_SP_ADDRESS.try_into().unwrap(), - SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap(), - ); - sp_address2shared_secrets.insert( - BOB_SP_ADDRESS.try_into().unwrap(), - SharedSecret::from_str(BOB_SHARED_SECRET).unwrap(), - ); - shared_secrets.insert( - Txid::from_str(TRANSACTION).unwrap(), - sp_address2shared_secrets, - ); - - aes_enc.set_shared_secret(shared_secrets); - - let mut sp_address2encrypted_keys = aes_enc.encrypt_keys_with_shared_secrets(); - - assert!(sp_address2encrypted_keys.is_ok()); - - // Alice - let encrypted_key = sp_address2encrypted_keys - .as_mut() - .unwrap() - .get(&ALICE_SP_ADDRESS.try_into().unwrap()) - .cloned(); - - let ciphertext = aes_enc.encrypt_with_aes_key(); - - let aes_dec = Aes256Decryption::new( - Purpose::ThirtyTwoBytes, - ciphertext.unwrap(), - encrypted_key.unwrap(), - Some(SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap()), - ); - - let retrieved_plain = aes_dec.unwrap().decrypt_with_key(); - - assert!(retrieved_plain.unwrap() == plaintext); - - // Bob - let encrypted_key = sp_address2encrypted_keys - .unwrap() - .get(&BOB_SP_ADDRESS.try_into().unwrap()) - .cloned(); - - let ciphertext = aes_enc.encrypt_with_aes_key(); - - let aes_dec = Aes256Decryption::new( - Purpose::ThirtyTwoBytes, - ciphertext.unwrap(), - encrypted_key.unwrap(), - Some(SharedSecret::from_str(BOB_SHARED_SECRET).unwrap()), - ); - - let retrieved_plain = aes_dec.unwrap().decrypt_with_key(); - - assert!(retrieved_plain.unwrap() == plaintext); - } -} diff --git a/crates/sp_client/src/api.rs b/crates/sp_client/src/api.rs index c28fd22..4c44949 100644 --- a/crates/sp_client/src/api.rs +++ b/crates/sp_client/src/api.rs @@ -173,19 +173,49 @@ pub fn get_processes() -> ApiResult { //instances of process let process1 = Process { - id: String::from("1"), + id: 1, + name: String::from("CREATE_ID"), version: String::from("1.0"), - gestionnaires: vec![member1.clone(), member2.clone()], + members: vec![member1.clone(), member2.clone()], + html: crate::process::HTML_CREATE_ID.to_owned(), + style: crate::process::CSS.to_owned(), + script: "".to_owned(), }; let process2 = Process { - id: String::from("2"), - version: String::from("2.0"), - gestionnaires: vec![member2.clone(), member3.clone()], + id: 2, + name: String::from("UPDATE_ID"), + version: String::from("1.0"), + members: vec![member1.clone(), member2.clone()], + html: crate::process::HTML_UPDATE_ID.to_owned(), + style: crate::process::CSS.to_owned(), + script: "".to_owned(), }; let process3 = Process { - id: String::from("3"), + id: 3, + name: String::from("RECOVER"), version: String::from("1.0"), - gestionnaires: vec![member3.clone(), member1.clone()], + members: vec![member1.clone(), member2.clone()], + html: crate::process::HTML_RECOVER.to_owned(), + style: crate::process::CSS.to_owned(), + script: "".to_owned(), + }; + let process4 = Process { + id: 4, + name: String::from("REVOKE_IMAGE"), + version: String::from("1.0"), + members: vec![member1.clone(), member2.clone()], + html: crate::process::HTML_REVOKE_IMAGE.to_owned(), + style: crate::process::CSS.to_owned(), + script: "".to_owned(), + }; + let process5 = Process { + id: 5, + name: String::from("REVOKE"), + version: String::from("1.0"), + members: vec![member1.clone(), member2.clone()], + html: crate::process::HTML_REVOKE.to_owned(), + style: crate::process::CSS.to_owned(), + script: "".to_owned(), }; // vec with the instances of processes @@ -193,6 +223,8 @@ pub fn get_processes() -> ApiResult { data_process.push(process1); data_process.push(process2); data_process.push(process3); + data_process.push(process4); + data_process.push(process5); Ok(get_process_return(data_process)) } diff --git a/crates/sp_client/src/crypto.rs b/crates/sp_client/src/crypto.rs index 0372315..59ab295 100644 --- a/crates/sp_client/src/crypto.rs +++ b/crates/sp_client/src/crypto.rs @@ -1,9 +1,447 @@ -use aes_gcm; +use std::collections::HashMap; -pub enum KeyType { - Aes256GcmIv96BitKey([u8;32]) +use anyhow::{Error, Result}; +use sp_backend::{ + bitcoin::{ + consensus::serde::hex, + hex::DisplayHex, + key::constants::SECRET_KEY_SIZE, + secp256k1::{ecdh::SharedSecret, SecretKey}, + Txid, + }, + silentpayments::sending::SilentPaymentAddress, +}; +use wasm_bindgen::JsValue; + +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; + +use aes::cipher::generic_array::GenericArray; +use aes::{ + cipher::consts::{U32, U8}, + Aes256, +}; +use aes_gcm::{ + aead::{Aead, AeadInPlace, KeyInit, Nonce}, + AeadCore, Aes256Gcm, AesGcm, Key, TagSize, +}; +use rand::{thread_rng, RngCore}; + +const HALFKEYSIZE: usize = SECRET_KEY_SIZE / 2; + +const THIRTYTWO: usize = 32; + +pub struct HalfKey([u8; HALFKEYSIZE]); + +impl TryFrom> for HalfKey { + type Error = anyhow::Error; + fn try_from(value: Vec) -> std::prelude::v1::Result { + if value.len() == HALFKEYSIZE { + let mut buf = [0u8; HALFKEYSIZE]; + buf.copy_from_slice(&value); + Ok(HalfKey(buf)) + } else { + Err(Error::msg("Invalid length for HalfKey")) + } + } } -pub struct EncryptionKey { - sk: KeyType +impl HalfKey { + pub fn as_slice(&self) -> &[u8] { + &self.0 + } + + pub fn to_inner(&self) -> Vec { + self.0.to_vec() + } +} + +pub enum Purpose { + Login, + ThirtyTwoBytes, +} + +pub type CipherText = Vec; + +pub type EncryptedKey = Vec; + +pub struct Aes256Decryption { + pub purpose: Purpose, + cipher_text: CipherText, + aes_key: [u8; 32], + nonce: [u8; 12], +} + +impl Aes256Decryption { + pub fn new( + purpose: Purpose, + cipher_text: CipherText, + encrypted_aes_key: Vec, // If shared_secret is none this is actually the aes_key + shared_secret: Option, // We don't need that for certain purpose, like Login + ) -> Result { + let mut aes_key = [0u8; 32]; + if let Some(shared_secret) = shared_secret { + if encrypted_aes_key.len() <= 12 { + return Err(Error::msg("encrypted_aes_key is shorter than nonce length")); + } // Actually we could probably test that if the remnant is not a multiple of 32, something's wrong + // take the first 12 bytes form encrypted_aes_key as nonce + let (decrypt_key_nonce, encrypted_key) = encrypted_aes_key.split_at(12); + // decrypt key with shared_secret obtained from transaction + let decrypt_key_cipher = Aes256Gcm::new_from_slice(shared_secret.as_ref()) + .map_err(|e| Error::msg(format!("{}", e)))?; + let aes_key_plain = decrypt_key_cipher + .decrypt(decrypt_key_nonce.into(), encrypted_key) + .map_err(|e| Error::msg(format!("{}", e)))?; + if aes_key_plain.len() != 32 { + return Err(Error::msg("Invalid length for decrypted key")); + } + aes_key.copy_from_slice(&aes_key_plain); + } else { + if encrypted_aes_key.len() != 32 { + return Err(Error::msg("Invalid length for decrypted key")); + } + aes_key.copy_from_slice(&encrypted_aes_key); + } + if cipher_text.len() <= 12 { + return Err(Error::msg("cipher_text is shorter than nonce length")); + } + let (message_nonce, message_cipher) = cipher_text.split_at(12); + let mut nonce = [0u8; 12]; + nonce.copy_from_slice(message_nonce); + Ok(Self { + purpose, + cipher_text: message_cipher.to_vec(), + aes_key, + nonce, + }) + } + + pub fn decrypt_with_key(&self) -> Result> { + match self.purpose { + Purpose::Login => { + let half_key = self.decrypt_login()?; + Ok(half_key.to_inner()) + } + Purpose::ThirtyTwoBytes => { + let thirty_two_buf = self.decrypt_thirty_two()?; + Ok(thirty_two_buf.to_vec()) + } + } + } + + fn decrypt_login(&self) -> Result { + let cipher = Aes256Gcm::new(&self.aes_key.into()); + let plain = cipher + .decrypt(&self.nonce.into(), &*self.cipher_text) + .map_err(|e| Error::msg(format!("{}", e)))?; + if plain.len() != SECRET_KEY_SIZE / 2 { + return Err(Error::msg("Plain text of invalid lenght for a login")); + } + let mut key_half = [0u8; SECRET_KEY_SIZE / 2]; + key_half.copy_from_slice(&plain); + Ok(HalfKey(key_half)) + } + + fn decrypt_thirty_two(&self) -> Result<[u8; THIRTYTWO]> { + let cipher = Aes256Gcm::new(&self.aes_key.into()); + let plain = cipher + .decrypt(&self.nonce.into(), &*self.cipher_text) + .map_err(|e| Error::msg(format!("{}", e)))?; + if plain.len() != THIRTYTWO { + return Err(Error::msg("Plain text of invalid length, should be 32")); + } + let mut thirty_two = [0u8; THIRTYTWO]; + thirty_two.copy_from_slice(&plain); + Ok(thirty_two) + } +} + +pub struct Aes256Encryption { + pub purpose: Purpose, + plaintext: Vec, + aes_key: [u8; 32], + nonce: [u8; 12], + shared_secrets: HashMap>, +} + +impl Aes256Encryption { + pub fn new(purpose: Purpose, plaintext: Vec) -> Result { + let mut rng = thread_rng(); + let aes_key: [u8; 32] = Aes256Gcm::generate_key(&mut rng).into(); + let nonce: [u8; 12] = Aes256Gcm::generate_nonce(&mut rng).into(); + Self::import_key(purpose, plaintext, aes_key, nonce) + } + + pub fn set_shared_secret( + &mut self, + shared_secrets: HashMap>, + ) { + self.shared_secrets = shared_secrets; + } + + pub fn encrypt_keys_with_shared_secrets( + &self, + ) -> Result> { + let mut res = HashMap::new(); + let mut rng = thread_rng(); + + for (_, sp_address2shared_secret) in self.shared_secrets.iter() { + for (sp_address, shared_secret) in sp_address2shared_secret { + let cipher = Aes256Gcm::new_from_slice(shared_secret.as_ref()) + .map_err(|e| Error::msg(format!("{}", e)))?; + let nonce = Aes256Gcm::generate_nonce(&mut rng); + let encrypted_key = cipher + .encrypt(&nonce, self.aes_key.as_slice()) + .map_err(|e| Error::msg(format!("{}", e)))?; + + let mut ciphertext = Vec::::with_capacity(nonce.len() + encrypted_key.len()); + ciphertext.extend(nonce); + ciphertext.extend(encrypted_key); + + res.insert(sp_address.to_owned(), ciphertext); + } + } + Ok(res) + } + + pub fn import_key( + purpose: Purpose, + plaintext: Vec, + aes_key: [u8; 32], + nonce: [u8; 12], + ) -> Result { + if plaintext.len() == 0 { + return Err(Error::msg("Can't create encryption for an empty message")); + } + Ok(Self { + purpose, + plaintext, + aes_key, + nonce, + shared_secrets: HashMap::new(), + }) + } + + pub fn encrypt_with_aes_key(&self) -> Result { + match self.purpose { + Purpose::Login => self.encrypt_login(), + Purpose::ThirtyTwoBytes => self.encrypt_thirty_two(), + } + } + + fn encrypt_login(&self) -> Result { + let half_key: HalfKey = self.plaintext.clone().try_into()?; + let cipher = Aes256Gcm::new(&self.aes_key.into()); + let cipher_text = cipher + .encrypt(&self.nonce.into(), half_key.as_slice()) + .map_err(|e| Error::msg(format!("{}", e)))?; + let mut res = Vec::with_capacity(self.nonce.len() + cipher_text.len()); + res.extend_from_slice(&self.nonce); + res.extend_from_slice(&cipher_text); + Ok(res) + } + + fn encrypt_thirty_two(&self) -> Result { + if self.plaintext.len() != 32 { + return Err(Error::msg("Invalid length, should be 32")); + } + let mut thirty_two = [0u8; 32]; + thirty_two.copy_from_slice(&self.plaintext); + let cipher = Aes256Gcm::new(&self.aes_key.into()); + let cipher_text = cipher + .encrypt(&self.nonce.into(), thirty_two.as_slice()) + .map_err(|e| Error::msg(format!("{}", e)))?; + let mut res = Vec::with_capacity(self.nonce.len() + cipher_text.len()); + log::info!("{}", cipher_text.len()); + res.extend_from_slice(&self.nonce); + res.extend_from_slice(&cipher_text); + Ok(res) + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use super::*; + + const ALICE_SP_ADDRESS: &str = "tsp1qqw3lqr6xravz9nf8ntazgwwl0fqv47kfjdxsnxs6eutavqfwyv5q6qk97mmyf6dtkdyzqlu2zv6h9j2ggclk7vn705q5u2phglpq7yw3dg5rwpdz"; + const BOB_SP_ADDRESS: &str = "tsp1qq2hlsgrj0gz8kcfkf9flqw5llz0u2vr04telqndku9mcqm6dl4fhvq60t8r78srrf56w9yr7w9e9dusc2wjqc30up6fjwnh9mw3e3veqegdmtf08"; + const TRANSACTION: &str = "4e6d03dec558e1b6624f813bf2da7cd8d8fb1c2296684c08cf38724dcfd8d10b"; + const ALICE_SHARED_SECRET: &str = + "ccf02d364c2641ca129a3fdf49de57b705896e233f7ba6d738991993ea7e2106"; + const BOB_SHARED_SECRET: &str = + "15ef3e377fb842e81de52dbaaea8ba30aeb051a81043ee19264afd27353da521"; + + #[test] + fn new_aes_empty_plaintext() { + let plaintext = Vec::new(); + let aes_enc = Aes256Encryption::new(Purpose::Login, plaintext); + + assert!(aes_enc.is_err()); + } + + #[test] + fn aes_encrypt_login_invalid_length() { + let plaintext = "example"; + let aes_enc_short = Aes256Encryption::new(Purpose::Login, plaintext.as_bytes().to_vec()); + + assert!(aes_enc_short.is_ok()); + + let cipher = aes_enc_short.unwrap().encrypt_with_aes_key(); + + assert!(cipher.is_err()); + + let plaintext = [1u8; 64]; + let aes_enc_long = Aes256Encryption::new(Purpose::Login, plaintext.to_vec()); + + assert!(aes_enc_long.is_ok()); + + let cipher = aes_enc_long.unwrap().encrypt_with_aes_key(); + + assert!(cipher.is_err()); + } + + #[test] + fn aes_encrypt_login() { + let plaintext = [1u8; HALFKEYSIZE]; + let aes_key = Aes256Gcm::generate_key(&mut thread_rng()); + let nonce = Aes256Gcm::generate_nonce(&mut thread_rng()); + let aes_enc = Aes256Encryption::import_key( + Purpose::Login, + plaintext.to_vec(), + aes_key.into(), + nonce.into(), + ); + + assert!(aes_enc.is_ok()); + + let cipher = aes_enc.unwrap().encrypt_with_aes_key(); + + assert!(cipher.is_ok()); + + let mut plain_key = [0u8; 32]; + plain_key.copy_from_slice(&aes_key.to_vec()); + + let aes_dec = + Aes256Decryption::new(Purpose::Login, cipher.unwrap(), plain_key.to_vec(), None); + + assert!(aes_dec.is_ok()); + } + + #[test] + fn aes_encrypt_key() { + let plaintext = [1u8; HALFKEYSIZE]; + let mut aes_enc = Aes256Encryption::new(Purpose::Login, plaintext.to_vec()).unwrap(); + + let mut shared_secrets: HashMap = HashMap::new(); + let mut sp_address2shared_secrets: HashMap = + HashMap::new(); + sp_address2shared_secrets.insert( + ALICE_SP_ADDRESS.try_into().unwrap(), + SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap(), + ); + shared_secrets.insert( + Txid::from_str(TRANSACTION).unwrap(), + sp_address2shared_secrets, + ); + + aes_enc.set_shared_secret(shared_secrets); + + let sp_address2encrypted_keys = aes_enc.encrypt_keys_with_shared_secrets(); + + assert!(sp_address2encrypted_keys.is_ok()); + + let encrypted_key = sp_address2encrypted_keys + .unwrap() + .get(&ALICE_SP_ADDRESS.try_into().unwrap()) + .cloned(); + + let ciphertext = aes_enc.encrypt_with_aes_key(); + + assert!(ciphertext.is_ok()); + + let aes_dec = Aes256Decryption::new( + Purpose::Login, + ciphertext.unwrap(), + encrypted_key.unwrap(), + Some(SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap()), + ); + + assert!(aes_dec.is_ok()); + + let retrieved_plain = aes_dec.unwrap().decrypt_with_key(); + + assert!(retrieved_plain.is_ok()); + + assert!(retrieved_plain.unwrap() == plaintext); + } + + #[test] + fn aes_encrypt_key_many() { + let plaintext = [1u8; THIRTYTWO]; + let mut aes_enc = + Aes256Encryption::new(Purpose::ThirtyTwoBytes, plaintext.to_vec()).unwrap(); + + let mut shared_secrets: HashMap = HashMap::new(); + let mut sp_address2shared_secrets: HashMap = + HashMap::new(); + sp_address2shared_secrets.insert( + ALICE_SP_ADDRESS.try_into().unwrap(), + SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap(), + ); + sp_address2shared_secrets.insert( + BOB_SP_ADDRESS.try_into().unwrap(), + SharedSecret::from_str(BOB_SHARED_SECRET).unwrap(), + ); + shared_secrets.insert( + Txid::from_str(TRANSACTION).unwrap(), + sp_address2shared_secrets, + ); + + aes_enc.set_shared_secret(shared_secrets); + + let mut sp_address2encrypted_keys = aes_enc.encrypt_keys_with_shared_secrets(); + + assert!(sp_address2encrypted_keys.is_ok()); + + // Alice + let encrypted_key = sp_address2encrypted_keys + .as_mut() + .unwrap() + .get(&ALICE_SP_ADDRESS.try_into().unwrap()) + .cloned(); + + let ciphertext = aes_enc.encrypt_with_aes_key(); + + let aes_dec = Aes256Decryption::new( + Purpose::ThirtyTwoBytes, + ciphertext.unwrap(), + encrypted_key.unwrap(), + Some(SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap()), + ); + + let retrieved_plain = aes_dec.unwrap().decrypt_with_key(); + + assert!(retrieved_plain.unwrap() == plaintext); + + // Bob + let encrypted_key = sp_address2encrypted_keys + .unwrap() + .get(&BOB_SP_ADDRESS.try_into().unwrap()) + .cloned(); + + let ciphertext = aes_enc.encrypt_with_aes_key(); + + let aes_dec = Aes256Decryption::new( + Purpose::ThirtyTwoBytes, + ciphertext.unwrap(), + encrypted_key.unwrap(), + Some(SharedSecret::from_str(BOB_SHARED_SECRET).unwrap()), + ); + + let retrieved_plain = aes_dec.unwrap().decrypt_with_key(); + + assert!(retrieved_plain.unwrap() == plaintext); + } } diff --git a/crates/sp_client/src/injecteurhtml.rs b/crates/sp_client/src/injecteurhtml.rs deleted file mode 100644 index 1d18e90..0000000 --- a/crates/sp_client/src/injecteurhtml.rs +++ /dev/null @@ -1,105 +0,0 @@ -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -pub fn inject_html_create_id() -> String { - String::from( - " -
-
-

Create an Id

- -
-
- -
- -
-
- -
- Recover -
-
-

-
-
- ", - ) -} - -#[wasm_bindgen] -pub fn inject_html_recover() -> String { - String::from(" -
-
-

Recover my Id

- -
-
- - - -
-
- - -

- Revoke -

-
-
- ") -} - -#[wasm_bindgen] -pub fn inject_html_revokeimage() -> String { - String::from(" -
-
-

Revoke image

- -
-
-
- - - - - -
- -
-
- ") -} - -#[wasm_bindgen] -pub fn inject_html_revoke() -> String { - String::from( - " -
-
-

Revoke an Id

-
- Recover -
-
-
- - -
-
- - -
-
- -
-
- ", - ) -} diff --git a/crates/sp_client/src/lib.rs b/crates/sp_client/src/lib.rs index bceda87..9898215 100644 --- a/crates/sp_client/src/lib.rs +++ b/crates/sp_client/src/lib.rs @@ -1,8 +1,7 @@ #![allow(warnings)] mod Prd_list; -mod aesgcm; pub mod api; -mod injecteurhtml; +mod crypto; mod peers; mod process; mod user; diff --git a/crates/sp_client/src/process.rs b/crates/sp_client/src/process.rs index 4f384e8..3338cf6 100644 --- a/crates/sp_client/src/process.rs +++ b/crates/sp_client/src/process.rs @@ -3,6 +3,321 @@ use serde_json::{json, Value}; use tsify::Tsify; use wasm_bindgen::prelude::*; +pub const HTML_CREATE_ID: &str = " +
+
+

Create an Id

+ +
+
+ +
+ +
+
+ +
+ Recover +
+
+

+
+
+ "; + +pub const HTML_UPDATE_ID: &str = " + +
+
+

Update an Id

+
+
+
+ + + + + + + + + + + + + +
+
+ + +
+
+
+ +
+ +
+
+ + "; + +pub const HTML_RECOVER: &str = " +
+
+

Recover my Id

+ +
+
+ + + +
+
+ + +

+ Revoke +

+
+
+ "; + +pub const HTML_REVOKE_IMAGE: &str = " +
+
+

Revoke image

+ +
+
+
+ + + + + +
+ +
+
+ "; + +pub const HTML_REVOKE: &str = " +
+
+

Revoke an Id

+
+ Recover +
+
+
+ + +
+
+ + +
+
+ +
+
+ "; + +pub const CSS: &str = " + body { + margin: 0; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + min-height: 100vh; + background-color: #f4f4f4; + font-family: 'Arial', sans-serif; + } + .container { + text-align: center; + } + .card { + max-width: 400px; + width: 100%; + padding: 20px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + background-color: #ffffff; + border-radius: 8px; + text-align: left; + overflow: hidden; + } + form { + display: flex; + flex-direction: column; + /* flex-wrap: wrap; */ + } + label { + font-weight: bold; + margin-bottom: 8px; + } + hr { + border: 0; + height: 1px; + background-color: #ddd; + margin: 10px 0; + } + input, select { + width: 100%; + padding: 10px; + margin: 8px 0; + box-sizing: border-box; + } + select { + padding: 10px; + background-color: #f9f9f9; + border: 1px solid #ddd; + border-radius: 4px; + cursor: pointer; + } + button { + display: inline-block; + background-color: #4caf50; + color: #fff; + border: none; + padding: 12px 17px; + border-radius: 4px; + cursor: pointer; + } + button:hover { + background-color: #45a049; + } + .side-by-side { + display: flex; + align-items: center; + justify-content: space-between; + } + .side-by-side>* { + display: inline-block; + } + button.recover { + display: inline-block; + text-align: center; + text-decoration: none; + display: inline-block; + background-color: #4caf50; + color: #fff; + border: none; + padding: 12px 17px; + border-radius: 4px; + cursor: pointer; + } + button.recover:hover { + background-color: #45a049; + } + a.btn { + display: inline-block; + text-align: center; + text-decoration: none; + display: inline-block; + background-color: #4caf50; + color: #fff; + border: none; + padding: 12px 17px; + border-radius: 4px; + cursor: pointer; + } + + a.btn:hover { + background-color: #45a049; + } + + a { + text-decoration: none; + color: #78a6de; + } + .bg-secondary { + background-color: #2b81ed; + } + .bg-primary { + background-color: #1A61ED; + } + .bg-primary:hover { + background-color: #457be8; + } + .card-revoke { + display: flex; + flex-direction: column; + max-width: 400px; + width: 100%; + padding: 20px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + background-color: #ffffff; + border-radius: 8px; + text-align: center; + align-items: center; + overflow: hidden; + } + .card-revoke a { + max-width: 50px; + width: 100%; + background: none; + border: none; + cursor: pointer; + } + .card-revoke button { + max-width: 200px; + width: 100%; + background: none; + border: none; + cursor: pointer; + color: #78a6de; + } + .card-revoke svg { + width: 100%; + height: auto; + fill: #333; + } + .image-label { + display: block; + color: #fff; + padding: 5px; + margin-top: 10px; + } + .image-container { + width: 400px; + height: 300px; + overflow: hidden; + } + .image-container img { + text-align: center; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center center; + } + .passwordalert { + color: #FF0000; + } +"; + // process member (gestionnaire for now) #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub enum Role { @@ -10,6 +325,7 @@ pub enum Role { #[default] User, } + #[derive(Debug, Serialize, Deserialize, Default, Tsify, Clone)] #[tsify(into_wasm_abi)] pub struct ItemMember { @@ -31,8 +347,12 @@ impl ItemMember { #[derive(Debug, Serialize, Deserialize, Default, Tsify)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Process { - pub id: String, + pub id: u32, + pub name: String, pub version: String, - pub gestionnaires: Vec, + pub members: Vec, + pub html: String, + pub style: String, + pub script: String, //item_name : String, } diff --git a/crates/sp_client/src/user.rs b/crates/sp_client/src/user.rs index 8058cdd..70835e2 100644 --- a/crates/sp_client/src/user.rs +++ b/crates/sp_client/src/user.rs @@ -18,7 +18,7 @@ use wasm_bindgen::prelude::*; use shamir::SecretData; use std::collections::HashMap; use std::fs::File; -use std::io::{Read, Write, Cursor}; +use std::io::{Cursor, Read, Write}; use std::str::FromStr; use std::sync::{Mutex, OnceLock}; @@ -28,9 +28,7 @@ use sp_backend::silentpayments::sending::SilentPaymentAddress; use sp_backend::spclient::SpendKey; use sp_backend::spclient::{OutputList, SpClient}; -use crate::aesgcm::Aes256Decryption; -use crate::aesgcm::HalfKey; -use crate::aesgcm::{Aes256Encryption, Purpose}; +use crate::crypto::{Aes256Decryption, Aes256Encryption, HalfKey, Purpose}; use crate::peers::Peer; use crate::user; @@ -200,7 +198,7 @@ impl User { let mut entropy1 = [0u8; 32]; let mut entropy2 = [0u8; 32]; let mut entropy3 = [0u8; 32]; - let mut cipher_scan_key = [0u8; 60]; + let mut cipher_scan_key = [0u8; 60]; // cipher length == plain.len() + 16 + nonce.len() let mut part1_ciphertext = [0u8; 44]; let mut reader = Cursor::new(recover_data); diff --git a/src/database.ts b/src/database.ts index 9d7245a..7413e88 100644 --- a/src/database.ts +++ b/src/database.ts @@ -38,8 +38,14 @@ class Database { }, AnkProcess: { name: "process", - options: {}, - indices: [] + options: {'keyPath': 'id'}, + indices: [{ + name: 'by_name', + keyPath: 'name', + options: { + 'unique': true + } + }] } } @@ -101,7 +107,7 @@ class Database { return objectList; } - public writeObject(db: IDBDatabase, storeName: string, obj: any, key: string | null): Promise { + public writeObject(db: IDBDatabase, storeName: string, obj: any, key: IDBValidKey | null): Promise { return new Promise((resolve, reject) => { const transaction = db.transaction(storeName, 'readwrite'); const store = transaction.objectStore(storeName); @@ -117,7 +123,7 @@ class Database { }); } - public getObject(db: IDBDatabase, storeName: string, key: string | number): Promise { + public getObject(db: IDBDatabase, storeName: string, key: IDBValidKey): Promise { return new Promise((resolve, reject) => { const transaction = db.transaction(storeName, 'readonly'); const store = transaction.objectStore(storeName); @@ -128,6 +134,25 @@ class Database { }); } + public getFirstMatchWithIndex(db: IDBDatabase, storeName: string, indexName: string, lookup: string): Promise { + return new Promise((resolve, reject) => { + const transaction = db.transaction(storeName, 'readonly'); + const store = transaction.objectStore(storeName); + const index = store.index(indexName); + const request = index.openCursor(IDBKeyRange.only(lookup)); + + request.onerror = () => reject(request.error); + request.onsuccess = () => { + const cursor = request.result; + if (cursor) { + resolve(cursor.value); + } else { + resolve(null) + } + } + }); + } + public setObject(db: IDBDatabase, storeName: string, obj: any, key: string | null): Promise { return new Promise((resolve, reject) => { const transaction = db.transaction(storeName, 'readwrite'); diff --git a/src/index.ts b/src/index.ts index ee9560d..4327e11 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import IndexedDB from './database' document.addEventListener('DOMContentLoaded', async () => { try { const services = await Services.getInstance(); + if ((await services.isNewUser())) { await services.displayCreateId(); } diff --git a/src/services.ts b/src/services.ts index 59f7215..a44ca3b 100644 --- a/src/services.ts +++ b/src/services.ts @@ -1,6 +1,5 @@ -import { createUserReturn, User } from '../dist/pkg/sdk_client'; +import { createUserReturn, User, Process } from '../dist/pkg/sdk_client'; import IndexedDB from './database' -import Processstore from './store/processstore'; class Services { private static instance: Services; @@ -60,14 +59,11 @@ class Services { } public async displayCreateId(): Promise { - Services.instance.injectHtml(Services.instance.get_html_create_id()); - Services.instance.attachSubmitListener("form4nk", (event) => Services.instance.createId(event)); - Services.instance.attachClickListener("displayrecover", Services.instance.displayRecover); - Services.instance.displayProcess(await Services.instance.getAllProcess()); - } - - public get_html_create_id(): string { - return this.sdkClient.inject_html_create_id(); + const services = await Services.getInstance(); + await services.injectHtml('CREATE_ID'); + services.attachSubmitListener("form4nk", (event) => services.createId(event)); + services.attachClickListener("displayrecover", services.displayRecover); + services.displayProcess(); } public async createId(event: Event): Promise { @@ -88,15 +84,14 @@ class Services { // if (!Services.instance.isPasswordValid(password)) return; let label = null; - let birthday = 50000; - const user: createUserReturn = this.sdkClient.create_user(password, label, birthday, this.current_process); + let birthday_signet = 50000; + let birthday_main = 500000; + const user: createUserReturn = this.sdkClient.create_user(password, label, birthday_main, birthday_signet, this.current_process); try { const indexedDb = await IndexedDB.getInstance(); const db = indexedDb.getDb(); await indexedDb.writeObject(db, indexedDb.getStoreList().AnkUser, user.user, null); - // console.log("JS User added"); - await indexedDb.writeObject(db, indexedDb.getStoreList().SpOutputs, user.output_list_vec, null); } catch (error) { console.error("Failed to write user object :", error); @@ -106,16 +101,13 @@ class Services { } public async displayRecover(): Promise { - Services.instance.injectHtml(Services.instance.get_html_recover()); - Services.instance.attachSubmitListener("form4nk", Services.instance.recover); - Services.instance.attachClickListener("displaycreateid", Services.instance.displayCreateId); - Services.instance.attachClickListener("displayrevoke", Services.instance.displayRevoke); - Services.instance.attachClickListener("submitButtonRevoke", Services.instance.revoke); - Services.instance.displayProcess(await Services.instance.getAllUserProcess()); - } - - public get_html_recover(): string { - return this.sdkClient.inject_html_recover(); + const services = await Services.getInstance(); + services.injectHtml('RECOVER'); + services.attachSubmitListener("form4nk", services.recover); + services.attachClickListener("displaycreateid", services.displayCreateId); + services.attachClickListener("displayrevoke", services.displayRevoke); + services.attachClickListener("submitButtonRevoke", services.revoke); + services.displayProcess(); } public async recover(event: Event) { @@ -141,8 +133,7 @@ class Services { } public async displayRevokeImage(): Promise { - const html = Services.instance.get_html_revokeimage(); - Services.instance.injectHtml(html); + Services.instance.injectHtml('REVOKE_IMAGE'); Services.instance.attachClickListener("displayupdateanid", Services.instance.displayUpdateAnId); let imageBytes = await Services.instance.getRecoverImage('assets/4nk_revoke.jpg'); @@ -155,10 +146,6 @@ class Services { } } - public get_html_revokeimage(): string { - return this.sdkClient.inject_html_revokeimage(); - } - private async getRecoverImage(imageUrl:string): Promise { let imageBytes = null; try { @@ -175,16 +162,11 @@ class Services { } public async displayRevoke(): Promise { - const html = Services.instance.get_html_revoke(); - Services.instance.injectHtml(html); + Services.instance.injectHtml('REVOKE'); Services.instance.attachClickListener("displayrecover", Services.instance.displayRecover); Services.instance.attachSubmitListener("form4nk", Services.instance.revoke); } - public get_html_revoke(): string { - return this.sdkClient.inject_html_revoke(); - } - public async revoke(event: Event): Promise { event.preventDefault(); console.log("JS revoke click "); @@ -198,18 +180,14 @@ class Services { let style = ""; let script = ""; try { - const indexedDB = await IndexedDB.getInstance(); - const db = indexedDB.getDb(); - try { - let processObject = await indexedDB.getObject(db, indexedDB.getStoreList().AnkProcess, Services.instance.current_process!); + const processObject = await this.getProcessByName(Services.instance.current_process!); + if (processObject) { body = processObject.html; style = processObject.style; script = processObject.script; - } catch (error) { - console.log("JS Processstore not exist "); } } catch (error) { - console.error("Failed to retrieve user object :", error); + console.error("Failed to retrieve process with Error:", error); } Services.instance.injectUpdateAnIdHtml(body, style, script); @@ -253,76 +231,82 @@ class Services { // TODO Mock add user member to process } - public displayProcess(processList: string[]): void { - console.log("JS processList : "+processList); + public async displayProcess(): Promise { + const services = await Services.getInstance(); + const processList = await services.getAllProcess(); const selectProcess = document.getElementById("selectProcess"); if (selectProcess) { processList.forEach((process) => { - let child = new Option(process, process); + let child = new Option(process.name, process.name); if (!selectProcess.contains(child)) { selectProcess.appendChild(child); } }) } } - - public async getAllUserProcess(): Promise { - let userProcessList: string[] = []; + + public async getAllProcess(): Promise { try { const indexedDB = await IndexedDB.getInstance(); const db = indexedDB.getDb(); - let processListObject = await indexedDB.getAll(db, indexedDB.getStoreList().AnkProcess); + let processListObject = await indexedDB.getAll(db, indexedDB.getStoreList().AnkProcess); + return processListObject; + } catch (error) { + console.log('getAllProcess failed: ',error); + return []; + } + } + + public async getAllProcessForUser(pre_id: string): Promise { + const services = await Services.getInstance(); + let user: User; + let userProcessList: Process[] = []; + try { + const indexedDB = await IndexedDB.getInstance(); + const db = indexedDB.getDb(); + user = await indexedDB.getObject(db, indexedDB.getStoreList().AnkUser, pre_id); + } catch (error) { + console.error('getAllUserProcess failed: ',error); + return []; + } + + try { + const processListObject = await services.getAllProcess(); processListObject.forEach(async (processObject) => { - const listMember = processObject.listMember; - const processName = processObject.process; - listMember.forEach(async (member) => { - if (member == "user1") { - userProcessList.push(processName); - console.log("JS UserProcess found"); - } - }) + if (processObject.members.includes(user.pre_id)) { + userProcessList.push(processObject); + } }) } catch (error) { - console.log("JS Processstore not found"); + console.error('getAllUserProcess failed: ',error); + return []; } return userProcessList; } - public async getAllProcess(): Promise { - // if indexedDB is empty, get list from wasm - let processList: string[] = []; - try { - const indexedDB = await IndexedDB.getInstance(); - const db = indexedDB.getDb(); - let processListObject = await indexedDB.getAll(db, indexedDB.getStoreList().AnkProcess); - processListObject.forEach(async (processObject) => { - const processName = processObject.process; - processList.push(processName); - console.log("JS Processstore found"); - }) - } catch (error) { - console.log("JS Processstore not found"); - } - if (processList.length == 0) { - processList = await this.addProcessStore(); - } - return processList; + public async getProcessByName(name: string): Promise { + const indexedDB = await IndexedDB.getInstance(); + const db = indexedDB.getDb(); + const process = await indexedDB.getFirstMatchWithIndex(db, indexedDB.getStoreList().AnkProcess, 'by_name', name); + + return process; } - public async addProcessStore(): Promise { - const processList = this.sdkClient.get_process() - processList.forEach(async (process: string) => { - // TODO process mock - let processstore = new Processstore; - processstore.process = process; + public async loadProcesses(): Promise { + const services = await Services.getInstance(); + const processList: Process[] = services.sdkClient.get_processes(); + processList.forEach(async (process: Process) => { const indexedDB = await IndexedDB.getInstance(); const db = indexedDB.getDb(); - await indexedDB.writeObject(db, indexedDB.getStoreList().AnkProcess, processstore, process); - console.log("JS Processstore mock added"); + try { + if (await indexedDB.getObject(db, indexedDB.getStoreList().AnkProcess, process.id) === null) { + await indexedDB.writeObject(db, indexedDB.getStoreList().AnkProcess, process, null); + } + } catch (error) { + console.warn('Error while writing process', process.name, 'to indexedDB:', error); + } }) - return processList; } - public attachClickListener(elementId: string, callback: (event: Event) => void): void { const element = document.getElementById(elementId); @@ -336,15 +320,25 @@ class Services { element?.addEventListener("submit", callback); } - public injectHtml(html: string) { - console.log("JS html : "+html); + public async injectHtml(processName: string) { + // console.log("JS html : "+html); const container = document.getElementById('containerId'); if (!container) { console.error("No html container"); return; } - container.innerHTML = html; + + const services = await Services.getInstance(); + + await services.loadProcesses(); + + const process = await services.getProcessByName(processName); + if (process) { + container.innerHTML = process.html; + } else { + console.error("No process ", processName); + } } // public async getCurrentProcess(): Promise { diff --git a/src/store/processstore.ts b/src/store/processstore.ts deleted file mode 100644 index 76eb6b5..0000000 --- a/src/store/processstore.ts +++ /dev/null @@ -1,237 +0,0 @@ -class Processstore { - process: string; - html: string; - style: string; - script: string; - listMember: string[]; - createDate: Date; - - constructor() { - this.process = ""; - this.html = getMockHtml(); - this.style = getMockStyle(); - this.script = getMockScript(); - this.script = getMockScript(); - this.listMember = getMockListMember(); - this.createDate = new Date; - } -} - -export default Processstore; - -function getMockHtml(): string { - let html: string = ` - -
-
-

Update an Id

-
-
-
- - - - - - - - - - - - - -
-
- - -
-
-
- -
- -
-
- - `; - return html; -} - -function getMockStyle(): string { - let style: string = ` - `; - return style; -} - -function getMockScript(): string { - let script: string = ` - var addSpAddressBtn = document.getElementById('add-sp-address-btn'); - var removeSpAddressBtn = document.querySelectorAll('.minus-sp-address-btn'); - - addSpAddressBtn.addEventListener('click', function (event) { - addDynamicField(this); - }); - - function addDynamicField(element) { - var addSpAddressBlock = document.getElementById('sp-address-block'); - var spAddress = addSpAddressBlock.querySelector('#sp-address').value; - addSpAddressBlock.querySelector('#sp-address').value = ''; - spAddress = spAddress.trim(); - if (spAddress != '') { - var sideBySideDiv = document.createElement('div'); - sideBySideDiv.className = 'side-by-side'; - - var inputElement = document.createElement('input'); - inputElement.type = 'text'; - inputElement.name = 'spAddresses[]'; - inputElement.setAttribute('form', 'no-form'); - inputElement.value = spAddress; - inputElement.disabled = true; - - var buttonElement = document.createElement('button'); - buttonElement.type = 'button'; - buttonElement.className = - 'circle-btn bg-secondary minus-sp-address-btn'; - buttonElement.innerHTML = '-'; - - buttonElement.addEventListener('click', function (event) { - removeDynamicField(this.parentElement); - }); - - sideBySideDiv.appendChild(inputElement); - sideBySideDiv.appendChild(buttonElement); - - addSpAddressBlock.appendChild(sideBySideDiv); - } - function removeDynamicField(element) { - element.remove(); - } - } - `; - return script; -} - -function getMockListMember(): string[] { - return ["user1","user2","user3"]; -} \ No newline at end of file