...
 
Commits (13)
This diff is collapsed.
......@@ -10,7 +10,7 @@ import { ManageBackupCodeContainer } from "./manage-backup-code/manage-backup-co
import { DeleteContainer } from "./delete/delete.container";
import { ResetPasswordVerifyContainer } from "./reset-password/reset-password-verify/reset-password-verify.container";
import { SetPasswordContainer } from "./reset-password/set-password/set-password.container";
// import { ManageMfaContainer } from "./manage-mfa/manage-mfa.container";
export const appRoutes: Routes = [
...routes,
......@@ -54,13 +54,18 @@ export const appRoutes: Routes = [
showNavBar: false
}
}
// {
// path: "manage-mfa",
// component: ManageMfaContainer,
// canActivate: [LoggedInGuard],
// data: {
// title: "Manage MFA",
// }
// }
];
@NgModule({
imports: [
RouterModule.forChild(appRoutes),
],
imports: [RouterModule.forChild(appRoutes)],
exports: [RouterModule]
})
export class AccountRoutingModule {}
......@@ -8,8 +8,10 @@ import { RegisterContainer } from "./register/register.container";
import { ConfirmEmailContainer } from "./confirm-email";
import { ConfirmEmailGuard } from "./confirm-email/confirm-email.guard";
import { ResetPasswordContainer } from "./reset-password/reset-password.container";
import { ManageBackupCodeContainer } from "./manage-backup-code/manage-backup-code.container";
export const routes: Routes = [
{ path: "", component: ManageBackupCodeContainer },
{
path: "change-password",
component: ChangePasswordContainer,
......@@ -66,5 +68,5 @@ export const routes: Routes = [
title: "Reset Password",
showNavBar: false
}
},
}
];
......@@ -4,21 +4,26 @@
<ul class="account__list">
<li class="account__list-item">
<h3 class="account__item-heading">
<app-text-link
[link]="['./change-password']"
caret="right"
>
<app-text-link [link]="['./change-password']" caret="right">
Change Account Password &amp; Backup&nbsp;Code
</app-text-link>
</h3>
</li>
<li class="account__list-item">
<!-- <li class="account__list-item">
<h3 class="account__item-heading">
<app-text-link
[link]="['./change-backup-code']"
[link]="['./manage-mfa']"
caret="right"
>
Manage Two-Factor Authentication
</app-text-link>
</h3>
</li> -->
<li class="account__list-item">
<h3 class="account__item-heading">
<app-text-link [link]="['./change-backup-code']" caret="right">
Change Backup Code
</app-text-link>
</h3>
......@@ -26,10 +31,7 @@
<li class="account__list-item">
<h3 class="account__item-heading">
<app-text-link
[link]="['./error-reporting']"
caret="right"
>
<app-text-link [link]="['./error-reporting']" caret="right">
Error Reporting
</app-text-link>
</h3>
......@@ -104,7 +106,7 @@
<li class="account__list-item">
<h3 class="account__item-heading">
<app-text-link caret="right" [link]="['../export']">
Export Passwords
Export Passwords
</app-text-link>
</h3>
</li>
......
......@@ -49,6 +49,8 @@ import { ConfirmEmailGuard } from "./confirm-email/confirm-email.guard";
import { UserService } from "./user";
import { DeleteEffects } from "./delete/delete.effects";
import { AccountRoutingModule } from "./account-routing.module";
import { ManageMfaContainer } from "./manage-mfa/manage-mfa.container";
import { ManageMfaComponent } from "./manage-mfa/manage-mfa.component";
export const COMPONENTS = [
RegisterComponent,
......@@ -74,7 +76,9 @@ export const COMPONENTS = [
ManageBackupCodeComponent,
ManageBackupCodeContainer,
ForgotLearnMoreContainer,
ForgotLearnMoreComponent
ForgotLearnMoreComponent,
ManageMfaContainer,
ManageMfaComponent
];
export const SERVICES = [BackupCodePdfService, UserService, ConfirmEmailGuard];
......
......@@ -13,29 +13,28 @@ import { BackupCodePdfService } from "../backup-code-pdf.service";
import { SetValueAction } from "ngrx-forms";
import { FORM_ID } from "./manage-backup-code.reducer";
// import { Printer } from "nativescript-printer";
// const ZXing = require("nativescript-zxing");
// import { Image } from "tns-core-modules/ui/image";
import { Printer } from "nativescript-printer";
const ZXing = require("nativescript-zxing");
import { Image } from "tns-core-modules/ui/image";
@Component({
// template: `
// <FlexboxLayout
// #foo
// dock="bottom"
// class="auth-actions"
// flexDirection="row"
// justifyContent="flex-end"
// >
// <image #image></image>
// <app-button
// height="36"
// text="Sign Up"
// type="border"
// (tap)="print()"
// ></app-button>
// </FlexboxLayout>
// `,
template: ``,
template: `
<FlexboxLayout
#foo
dock="bottom"
class="auth-actions"
flexDirection="row"
justifyContent="flex-end"
>
<image #image></image>
<app-button
height="36"
text="Sign Up"
type="border"
(tap)="print()"
></app-button>
</FlexboxLayout>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ManageBackupCodeContainer implements OnInit {
......@@ -67,25 +66,25 @@ export class ManageBackupCodeContainer implements OnInit {
}
ngOnInit() {
// const zx = new ZXing();
// const img = zx.createBarcode({
// encode: "Text",
// height: 100,
// width: 100,
// format: ZXing.QR_CODE
// });
// const image: Image = this.image.nativeElement;
// image.imageSource = img;
// image.src = img;
// image.height = 100;
// image.width = 100;
const zx = new ZXing();
const img = zx.createBarcode({
encode: "Text",
height: 100,
width: 100,
format: ZXing.QR_CODE
});
const image: Image = this.image.nativeElement;
image.imageSource = img;
image.src = img;
image.height = 100;
image.width = 100;
}
print() {
// const printer = new Printer();
// printer.isSupported().then((supported: boolean) => {
// printer.printScreen({ view: this.codeInput.nativeElement });
// });
const printer = new Printer();
printer.isSupported().then((supported: boolean) => {
printer.printScreen({ view: this.codeInput.nativeElement });
});
}
getBackupPDF() {
......
<p>
<a [href]="uri">{{ uri }}</a>
<img [src]="qrCode" />
<button (click)="generateMfa.emit()">Generate MFA</button>
</p>
/* Add mobile styles for the component here. */
<Button text="manage-mfa works!" class="btn btn-primary"></Button>
\ No newline at end of file
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-manage-mfa',
templateUrl: './manage-mfa.component.html',
styleUrls: ['./manage-mfa.component.css']
})
export class ManageMfaComponent {
@Input() uri: string;
@Input() qrCode: string;
@Output() generateMfa = new EventEmitter();
}
import { Component } from '@angular/core';
import * as QRCode from "qrcode";
import { UserService } from '../user';
@Component({
template: `
<app-manage-mfa
[uri]="uri"
[qrCode]="qrCode"
(generateMfa)="generateMfa()"
></app-manage-mfa>
`,
})
export class ManageMfaContainer {
uri: string;
qrCode: string;
constructor(private service: UserService) { }
generateMfa() {
this.service.generateMfa().toPromise().then(resp => {
this.uri = resp.provisioning_uri;
QRCode.toDataURL(this.uri).then(dataUrl => this.qrCode = dataUrl);
});
}
}
......@@ -422,6 +422,18 @@ export class UserService {
);
}
/** Generate new MFA code, returns the uri that can be imported into MFA apps like Google Authenticator */
generateMfa() {
const url = this.sdk.formUrl("generate-mfa/");
return this.http.post<{ provisioning_uri: string }>(url, "");
}
verifyMfa(otp: string) {
const url = this.sdk.formUrl("verify-mfa/");
const data = { otp };
return this.http.post<null>(url, data);
}
private setSdkUrl(url: string) {
this.store.dispatch(new SetUrlAction(url));
}
......
......@@ -2,9 +2,11 @@ import { Routes } from "@angular/router";
import { LoginContainer } from "./login/login.container";
import { AlreadyLoggedInGuard, LoggedInGuard } from "./guards";
import { SecretListContainer } from "./list";
// import { VerifyMfaContainer } from "./login/verify-mfa/verify-mfa.container";
export const routes: Routes = [
{ path: "", redirectTo: "list", pathMatch: "full" },
// { path: "", redirectTo: "list", pathMatch: "full" },
{ path: "", loadChildren: "./account/account.module#AccountModule" },
{
path: "account/login",
component: LoginContainer,
......@@ -14,6 +16,14 @@ export const routes: Routes = [
showNavBar: false
}
},
// {
// path: "account/login/verify-mfa",
// component: VerifyMfaContainer,
// data: {
// title: "Verify MFA",
// showNavBar: false
// }
// },
{
path: "list",
component: SecretListContainer,
......
......@@ -21,7 +21,7 @@ describe("Login Effects", () => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot(fromRoot.reducers),
StoreModule.forFeature("login", fromLogin.reducer),
StoreModule.forFeature("login", fromLogin.reducers),
EffectsModule.forRoot([LoginEffects]),
RouterTestingModule
],
......
......@@ -11,12 +11,13 @@ import { StoreModule } from "@ngrx/store";
import { SharedModule } from "../shared/shared.module";
import { NgrxFormsModule } from "ngrx-forms";
import { ProgressIndicatorModule } from "../progress-indicator/progress-indicator.module";
import { VerifyMfaComponent } from "./verify-mfa/verify-mfa.component";
@NgModule({
declarations: [LoginComponent, LoginContainer],
declarations: [LoginComponent, LoginContainer, VerifyMfaComponent],
imports: [
NativeScriptCommonModule,
StoreModule.forFeature("login", fromLogin.reducer),
StoreModule.forFeature("login", fromLogin.reducers),
EffectsModule.forFeature([LoginEffects, TNSLoginEffects]),
SharedModule,
NgrxFormsModule,
......
......@@ -10,17 +10,25 @@ import { LoginEffects } from "./login.effects";
import { SharedModule } from "../shared/shared.module";
import { NgrxFormsModule } from "ngrx-forms";
import { ProgressIndicatorModule } from "../progress-indicator/progress-indicator.module";
import { VerifyMfaComponent } from "./verify-mfa/verify-mfa.component";
import { VerifyMfaContainer } from "./verify-mfa/verify-mfa.container";
import { VerifyMFAEffects } from "./verify-mfa/verify-mfa.effects";
@NgModule({
declarations: [LoginComponent, LoginContainer],
declarations: [
LoginComponent,
LoginContainer,
VerifyMfaComponent,
VerifyMfaContainer
],
imports: [
CommonModule,
StoreModule.forFeature("login", fromLogin.reducer),
EffectsModule.forFeature([LoginEffects]),
StoreModule.forFeature("login", fromLogin.reducers),
EffectsModule.forFeature([LoginEffects, VerifyMFAEffects]),
SharedModule,
NgrxFormsModule,
ProgressIndicatorModule
],
exports: [LoginContainer]
exports: [LoginContainer, VerifyMfaContainer]
})
export class LoginModule {}
import { initialState, reducer } from "./login.reducer";
import * as fromLogin from "./login.reducer";
import { LoginFailureAction } from "../app.actions";
describe("LoginReducer", () => {
......@@ -6,7 +6,10 @@ describe("LoginReducer", () => {
it("should return error message", () => {
const error = "some error";
const createAction = new LoginFailureAction(error);
const result = reducer(initialState, createAction);
const result = fromLogin.loginFormReducer(
fromLogin.initialLoginFormState,
createAction
);
expect(result.errorMessage).toBe(error);
expect(result.hasFinished).toBe(false);
expect(result.hasStarted).toBe(false);
......
......@@ -15,8 +15,13 @@ import { ILoginForm } from "./interfaces";
import { DEFAULT_API } from "../constants";
import { oldPasswordValidators } from "../account/constants";
import { IBaseFormState } from "../utils/interfaces";
import { createSelector, createFeatureSelector } from "@ngrx/store";
import {
createSelector,
createFeatureSelector,
ActionReducerMap
} from "@ngrx/store";
import * as fromRoot from "../app.reducers";
import * as fromVerifyMfa from "./verify-mfa/verify-mfa.reducer";
const FORM_ID = "Login Form";
......@@ -36,12 +41,12 @@ const initialFormState = validateAndUpdateFormState(
})
);
export interface ILoginState extends IBaseFormState {
export interface ILoginFormState extends IBaseFormState {
form: FormGroupState<ILoginForm>;
errorMessage: string | null;
}
export const initialState: ILoginState = {
export const initialLoginFormState: ILoginFormState = {
form: initialFormState,
hasStarted: false,
hasFinished: false,
......@@ -52,10 +57,10 @@ export const formReducer = createFormStateReducerWithUpdate<ILoginForm>(
validateAndUpdateFormState
);
export function reducer(
state = initialState,
export function loginFormReducer(
state = initialLoginFormState,
action: AppActions
): ILoginState {
): ILoginFormState {
let form = formReducer(state.form, action);
state = { ...state, form };
......@@ -101,6 +106,16 @@ export function reducer(
}
}
interface ILoginState {
loginForm: ILoginFormState;
verifyMfa: fromVerifyMfa.IVerifyMfaState;
}
export const reducers: ActionReducerMap<ILoginState> = {
loginForm: loginFormReducer,
verifyMfa: fromVerifyMfa.reducer
};
export interface IState extends fromRoot.IState {
login: ILoginState;
}
......@@ -109,19 +124,46 @@ export const selectLoginState = createFeatureSelector<IState, ILoginState>(
"login"
);
export const getLoginHasStarted = createSelector(
const selectLoginFormState = createSelector(
selectLoginState,
state => state.loginForm
);
const selectVerifyMfaState = createSelector(
selectLoginState,
state => state.verifyMfa
);
export const getLoginHasStarted = createSelector(
selectLoginFormState,
state => state.hasStarted
);
export const getLoginHasFinished = createSelector(
selectLoginState,
selectLoginFormState,
state => state.hasFinished
);
export const getLoginErrorMessage = createSelector(
selectLoginState,
selectLoginFormState,
state => state.errorMessage
);
export const getLoginForm = createSelector(
selectLoginState,
selectLoginFormState,
state => state.form
);
export const selectVerifyMfaForm = createSelector(
selectVerifyMfaState,
state => state.form
);
export const getVerifyMfaHasStarted = createSelector(
selectVerifyMfaState,
state => state.hasStarted
);
export const getVerifyMfaFinished = createSelector(
selectVerifyMfaState,
state => state.hasFinished
);
export const getVerifyMfaErrorMessage = createSelector(
selectVerifyMfaState,
state => state.errorMessage
);
......@@ -27,7 +27,7 @@ describe("LoginComponent", () => {
HttpClientTestingModule,
RouterTestingModule,
StoreModule.forRoot(fromRoot.reducers),
StoreModule.forFeature("login", fromLogin.reducer)
StoreModule.forFeature("login", fromLogin.reducers)
]
}).compileComponents();
}));
......
import { Action } from "@ngrx/store";
export enum VerifyMfaActionTypes {
VERIFY_MFA = "[Verify MFA] Verify",
VERIFY_MFA_SUCCESS = "[Verify MFA] Verify Success",
VERIFY_MFA_FAILURE = "[Verify MFA] Verify Failure"
}
export class VerifyMfa implements Action {
readonly type = VerifyMfaActionTypes.VERIFY_MFA;
}
export class VerifyMfaSuccess implements Action {
readonly type = VerifyMfaActionTypes.VERIFY_MFA_SUCCESS;
}
export class VerifyMfaFailure implements Action {
readonly type = VerifyMfaActionTypes.VERIFY_MFA_FAILURE;
}
export type VerifyMfaActions = VerifyMfa | VerifyMfaSuccess | VerifyMfaFailure;
<app-heading text="Verify MFA"></app-heading>
<form novalidate [ngrxFormState]="form" (submit)="onSubmit.emit()">
<app-text-field
label="One Time Pass"
[ngrxFormControl]="form.controls.otp"
[isFormSubmitted]="form.isSubmitted"
></app-text-field>
<button id="submit" class="button button--large button--green" type="submit">
Verify
</button>
</form>
/* Add mobile styles for the component here. */
<Button text="verify-mfa works!" class="btn btn-primary"></Button>
\ No newline at end of file
import {
Component,
ChangeDetectionStrategy,
Input,
Output,
EventEmitter
} from "@angular/core";
import { FormGroupState } from "ngrx-forms";
import { IVerifyMfaForm } from "./verify-mfa.reducer";
@Component({
selector: "app-verify-mfa",
templateUrl: "./verify-mfa.component.html",
styleUrls: ["./verify-mfa.component.css"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class VerifyMfaComponent {
@Input() form: FormGroupState<IVerifyMfaForm>;
@Input() errorMessage: string;
@Input() isPopup: boolean;
@Input() hasStarted: boolean;
@Input() hasFinished: boolean;
@Input() isExtension: boolean;
@Output() goToLogin = new EventEmitter();
@Output() onSubmit = new EventEmitter();
}
import { Component, ChangeDetectionStrategy } from "@angular/core";
import { Store, select } from "@ngrx/store";
import {
IState,
selectVerifyMfaForm,
getVerifyMfaHasStarted,
getVerifyMfaFinished,
getVerifyMfaErrorMessage
} from "../login.reducer";
import { VerifyMfa } from "./verify-mfa.actions";
import * as fromRoot from "../../app.reducers";
import { IS_EXTENSION } from "../../constants";
import { Router } from "@angular/router";
@Component({
template: `
<app-verify-mfa
[form]="form$ | async"
[form]="form$ | async"
[isExtension]="isExtension"
[hasStarted]="hasStarted$ | async"
[hasFinished]="hasFinished$ | async"
[errorMessage]="errorMessage$ | async"
[isPopup]="isPopup"
(goToLogin)="goToLogin()"
(onSubmit)="onSubmit()"
></app-verify-mfa>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class VerifyMfaContainer {
form$ = this.store.pipe(select(selectVerifyMfaForm));
hasStarted$ = this.store.pipe(select(getVerifyMfaHasStarted));
hasFinished$ = this.store.pipe(select(getVerifyMfaFinished));
errorMessage$ = this.store.pipe(select(getVerifyMfaErrorMessage));
isExtension = IS_EXTENSION;
isPopup = false;
constructor(private router: Router, private store: Store<IState>) {
store
.pipe(select(fromRoot.getIsPopup))
.subscribe(isPopup => (this.isPopup = isPopup));
}
goToLogin() {
if (this.isPopup) {
browser.tabs.create({
url: "/index.html#/account/login"
});
window.close();
} else {
this.router.navigate(["/account/login"]);
}
}
onSubmit() {
this.store.dispatch(new VerifyMfa());
}
}
import { Injectable } from "@angular/core";
import { Effect, Actions, ofType } from "@ngrx/effects";
import {
VerifyMfaActionTypes,
VerifyMfaSuccess,
VerifyMfaFailure
} from "./verify-mfa.actions";
import { withLatestFrom, map, exhaustMap, catchError } from "rxjs/operators";
import { Store, select } from "@ngrx/store";
import { IState, selectVerifyMfaForm } from "../login.reducer";
import { UserService } from "../../account/user";
import { of } from "rxjs";
@Injectable()
export class VerifyMFAEffects {
@Effect()
verifyMfa$ = this.actions$.pipe(
ofType(VerifyMfaActionTypes.VERIFY_MFA),
withLatestFrom(this.store.pipe(select(selectVerifyMfaForm))),
map(([action, form]) => form.value),
exhaustMap(formValue => {
return this.service.verifyMfa(formValue.otp).pipe(
map(resp => new VerifyMfaSuccess()),
catchError(err => of(new VerifyMfaFailure()))
);
})
);
constructor(
private actions$: Actions,
private store: Store<IState>,
private service: UserService
) {}
}
import {
updateGroup,
validate,
createFormGroupState,
FormGroupState,
createFormStateReducerWithUpdate,
markAsSubmitted
} from "ngrx-forms";
import { minLength, maxLength, required } from "ngrx-forms/validation";
import { IBaseFormState } from "../../utils/interfaces";
import { VerifyMfaActionTypes, VerifyMfaActions } from "./verify-mfa.actions";
const FORM_ID = "Login Form";
export interface IVerifyMfaForm {
otp: string;
}
const validateAndUpdateFormState = updateGroup<IVerifyMfaForm>({
otp: validate(required, minLength(6), maxLength(6))
});
const initialFormState = validateAndUpdateFormState(
createFormGroupState<IVerifyMfaForm>(FORM_ID, {
otp: ""
})
);
export interface IVerifyMfaState extends IBaseFormState {
form: FormGroupState<IVerifyMfaForm>;
errorMessage: string | null;
}
export const initialState: IVerifyMfaState = {
form: initialFormState,
hasStarted: false,
hasFinished: false,
errorMessage: null
};
export const formReducer = createFormStateReducerWithUpdate<IVerifyMfaForm>(
validateAndUpdateFormState
);
export function reducer(
state = initialState,
action: VerifyMfaActions
): IVerifyMfaState {
const form = formReducer(state.form, action);
state = { ...state, form };
switch (action.type) {
case VerifyMfaActionTypes.VERIFY_MFA:
return {
...state,
form: markAsSubmitted(state.form),
hasStarted: true,
hasFinished: false
};
case VerifyMfaActionTypes.VERIFY_MFA_SUCCESS:
return {
...state,
hasFinished: true
};
case VerifyMfaActionTypes.VERIFY_MFA_FAILURE:
return {
...state,
errorMessage: "An error occured",
hasFinished: false,
hasStarted: false
};
}
return state;
}
......@@ -9,11 +9,9 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { BackupCodeComponent } from "../app/account/backup-code/backup-code.component";
import { ResetPasswordComponent } from "../app/account/reset-password/reset-password.component";
import { LoginComponent } from "../app/login/login.component";
import { SharedModule } from "../app/shared/shared.module";
import { ProgressIndicatorModule } from "../app/progress-indicator/progress-indicator.module";
import * as fromResetPassword from "../app/account/reset-password/reset-password.reducer";
import * as fromLogin from "../app/login/login.reducer";
import * as fromChangePassword from "../app/account/change-password/change-password.reducer";
import * as fromDelete from "../app/account/delete/delete.reducer";
import * as fromSetPassword from "../app/account/reset-password/set-password/set-password.reducer";
......@@ -28,6 +26,7 @@ import { ResetPasswordVerifyComponent } from "../app/account/reset-password/rese
import { DownloadBackupCodeComponent } from "../app/account/manage-backup-code/download-backup-code/download-backup-code.component";
import { ServerSelectComponent } from "../app/shared/server-select/server-select.component";
import { DeleteComponent } from "~/app/account/delete/delete.component";
import { ManageMfaComponent } from "../app/account/manage-mfa/manage-mfa.component";
storiesOf("Account", module)
.addDecorator(withKnobs)
......@@ -194,12 +193,6 @@ storiesOf("Account", module)
`
};
})
.add("Login", () => ({
component: LoginComponent,
props: {
form: fromLogin.initialState.form
}
}))
.add("Change Password", () => ({
component: ChangePasswordComponent,
props: {
......@@ -248,4 +241,13 @@ storiesOf("Account", module)
form: fromDelete.initialState.form,
hasStarted: boolean("hasStarted", false)
}
}))
.add("Multi Factor Auth", () => ({
component: ManageMfaComponent,
props: {
uri:
"otpauth://totp/Passit:test%40example.com?secret=JE4ZRQPUIWJV6APW&issuer=Passit",
qrCode:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAYAAAA9zQYyAAANIElEQVR4Xu2d0W7kOAwEk///6CxwT2cP4EKhKctxel9pUWSzSGs8zuz3z8/Pz1f/VYGXKPBdoF9SyabxnwIFuiC8SoEC/apyNpkCXQZepUCBflU5m0yBLgOvUqBAv6qcTaZAl4FXKVCgX1XOJlOgy8CrFCjQrypnkynQZeBVChToV5WzyRToMvAqBQr0q8rZZGKgv7+/b1Xx/Pp2uj/5S+2pOPS6+nT+53hT/zZ/ypf8FejT3zecC1ig9w4sAvijAdMX/Hd3cLp/CiyttwU5X08Tazr/TugeOQ4MpIAV6OwvAsePHDRR7MRKjwDTE8fmlwI+fQdI47frqd5UX1q//Mhxd8JWkGnASPDp/ab92fjvri/FV6DDI5It6DSA0/4IGDswyB/dMa2+BbpAK+YKNDwGIzXpzGjttJ+deHZ/CwRdvzpeO0HvjgfrOf3YjgpOAdF6a6f9VheEAH06QBT/av2ofrcfOVYnTIKTIE+Lj/JZHe/TGwzr2QntvgmzdwgC9OkAUfx3N9jrgbZAkCDkz66fbgALmN2f8id/pA+tf/xTjrs7mApOglNB7XpbQIp/tZ3yp3xIH1pfoE8KUsFJcCqoXW8LSPGvtlP+lA/pQ+sLdIE+KJACX6BPX1RQB053MBWQ9qP1ZCcAaH+rl51gq498q/2Tfn1sJyd6gT4+BbINeAaO1tuGLdAF+nLoWeA6oeEekgpqO5wmMNl75Fj7HP9xRw4bEAFigaX97YQhf2SnhqX19hZO19N+tqHJ3+r6/voX/K2AJGjqj9YX6KNC0w1ToMPXSQlgmpDpHcI2iL3DTQNHA8XGd/uHQlvw1QmT/zReWm8BnPZngfnzQFMBUjsBUfvxj0wJSGtP60frbcMtn9AUcGovsGuBJX3T+tH6Ai2/qaSC1X7dMARkai/QBfrA0OqGTIGl9duBpgB32+kpghXQ+kuvn46P6mH3I3932+PHdncHbPezQJF/6y+93gJG+1F+dj/yd7e9QJ/+Sp0KQMDQLf/sn663gFF8lJ/dj/zdbS/QBfryDH43kOl+MdD0HPPjOeHib+ZowlC8NOHsRLX7Wf8xAFCP1XrSHcvmV6DlD+NY4Ap09jZegYYjhAWMJoj1N90QtuD2jtkJLYFKC0LAUQEJMPJfoNf+RYvlY/zIYQMgoGhCELBpPHY9ncHJH+VL/klP26D2+nR/0ofsBfqkEAGFgoYfemn/An1dgQJdoA8KpA3TCS3/F6rdE3L3/ilw9ghhr3890JRgeoslwKyd4iF/9kMi+SOgaD3FQ+tXf0YZj2/610epADYBmki2IHR9gYYz6vBnBMsD1W/5GboTevb9Yttw48AUaPcXFtO3OOpouqPY9QRQescp0Dc/5bCCE8A04VMgUwDT+NL9KX9qIFuv6Xgpfj1Qps/QVqACvfabtgItW4I6Vrr7In9PK1An9N7PCB8DsRM6m5AF+uVA00S2R5J0ItN6inf8jCffP7Z3LNJ3tR4Urz1i2vosf2yXAkEF2F1ALXiBPkhmG4D0LtCk0MlODUTubINSwclOE5HiJTsduUgvGz/FU6BJoQJ9qdDrgH7aBJiegNP5pROLJlpqp/62+pJ+pAfFM/6UgwK2AdGZ2xYs9TedHxXQ5mcnJPmnehVoUgjsacEKdPbqgW3otGFDXL7iM7RN2AZcoB2QNIHJTvV5/YSmBEmg6QmaNhg1UBpvqpeNL9U/1XN6f/IXT+i0QCkgmKB83dECQ9eTneK3+lh/5L9Ah4oSAHRGSwtA+xMAdEtPB4CNz5aD9E3jp3hof1rfCX1SyAJD15OdCmQbyPoj/+mAsPFsB5oCTjvaAhELMvwD6gQMTXTSl+zkn+zkPwU+rdfH/unbdpRwgT4qRA06XuCwQam+BVoqRADQBJTb6fex0/imJyQBRvGmDWUHWLpfJzQQToBZOzUc+bMNWaDDFpnuSOsvBSYFiuK18lI81k4NYeMjf6QHrU/juf0pBwU8LYgFgOKjCUgNRgVN47X62Xxt/HR9qtf4kWNaQOuPBEkBoYJQvBaYNF6Kh/SifMlu95+OpxMangKkBSzQpOD1UyC3+mv+5SQdQAiU3c9ebyem9W+BpyMP+aMJatfTU5PpCUz6xhOaNiA7AUPrV9spPgKE4iOAaD3FRw1ggaP9SI80X9Rj9RcrGEAnNEl0aSfACnQkr19sC+J3yFZQfDSRaPd0YlF8BZoqsNieAkLh0ZmPACOAyE7xpesJ4DR/in/3/tvP0CSAFZCuTwtKwJGd4kvXk55p/hT/7v0LtDzDE3BkJyDS9buB2r1/gS7QBwbpyEUN+eeBnp5IJOjZTrdguj4tMPknfegzBwFq19P1aT5Wz496v/2xHRWgQF//X9xWnwItb/m2gwv08WcQVt/BCnSBvjzD9sjhRlj8oZAmIHVsOjHS9TZ+yof80Zk2zSddb+OnhqN4rB6Ed4GWv9tRoN0vORVo+YPgJFgKIE0E+6HJTiQ7MVfrQflSfnaik/7jTzlIcAIqLUC63sZP+ZA/KniaT7rexm8BtddvBzotmF1PBZyeKCQw7WcbgvzdbV+tN+lL9vEztAVyumPJH9mpYCQoAVagrx8jkr5kL9A/2XNaC6i9nhrkbjs1vI2HALX2Al2gD8ykd7DXAU0dZROmDym0XzoRaSKt3n86f6uHvX77kTN9l8MKXqDdc1yr73SDFWhQtEAX6P8jYo841LDjZ2jasEAX6EcDTQDbM2h6BqNbJMVjG876I73oiJHGR/tbO8VL9XjchJ4WoEBn7ydb/Wz9qIHJ3+qGjI8clIAVwBaEJgT5owlBdsqP9qf1d0+46XreHX+BHv6Fe9sABfp4R7ID4EO/9LEddfTqCWr3p1se+ZteT/vZBiG9aWLaBrP+yH+BBiIIiBSAdH2B7oQmBg72An0tF03EpzUsFX/5GZoEsYLS9faWRvHRLTVdjwWSf3M5Hc+0P1sf0mf8DG0TtgGmZ1YCkuJJJzw1IPknACh+slN8tD/Vx/qneMkeT+gC7Z4bW0Doeiow2S1w1IBkp3hSe4Fe/KGSgLEATA8Qio8aqhNatiAJJt19rS6gPeIUaFvB6+uXT2gCkuw2XfL3dKCpIWhC2/xoApP+pDetp3zt+gL9sCMHFbhAd0IfFLATjI4EKWB2vb1eTzj5wzud0PLdCVsQErhAw4Qr0LNfXRLAdmKuBpj8p/HSetKL7OR/t53i//gMkL6cRAnbgOh62o/sd/uneFI75UP2dP/V6yn+Aj38IbAT+voOnQJfoOW7D/TYKj2TpwWl9bbg0/lSfKnd5jf+2I4mFglKCVj/tB/5o4JQvOn+dj3FS09JbD7pY0Zab+Mp0PKXk6gBqAAEXLqe/Bfo8MxpC0TX3w0UAULx2glL/igea6f9rJ2OaOQvru/upxx2YsQJyzM2AUIFKtDXbyM+7shhCzp9PTWEbQACmOwEsM2f4p+Oh/a7Oz+rV3yGthtOX1+g3Rdbq/Wy9bUNRP4L9Ekhmnhkv3uCTcdjAaMGIQDtfuSvQBfogwIWsNcBnSZEHUcfGuyEunuC0n7pUwHSh/Ql/XbbKf4PfaefctgA7PUEwNMnjAUk1YfW23is/ql/ir9AwxHDCpg2EAHytHgo3mm7zT8+Q/fI4f5XJzuxbEFXN9g0sKSHzX8caCsoBUwJU0OtjofiJ/vq+AjA6TM4+aN6kl5kL9Ck0PARhQouw/m4nICZHgDWH8UX5z/9ofC3TxwSlApI6wu0+yLI6tkJLRUr0EfBSA868owPwNUTmhKmiUW3qLv9pwWgeFMApvWS/T7+Qz52/+UTmgpYoI8KFGj31Gj5c2gqCHUcrSf7av+d0NcKW33ojkL1LNCnv1AhQcmuBYffvaCGJWAoXnvHtPlRfB8AyvfTKZ7XHzkIEBKICjQNkN3Pxp/Ga+Mj/ckf5ffnJjQJSoKR4Ckg9BmCJpqNP413Wg/yR/kV6PCnrgiwuxuICk7xkD1tOPJfoOEMTAWwZ0gSPJ14FC81UIE+KvDrz9C24AQoAUINQROJ/BPg1ECkh40vvZ7ioXytXgVaKlagZ/9PmQItfxhmXLDwsZvsn49v3jqhrxXshJaEdUL/8Qktefm4PJ1INKHJv7XTflYPuz+deaftlA8NgHG9Vr+cRAmTnQqafugg/9Y+XiD5Px5MA0v52/rR9fGH9gI9+/vGBfr0GG34uT81xPIzNAVAdjsh6Ba3eoIV6JcBTYCmdguk3Y8agvzRLZP82/V0PcU7HY9taDuwMJ/pIwdtmNoL9OyfMBXoE5EkSAowdfx4h8szH8WXfmil9Z3QpyNOJ3R25ivQs8+l4wZNgZ6ewPVXBRIF4qccyeZdWwWmFSjQ04rW31YFCvRW+bv5tAIFelrR+tuqQIHeKn83n1agQE8rWn9bFSjQW+Xv5tMKFOhpRetvqwIFeqv83XxagQI9rWj9bVWgQG+Vv5tPK1CgpxWtv60KFOit8nfzaQUK9LSi9bdVgQK9Vf5uPq3AP3PXlwIEkq/DAAAAAElFTkSuQmCC"
}
}));
import { storiesOf, moduleMetadata } from "@storybook/angular";
import { boolean, withKnobs } from "@storybook/addon-knobs";
import { StoreModule } from "@ngrx/store";
import { VerifyMfaComponent } from "../app/login/verify-mfa/verify-mfa.component";
import * as fromVerifyMfa from "../app/login/verify-mfa/verify-mfa.reducer";
import { SharedModule } from "../app/shared/shared.module";
import { HttpClientModule } from "@angular/common/http";
import { InlineSVGModule } from "ng-inline-svg";
import { LoginComponent } from "../app/login/login.component";
import * as fromLogin from "../app/login/login.reducer";
import { NgrxFormsModule } from "ngrx-forms";
import { RouterTestingModule } from "@angular/router/testing";
import { ProgressIndicatorModule } from "../app/progress-indicator/progress-indicator.module";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
storiesOf("Login", module)
.addDecorator(withKnobs)
.addDecorator(
moduleMetadata({
imports: [
SharedModule,
StoreModule.forRoot({}),
HttpClientModule,
InlineSVGModule.forRoot(),
HttpClientModule,
NgrxFormsModule,
SharedModule,
RouterTestingModule,
ProgressIndicatorModule,
BrowserAnimationsModule,
StoreModule.forRoot({})
]
})
)
.add("Login", () => ({
component: LoginComponent,
props: {
form: fromLogin.initialLoginFormState.form
}
}))
.add("Verify MFA", () => {
return {
component: VerifyMfaComponent,
props: {
form: fromVerifyMfa.initialState.form,
hasStarted: boolean("hasStarted", true),
hasFinished: boolean("hasFinished", true)
}
};
});