Skip to content

Commit

Permalink
Merge pull request #470 from buttercup/fix/post-release-fixes
Browse files Browse the repository at this point in the history
Post V3 release fixes
  • Loading branch information
perry-mitchell authored Mar 27, 2024
2 parents 6d2e160 + ed25434 commit ded6ee3
Show file tree
Hide file tree
Showing 15 changed files with 313 additions and 66 deletions.
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"@blueprintjs/icons": "^4.16.0",
"@blueprintjs/popover2": "^1.14.11",
"@buttercup/channel-queue": "^1.4.0",
"@buttercup/locust": "^2.2.0",
"@buttercup/locust": "^2.2.1",
"@buttercup/ui": "^6.2.2",
"@types/chrome": "^0.0.251",
"@types/ms": "^0.7.34",
Expand Down
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
126 changes: 126 additions & 0 deletions source/popup/components/entries/EntryInfoDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React, { useCallback, useMemo } from "react";
import { Button, Classes, Dialog, DialogBody, InputGroup, Intent } from "@blueprintjs/core";
import { SearchResult } from "buttercup";
import cn from "classnames";
import styled from "styled-components";
import { t } from "../../../shared/i18n/trans.js";
import { copyTextToClipboard } from "../../services/clipboard.js";
import { getToaster } from "../../../shared/services/notifications.js";
import { localisedErrorMessage } from "../../../shared/library/error.js";

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%;
`;

export function EntryInfoDialog(props: EntryInfoDialogProps) {
const { entry, onClose } = props;
const properties = useMemo(() => entry ? orderProperties(entry.properties) : [], [entry]);
const handleCopyClick = useCallback(async (property: string, value: string) => {
try {
await copyTextToClipboard(value);
getToaster().show({
intent: Intent.SUCCESS,
message: t("popup.entries.info.copy-success", { property }),
timeout: 4000
});
} catch (err) {
getToaster().show({
intent: Intent.DANGER,
message: t("popup.entries.info.copy-error", { message: localisedErrorMessage(err) }),
timeout: 10000
});
}
}, []);
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 />
<InputGroup
type={property.sensitive ? "password" : "text"}
value={property.value}
readOnly
rightElement={
<Button
icon="clipboard"
minimal
onClick={() => handleCopyClick(property.title, property.value)}
title={t("popup.entries.info.copy-tooltip")}
/>
}
/>
</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
14 changes: 10 additions & 4 deletions source/popup/components/otps/OTPItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,19 @@ export function OTPItem(props: OTPItemProps) {
onClick();
}, [onClick]);
const [codeFirst, codeSecond] = useMemo(() => {
if (otp.errored) return [otp.digits, ""];
return otp.digits.length === 8
? [otp.digits.substring(0, 4), otp.digits.substring(4)]
: [otp.digits.substring(0, 3), otp.digits.substring(3)]
}, [otp.digits]);
const spinnerLeft = otp.remaining / otp.period;
const spinnerLeft = useMemo(() => {
if (otp.errored) return 1;
return otp.remaining / otp.period;
}, [otp]);
const spinnerIntent = useMemo(() => {
if (otp.errored) return Intent.DANGER;
return spinnerLeft < 0.15 ? Intent.DANGER : spinnerLeft < 0.35 ? Intent.WARNING : Intent.SUCCESS;
}, [otp.errored, spinnerLeft]);
return (
<Container isActive={false} onClick={handleOTPClick}>
<OTPRow>
Expand All @@ -102,15 +110,13 @@ 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>
<Spinner
size={19}
value={spinnerLeft}
intent={spinnerLeft < 0.15 ? Intent.DANGER : spinnerLeft < 0.35 ? Intent.WARNING : Intent.SUCCESS}
intent={spinnerIntent}
/>
<OTPCodePart>{codeFirst}</OTPCodePart>
<OTPCodePart>{codeSecond}</OTPCodePart>
Expand Down
Loading

0 comments on commit ded6ee3

Please sign in to comment.