Commit 3f4e77ba authored by Emily Jensen's avatar Emily Jensen

Created backup code download component

parent 44d04ec0
Pipeline #35922853 passed with stage
in 7 minutes and 11 seconds
<div class="u-margin-b-20">
<button id="backup-download-button" class="button button--primary" (click)="download()">
Download
</button>
<button class="button button--text u-margin-l-5" id="backup-skip" href="javascript:void(0)" (click)="goToNext.emit()">
Skip (not recommended)
</button>
</div>
\ No newline at end of file
import { Component, Input, EventEmitter, Output } from "@angular/core";
import * as jsPDF from "jspdf";
import * as QRCode from "qrcode";
import * as canvg from "canvg";
@Component({
selector: 'app-backup-code-download',
templateUrl: './backup-code-download.component.html',
styleUrls: ['./backup-code-download.component.scss', "../account.component.scss"],
})
export class BackupCodeDownloadComponent {
@Input()
code: string;
@Output()
goToNext = new EventEmitter();
constructor() {}
/** Convert svg to canvas which is compatible with jspdf
* Optional width and height values are useful to resize the svg
* Return canvas contains a png.
*/
private svgToCanvas(svg: any, width?: number, height?: number) {
const canvas = document.createElement("canvas");
if (width) {
canvas.width = width;
}
if (height) {
canvas.height = height;
}
canvg(canvas, svg);
return canvas;
}
/* Wrapper around svgToCanvas with canvg height width based resizing */
private svgToPdf(
pdf: jsPDF,
svg: any,
x: number,
y: number,
width?: number,
height?: number
) {
pdf.addImage(
this.svgToCanvas(svg, width, height),
"PNG",
x,
y,
undefined,
undefined,
undefined,
"FAST"
);
}
/** Set pdf document text to header1 styling */
private header1(doc: jsPDF) {
doc
.setFont("helvetica")
.setFontType("bold")
.setFontSize(24)
.setTextColor(25, 22, 25);
}
/** Set pdf document text to header2 styling */
private header2(doc: jsPDF) {
doc
.setFont("helvetica")
.setFontType("bold")
.setFontSize(18)
.setTextColor(0, 146, 168);
}
/** Set pdf document text to paragraph styling */
private paragraph(doc: jsPDF) {
doc
.setFont("helvetica")
.setFontType("normal")
.setFontSize(13.5)
.setTextColor(25, 22, 25);
}
/** Set pdf document text to qrText styling */
private qrText(doc: jsPDF) {
doc
.setFont("courier")
.setFontType("normal")
.setFontSize(17)
.setTextColor(0, 0, 0);
}
/**
* Adds text do a pdf document.
* @param doc: jsPDF document
* @param style: function to add style to the doc
* @param docText: Array of text to add to doc, we use this instead of textwrap
* @param margin: determines the placement on the x axis (in inches)
* @param yAxis: determines the placement on the y axis (in inches)
* @param lineHeight: determines the spacing between each line (in inches)
*/
private docAddText(
doc: jsPDF,
style: (doc: jsPDF) => void,
docText: string[],
margin: number,
yAxis: number,
lineHeight: number
) {
docText.forEach((textString, iText) => {
style(doc);
doc.text(textString, margin, yAxis + iText * lineHeight);
});
}
download() {
// Places elements on Y access
const logoYAxis = 0.5,
mainHeaderYAxis = 2.1,
savedHeaderYAxis = mainHeaderYAxis + 0.6,
backupSentence1YAxis = savedHeaderYAxis + 0.4,
backupSentence2YAxis = backupSentence1YAxis + 0.7,
backupSentence3YAxis = backupSentence2YAxis + 0.7,
recoveryHeaderYAxis = 5.4,
recoveryParagraphYAxis = recoveryHeaderYAxis + 0.4,
rectangleYAxis = 7.1,
issuesParagraphYAxis = rectangleYAxis + 1.2,
qrCodeYAxis = rectangleYAxis + 0.15,
recoveryCodeYAxis = rectangleYAxis + 2.45;
// Placing an svg into a pdf is not easy
// https://stackoverflow.com/a/35788928/443457
QRCode.toString(this.code).then(image => {
const doc = new jsPDF({
unit: "in",
format: [8.5, 11]
});
// Styles
const defaultMargin = 1;
const defaultLineHeight = 0.3;
const logo = "/assets/svg/passit-logo-for-backup-code-pdf.svg";
const logoCanvas = this.svgToCanvas(logo);
// This svg must be resized via jspdf instead of canvas. Who knows why.
const logoWidth = 1.46;
// PDF geometry is not match real world geometry.
const funkyAspectRatio = 0.75;
// Aspect ratio based on 800 x 600 canvas size
// Note in other cases addImage does not resize but svgToPdf does. Argh.
doc.addImage(
logoCanvas,
"PNG",
1,
logoYAxis,
logoWidth,
logoWidth * funkyAspectRatio,
undefined,
"FAST"
);
// header 1
const mainHeader = ["Backup Code for Account Recovery"];
this.docAddText(
doc,
this.header1,
mainHeader,
defaultMargin,
mainHeaderYAxis,
defaultLineHeight
);
// backup code section
const backupCodeHeader = ["Save this somewhere safe"];
this.docAddText(
doc,
this.header2,
backupCodeHeader,
defaultMargin,
savedHeaderYAxis,
defaultLineHeight
);
const backupSentence1 = [
"This is your backup code for app.passit.io. Use this to recover your account",
"if you forget your password."
];
this.docAddText(
doc,
this.paragraph,
backupSentence1,
defaultMargin,
backupSentence1YAxis,
defaultLineHeight
);
const backupSentence2 = [
"Either print this out and put it in a secure location, or save it on a USB or",
"external hard drive."
];
this.docAddText(
doc,
this.paragraph,
backupSentence2,
defaultMargin,
backupSentence2YAxis,
defaultLineHeight
);
const backupSentence3 = [
"Remember, anyone who gains access to this code and your email account",
"can access your Passit account."
];
this.docAddText(
doc,
this.paragraph,
backupSentence3,
defaultMargin,
backupSentence3YAxis,
defaultLineHeight
);
// recover account section
const recoveryCodeHeader = ["Recover your account"];
this.docAddText(
doc,
this.header2,
recoveryCodeHeader,
defaultMargin,
recoveryHeaderYAxis,
defaultLineHeight
);
const recoveryCodeParagraph = [
"1. Go to app.passit.io",
"2. On the login page, press 'Recover your account'",
"3. Confirm your email address",
"4. Scan the QR code below, or paste/type in the code manually"
];
this.docAddText(
doc,
this.paragraph,
recoveryCodeParagraph,
defaultMargin,
recoveryParagraphYAxis,
defaultLineHeight
);
// rectangle
doc.setLineWidth(0.02);
doc.setDrawColor(229, 229, 229);
doc.rect(1, rectangleYAxis, 6.5, 2.85);
const paragraphIssues = [
"If you have issues scanning the QR code, ",
"enter the code below manually."
];
const paragraphIssuesMargin = 3.5;
this.docAddText(
doc,
this.paragraph,
paragraphIssues,
paragraphIssuesMargin,
issuesParagraphYAxis,
defaultLineHeight
);
// Add the qr code and text version of the code
this.svgToPdf(doc, image, 1.2, qrCodeYAxis, 200, 200);
// Split up in groups of 4, spaces are fine because we'll strip them out on re-entry
const code = [
this.code
.replace(/[^\dA-Z]/g, "")
.replace(/(.{4})/g, "$1 ")
.trim()
];
const codeMargin = 1.5;
this.docAddText(
doc,
this.qrText,
code,
codeMargin,
recoveryCodeYAxis,
defaultLineHeight
);
doc.save("Passit Backup Code.pdf");
this.goToNext.emit();
});
}
}
......@@ -27,6 +27,7 @@ import { SharedModule } from "../../shared";
import { BackupCodeComponent } from "../backup-code/backup-code.component";
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { MarketingFrameComponent } from "../marketing-frame/marketing-frame.component";
import { BackupCodeDownloadComponent } from '../backup-code-download/backup-code-download.component';
/*
* test setup
......@@ -59,7 +60,8 @@ describe("Register Component", () => {
ConfirmEmailComponent,
ConfirmEmailContainer,
BackupCodeComponent,
MarketingFrameComponent
MarketingFrameComponent,
BackupCodeDownloadComponent
],
providers: [
{
......
......@@ -57,6 +57,7 @@ import { AppDataService } from "./shared/app-data/app-data.service";
import { PopupLoggedInGuard } from "./guards/popup-logged-in.guard";
import { RavenErrorHandler } from "./error-handler";
import { AuthInterceptor } from "./api/auth.interceptor";
import { BackupCodeDownloadComponent } from './account/backup-code-download/backup-code-download.component';
// Why is this not default ngrx store, why is crashing default?
export interface IRouterStateUrl {
......@@ -84,7 +85,8 @@ export class CustomSerializer
ImporterComponent,
NoContentContainer,
NoContentComponent,
ExporterComponent
ExporterComponent,
BackupCodeDownloadComponent,
],
imports: [
BrowserModule,
......
......@@ -24,6 +24,7 @@ import { PasswordInputComponent } from "../app/account/change-password/password-
import { SetPasswordComponent } from "../app/account/forgot-password/set-password/set-password.component";
import { ResetPasswordVerifyComponent } from "../app/account/forgot-password/reset-password-verify/reset-password-verify.component";
import { ManageBackupCodeComponent } from "../app/account/manage-backup-code/manage-backup-code.component";
import { BackupCodeDownloadComponent } from '../app/account/backup-code-download/backup-code-download.component';
storiesOf("Account", module)
.addDecorator(withKnobs)
......@@ -130,4 +131,10 @@ storiesOf("Account", module)
props: {
form: fromSetPassword.initialState.form
}
}))
.add("Backup Code Download", () => ({
component: BackupCodeDownloadComponent,
props: {
code: "CBVDCH4G23MJ46FXJBR8GDX4WKBF97VR"
}
}));
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment