finish basic Docker credential helper implementation
This commit is contained in:
parent
479a0a96eb
commit
e0e758554c
@ -14,6 +14,14 @@ use crate::state::AppState;
|
||||
use crate::terminal;
|
||||
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum RequestAction {
|
||||
Access,
|
||||
Delete,
|
||||
Save,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AwsRequestNotification {
|
||||
pub client: Client,
|
||||
@ -31,6 +39,7 @@ pub struct SshRequestNotification {
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DockerRequestNotification {
|
||||
pub action: RequestAction,
|
||||
pub client: Client,
|
||||
pub server_url: String,
|
||||
}
|
||||
@ -53,8 +62,8 @@ impl RequestNotificationDetail {
|
||||
Self::Ssh(SshRequestNotification {client, key_name})
|
||||
}
|
||||
|
||||
pub fn new_docker(client: Client, server_url: String) -> Self {
|
||||
Self::Docker(DockerRequestNotification {client, server_url})
|
||||
pub fn new_docker(action: RequestAction, client: Client, server_url: String) -> Self {
|
||||
Self::Docker(DockerRequestNotification {action, client, server_url})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,11 @@ use crate::credentials::{
|
||||
DockerCredential,
|
||||
};
|
||||
use crate::errors::*;
|
||||
use crate::ipc::{Approval, RequestNotificationDetail};
|
||||
use crate::ipc::{
|
||||
Approval,
|
||||
RequestAction,
|
||||
RequestNotificationDetail
|
||||
};
|
||||
use crate::shortcuts::{self, ShortcutAction};
|
||||
use crate::state::AppState;
|
||||
use super::{
|
||||
@ -58,10 +62,10 @@ async fn handle(
|
||||
server_url, client, app_handle, waiter
|
||||
).await,
|
||||
CliRequest::StoreDockerCredential(docker_credential) => store_docker_credential(
|
||||
docker_credential, app_handle, waiter
|
||||
docker_credential, app_handle, client, waiter
|
||||
).await,
|
||||
CliRequest::EraseDockerCredential { server_url } => erase_docker_credential(
|
||||
server_url, app_handle, waiter
|
||||
server_url, app_handle, client, waiter
|
||||
).await,
|
||||
CliRequest::InvokeShortcut{ action } => invoke_shortcut(action).await,
|
||||
};
|
||||
@ -115,8 +119,8 @@ async fn get_docker_credential(
|
||||
waiter: CloseWaiter<'_>,
|
||||
) -> Result<CliResponse, HandlerError> {
|
||||
let state = app_handle.state::<AppState>();
|
||||
let credential_id = state.credential_id(&server_url).await.unwrap_or(None);
|
||||
if credential_id.is_none() {
|
||||
let meta = state.docker_credential_meta(&server_url).await.unwrap_or(None);
|
||||
if meta.is_none() {
|
||||
return Err(
|
||||
HandlerError::NoCredentials(
|
||||
GetCredentialsError::Load(
|
||||
@ -126,7 +130,11 @@ async fn get_docker_credential(
|
||||
);
|
||||
}
|
||||
|
||||
let detail = RequestNotificationDetail::new_docker(client, server_url.clone());
|
||||
let detail = RequestNotificationDetail::new_docker(
|
||||
RequestAction::Access,
|
||||
client,
|
||||
server_url.clone()
|
||||
);
|
||||
let response = super::send_credentials_request(detail, app_handle.clone(), waiter).await?;
|
||||
match response.approval {
|
||||
Approval::Approved => {
|
||||
@ -142,22 +150,45 @@ async fn get_docker_credential(
|
||||
async fn store_docker_credential(
|
||||
docker_credential: DockerCredential,
|
||||
app_handle: AppHandle,
|
||||
_waiter: CloseWaiter<'_>,
|
||||
client: Client,
|
||||
waiter: CloseWaiter<'_>,
|
||||
) -> Result<CliResponse, HandlerError> {
|
||||
let state = app_handle.state::<AppState>();
|
||||
|
||||
// eventually ask the frontend to confirm here
|
||||
// We want to do this before asking for confirmation from the user, because Docker has an annoying
|
||||
// habit of calling `get` and then immediately turning around and calling `store` with the same
|
||||
// data. In that case we want to avoid asking for confirmation at all.
|
||||
match state.get_docker_credential(&docker_credential.server_url).await {
|
||||
// if there is already a credential with this server_url, and it is unchanged, we're done
|
||||
Ok(c) if c == docker_credential => return Ok(CliResponse::Empty),
|
||||
// otherwise we are making an update, so proceed
|
||||
Ok(_) => (),
|
||||
// if the app is locked, then this isn't the situation described above, so proceed
|
||||
Err(GetCredentialsError::Locked) => (),
|
||||
// if the app is unlocked, and there is no matching credential, proceed
|
||||
Err(GetCredentialsError::Load(LoadCredentialsError::NoCredentials)) => (),
|
||||
// any other error is a failure
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
// for some reason Docker likes to call `store` immediately with whatever it gets
|
||||
// back from every `get` operation, so we have to check for an existing credential
|
||||
let id = state.credential_id(&docker_credential.server_url)
|
||||
let detail = RequestNotificationDetail::new_docker(
|
||||
RequestAction::Save,
|
||||
client,
|
||||
docker_credential.server_url.clone(),
|
||||
);
|
||||
let response = super::send_credentials_request(detail, app_handle.clone(), waiter).await?;
|
||||
if matches!(response.approval, Approval::Denied) {
|
||||
return Err(HandlerError::Denied);
|
||||
}
|
||||
|
||||
let (id, name) = state.docker_credential_meta(&docker_credential.server_url)
|
||||
.await
|
||||
.map_err(|e| GetCredentialsError::Load(e))?
|
||||
.unwrap_or_else(|| credentials::random_uuid());
|
||||
.unwrap_or_else(|| (credentials::random_uuid(), docker_credential.server_url.clone()));
|
||||
|
||||
let record = CredentialRecord {
|
||||
id,
|
||||
name: docker_credential.server_url.clone(),
|
||||
name,
|
||||
is_default: false,
|
||||
credential: Credential::Docker(docker_credential)
|
||||
};
|
||||
@ -169,12 +200,24 @@ async fn store_docker_credential(
|
||||
async fn erase_docker_credential(
|
||||
server_url: String,
|
||||
app_handle: AppHandle,
|
||||
_waiter: CloseWaiter<'_>
|
||||
client: Client,
|
||||
waiter: CloseWaiter<'_>
|
||||
) -> Result<CliResponse, HandlerError> {
|
||||
let state = app_handle.state::<AppState>();
|
||||
|
||||
// eventually ask the frontend to confirm here
|
||||
|
||||
let detail = RequestNotificationDetail::new_docker(
|
||||
RequestAction::Delete,
|
||||
client,
|
||||
server_url.clone(),
|
||||
);
|
||||
let resp = super::send_credentials_request(detail, app_handle.clone(), waiter).await?;
|
||||
match resp.approval {
|
||||
Approval::Approved => {
|
||||
state.delete_credential_by_name(&server_url).await?;
|
||||
Ok(CliResponse::Empty)
|
||||
}
|
||||
Approval::Denied => {
|
||||
Err(HandlerError::Denied)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -148,13 +148,6 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn credential_id(&self, name: &str) -> Result<Option<Uuid>, LoadCredentialsError> {
|
||||
let res = sqlx::query_scalar!(r#"SELECT id as "id: Uuid" FROM credentials WHERE name = ?"#, name)
|
||||
.fetch_optional(&self.pool)
|
||||
.await;
|
||||
Ok(res?)
|
||||
}
|
||||
|
||||
pub async fn save_credential(&self, record: CredentialRecord) -> Result<(), SaveCredentialsError> {
|
||||
let session = self.app_session.read().await;
|
||||
let crypto = session.try_get_crypto()?;
|
||||
@ -337,6 +330,23 @@ impl AppState {
|
||||
Ok(k)
|
||||
}
|
||||
|
||||
pub async fn docker_credential_meta(
|
||||
&self, server_url: &str
|
||||
) -> Result<Option<(Uuid, String)>, LoadCredentialsError> {
|
||||
let res = sqlx::query!(
|
||||
r#"SELECT
|
||||
c.id as "id: Uuid",
|
||||
c.name
|
||||
FROM
|
||||
credentials c
|
||||
JOIN docker_credentials d
|
||||
ON d.id = c.id
|
||||
WHERE d.server_url = ?"#,
|
||||
server_url
|
||||
).fetch_optional(&self.pool).await?;
|
||||
Ok(res.map(|row| (row.id, row.name)))
|
||||
}
|
||||
|
||||
pub async fn get_docker_credential(&self, server_url: &str) -> Result<DockerCredential, GetCredentialsError> {
|
||||
let app_session = self.app_session.read().await;
|
||||
let crypto = app_session.try_get_crypto()?;
|
||||
|
@ -26,6 +26,12 @@
|
||||
};
|
||||
dispatch('response');
|
||||
}
|
||||
|
||||
const actionDescriptions = {
|
||||
Access: 'access your',
|
||||
Delete: 'delete your',
|
||||
Save: 'create new',
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@ -52,7 +58,7 @@
|
||||
{:else if $appState.currentRequest.type === 'Ssh'}
|
||||
{appName ? `"${appName}"` : 'An application'} would like to use your SSH key "{$appState.currentRequest.key_name}".
|
||||
{:else if $appState.currentRequest.type === 'Docker'}
|
||||
{appName ? `"${appName}"` : 'An application'} would like to use your Docker credentials for <code>{$appState.currentRequest.server_url}</code>.
|
||||
{appName ? `"${appName}"` : 'An application'} would like to {actionDescriptions[$appState.currentRequest.action]} Docker credentials for <code>{$appState.currentRequest.server_url}</code>.
|
||||
{/if}
|
||||
</h2>
|
||||
|
||||
|
@ -83,6 +83,13 @@
|
||||
bind:value={local.credential.ServerURL}
|
||||
>
|
||||
|
||||
<span class="justify-self-end">Username</span>
|
||||
<input
|
||||
type="text"
|
||||
class="input input-bordered font-mono bg-transparent"
|
||||
bind:value={local.credential.Username}
|
||||
>
|
||||
|
||||
<span>Password</span>
|
||||
<div class="font-mono">
|
||||
<PassphraseInput class="bg-transparent" bind:value={local.credential.Secret} />
|
||||
|
Loading…
x
Reference in New Issue
Block a user