implement all register/login flow components

This commit is contained in:
Kyle Spearrin 2018-04-04 14:19:44 -04:00
parent 4e20efdee1
commit 167acd1929
30 changed files with 949 additions and 257 deletions

View File

@ -17,7 +17,8 @@ const paths = {
coverage: './coverage/',
npmDir: './node_modules/',
popupDir: './src/popup/',
cssDir: './src/popup/css/'
cssDir: './src/popup/css/',
cssDir2: './src/css/'
};
const filters = {
@ -181,6 +182,8 @@ function safariZip(buildPath) {
gulp.task('build', ['lint', 'webfonts']);
gulp.task('build2', ['webfonts2']);
gulp.task('webfonts', () => {
return gulp.src('./webfonts.list')
.pipe(googleWebFonts({
@ -190,6 +193,15 @@ gulp.task('webfonts', () => {
.pipe(gulp.dest(paths.cssDir));
});
gulp.task('webfonts2', () => {
return gulp.src('./webfonts.list')
.pipe(googleWebFonts({
fontsDir: 'webfonts',
cssFilename: 'webfonts.css'
}))
.pipe(gulp.dest(paths.cssDir2));
});
gulp.task('ci', ['ci:coverage']);
gulp.task('ci:coverage', (cb) => {

2
jslib

@ -1 +1 @@
Subproject commit f855a8272c7dc3c3099643cf34e49d3089e6575e
Subproject commit f673bd62d7abb773fa5a6abfb5307b7c3feca59b

View File

@ -9,8 +9,8 @@
"start:firefox": "web-ext run --source-dir ./dist/",
"dev": "gulp build && webpack --config webpack.dev.js",
"dev:watch": "gulp build && webpack --config webpack.dev.js --watch",
"dev2": "webpack --config webpack2.js",
"dev2:watch": "webpack --config webpack2.js --watch",
"dev2": "gulp build2 && webpack --config webpack2.js",
"dev2:watch": "gulp build2 && webpack --config webpack2.js --watch",
"prod": "gulp build && webpack --config webpack.prod.js",
"dist": "npm run prod && gulp dist",
"dist:firefox": "npm run prod && gulp dist:firefox",

View File

@ -668,7 +668,22 @@
"message": "Enter the 6 digit verification code from your authenticator app."
},
"enterVerificationCodeEmail": {
"message": "Enter the 6 digit verification code that was emailed to"
"message": "Enter the 6 digit verification code that was emailed to $EMAIL$.",
"placeholders": {
"email": {
"content": "$1",
"example": "example@gmail.com"
}
}
},
"verificationCodeEmailSent": {
"message": "Verification email sent to $EMAIL$.",
"placeholders": {
"email": {
"content": "$1",
"example": "example@gmail.com"
}
}
},
"rememberMe": {
"message": "Remember me"

View File

@ -1,59 +1,57 @@
<div class="modal fade">
<div class="modal-dialog">
<form class="modal-content" (ngSubmit)="submit()">
<div class="modal-body">
<div class="box">
<div class="box-header">
{{'selfHostedEnvironment' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="baseUrl">{{'baseUrl' | i18n}}</label>
<input id="baseUrl" type="text" name="BaseUrl" [(ngModel)]="baseUrl"
placeholder="ex. https://bitwarden.company.com">
</div>
</div>
<div class="box-footer">
{{'selfHostedEnvironmentFooter' | i18n}}
</div>
</div>
<div class="box">
<div class="box-header">
<button type="button" (click)="toggleCustom()">
<i class="fa fa-plus-square-o" [hidden]="showCustom"></i>
<i class="fa fa-minus-square-o" [hidden]="!showCustom"></i>
{{'customEnvironment' | i18n}}
</button>
</div>
<div class="box-content" [hidden]="!showCustom">
<div class="box-content-row" appBoxRow>
<label for="webVaultUrl">{{'webVaultUrl' | i18n}}</label>
<input id="webVaultUrl" type="text" name="WebVaultUrl" [(ngModel)]="webVaultUrl">
</div>
<div class="box-content-row" appBoxRow>
<label for="apiUrl">{{'apiUrl' | i18n}}</label>
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl">
</div>
<div class="box-content-row" appBoxRow>
<label for="identityUrl">{{'identityUrl' | i18n}}</label>
<input id="identityUrl" type="text" name="IdentityUrl" [(ngModel)]="identityUrl">
</div>
<div class="box-content-row" appBoxRow>
<label for="iconsUrl">{{'iconsUrl' | i18n}}</label>
<input id="iconsUrl" type="text" name="IconsUrl" [(ngModel)]="iconsUrl">
</div>
</div>
<div class="box-footer" [hidden]="!showCustom">
{{'customEnvironmentFooter' | i18n}}
</div>
</div>
<form #form class="modal-content" (ngSubmit)="submit()">
<header>
<div class="left">
<a routerLink="/">{{'close' | i18n}}</a>
</div>
<div class="center">
<span class="title">{{'appName' | i18n}}</span>
</div>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'save' | i18n}}</span>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading"></i>
</button>
</div>
</header>
<div class="box">
<div class="box-header">
{{'selfHostedEnvironment' | i18n}}
</div>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="baseUrl">{{'baseUrl' | i18n}}</label>
<input id="baseUrl" type="text" name="BaseUrl" [(ngModel)]="baseUrl"
placeholder="ex. https://bitwarden.company.com">
</div>
<div class="modal-footer">
<button appBlurClick type="submit" class="primary" title="{{'save' | i18n}}">
<i class="fa fa-save fa-lg fa-fw"></i>
</button>
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
</form>
</div>
<div class="box-footer">
{{'selfHostedEnvironmentFooter' | i18n}}
</div>
</div>
</div>
<div class="box">
<div class="box-header">
{{'customEnvironment' | i18n}}
</div>
<div class="box-content" [hidden]="!showCustom">
<div class="box-content-row" appBoxRow>
<label for="webVaultUrl">{{'webVaultUrl' | i18n}}</label>
<input id="webVaultUrl" type="text" name="WebVaultUrl" [(ngModel)]="webVaultUrl">
</div>
<div class="box-content-row" appBoxRow>
<label for="apiUrl">{{'apiUrl' | i18n}}</label>
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl">
</div>
<div class="box-content-row" appBoxRow>
<label for="identityUrl">{{'identityUrl' | i18n}}</label>
<input id="identityUrl" type="text" name="IdentityUrl" [(ngModel)]="identityUrl">
</div>
<div class="box-content-row" appBoxRow>
<label for="iconsUrl">{{'iconsUrl' | i18n}}</label>
<input id="iconsUrl" type="text" name="IconsUrl" [(ngModel)]="iconsUrl">
</div>
</div>
<div class="box-footer" [hidden]="!showCustom">
{{'customEnvironmentFooter' | i18n}}
</div>
</div>
</form>

View File

@ -20,10 +20,11 @@ export class EnvironmentComponent extends BaseEnvironmentComponent {
environmentService: EnvironmentService, i18nService: I18nService,
private router: Router) {
super(analytics, toasterService, environmentService, i18nService);
this.showCustom = true;
}
saved() {
super.saved();
this.router.navigate(['login']);
this.router.navigate(['']);
}
}

View File

@ -1,23 +1,27 @@
<form id="hint-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<h1>{{'passwordHint' | i18n}}</h1>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required appAutofocus>
</div>
</div>
<div class="box-footer">
{{'enterEmailToGetHint' | i18n}}
</div>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header>
<div class="left">
<a routerLink="/login">{{'cancel' | i18n}}</a>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{'submit' | i18n}}</b>
<div class="center">
<span class="title">{{'passwordHint' | i18n}}</span>
</div>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'submit' | i18n}}</span>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading"></i>
</button>
<a routerLink="/login" class="btn block">{{'cancel' | i18n}}</a>
</div>
</header>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required appAutofocus>
</div>
</div>
<div class="box-footer">
{{'enterEmailToGetHint' | i18n}}
</div>
</div>
</form>

View File

@ -0,0 +1,15 @@
<div class="home-page">
<a routerLink="/environment" class="settings-icon">
<i class="fa fa-cog fa-lg"></i><span>&nbsp;{{'settings' | i18n}}</span>
</a>
<img src="../images/logo@2x.png" alt="bitwarden" />
<p>{{'loginOrCreateNewAccount' | i18n}}</p>
<div class="bottom-buttons">
<a class="btn btn-lg btn-primary btn-block" routerLink="/register">
<strong>{{'createAccount' | i18n}}</strong>
</a>
<a class="btn btn-lg btn-link btn-block" routerLink="/login">
{{'login' | i18n}}
</a>
</div>
</div>

View File

@ -0,0 +1,18 @@
import * as template from './home.component.html';
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { ToasterService } from 'angular2-toaster';
import { Angulartics2 } from 'angulartics2';
import { I18nService } from 'jslib/abstractions/i18n.service';
@Component({
selector: 'app-home',
template: template,
})
export class HomeComponent {
constructor(private router: Router, i18nService: I18nService,
analytics: Angulartics2, toasterService: ToasterService) { }
}

View File

@ -1,43 +1,42 @@
<form id="login-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<p class="lead">{{'loginOrCreateNewAccount' | i18n}}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required
[appAutofocus]="email === ''">
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header>
<div class="left">
<a routerLink="/">{{'cancel' | i18n}}</a>
</div>
<div class="center">
<span class="title">{{'appName' | i18n}}</span>
</div>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'login' | i18n}}</span>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading"></i>
</button>
</div>
</header>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required
[appAutofocus]="email === ''">
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
class="monospaced" [(ngModel)]="masterPassword" required [appAutofocus]="email !== ''">
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
class="monospaced" [(ngModel)]="masterPassword" required [appAutofocus]="email !== ''">
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick
title="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick
title="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<span [hidden]="form.loading"><i class="fa fa-sign-in"></i> {{'logIn' | i18n}}</span>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading"></i>
</button>
<a routerLink="/register" class="btn block">
<i class="fa fa-pencil-square-o"></i> {{'createAccount' | i18n}}
</a>
</div>
<div class="sub-options">
<a routerLink="/hint">{{'getMasterPasswordHint' | i18n}}</a>
</div>
<a href="#" appStopClick (click)="settings()" class="settings-icon">
<i class="fa fa-cog fa-lg"></i><span>&nbsp;{{'settings' | i18n}}</span>
</a>
</div>
<p class="text-center">
<a routerLink="/hint">{{'getMasterPasswordHint' | i18n}}</a>
</p>
</form>

View File

@ -1,64 +1,68 @@
<form id="register-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<h1>{{'createAccount' | i18n}}</h1>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required
[appAutofocus]="email === ''">
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
class="monospaced" [(ngModel)]="masterPassword" required [appAutofocus]="email !== ''">
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick
title="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
<i class="fa fa-lg"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
</div>
<div class="box-footer">
{{'masterPassDesc' | i18n}}
</div>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header>
<div class="left">
<a routerLink="/">{{'cancel' | i18n}}</a>
</div>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPasswordRetype" class="monospaced" [(ngModel)]="confirmMasterPassword"
required>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick
title="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)">
<i class="fa fa-lg"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
</div>
</div>
<div class="box-footer">
{{'masterPassHintDesc' | i18n}}
</div>
<div class="center">
<span class="title">{{'createAccount' | i18n}}</span>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick>
<b [hidden]="form.loading">{{'submit' | i18n}}</b>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'submit' | i18n}}</span>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading"></i>
</button>
<a routerLink="/login" class="btn block">{{'cancel' | i18n}}</a>
</div>
</header>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required
[appAutofocus]="email === ''">
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
class="monospaced" [(ngModel)]="masterPassword" required [appAutofocus]="email !== ''">
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick
title="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
<i class="fa fa-lg"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
</div>
<div class="box-footer">
{{'masterPassDesc' | i18n}}
</div>
</div>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPasswordRetype" class="monospaced" [(ngModel)]="confirmMasterPassword"
required>
</div>
<div class="action-buttons">
<a class="row-btn" href="#" appStopClick appBlurClick
title="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)">
<i class="fa fa-lg"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</a>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
</div>
</div>
<div class="box-footer">
{{'masterPassHintDesc' | i18n}}
</div>
</div>
</form>

View File

@ -1,28 +1,22 @@
<div class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<div class="box">
<div class="box-header">
{{'twoStepOptions' | i18n}}
</div>
<div class="box-content">
<a href="#" appStopClick *ngFor="let p of providers" class="box-content-row"
(click)="choose(p)">
<img [src]="'images/two-factor/' + p.type + '.png'" alt="" class="img-right">
<span class="text">{{p.name}}</span>
<span class="detail">{{p.description}}</span>
</a>
<a href="#" appStopClick class="box-content-row" (click)="recover()">
<span class="text">{{'recoveryCodeTitle' | i18n}}</span>
<span class="detail">{{'recoveryCodeDesc' | i18n}}</span>
</a>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
</div>
<header>
<div class="left">
<a routerLink="/2fa">{{'close' | i18n}}</a>
</div>
<div class="center">
<span class="title">{{'twoStepOptions' | i18n}}</span>
</div>
<div class="right"></div>
</header>
<div class="box">
<div class="box-content">
<a href="#" appStopClick *ngFor="let p of providers" class="box-content-row"
(click)="choose(p)">
<span class="text">{{p.name}}</span>
<span class="detail">{{p.description}}</span>
</a>
<a href="#" appStopClick class="box-content-row" (click)="recover()">
<span class="text">{{'recoveryCodeTitle' | i18n}}</span>
<span class="detail">{{'recoveryCodeDesc' | i18n}}</span>
</a>
</div>
</div>

View File

@ -24,4 +24,10 @@ export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
i18nService: I18nService, platformUtilsService: PlatformUtilsService) {
super(authService, router, analytics, toasterService, i18nService, platformUtilsService);
}
choose(p: any) {
super.choose(p);
this.authService.selectedTwoFactorProviderType = p.type;
this.router.navigate(['2fa']);
}
}

View File

@ -1,12 +1,29 @@
<form id="two-factor-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<h1>{{title}}</h1>
<p *ngIf="selectedProviderType === providerType.Authenticator">{{'enterVerificationCodeApp' | i18n}}</p>
<p *ngIf="selectedProviderType === providerType.Email">
{{'enterVerificationCodeEmail' | i18n : twoFactorEmail}}
</p>
<div class="box last"
*ngIf="selectedProviderType === providerType.Email || selectedProviderType === providerType.Authenticator">
<header>
<div class="left">
<a routerLink="/login">{{'cancel' | i18n}}</a>
</div>
<div class="center">
<span class="title">{{title}}</span>
</div>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading"
*ngIf="selectedProviderType != null && selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo">
<span [hidden]="form.loading">{{'continue' | i18n}}</span>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading"></i>
</button>
</div>
</header>
<ng-container *ngIf="selectedProviderType === providerType.Authenticator ||
selectedProviderType === providerType.Email">
<div class="content">
<p *ngIf="selectedProviderType === providerType.Authenticator">{{'enterVerificationCodeApp' | i18n}}</p>
<p *ngIf="selectedProviderType === providerType.Email">
{{'enterVerificationCodeEmail' | i18n : twoFactorEmail}}
</p>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code">{{'verificationCode' | i18n}}</label>
@ -18,57 +35,49 @@
</div>
</div>
</div>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
<div class="content">
<p>{{'insertYubiKey' | i18n}}</p>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code" class="sr-only">{{'verificationCode' | i18n}}</label>
<input id="code" type="password" name="Code" [(ngModel)]="token" required appAutofocus>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo">
<div id="duo-frame"><iframe id="duo_iframe"></iframe></div>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<div class="box last" *ngIf="selectedProviderType == null">
<img src="../images/yubikey.jpg" class="img-rounded img-responsive" alt="">
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row">
<p>{{'noTwoStepProviders' | i18n}}</p>
<p>{{'noTwoStepProviders2' | i18n}}</p>
<div class="box-content-row" appBoxRow>
<label for="code" class="sr-only">{{'verificationCode' | i18n}}</label>
<input id="code" type="password" name="Code" [(ngModel)]="token" required appAutofocus>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading" appBlurClick
*ngIf="selectedProviderType != null && selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo">
<span [hidden]="form.loading"><i class="fa fa-sign-in"></i> {{'continue' | i18n}}</span>
<i class="fa fa-spinner fa-spin" [hidden]="!form.loading"></i>
</button>
<a routerLink="/login" class="btn block">{{'cancel' | i18n}}</a>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo">
<div id="duo-frame"><iframe id="duo_iframe"></iframe></div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
<div class="sub-options">
</ng-container>
<div class="content" *ngIf="selectedProviderType == null">
<p>{{'noTwoStepProviders' | i18n}}</p>
<p>{{'noTwoStepProviders2' | i18n}}</p>
</div>
<div class="content">
<p class="text-center">
<a href="#" appStopClick (click)="anotherMethod()">{{'useAnotherTwoStepMethod' | i18n}}</a>
<a href="#" appStopClick (click)="sendEmail(true)" [appApiAction]="emailPromise"
*ngIf="selectedProviderType === providerType.Email">
</p>
<p class="text-center" *ngIf="selectedProviderType === providerType.Email">
<a href="#" appStopClick (click)="sendEmail(true)" [appApiAction]="emailPromise">
{{'sendVerificationCodeEmailAgain' | i18n}}
</a>
</div>
</p>
</div>
</form>
<ng-template #twoFactorOptions></ng-template>

View File

@ -6,13 +6,14 @@ import {
import { EnvironmentComponent } from './accounts/environment.component';
import { HintComponent } from './accounts/hint.component';
import { HomeComponent } from './accounts/home.component';
import { LoginComponent } from './accounts/login.component';
import { RegisterComponent } from './accounts/register.component';
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
import { TwoFactorComponent } from './accounts/two-factor.component';
const routes: Routes = [
{ path: '', redirectTo: '/login', pathMatch: 'full' },
{ path: '', component: HomeComponent },
{ path: 'login', component: LoginComponent },
{ path: '2fa', component: TwoFactorComponent },
{ path: '2fa-options', component: TwoFactorOptionsComponent },

View File

@ -30,8 +30,10 @@ export class AppComponent {
toasterConfig: ToasterConfig = new ToasterConfig({
showCloseButton: true,
mouseoverTimerStop: true,
animation: 'flyRight',
limit: 5,
animation: 'slideUp',
limit: 2,
positionClass: 'toast-bottom-full-width',
newestOnTop: false
});
constructor(private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics, private analytics: Angulartics2,

View File

@ -17,6 +17,7 @@ import { AppComponent } from './app.component';
import { EnvironmentComponent } from './accounts/environment.component';
import { HintComponent } from './accounts/hint.component';
import { HomeComponent } from './accounts/home.component';
import { LoginComponent } from './accounts/login.component';
import { RegisterComponent } from './accounts/register.component';
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
@ -25,6 +26,7 @@ import { TwoFactorComponent } from './accounts/two-factor.component';
import { ApiActionDirective } from 'jslib/angular/directives/api-action.directive';
import { AutofocusDirective } from 'jslib/angular/directives/autofocus.directive';
import { BlurClickDirective } from 'jslib/angular/directives/blur-click.directive';
import { BoxRowDirective } from 'jslib/angular/directives/box-row.directive';
import { FallbackSrcDirective } from 'jslib/angular/directives/fallback-src.directive';
import { StopClickDirective } from 'jslib/angular/directives/stop-click.directive';
import { StopPropDirective } from 'jslib/angular/directives/stop-prop.directive';
@ -50,8 +52,10 @@ import { I18nPipe } from 'jslib/angular/pipes/i18n.pipe';
AppComponent,
AutofocusDirective,
BlurClickDirective,
BoxRowDirective,
EnvironmentComponent,
FallbackSrcDirective,
HomeComponent,
HintComponent,
I18nPipe,
LoginComponent,

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 100% 100%">
<text fill="%23333333" x="50%" y="50%" font-family="\'Open Sans\', \'Helvetica Neue\', Helvetica, Arial, sans-serif"
font-size="18" text-anchor="middle">
Loading...
</text>
</svg>

BIN
src/popup2/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

143
src/scss/base.scss Normal file
View File

@ -0,0 +1,143 @@
@import "variables.scss";
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html, body {
background-color: $background-color-alt2;
font-family: $font-family-sans-serif;
font-size: $font-size-base;
color: $text-color;
line-height: $line-height-base;
-webkit-font-smoothing: antialiased;
}
body {
width: 375px !important;
height: 600px !important;
overflow: hidden;
&.sm {
width: 375px !important;
height: 500px !important;
}
&.xs {
width: 375px !important;
height: 300px !important;
}
}
h1, h2, h3, h4, h5, h6 {
font-family: $font-family-sans-serif;
color: $text-color;
}
p {
margin-bottom: 10px;
}
ul, ol {
margin-bottom: 10px;
}
img {
border: none;
}
a {
color: $brand-primary;
text-decoration: none;
&:hover, &:focus {
color: darken($brand-primary, 6%);
}
}
input, select, textarea, button {
font-size: $font-size-base;
font-family: $font-family-sans-serif;
}
button {
white-space: nowrap;
cursor: pointer;
}
textarea {
resize: vertical;
}
div::-webkit-scrollbar {
width: 10px;
height: 10px;
}
div::-webkit-scrollbar-track {
background-color: transparent;
}
div::-webkit-scrollbar-thumb {
background-color: rgba(100,100,100,.2);
border-radius: 10px;
margin-right: 1px;
&:hover {
background-color: rgba(100,100,100,.4);
}
}
header {
background-color: $brand-primary;
min-height: 44px;
max-height: 44px;
color: white;
display: flex;
.left, .right {
flex: 1;
display: flex;
min-width: -webkit-min-content; /* Workaround to Chrome bug */
}
.right {
justify-content: flex-end;
}
.center {
display: flex;
align-items: center;
text-align: center;
}
button, a {
background: $brand-primary;
border: none;
color: white;
padding: 0 10px;
text-decoration: none;
display: flex;
flex-direction: column;
justify-content: center;
&:hover, &:focus {
background-color: rgba(255, 255, 255, 0.1);
color: white;
}
&:focus {
text-decoration: underline;
}
}
.title {
font-weight: bold;
}
}
.content {
padding: 15px;
}

254
src/scss/box.scss Normal file
View File

@ -0,0 +1,254 @@
@import "variables.scss";
.box {
width: 100%;
margin: 10px 0;
.box-header {
margin: 0 10px 5px 10px;
color: $gray-light;
text-transform: uppercase;
}
.box-content {
background: $box-background-color;
border-top: 1px solid $border-color-dark;
border-bottom: 1px solid $border-color-dark;
&.box-content-padded {
padding: 10px 15px;
}
.box-content-row {
display: block;
padding: 10px 15px;
position: relative;
z-index: 1;
&:before {
content: "";
position: absolute;
right: 0;
bottom: 0;
height: 1px;
width: calc(100% - 10px);
border-bottom: 1px solid $box-border-color;
}
&:first-child, &:last-child {
border-radius: $border-radius;
}
&:last-child {
&:before {
border: none;
height: 0;
}
}
&:after {
content: "";
display: table;
clear: both;
}
&:hover, &:focus, &.active {
background-color: $box-background-hover-color;
}
&.pre {
white-space: pre;
overflow-x: auto;
}
.row-label, label {
font-size: $font-size-small;
color: $text-muted;
display: block;
width: 100%;
margin-bottom: 5px;
}
.text, .detail {
display: block;
color: $text-color;
}
.detail {
font-size: $font-size-small;
color: $text-muted;
}
.img-right {
float: right;
margin-left: 10px;
}
.row-main {
flex-grow: 1;
}
&.box-content-row-flex, &.box-content-row-checkbox, &.box-content-row-input,
&.box-content-row-slider, &.box-content-row-multi {
display: flex;
align-items: center;
word-break: break-word;
}
&.box-content-row-multi {
width: 100%;
input:not([type="checkbox"]) {
width: 100%;
}
input + label.sr-only + select {
margin-top: 5px;
}
> a {
padding: 8px 8px 8px 4px;
color: $brand-danger;
margin: 0;
}
}
&.box-content-row-checkbox, &.box-content-row-input, &.box-content-row-slider {
label, .row-label {
font-size: $font-size-base;
color: $text-color;
display: inline;
width: initial;
margin-bottom: 0;
}
> span {
color: $text-muted;
}
> input {
margin: 0 0 0 auto;
padding: 0;
}
> * {
margin-right: 15px;
&:last-child {
margin-right: 0;
}
}
}
&.box-content-row-input {
label {
white-space: nowrap;
}
input {
text-align: right;
}
}
&.box-content-row-slider {
input[type="range"] {
height: 10px;
}
input[type="number"] {
width: 45px;
}
label {
white-space: nowrap;
}
}
input:not([type="checkbox"]), textarea {
border: none;
width: 100%;
background-color: transparent;
&::-webkit-input-placeholder {
color: lighten($gray-light, 35%);
}
&:focus {
outline: none;
}
}
select {
width: 100%;
border: 1px solid darken($border-color-dark, 7%);
border-radius: $border-radius;
}
.action-buttons {
display: flex;
margin-left: 5px;
.row-btn {
cursor: pointer;
padding: 10px 8px;
background: none;
border: none;
color: $brand-primary;
&:hover, &:focus {
color: darken($brand-primary, 10%);
}
&.disabled {
color: $list-icon-color;
&:hover {
color: $list-icon-color;
}
}
&:last-child {
padding-right: 2px !important;
}
}
&.no-pad .row-btn {
padding-top: 0;
padding-bottom: 0;
}
}
select.field-type {
margin: 5px 0 0 25px;
width: calc(100% - 25px);
}
.row-sub-icon {
color: $list-icon-color;
}
.row-sub-label {
margin: 0 15px;
color: $gray-light;
white-space: nowrap;
}
}
&.condensed .box-content-row, .box-content-row.condensed {
padding-top: 5px;
padding-bottom: 5px;
}
&.no-hover .box-content-row, .box-content-row.no-hover {
&:hover, &:focus {
background-color: initial;
}
}
}
.box-footer {
margin: 5px 10px;
font-size: $font-size-small;
color: $text-muted;
}
}

View File

@ -0,0 +1,8 @@
@import "variables.scss";
html.browser_firefox, html.browser_edge {
body {
width: 320px !important;
height: 568px !important;
}
}

76
src/scss/misc.scss Normal file
View File

@ -0,0 +1,76 @@
@import "variables.scss";
small {
font-size: $font-size-small;
}
.text-primary {
color: $brand-primary !important;
}
.text-success {
color: $brand-success !important;
}
.text-muted {
color: $text-muted !important;
}
.text-default {
color: $text-color !important;
}
.text-accent {
color: $brand-primary-accent;
}
.text-center {
text-align: center;
}
.no-margin {
margin: 0 !important;
}
[hidden] {
display: none !important;
}
.monospaced {
font-family: $font-family-monospace;
}
.img-responsive {
display: block;
max-width: 100%;
height: auto;
}
.img-rounded {
border-radius: 6px;
}
.sr-only {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
border: 0 !important;
}
#duo-frame {
// TODO
}
app-root > #loading {
display: flex;
text-align: center;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
color: $text-muted;
}

81
src/scss/plugins.scss Normal file
View File

@ -0,0 +1,81 @@
$fa-font-path: "~font-awesome/fonts";
@import "~font-awesome/scss/font-awesome.scss";
@import "~angular2-toaster/toaster";
@import "variables.scss";
#toast-container {
.toast-close-button {
right: -0.15em;
}
&.toast-bottom-full-width div.toast {
margin: 0 10px 10px;
width: calc(100% - 20px);
}
.toast {
opacity: 1 !important;
background-image: none !important;
border-radius: $border-radius;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.35);
display: flex;
align-items: center;
&:hover {
box-shadow: 0 0 10px rgba(0, 0, 0, 0.6);
}
&:before {
position: fixed;
font-family: FontAwesome;
font-size: 25px;
line-height: 20px;
float: left;
color: #ffffff;
padding-right: 0.5em;
margin: auto 0 auto -36px;
}
.toaster-icon {
display: none;
}
&.toast-danger, &.toast-error {
background-image: none !important;
background-color: $brand-danger;
&:before {
content: "\f0e7";
margin-left: -30px;
}
}
&.toast-warning {
background-image: none !important;
background-color: $brand-warning;
&:before {
content: "\f071";
}
}
&.toast-info {
background-image: none !important;
background-color: $brand-info;
&:before {
content: "\f05a";
}
}
&.toast-success {
background-image: none !important;
background-color: $brand-success;
&:before {
content: "\f00C";
}
}
}
}

View File

@ -1,3 +1,7 @@
body {
color: black;
}
@import "../css/webfonts.css";
@import "variables.scss";
@import "base.scss";
@import "box.scss";
@import "misc.scss";
@import "plugins.scss";
@import "environment.scss";

38
src/scss/variables.scss Normal file
View File

@ -0,0 +1,38 @@
$font-family-sans-serif: 'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;
$font-family-monospace: Menlo, Monaco, Consolas, 'Courier New', monospace;
$font-size-base: 14px;
$font-size-large: 18px;
$font-size-small: 12px;
$text-color: #000000;
$background-color: #efeff4;
$border-color: #f0f0f0;
$border-color-dark: #ddd;
$list-item-hover: #fbfbfb;
$list-icon-color: #c7c7cd;
$border-radius: 3px;
$line-height-base: 1.42857143;
$gray: #555;
$gray-light: #777;
$text-muted: $gray-light;
$brand-primary: #3c8dbc;
$brand-danger: #dd4b39;
$brand-success: #00a65a;
$brand-info: #555555;
$brand-warning: #bf7e16;
$brand-primary-accent: #286090;
$background-color: white;
$background-color-alt: #f9fafc;
$background-color-alt2: #ecf0f5;
$box-background-color: $background-color;
$box-background-hover-color: $list-item-hover;
$box-border-color: $border-color;
$button-border-color: darken($border-color-dark, 12%);
$button-backgound-color: white;
$button-color: lighten($text-color, 40%);
$button-color-primary: darken($brand-primary, 8%);
$button-color-danger: darken($brand-danger, 10%);