Skip to content

Commit

Permalink
Merge pull request #15588 from mozilla/FXA-7223
Browse files Browse the repository at this point in the history
feat(settings): Account recovery key PDF download
  • Loading branch information
vpomerleau authored Aug 11, 2023
2 parents bb63cca + fa400c5 commit 032839c
Show file tree
Hide file tree
Showing 60 changed files with 1,586 additions and 491 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/upload-assets-to-cdn.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ jobs:
aws s3 sync --cache-control 'public,max-age=86400' --exclude "*" --include "*.svg" --include "*.png" assets/product-icons s3://fxa-content-cdn-stage-distbucket-bpquvfnty86g/product-icons
aws s3 sync --cache-control 'public,max-age=86400' --exclude "*" --include "*.svg" --include "*.png" assets/other s3://fxa-content-cdn-stage-distbucket-bpquvfnty86g/other
aws s3 sync --cache-control 'public,max-age=86400' --exclude "*" --include "*.pdf" --content-disposition attachment assets/legal s3://fxa-content-cdn-stage-distbucket-bpquvfnty86g/legal
aws s3 sync --cache-control 'public,max-age=86400' --exclude "*" --include "*.ttf" --include "*.otf" assets/fonts s3://fxa-content-cdn-stage-distbucket-bpquvfnty86g/other
- name: Configure Production AWS credentials
uses: aws-actions/configure-aws-credentials@master
Expand All @@ -49,7 +48,6 @@ jobs:
aws s3 sync --cache-control 'public,max-age=86400' --exclude "*" --include "*.svg" --include "*.png" assets/product-icons s3://fxa-content-cdn-prod-distbucket-gqg70i8xqycy/product-icons
aws s3 sync --cache-control 'public,max-age=86400' --exclude "*" --include "*.svg" --include "*.png" assets/other s3://fxa-content-cdn-prod-distbucket-gqg70i8xqycy/other
aws s3 sync --cache-control 'public,max-age=86400' --exclude "*" --include "*.pdf" --content-disposition attachment assets/legal s3://fxa-content-cdn-prod-distbucket-gqg70i8xqycy/legal
aws s3 sync --cache-control 'public,max-age=86400' --exclude "*" --include "*.ttf" --include "*.otf" assets/fonts s3://fxa-content-cdn-prod-distbucket-gqg70i8xqycy/other
- name: "Post to fxa-team Slack channel"
uses: slackapi/slack-github-action@v1.24.0
Expand Down
1 change: 1 addition & 0 deletions packages/functional-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"fxa-payments-server": "workspace:*",
"fxa-settings": "workspace:*",
"jsqr": "^1.4.0",
"pdf-parse": "^1.1.1",
"upng-js": "^2.1.0"
}
}
4 changes: 3 additions & 1 deletion packages/functional-tests/pages/settings/recoveryKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ export class RecoveryKeyPage extends SettingsLayout {

async clickNext() {
return this.page
.getByRole('link', { name: 'Continue without downloading' })
.getByRole('link', {
name: 'Continue without downloading',
})
.click();
}

Expand Down
61 changes: 40 additions & 21 deletions packages/functional-tests/tests/settings/recoveryKey.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { test, expect } from '../../lib/fixtures/standard';
import { EmailHeader, EmailType } from '../../lib/email';
import fs from 'fs';
import pdfParse from 'pdf-parse';

let status;
let key;
Expand Down Expand Up @@ -258,7 +259,7 @@ test.describe('new recovery key test', () => {
}
);

test('can copy and download recovery key', async ({
test('can copy recovery key', async ({
credentials,
pages: { recoveryKey, settings },
}) => {
Expand All @@ -277,40 +278,58 @@ test.describe('new recovery key test', () => {
// Test copy
const clipboard = await recoveryKey.clickCopy();
expect(clipboard).toEqual(newKey);
});

test('can download recovery key as PDF', async ({
credentials,
pages: { recoveryKey, settings },
}) => {
// Create new recovery key
await settings.recoveryKey.clickCreate();
// View 1/4 info
await recoveryKey.clickStart();
// View 2/4 confirm password and generate key
await recoveryKey.setPassword(credentials.password);
await recoveryKey.submit();

// View 3/4 key download
// Store key to be used later
const newKey = await recoveryKey.getKey();

// Test download
const dl = await recoveryKey.clickDownload();
// Verify filename is as expected
const date = new Date().toISOString().split('T')[0];
const suggestedFileName = dl.suggestedFilename();
expect(suggestedFileName.length).toBeLessThanOrEqual(75);
expect(suggestedFileName).toBe(
`Firefox-Recovery-Key_${date}_${credentials.email}.txt`
const filename = dl.suggestedFilename();
expect(filename.length).toBeLessThanOrEqual(75);
expect(filename).toBe(
`Firefox-Recovery-Key_${date}_${credentials.email}.pdf`
);

// Test uses try/finally to ensure the downloaded file is deleted after tests
// whether or not the assertions passed
const filePath = suggestedFileName;
try {
// Verify file is downloaded
await dl.saveAs(filePath);
expect(fs.existsSync(filePath)).toBeTruthy();
// Verify downloaded file contains key
const downloadedContent = fs.readFileSync(filePath, 'utf-8');
expect(downloadedContent).toContain(newKey);
await dl.saveAs(filename);
expect(fs.existsSync(filename)).toBeTruthy();

const getPDF = async (file) => {
const readFileSync = fs.readFileSync(file);
try {
const pdfExtract = await pdfParse(readFileSync);
// Verify downloaded file contains key
expect(pdfExtract.text).toContain(newKey);
// Verify the PDF file contains only one page
expect(pdfExtract.numpages).toEqual(1);
} catch (error) {
throw new Error(error);
}
};
getPDF(filename);
} finally {
// Delete the downloaded file
await fs.promises.unlink(filePath);
await fs.promises.unlink(filename);
}

// After download, navigated to 'hint' page
const newHint = 'secret key location';
await recoveryKey.setHint(newHint);
await recoveryKey.clickFinish();

// Verify status as 'enabled'
status = await settings.recoveryKey.statusText();
expect(status).toEqual('Enabled');
});

test('use account recovery key', async ({
Expand Down
14 changes: 14 additions & 0 deletions packages/fxa-content-server/server/lib/amplitude.js
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,16 @@ const EVENTS = {
group: GROUPS.settings,
event: 'account_recovery_option_download',
},
// Recovery key download was successful
'flow.settings.account-recovery.recovery-key.download-success': {
group: GROUPS.settings,
event: 'account_recovery_download_success',
},
// An error occured while download the recovery key
'flow.settings.account-recovery.recovery-key.download-failed': {
group: GROUPS.settings,
event: 'account_recovery_download_failed',
},
// A user on the "download" step of the account recovery flow copies their key to their clipboard
'flow.settings.account-recovery.recovery-key.copy-option': {
group: GROUPS.settings,
Expand All @@ -477,6 +487,10 @@ const EVENTS = {
event: 'account_recovery_option_print',
},
// Save hint page
'flow.settings.account-recovery.create-hint.view': {
group: GROUPS.settings,
event: 'account_recovery_create_hint_view',
},
// A user on the "hint" step of the account recovery flow clicks the submit button to save the hint they entered into the text input
'flow.settings.account-recovery.create-hint.submit': {
group: GROUPS.settings,
Expand Down
6 changes: 6 additions & 0 deletions packages/fxa-react/lib/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export function hardNavigate(href: string) {
export enum LocalizedDateOptions {
NumericDate,
NumericDateAndTime,
MediumDateStyle,
}

/**
Expand Down Expand Up @@ -61,6 +62,11 @@ export const getLocalizedDate = (
minute: 'numeric',
};
break;
case LocalizedDateOptions.MediumDateStyle:
options = {
dateStyle: 'medium',
};
break;
default:
options = undefined;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/fxa-settings/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,13 @@
"@emotion/styled": "^11.10.4",
"@material-ui/core": "v5.0.0-alpha.24",
"@reach/router": "^1.3.4",
"@react-pdf/renderer": "^3.1.12",
"@types/material-ui": "^0.21.8",
"@types/react-webcam": "^3.0.0",
"base32-decode": "^1.0.0",
"base32-encode": "^1.2.0",
"classnames": "^2.3.1",
"file-saver": "^2.0.5",
"fxa-auth-client": "workspace:*",
"fxa-common-password-list": "^0.0.4",
"fxa-crypto-relier": "^2.7.0",
Expand Down Expand Up @@ -120,6 +122,7 @@
"@testing-library/user-event": "^14.4.3",
"@types/babel__core": "7.1.14",
"@types/classnames": "^2.3.1",
"@types/file-saver": "^2.0.5",
"@types/jest": "^26.0.23",
"@types/lodash.groupby": "^4",
"@types/node": "^18.14.2",
Expand Down

This file was deleted.

This file was deleted.

Loading

0 comments on commit 032839c

Please sign in to comment.