Commit 8b364657 authored by Emily Jensen's avatar Emily Jensen

Merge from dev

parents 867f201f bb9d1c40
......@@ -5,6 +5,12 @@
</h2>
<ul class="account__list">
<li class="account__list-item">
<a [routerLink]=" ['./change-backup-code'] " class="text-link text-link--caret text-link--large">
<h3 class="account__item-heading">Change Backup Code</h3>
</a>
</li>
<li class="account__list-item">
<a [routerLink]=" ['./change-password'] " class="text-link text-link--caret text-link--large">
<h3 class="account__item-heading">Change Account Password</h3>
......
......@@ -31,6 +31,7 @@ import {
SetPasswordActionTypes,
ForceSetPassword
} from "./reset-password/set-password/set-password.actions";
import * as fromManageBackupCode from "./manage-backup-code/manage-backup-code.reducer";
export interface IAuthState {
email: string | null;
......@@ -127,6 +128,7 @@ export interface IAccountState {
errorReporting: fromErrorReporting.IState;
resetPassword: fromResetPassword.IResetPasswordState;
resetPasswordVerify: fromResetPasswordVerify.IResetPasswordVerifyState;
manageBackupCode: fromManageBackupCode.IState;
setPassword: fromSetPassword.ISetPasswordState;
}
......@@ -140,7 +142,8 @@ export const reducers: ActionReducerMap<IAccountState> = {
errorReporting: fromErrorReporting.reducer,
resetPassword: fromResetPassword.reducer,
resetPasswordVerify: fromResetPasswordVerify.reducer,
setPassword: fromSetPassword.reducer
setPassword: fromSetPassword.reducer,
manageBackupCode: fromManageBackupCode.reducer
};
export const selectAccountState = createFeatureSelector<IAccountState>(
......@@ -345,6 +348,36 @@ export const getResetPasswordErrorMessage = createSelector(
fromResetPassword.getErrorMessage
);
export const selectManageBackupCodeForm = createSelector(
selectAccountState,
(state: IAccountState) => state.manageBackupCode
);
export const manageBackupCode = createSelector(
selectManageBackupCodeForm,
fromManageBackupCode.getForm
);
export const manageBackupCodeHasStarted = createSelector(
selectManageBackupCodeForm,
fromManageBackupCode.getHasStarted
);
export const manageBackupCodeHasFinished = createSelector(
selectManageBackupCodeForm,
fromManageBackupCode.getHasFinished
);
export const selectManageBackupCodeNewBackupCode = createSelector(
selectManageBackupCodeForm,
fromManageBackupCode.getNewBackupCode
);
export const manageBackupCodeErrorMessage = createSelector(
selectManageBackupCodeForm,
fromManageBackupCode.getErrorMessage
);
export const selectResetPasswordVerifyState = createSelector(
selectAccountState,
(state: IAccountState) => state.resetPasswordVerify
......
import { TestBed, inject } from '@angular/core/testing';
import { BackupCodePdfService } from './backup-code-pdf.service';
describe('BackupCodePdfService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [BackupCodePdfService]
});
});
it('should be created', inject([BackupCodePdfService], (service: BackupCodePdfService) => {
expect(service).toBeTruthy();
}));
});
import { Injectable } from "@angular/core";
import * as jsPDF from "jspdf";
import * as QRCode from "qrcode";
import * as canvg from "canvg";
@Injectable({
providedIn: "root"
})
export class BackupCodePdfService {
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"
);
}
/** Format current date for styling */
private getDate() {
const date = new Date(),
locale = "en-us",
dateTodayoptions = {
month: "long",
day: "numeric",
year: "numeric"
},
dateToday = date
.toLocaleString(locale, dateTodayoptions)
/** Microsoft Edge outputs the .toLocalString weirdly
* .replace(regex) fixes it: https://github.com/MrRio/jsPDF/issues/937 */
.replace(/[\u200E]/g, ""),
timeTodayoptions = {
hour: "numeric",
minute: "2-digit",
hour12: true
},
timeToday = date
.toLocaleString(locale, timeTodayoptions)
.replace(/[\u200E]/g, "");
return "Created on " + dateToday + " at " + timeToday;
}
/** 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 aside styling */
private aside(doc: jsPDF) {
doc
.setFont("helvetica")
.setFontType("normal")
.setFontSize(12)
.setFontStyle("italic")
.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 to 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);
});
}
/**
* Downloads PDF containing backup code string and matching QR Code
* @param code: 32 character backupcode string
*/
download(code: string) {
// 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 + 0.9,
dateTimeStampYAxis = issuesParagraphYAxis + 0.7,
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(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
);
const dateTimeStamp = [this.getDate()];
// const dateTimeStamp = ["Created on November 7, 2018 at 10:28 AM."];
this.docAddText(
doc,
this.aside,
dateTimeStamp,
paragraphIssuesMargin,
dateTimeStampYAxis,
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 parsedCode = [
code
.replace(/[^\dA-Z]/g, "")
.replace(/(.{4})/g, "$1 ")
.trim()
];
const codeMargin = 1.5;
this.docAddText(
doc,
this.qrText,
parsedCode,
codeMargin,
recoveryCodeYAxis,
defaultLineHeight
);
doc.save("Passit Backup Code.pdf");
});
}
}
import { Component, Input, EventEmitter, Output } from "@angular/core";
import * as jsPDF from "jspdf";
import * as QRCode from "qrcode";
import * as canvg from "canvg";
import { BackupCodePdfService } from "../backup-code-pdf.service";
@Component({
selector: "app-backup-code",
......@@ -15,324 +13,10 @@ export class BackupCodeComponent {
@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"
);
}
/** Format current date for styling */
private getDate() {
const date = new Date(),
locale = "en-us",
dateTodayoptions = {
month: "long",
day: "numeric",
year: "numeric"
},
dateToday = date
.toLocaleString(locale, dateTodayoptions)
/** Microsoft Edge outputs the .toLocalString weirdly
* .replace(regex) fixes it: https://github.com/MrRio/jsPDF/issues/937 */
.replace(/[\u200E]/g, ""),
timeTodayoptions = {
hour: "numeric",
minute: "2-digit",
hour12: true
},
timeToday = date
.toLocaleString(locale, timeTodayoptions)
.replace(/[\u200E]/g, "");
return "Created on " + dateToday + " at " + timeToday;
}
/** 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 aside styling */
private aside(doc: jsPDF) {
doc
.setFont("helvetica")
.setFontType("normal")
.setFontSize(12)
.setFontStyle("italic")
.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);
});
constructor(private backupCodeToPdf: BackupCodePdfService) {
}
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 + 0.9,
dateTimeStampYAxis = issuesParagraphYAxis + 0.7,
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);