Skip to content

Commit

Permalink
Add entry info popup dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
perry-mitchell committed Mar 27, 2024
1 parent 51646b2 commit 697dc66
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 44 deletions.
15 changes: 12 additions & 3 deletions source/background/services/cryptoKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,12 @@ export async function deriveSecretKey(privateKey: CryptoKey, publicKey: CryptoKe
}

async function exportECDHKey(key: CryptoKey): Promise<string> {
const exported = await window.crypto.subtle.exportKey("jwk", key);
return JSON.stringify(exported);
try {
const exported = await window.crypto.subtle.exportKey("jwk", key);
return JSON.stringify(exported);
} catch (err) {
throw new Layerr(err, "Failed exporting ECDH key");
}
}

export async function generateKeys(): Promise<void> {
Expand All @@ -73,7 +77,12 @@ export async function generateKeys(): Promise<void> {
}

export async function importECDHKey(key: string): Promise<CryptoKey> {
const jwk = JSON.parse(key) as JsonWebKey;
let jwk: JsonWebKey;
try {
jwk = JSON.parse(key) as JsonWebKey;
} catch (err) {
throw new Layerr(err, "Failed importing ECDH key");
}
const usages: Array<KeyUsage> = jwk.key_ops && jwk.key_ops.includes("deriveKey") ? ["deriveKey"] : [];
return window.crypto.subtle.importKey(
"jwk",
Expand Down
97 changes: 97 additions & 0 deletions source/popup/components/entries/EntryInfoDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React, { useMemo } from "react";
import { Button, Classes, Dialog, DialogBody } from "@blueprintjs/core";
import { SearchResult } from "buttercup";
import cn from "classnames";
import styled from "styled-components";

interface EntryInfoDialogProps {
entry: SearchResult | null;
onClose: () => void;
}

interface EntryProperty {
key: string;
sensitive: boolean;
title: string;
value: string;
}

const InfoDialog = styled(Dialog)`
max-width: 90%;
`;
const InfoDialogBody = styled(DialogBody)`
display: flex;
flex-direction: row;
justify-content: stretch;
align-items: flex-start;
overflow-x: hidden;
`;
const InfoTable = styled.table`
table-layout: fixed;
width: 100%;
`;
const ValueInput = styled.input`
width: 100%;
`;

export function EntryInfoDialog(props: EntryInfoDialogProps) {
const { entry, onClose } = props;
const properties = useMemo(() => entry ? orderProperties(entry.properties) : [], [entry]);
return (
<InfoDialog
icon="info-sign"
isCloseButtonShown
isOpen={!!entry}
onClose={onClose}
title={entry?.properties.title ?? "Untitled Entry"}
>
<InfoDialogBody>
<InfoTable className={cn(Classes.HTML_TABLE, Classes.COMPACT, Classes.HTML_TABLE_STRIPED)}>
<tbody>
{properties.map(property => (
<tr key={property.key}>
<td style={{ width: "100%" }}>
{property.title}<br />
<ValueInput className={Classes.INPUT} type={property.sensitive ? "password" : "text"} value={property.value} readOnly />
</td>
</tr>
))}
</tbody>
</InfoTable>
</InfoDialogBody>
</InfoDialog>
);
}

function orderProperties(properties: Record<string, string>): Array<EntryProperty> {
const working = { ...properties };
delete working["title"];
const output: Array<EntryProperty> = [];
if (working["username"]) {
output.push({
key: "username",
sensitive: false,
title: "Username",
value: properties["username"]
});
delete working["username"];
}
if (working["password"]) {
output.push({
key: "password",
sensitive: true,
title: "Password",
value: properties["password"]
});
delete working["password"];
}
for (const prop in working) {
output.push({
key: prop,
sensitive: false,
title: prop,
value: working[prop]
});
}
return output;
}
18 changes: 17 additions & 1 deletion source/popup/components/entries/EntryItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface EntryItemProps {
fetchIcons: boolean;
onAutoClick: () => void;
onClick: () => void;
onInfoClick: () => void;
}

const CenteredText = styled(Text)`
Expand Down Expand Up @@ -66,7 +67,8 @@ export function EntryItem(props: EntryItemProps) {
entry,
fetchIcons,
onAutoClick,
onClick
onClick,
onInfoClick
} = props;
const { source: popupSource } = useContext(LaunchContext);
const entryDomain = useMemo(() => {
Expand All @@ -91,6 +93,11 @@ export function EntryItem(props: EntryItemProps) {
},
[onAutoClick]
);
const handleEntryInfoClick = useCallback((evt: MouseEvent) => {
evt.preventDefault();
evt.stopPropagation();
onInfoClick();
}, [onInfoClick]);
return (
<Container isActive={false} onClick={handleEntryClick}>
<EntryRow>
Expand Down Expand Up @@ -119,6 +126,15 @@ export function EntryItem(props: EntryItemProps) {
onClick={handleEntryLoginClick}
/>
</Tooltip2>
<Tooltip2
content={t("popup.entries.info.tooltip")}
>
<Button
icon="info-sign"
minimal
onClick={handleEntryInfoClick}
/>
</Tooltip2>
</ButtonGroup>
)}
</EntryRow>
Expand Down
5 changes: 4 additions & 1 deletion source/popup/components/entries/EntryItemList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface EntryItemListProps {
entries: Array<SearchResult> | Record<string, Array<SearchResult>>;
onEntryAutoClick: (entry: SearchResult) => void;
onEntryClick: (entry: SearchResult) => void;
onEntryInfoClick: (entry: SearchResult) => void;
}

const ScrollList = styled.div`
Expand All @@ -21,7 +22,7 @@ const ScrollList = styled.div`
`;

export function EntryItemList(props: EntryItemListProps) {
const { entries, onEntryAutoClick, onEntryClick } = props;
const { entries, onEntryAutoClick, onEntryClick, onEntryInfoClick } = props;
const [config] = useConfig();
if (!config) return null;
return (
Expand All @@ -35,6 +36,7 @@ export function EntryItemList(props: EntryItemListProps) {
fetchIcons={config.entryIcons}
onAutoClick={() => onEntryAutoClick(entry)}
onClick={() => onEntryClick(entry)}
onInfoClick={() => onEntryInfoClick(entry)}
/>
<Divider />
</Fragment>
Expand All @@ -54,6 +56,7 @@ export function EntryItemList(props: EntryItemListProps) {
fetchIcons={config.entryIcons}
onAutoClick={() => onEntryAutoClick(entry)}
onClick={() => onEntryClick(entry)}
onInfoClick={() => onEntryInfoClick(entry)}
/>
<Divider />
</Fragment>
Expand Down
2 changes: 0 additions & 2 deletions source/popup/components/otps/OTPItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,6 @@ export function OTPItem(props: OTPItemProps) {
</Title>
<CenteredText ellipsize className={cn(Classes.TEXT_SMALL, Classes.TEXT_MUTED)}>
{otp.entryTitle}
{/* {t(`vault-state.${vault.state}`)} */}
{/* Test Test Test */}
</CenteredText>
</DetailRow>
<OTPCode>
Expand Down
76 changes: 40 additions & 36 deletions source/popup/components/pages/EntriesPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useContext, useMemo } from "react";
import React, { useCallback, useContext, useMemo, useState } from "react";
import styled from "styled-components";
import { Button, InputGroup, Intent, NonIdealState, Spinner } from "@blueprintjs/core";
import { SearchResult, VaultSourceStatus } from "buttercup";
Expand All @@ -12,6 +12,7 @@ import { getToaster } from "../../../shared/services/notifications.js";
import { localisedErrorMessage } from "../../../shared/library/error.js";
import { DesktopConnectionState } from "../../types.js";
import { openPageForEntry } from "../../services/entry.js";
import { EntryInfoDialog } from "../entries/EntryInfoDialog.js";

interface EntriesPageProps {
onConnectClick: () => Promise<void>;
Expand Down Expand Up @@ -91,6 +92,7 @@ function EntriesPageList(props: EntriesPageProps) {
);
const searchedEntries = useSearchedEntries(props.searchTerm);
const { formID, source: popupSource, url } = useContext(LaunchContext);
const [selectedEntryInfo, setSelectedEntryInfo] = useState<SearchResult | null>(null);
const urlEntries = useEntriesForURL(url);
const recentEntries = useRecentEntries();
const handleEntryClick = useCallback((entry: SearchResult, autoLogin: boolean) => {
Expand Down Expand Up @@ -131,42 +133,44 @@ function EntriesPageList(props: EntriesPageProps) {
const handleEntryBodyClick = useCallback((entry: SearchResult) => {
handleEntryClick(entry, false);
}, [handleEntryClick]);
if (unlockedCount === 0) {
return (
<InvalidState
title={t("popup.all-locked.title")}
description={t("popup.all-locked.description")}
icon="folder-close"
/>
);
}
if (searchedEntries.length > 0) {
return (
<EntryItemList
entries={searchedEntries}
onEntryAutoClick={handleEntryAutoLoginClick}
onEntryClick={handleEntryBodyClick}
/>
);
}
if (urlEntries.length <= 0 && recentEntries.length <= 0) {
return (
<InvalidState
title={t("popup.no-entries.title")}
description={t("popup.no-entries.description")}
icon="clean"
/>
);
}
const handleEntryInfoClick = useCallback((entry: SearchResult) => {
setSelectedEntryInfo(entry);
}, []);
// Render
return (
<EntryItemList
entries={{
"URL Entries": urlEntries,
"Recents": recentEntries
}}
onEntryAutoClick={handleEntryAutoLoginClick}
onEntryClick={handleEntryBodyClick}
/>
<>
{unlockedCount === 0 && (
<InvalidState
title={t("popup.all-locked.title")}
description={t("popup.all-locked.description")}
icon="folder-close"
/>
) || (urlEntries.length <= 0 && recentEntries.length <= 0) && (
<InvalidState
title={t("popup.no-entries.title")}
description={t("popup.no-entries.description")}
icon="clean"
/>
) || searchedEntries.length > 0 && (
<EntryItemList
entries={searchedEntries}
onEntryAutoClick={handleEntryAutoLoginClick}
onEntryClick={handleEntryBodyClick}
onEntryInfoClick={handleEntryInfoClick}
/>
) || (
<EntryItemList
entries={{
"URL Entries": urlEntries,
"Recents": recentEntries
}}
onEntryAutoClick={handleEntryAutoLoginClick}
onEntryClick={handleEntryBodyClick}
onEntryInfoClick={handleEntryInfoClick}
/>
)}
<EntryInfoDialog entry={selectedEntryInfo} onClose={() => setSelectedEntryInfo(null)} />
</>
);
}

Expand Down
3 changes: 3 additions & 0 deletions source/shared/i18n/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@
"open-error": "Could not open page for entry: {{message}}",
"recent-set-error": "Failed recording recent entry: {{message}}"
},
"info": {
"tooltip": "Show entry properties"
},
"search": {
"button": "Search entries",
"placeholder": "Search..."
Expand Down
2 changes: 1 addition & 1 deletion source/shared/library/version.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Do not edit this file - it is generated automatically at build time

export const BUILD_DATE = "2024-03-26";
export const BUILD_DATE = "2024-03-27";
export const VERSION = "3.0.0";

0 comments on commit 697dc66

Please sign in to comment.