Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 49 additions & 6 deletions extension/chrome/settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,24 +496,67 @@ View.run(
private addKeyRowsHtml = async (privateKeys: KeyInfoWithIdentity[]) => {
let html = '';
const canRemoveKey = !this.clientConfiguration?.usesKeyManager();

// 1. Parse and prepare data
const rows = [];
for (let i = 0; i < privateKeys.length; i++) {
const ki = privateKeys[i];
const prv = await KeyUtil.parse(ki.private);
const created = new Date(prv.created);
const date = Str.monthName(created.getMonth()) + ' ' + created.getDate() + ', ' + created.getFullYear();
const isExpired = KeyUtil.expired(prv);
rows.push({
ki,
prv,
createdTime: prv.created,
dateStr: date,
isExpired,
originalIndex: i, // keep track for actions that might rely on index? actually actions seem to use data attributes mostly, except maybe tests relying on order
});
}

// 2. Sort: newest to oldest
rows.sort((a, b) => b.createdTime - a.createdTime);

// 3. Build HTML
// Header row
html += `
<div class="row key-list-header" style="font-weight: 600; padding: 10px 0; border-bottom: 2px solid #eee;">
<div class="col-4">Email</div>
<div class="col-2">Created</div>
<div class="col-4">Fingerprint</div>
<div class="col-2">Status</div>
</div>
`;

for (const row of rows) {
const { ki, prv, dateStr, isExpired, originalIndex } = row;

let removeKeyBtn = '';
if (canRemoveKey && privateKeys.length > 1) {
removeKeyBtn = `(<a href="#" class="action_remove_key" data-test="action-remove-key-${i}" data-fingerprint=${ki.fingerprints[0]} data-type="${ki.family}" data-id="${ki.id}" data-longid="${ki.longid}">remove</a>)`;
removeKeyBtn = `<a href="#" class="action_remove_key" style="margin-left: 5px;" data-test="action-remove-key-${originalIndex}" data-fingerprint=${ki.fingerprints[0]} data-type="${ki.family}" data-id="${ki.id}" data-longid="${ki.longid}">remove</a>`;
}

const escapedEmail = Xss.escape(prv.emails[0] || '');
const escapedLink = `<a href="#" data-test="action-show-key-${i}" class="action_show_key" page="modules/my_key.htm" addurltext="&fingerprint=${ki.id}">${escapedEmail}</a>`;
const fpHtml = `fingerprint:&nbsp;<span class="good">${Str.spaced(Xss.escape(ki.fingerprints[0]))}</span>`;
const space = `&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`;
html += `<div class="row key-content-row">`;
html += ` <div class="col-12">${escapedLink} from ${Xss.escape(date)}${space}${fpHtml}${space}${KeyUtil.statusHtml(ki.id, prv)}${space}${removeKeyBtn}</div>`;
const escapedLink = `<a href="#" data-test="action-show-key-${originalIndex}" class="action_show_key" page="modules/my_key.htm" addurltext="&fingerprint=${ki.id}">${escapedEmail}</a>`;
const fpHtml = `<span class="good" style="font-family: monospace;">${Str.spaced(Xss.escape(ki.fingerprints[0]))}</span>`;

const rowClass = isExpired ? 'key-content-row expired' : 'key-content-row';
const opacityStyle = isExpired ? 'opacity: 0.7;' : '';

html += `<div class="row ${rowClass}" style="padding: 10px 0; border-bottom: 1px solid #e6e6e6; align-items: center; ${opacityStyle}">`;
html += ` <div class="col-4" style="overflow: hidden; text-overflow: ellipsis;">${escapedLink}</div>`;
html += ` <div class="col-2">${Xss.escape(dateStr)}</div>`;
html += ` <div class="col-4">${fpHtml}</div>`;
html += ` <div class="col-2" style="display: flex; justify-content: space-between; align-items: center;">
${KeyUtil.statusHtml(ki.id, prv)}
${removeKeyBtn}
</div>`;
html += `</div>`;
}

Xss.sanitizeAppend('.key_list', html);

$('.action_show_key').on(
'click',
this.setHandler(async target => {
Expand Down
4 changes: 4 additions & 0 deletions extension/css/settings.css
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,10 @@ body#settings .settings-border .key_list {
height: 219px;
}

.key-content-row.expired {
opacity: 0.7;
}

#settings-row .box img.security-icon {
width: 70px;
}
Expand Down
6 changes: 3 additions & 3 deletions test/source/tests/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,7 @@ AN8G3r5Htj8olot+jm9mIa5XLXWzMNUZgg==
await addKeyPopup.waitAndType('@input-armored-key', updatedKey.privateKey);
await addKeyPopup.waitAndType('#input_passphrase', passphrase);
await addKeyPopup.waitAndClick('.action_add_private_key', { delay: 1 });
await Util.sleep(1);
await settingsPage.waitAll('@action-show-key-1');
await gmailPage.page.reload();
await Util.sleep(3);
await gmailPage.waitTillGone('@webmail-notification-notify_expiring_keys');
Expand Down Expand Up @@ -2250,15 +2250,15 @@ AN8G3r5Htj8olot+jm9mIa5XLXWzMNUZgg==
const title = await settingsPage.read('@container-overlay-prompt-text');
expect(title).to.contain(
'Failed to store newly generated key on FlowCrypt Email Key Manager, ' +
'No key has been generated for [email protected] yet. Please ask your administrator.'
'No key has been generated for [email protected] yet. Please ask your administrator.'
);
await settingsPage.click('@action-show-overlay-details');
await settingsPage.waitAll('@container-overlay-details');
await Util.sleep(0.5);
const details = await settingsPage.read('@container-overlay-details');
expect(details).to.contain(
`405 when PUT-ing https://localhost:${t.context.urls?.port}/flowcrypt-email-key-manager/v1/keys/private string: ` +
'privateKey -> No key has been generated for [email protected] yet'
'privateKey -> No key has been generated for [email protected] yet'
);
expect(details).to.not.contain('PRIVATE KEY');
})
Expand Down
Loading