rework approval buttons and add hotkey for base approval

This commit is contained in:
Joseph Montanaro 2024-01-20 11:06:27 -08:00
parent 46b8d810c5
commit 7fdb336c79
9 changed files with 73 additions and 45 deletions

View File

@ -1,8 +1,9 @@
## Definitely ## Definitely
* Switch to "process" provider for AWS credentials (much less hacky) * ~~Switch to "process" provider for AWS credentials (much less hacky)~~
* Frontend needs to react when request is cancelled from backend
* Session timeout (plain duration, or activity-based?) * Session timeout (plain duration, or activity-based?)
* ~Fix rehide behavior when new request comes in while old one is still being resolved~ * ~~Fix rehide behavior when new request comes in while old one is still being resolved~~
* Additional hotkey configuration (approve/deny at the very least) * Additional hotkey configuration (approve/deny at the very least)
* Logging * Logging
* Icon * Icon

View File

@ -1,6 +1,6 @@
{ {
"name": "creddy", "name": "creddy",
"version": "0.4.2", "version": "0.4.3",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",

2
src-tauri/Cargo.lock generated
View File

@ -1035,7 +1035,7 @@ dependencies = [
[[package]] [[package]]
name = "creddy" name = "creddy"
version = "0.4.1" version = "0.4.3"
dependencies = [ dependencies = [
"argon2", "argon2",
"auto-launch", "auto-launch",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "creddy" name = "creddy"
version = "0.4.2" version = "0.4.3"
description = "A friendly AWS credentials manager" description = "A friendly AWS credentials manager"
authors = ["Joseph Montanaro"] authors = ["Joseph Montanaro"]
license = "" license = ""

View File

@ -8,7 +8,7 @@
}, },
"package": { "package": {
"productName": "creddy", "productName": "creddy",
"version": "0.4.2" "version": "0.4.3"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {

View File

@ -1,13 +1,15 @@
<script> <script>
export let keys; export let keys;
let classes;
export {classes as class};
</script> </script>
<div class="flex gap-x-[0.2em] items-center"> <span class="inline-flex gap-x-[0.2em] items-center {classes}">
{#each keys as key, i} {#each keys as key, i}
{#if i > 0} {#if i > 0}
<span class="mt-[-0.1em]">+</span> <span class="mt-[-0.1em]">+</span>
{/if} {/if}
<kbd class="normal-case px-1 py-0.5 rounded border border-neutral">{key}</kbd> <kbd class="normal-case px-1 py-0.5 rounded border border-neutral">{key}</kbd>
{/each} {/each}
</div> </span>

View File

@ -24,12 +24,13 @@
function handleHotkey(event) { function handleHotkey(event) {
if (!hotkey) return; if (
if (ctrl && !event.ctrlKey) return; hotkey === event.key
if (alt && !event.altKey) return; && ctrl === event.ctrlKey
if (shift && !event.shiftKey) return; && alt === event.altKey
&& shift === event.shiftKey
if (event.key === hotkey) { ) {
console.log({hotkey, ctrl, alt, shift});
click(); click();
} }
} }

View File

@ -11,11 +11,13 @@
// Send response to backend, display error if applicable // Send response to backend, display error if applicable
let error, alert; let error, alert;
let base = $appState.currentRequest.base;
async function respond() { async function respond() {
let {id, approval} = $appState.currentRequest; const response = {
id: $appState.currentRequest.id,
...$appState.currentRequest.response,
};
try { try {
await invoke('respond', {response: {id, approval, base}}); await invoke('respond', {response});
navigate('ShowResponse'); navigate('ShowResponse');
} }
catch (e) { catch (e) {
@ -27,8 +29,8 @@
} }
// Approval has one of several outcomes depending on current credential state // Approval has one of several outcomes depending on current credential state
async function approve() { async function approve(base) {
$appState.currentRequest.approval = 'Approved'; $appState.currentRequest.response = {approval: 'Approved', base};
let status = await invoke('get_session_status'); let status = await invoke('get_session_status');
if (status === 'unlocked') { if (status === 'unlocked') {
await respond(); await respond();
@ -41,9 +43,16 @@
} }
} }
function approve_base() {
approve(true);
}
function approve_session() {
approve(false);
}
// Denial has only one // Denial has only one
async function deny() { async function deny() {
$appState.currentRequest.approval = 'Denied'; $appState.currentRequest.response = {approval: 'Denied', base: false};
await respond(); await respond();
} }
@ -59,7 +68,7 @@
// if the request has already been approved/denied, send response immediately // if the request has already been approved/denied, send response immediately
onMount(async () => { onMount(async () => {
if ($appState.currentRequest.approval) { if ($appState.currentRequest.response) {
await respond(); await respond();
} }
}) })
@ -67,7 +76,7 @@
<!-- Don't render at all if we're just going to immediately proceed to the next screen --> <!-- Don't render at all if we're just going to immediately proceed to the next screen -->
{#if error || !$appState.currentRequest.approval} {#if error || !$appState.currentRequest.response}
<div class="flex flex-col space-y-4 p-4 m-auto max-w-xl h-screen items-center justify-center"> <div class="flex flex-col space-y-4 p-4 m-auto max-w-xl h-screen items-center justify-center">
{#if error} {#if error}
<ErrorAlert bind:this={alert}> <ErrorAlert bind:this={alert}>
@ -102,27 +111,42 @@
</div> </div>
</div> </div>
<div class="w-full flex justify-between"> <div class="w-full grid grid-cols-[1fr_auto] items-center gap-y-6">
<Link target={deny} hotkey="Escape"> <!-- Don't display the option to approve with session credentials if base was specifically requested -->
<button class="btn btn-error justify-self-start"> {#if !$appState.currentRequest.base}
<span class="mr-2">Deny</span> <h3 class="font-semibold">
<KeyCombo keys={['Esc']} /> Approve with session credentials
</button> </h3>
</Link> <Link target={() => approve(false)} hotkey="Enter" shift={true}>
<button class="w-full btn btn-success">
<Link target={approve} hotkey="Enter" shift="{true}">
<button class="btn btn-success justify-self-end">
<span class="mr-2">Approve</span>
<KeyCombo keys={['Shift', 'Enter']} /> <KeyCombo keys={['Shift', 'Enter']} />
</button> </button>
</Link> </Link>
</div> {/if}
<div class="w-full"> <h3 class="font-semibold">
<label class="label cursor-pointer justify-end gap-x-2"> <span class="mr-2">
<span class="label-text">Send long-lived credentials</span> {#if $appState.currentRequest.base}
<input type="checkbox" class="checkbox checkbox-success" bind:checked={base}> Approve
</label> {:else}
Approve with base credentials
{/if}
</span>
</h3>
<Link target={() => approve(true)} hotkey="Enter" shift={true} ctrl={true}>
<button class="w-full btn btn-warning">
<KeyCombo keys={['Ctrl', 'Shift', 'Enter']} />
</button>
</Link>
<h3 class="font-semibold">
<span class="mr-2">Deny</span>
</h3>
<Link target={deny} hotkey="Escape">
<button class="w-full btn btn-error">
<KeyCombo keys={['Esc']} />
</button>
</Link>
</div> </div>
</div> </div>
{/if} {/if}

View File

@ -22,7 +22,7 @@
<div class="flex flex-col h-screen items-center justify-center max-w-max m-auto"> <div class="flex flex-col h-screen items-center justify-center max-w-max m-auto">
{#if $appState.currentRequest.approval === 'Approved'} {#if $appState.currentRequest.response.approval === 'Approved'}
<svg xmlns="http://www.w3.org/2000/svg" class="w-36 h-36" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="w-36 h-36" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor">
<path in:draw="{{duration: drawDuration}}" stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> <path in:draw="{{duration: drawDuration}}" stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg> </svg>
@ -33,6 +33,6 @@
{/if} {/if}
<div in:fade="{{duration: fadeDuration, delay: fadeDelay}}" class="text-2xl font-bold"> <div in:fade="{{duration: fadeDuration, delay: fadeDelay}}" class="text-2xl font-bold">
{$appState.currentRequest.approval}! {$appState.currentRequest.response.approval}!
</div> </div>
</div> </div>