Compare commits
2 Commits
v0.5.0
...
a32e36be7e
Author | SHA1 | Date | |
---|---|---|---|
a32e36be7e | |||
10231df860 |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "creddy",
|
"name": "creddy",
|
||||||
"version": "0.5.0",
|
"version": "0.5.2",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
67
src-tauri/Cargo.lock
generated
67
src-tauri/Cargo.lock
generated
@ -1071,7 +1071,7 @@ dependencies = [
|
|||||||
"cocoa-foundation",
|
"cocoa-foundation",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
"foreign-types",
|
"foreign-types 0.5.0",
|
||||||
"libc",
|
"libc",
|
||||||
"objc",
|
"objc",
|
||||||
]
|
]
|
||||||
@ -1146,7 +1146,7 @@ dependencies = [
|
|||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"core-graphics-types",
|
"core-graphics-types",
|
||||||
"foreign-types",
|
"foreign-types 0.5.0",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1196,7 +1196,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "creddy"
|
name = "creddy"
|
||||||
version = "0.4.9"
|
version = "0.5.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2",
|
"argon2",
|
||||||
"auto-launch",
|
"auto-launch",
|
||||||
@ -1211,13 +1211,17 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"is-terminal",
|
"is-terminal",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"openssl",
|
||||||
"rfd 0.13.0",
|
"rfd 0.13.0",
|
||||||
|
"rsa",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
"signature 2.2.0",
|
"signature 2.2.0",
|
||||||
"sodiumoxide",
|
"sodiumoxide",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"ssh-agent-lib",
|
"ssh-agent-lib",
|
||||||
|
"ssh-encoding",
|
||||||
"ssh-key",
|
"ssh-key",
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
@ -1841,6 +1845,15 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||||
|
dependencies = [
|
||||||
|
"foreign-types-shared 0.1.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types"
|
name = "foreign-types"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@ -1848,7 +1861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"foreign-types-macros",
|
"foreign-types-macros",
|
||||||
"foreign-types-shared",
|
"foreign-types-shared 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1862,6 +1875,12 @@ dependencies = [
|
|||||||
"syn 2.0.68",
|
"syn 2.0.68",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types-shared"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types-shared"
|
name = "foreign-types-shared"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@ -3426,12 +3445,50 @@ version = "0.3.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl"
|
||||||
|
version = "0.10.64"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"cfg-if",
|
||||||
|
"foreign-types 0.3.2",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"openssl-macros",
|
||||||
|
"openssl-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-macros"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.68",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-probe"
|
name = "openssl-probe"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-sys"
|
||||||
|
version = "0.9.102"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "option-ext"
|
name = "option-ext"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -4788,7 +4845,7 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
"foreign-types",
|
"foreign-types 0.5.0",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"objc2",
|
"objc2",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "creddy"
|
name = "creddy"
|
||||||
version = "0.5.0"
|
version = "0.5.2"
|
||||||
description = "A friendly AWS credentials manager"
|
description = "A friendly AWS credentials manager"
|
||||||
authors = ["Joseph Montanaro"]
|
authors = ["Joseph Montanaro"]
|
||||||
license = ""
|
license = ""
|
||||||
@ -58,6 +58,10 @@ tokio-stream = "0.1.15"
|
|||||||
sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio", "uuid"] }
|
sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio", "uuid"] }
|
||||||
tokio-util = { version = "0.7.11", features = ["codec"] }
|
tokio-util = { version = "0.7.11", features = ["codec"] }
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
|
openssl = "0.10.64"
|
||||||
|
rsa = "0.9.6"
|
||||||
|
sha2 = "0.10.8"
|
||||||
|
ssh-encoding = "0.2.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# by default Tauri runs in production mode
|
# by default Tauri runs in production mode
|
||||||
|
@ -53,6 +53,7 @@ pub fn run() -> tauri::Result<()> {
|
|||||||
ipc::delete_credential,
|
ipc::delete_credential,
|
||||||
ipc::list_credentials,
|
ipc::list_credentials,
|
||||||
ipc::sshkey_from_file,
|
ipc::sshkey_from_file,
|
||||||
|
ipc::sshkey_from_private_key,
|
||||||
ipc::get_config,
|
ipc::get_config,
|
||||||
ipc::save_config,
|
ipc::save_config,
|
||||||
ipc::launch_terminal,
|
ipc::launch_terminal,
|
||||||
|
@ -12,6 +12,8 @@ use serde::ser::{
|
|||||||
SerializeStruct,
|
SerializeStruct,
|
||||||
};
|
};
|
||||||
use serde::de::{self, Visitor};
|
use serde::de::{self, Visitor};
|
||||||
|
use sha2::{Sha256, Sha512};
|
||||||
|
use signature::{Signer, SignatureEncoding};
|
||||||
use sqlx::{
|
use sqlx::{
|
||||||
FromRow,
|
FromRow,
|
||||||
Sqlite,
|
Sqlite,
|
||||||
@ -19,11 +21,15 @@ use sqlx::{
|
|||||||
Transaction,
|
Transaction,
|
||||||
types::Uuid,
|
types::Uuid,
|
||||||
};
|
};
|
||||||
use ssh_agent_lib::proto::message::Identity;
|
use ssh_agent_lib::proto::message::{
|
||||||
|
Identity,
|
||||||
|
SignRequest,
|
||||||
|
};
|
||||||
|
use ssh_encoding::Encode;
|
||||||
use ssh_key::{
|
use ssh_key::{
|
||||||
Algorithm,
|
Algorithm,
|
||||||
LineEnding,
|
LineEnding,
|
||||||
private::PrivateKey,
|
private::{PrivateKey, KeypairData},
|
||||||
public::PublicKey,
|
public::PublicKey,
|
||||||
};
|
};
|
||||||
use tokio_stream::StreamExt;
|
use tokio_stream::StreamExt;
|
||||||
@ -74,6 +80,21 @@ impl SshKey {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_private_key(private_key: &str, passphrase: &str) -> Result<SshKey, LoadSshKeyError> {
|
||||||
|
let mut privkey = PrivateKey::from_openssh(private_key)?;
|
||||||
|
if privkey.is_encrypted() {
|
||||||
|
privkey = privkey.decrypt(passphrase)
|
||||||
|
.map_err(|_| LoadSshKeyError::InvalidPassphrase)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(SshKey {
|
||||||
|
algorithm: privkey.algorithm(),
|
||||||
|
comment: privkey.comment().into(),
|
||||||
|
public_key: privkey.public_key().clone(),
|
||||||
|
private_key: privkey,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn name_from_pubkey(pubkey: &[u8], pool: &SqlitePool) -> Result<String, LoadCredentialsError> {
|
pub async fn name_from_pubkey(pubkey: &[u8], pool: &SqlitePool) -> Result<String, LoadCredentialsError> {
|
||||||
let row = sqlx::query!(
|
let row = sqlx::query!(
|
||||||
"SELECT c.name
|
"SELECT c.name
|
||||||
@ -104,6 +125,33 @@ impl SshKey {
|
|||||||
|
|
||||||
Ok(identities)
|
Ok(identities)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sign_request(&self, req: &SignRequest) -> Result<Vec<u8>, HandlerError> {
|
||||||
|
let mut sig = Vec::new();
|
||||||
|
match self.private_key.key_data() {
|
||||||
|
KeypairData::Rsa(keypair) => {
|
||||||
|
// 2 is the flag value for `SSH_AGENT_RSA_SHA2_256`
|
||||||
|
if req.flags & 2 > 0 {
|
||||||
|
let signer = rsa::pkcs1v15::SigningKey::<Sha256>::try_from(keypair)?;
|
||||||
|
let sig_data = signer.try_sign(&req.data)?.to_vec();
|
||||||
|
"rsa-sha-256".encode(&mut sig)?;
|
||||||
|
sig_data.encode(&mut sig)?;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let signer = rsa::pkcs1v15::SigningKey::<Sha512>::try_from(keypair)?;
|
||||||
|
let sig_data = signer.try_sign(&req.data)?.to_vec();
|
||||||
|
"rsa-sha2-512".encode(&mut sig)?;
|
||||||
|
sig_data.encode(&mut sig)?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let sig_data = self.private_key.try_sign(&req.data)?;
|
||||||
|
self.algorithm.as_str().encode(&mut sig)?;
|
||||||
|
sig_data.as_bytes().encode(&mut sig)?;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ok(sig)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -195,6 +195,10 @@ pub enum HandlerError {
|
|||||||
SshAgent(#[from] ssh_agent_lib::error::AgentError),
|
SshAgent(#[from] ssh_agent_lib::error::AgentError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
SshKey(#[from] ssh_key::Error),
|
SshKey(#[from] ssh_key::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
Signature(#[from] signature::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
Encoding(#[from] ssh_encoding::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -142,6 +142,12 @@ pub async fn sshkey_from_file(path: &str, passphrase: &str) -> Result<SshKey, Lo
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn sshkey_from_private_key(private_key: &str, passphrase: &str) -> Result<SshKey, LoadSshKeyError> {
|
||||||
|
SshKey::from_private_key(private_key, passphrase)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_config(app_state: State<'_, AppState>) -> Result<AppConfig, ()> {
|
pub async fn get_config(app_state: State<'_, AppState>) -> Result<AppConfig, ()> {
|
||||||
let config = app_state.config.read().await;
|
let config = app_state.config.read().await;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use futures::SinkExt;
|
use futures::SinkExt;
|
||||||
use signature::Signer;
|
|
||||||
use ssh_agent_lib::agent::MessageCodec;
|
use ssh_agent_lib::agent::MessageCodec;
|
||||||
use ssh_agent_lib::proto::message::{
|
use ssh_agent_lib::proto::message::{
|
||||||
Message,
|
Message,
|
||||||
@ -36,12 +35,21 @@ async fn handle(
|
|||||||
adapter.send(resp).await?;
|
adapter.send(resp).await?;
|
||||||
},
|
},
|
||||||
Message::SignRequest(req) => {
|
Message::SignRequest(req) => {
|
||||||
// CloseWaiter could corrupt the framing, but this doesn't matter
|
// Note: If the client writes more data to the stream *while* at the
|
||||||
// since we don't plan to pull any more frames out of the stream
|
// same time waiting for a resopnse to a previous request, this will
|
||||||
|
// corrupt the framing. Clients don't seem to behave that way though?
|
||||||
let waiter = CloseWaiter { stream: adapter.get_mut() };
|
let waiter = CloseWaiter { stream: adapter.get_mut() };
|
||||||
let resp = sign_request(req, app_handle.clone(), client_pid, waiter).await?;
|
let resp = sign_request(req, app_handle.clone(), client_pid, waiter).await?;
|
||||||
|
|
||||||
|
// have to do this before we send since we can't inspect the message after
|
||||||
|
let is_failure = matches!(resp, Message::Failure);
|
||||||
adapter.send(resp).await?;
|
adapter.send(resp).await?;
|
||||||
break;
|
|
||||||
|
if is_failure {
|
||||||
|
// this way we don't get spammed with requests for other keys
|
||||||
|
// after denying the first
|
||||||
|
break
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_ => adapter.send(Message::Failure).await?,
|
_ => adapter.send(Message::Failure).await?,
|
||||||
};
|
};
|
||||||
@ -93,15 +101,8 @@ async fn sign_request(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let key = state.sshkey_by_name(&key_name).await?;
|
let key = state.sshkey_by_name(&key_name).await?;
|
||||||
let sig = Signer::sign(&key.private_key, &req.data);
|
let sig = key.sign_request(&req)?;
|
||||||
let key_type = key.algorithm.as_str().as_bytes();
|
Ok(Message::SignResponse(sig))
|
||||||
|
|
||||||
let payload_len = key_type.len() + sig.as_bytes().len() + 8;
|
|
||||||
let mut payload = Vec::with_capacity(payload_len);
|
|
||||||
encode_string(&mut payload, key.algorithm.as_str().as_bytes());
|
|
||||||
encode_string(&mut payload, sig.as_bytes());
|
|
||||||
|
|
||||||
Ok(Message::SignResponse(payload))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = proceed.await;
|
let res = proceed.await;
|
||||||
@ -112,10 +113,3 @@ async fn sign_request(
|
|||||||
lease.release();
|
lease.release();
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn encode_string(buf: &mut Vec<u8>, s: &[u8]) {
|
|
||||||
let len = s.len() as u32;
|
|
||||||
buf.extend(len.to_be_bytes());
|
|
||||||
buf.extend(s);
|
|
||||||
}
|
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"productName": "creddy",
|
"productName": "creddy",
|
||||||
"version": "0.5.0",
|
"version": "0.5.2",
|
||||||
"identifier": "creddy",
|
"identifier": "creddy",
|
||||||
"plugins": {},
|
"plugins": {},
|
||||||
"app": {
|
"app": {
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
|
|
||||||
// Extra 50ms so the window can finish disappearing before the redraw
|
// Extra 50ms so the window can finish disappearing before the redraw
|
||||||
const rehideDelay = Math.min(5000, $appState.config.rehide_ms + 50);
|
const rehideDelay = Math.min(5000, $appState.config.rehide_ms + 100);
|
||||||
|
|
||||||
let alert;
|
let alert;
|
||||||
let success = false;
|
let success = false;
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
async function saveCredential() {
|
async function saveCredential() {
|
||||||
await invoke('save_credential', {record: local});
|
await invoke('save_credential', {record: local});
|
||||||
dispatch('save', local);
|
dispatch('save', local);
|
||||||
showDetails = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyText(evt) {
|
async function copyText(evt) {
|
||||||
|
@ -13,20 +13,23 @@
|
|||||||
|
|
||||||
let name;
|
let name;
|
||||||
let file;
|
let file;
|
||||||
|
let privateKey = '';
|
||||||
let passphrase = '';
|
let passphrase = '';
|
||||||
let showDetails = true;
|
let showDetails = true;
|
||||||
|
let mode = 'file';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
let defaultPath = null;
|
let defaultPath = null;
|
||||||
homeDir().then(d => defaultPath = `${d}/.ssh`);
|
homeDir().then(d => defaultPath = `${d}/.ssh`);
|
||||||
|
|
||||||
|
|
||||||
let alert;
|
let alert;
|
||||||
let saving = false;
|
let saving = false;
|
||||||
async function saveCredential() {
|
async function saveCredential() {
|
||||||
saving = true;
|
saving = true;
|
||||||
try {
|
try {
|
||||||
let key = await invoke('sshkey_from_file', {path: file.path, passphrase});
|
let key = await getKey();
|
||||||
const payload = {
|
const payload = {
|
||||||
id: record.id,
|
id: record.id,
|
||||||
name,
|
name,
|
||||||
@ -41,9 +44,40 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getKey() {
|
||||||
|
if (mode === 'file') {
|
||||||
|
return await invoke('sshkey_from_file', {path: file.path, passphrase});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return await invoke('sshkey_from_private_key', {privateKey, passphrase});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<div role="tablist" class="join max-w-sm mx-auto flex justify-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
class="join-item flex-1 btn border border-primary hover:border-primary"
|
||||||
|
class:btn-primary={mode === 'file'}
|
||||||
|
on:click={() => mode = 'file'}
|
||||||
|
>
|
||||||
|
From file
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
class="join-item flex-1 btn border border-primary hover:border-primary"
|
||||||
|
class:btn-primary={mode === 'direct'}
|
||||||
|
on:click={() => mode = 'direct'}
|
||||||
|
>
|
||||||
|
From private key
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<form class="space-y-4" on:submit|preventDefault={alert.run(saveCredential)}>
|
<form class="space-y-4" on:submit|preventDefault={alert.run(saveCredential)}>
|
||||||
<ErrorAlert bind:this={alert} />
|
<ErrorAlert bind:this={alert} />
|
||||||
|
|
||||||
@ -55,15 +89,20 @@
|
|||||||
bind:value={name}
|
bind:value={name}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
{#if mode === 'file'}
|
||||||
<span class="justify-self-end">File</span>
|
<span class="justify-self-end">File</span>
|
||||||
<FileInput params={{defaultPath}} bind:value={file} on:update={() => name = file.name} />
|
<FileInput params={{defaultPath}} bind:value={file} on:update={() => name = file.name} />
|
||||||
|
{:else}
|
||||||
|
<span class="justify-self-end">Private key</span>
|
||||||
|
<textarea bind:value={privateKey} rows="5" class="textarea textarea-bordered bg-transparent font-mono whitespace-pre overflow-x-auto"></textarea>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<span class="justify-self-end">Passphrase</span>
|
<span class="justify-self-end">Passphrase</span>
|
||||||
<PassphraseInput class="bg-transparent" bind:value={passphrase} />
|
<PassphraseInput class="bg-transparent" bind:value={passphrase} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
{#if file?.path}
|
{#if file?.path || privateKey !== ''}
|
||||||
<button
|
<button
|
||||||
transition:fade={{duration: 100}}
|
transition:fade={{duration: 100}}
|
||||||
type="submit"
|
type="submit"
|
||||||
|
@ -64,7 +64,7 @@
|
|||||||
{#if record.isNew}
|
{#if record.isNew}
|
||||||
<NewSshKey {record} on:save on:save={handleSave} />
|
<NewSshKey {record} on:save on:save={handleSave} />
|
||||||
{:else}
|
{:else}
|
||||||
<EditSshKey bind:local={local} {isModified} on:save />
|
<EditSshKey bind:local={local} {isModified} on:save={handleSave} on:save />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
Reference in New Issue
Block a user