mirror of
https://github.com/bitwarden/web
synced 2025-12-06 00:03:28 +00:00
Compare commits
171 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
179884cf93 | ||
|
|
ca43db8d93 | ||
|
|
f4cb5e6632 | ||
|
|
da2e740e65 | ||
|
|
2f0d2bdf32 | ||
|
|
97eedb2034 | ||
|
|
3ac46e62cb | ||
|
|
97db3635af | ||
|
|
e3464da19a | ||
|
|
ec3ee8fbb3 | ||
|
|
96208d3760 | ||
|
|
5bb61c0730 | ||
|
|
858f86d9df | ||
|
|
aa1e5a11ad | ||
|
|
ded8865914 | ||
|
|
da1437a268 | ||
|
|
599f831a09 | ||
|
|
23b532e2bf | ||
|
|
9f1b8ae58f | ||
|
|
d62850f82d | ||
|
|
41a0cfd0a2 | ||
|
|
fb6e85c56b | ||
|
|
d58550c2b8 | ||
|
|
5bf3ca2708 | ||
|
|
3e4a7e7a56 | ||
|
|
5d17de227b | ||
|
|
0d985c0221 | ||
|
|
eaa6bc12ce | ||
|
|
b3337df774 | ||
|
|
6c8c5bcde6 | ||
|
|
d255f6add4 | ||
|
|
84dde72990 | ||
|
|
5dfeee548d | ||
|
|
09516b4d4e | ||
|
|
b7b74d8f1f | ||
|
|
80d3cd3126 | ||
|
|
bbd416ba24 | ||
|
|
75563660f0 | ||
|
|
c2197bcc53 | ||
|
|
12114c786b | ||
|
|
73c192ad18 | ||
|
|
465564325e | ||
|
|
7c0d093be5 | ||
|
|
a1fbe6b970 | ||
|
|
305d86f765 | ||
|
|
e7e5816ded | ||
|
|
cd9b1b906c | ||
|
|
0b5a74aa9f | ||
|
|
c3407ac35a | ||
|
|
c9699647d7 | ||
|
|
aac011d3b3 | ||
|
|
e2108ff85b | ||
|
|
5c492f893b | ||
|
|
2877b3c63d | ||
|
|
1d94185078 | ||
|
|
a27eddae56 | ||
|
|
5ed830205d | ||
|
|
aeca6f04f9 | ||
|
|
c099ff7662 | ||
|
|
83ba366558 | ||
|
|
6129fdb6e5 | ||
|
|
8db66bf282 | ||
|
|
b7cd18b715 | ||
|
|
6ed991593a | ||
|
|
ccf3d49fc4 | ||
|
|
7e95e44f1d | ||
|
|
a5de11d002 | ||
|
|
756bd82a46 | ||
|
|
f9ce4a2f81 | ||
|
|
088301c4be | ||
|
|
f7f70408c9 | ||
|
|
292d713423 | ||
|
|
e02eadc9f7 | ||
|
|
6e66df59b7 | ||
|
|
00b9f4cab6 | ||
|
|
f6fb56229e | ||
|
|
5b770084c9 | ||
|
|
a2472e0cf5 | ||
|
|
4de7b52044 | ||
|
|
1e100d1bf1 | ||
|
|
d00fb9e0a5 | ||
|
|
f5d8673ad4 | ||
|
|
bd2cba1f31 | ||
|
|
45c07b7c39 | ||
|
|
36244d58aa | ||
|
|
e968d5a2a5 | ||
|
|
84df9cca87 | ||
|
|
e550989ce2 | ||
|
|
94edc1e284 | ||
|
|
b9f8cad578 | ||
|
|
02eb382ae7 | ||
|
|
1ecc092f08 | ||
|
|
191fa922d2 | ||
|
|
fb817f1ca7 | ||
|
|
9c2f128585 | ||
|
|
9ebd700317 | ||
|
|
9ab6cf31fd | ||
|
|
bb5c114b8d | ||
|
|
1f2a724d32 | ||
|
|
9b28203757 | ||
|
|
ac9f30f5f0 | ||
|
|
b13b0a66ce | ||
|
|
fcfdd5bc76 | ||
|
|
cdbbc37d59 | ||
|
|
4ba4af7cf9 | ||
|
|
89708d1fd6 | ||
|
|
6cb48c186e | ||
|
|
a1c9c47c89 | ||
|
|
85cc2865b6 | ||
|
|
2dc74b26f3 | ||
|
|
3d0ed43920 | ||
|
|
dc54943a19 | ||
|
|
c6ae5368fe | ||
|
|
c947354517 | ||
|
|
076f01b65f | ||
|
|
e37292a276 | ||
|
|
7d76473580 | ||
|
|
8bafbbd2ff | ||
|
|
80c5dff5ad | ||
|
|
a4571a2617 | ||
|
|
18608a8b63 | ||
|
|
c9116ad7ab | ||
|
|
d982902986 | ||
|
|
3ab6868460 | ||
|
|
8d5974d0f8 | ||
|
|
35a64afdf9 | ||
|
|
1ed850324d | ||
|
|
8f886df84f | ||
|
|
55481b255b | ||
|
|
b0b9d8445e | ||
|
|
3a2f04006f | ||
|
|
1aacd4ece1 | ||
|
|
f0e3e3b6f9 | ||
|
|
26533713ff | ||
|
|
b55d54eb5b | ||
|
|
01cb57c9fb | ||
|
|
eb85464f8d | ||
|
|
d30fcf8dca | ||
|
|
004d14eaf4 | ||
|
|
d1a7c3390a | ||
|
|
132c4139ad | ||
|
|
0aa664fb4f | ||
|
|
d25dc1a23f | ||
|
|
3d5f22b67d | ||
|
|
cf6ae951d2 | ||
|
|
cca9384cd7 | ||
|
|
e7b2557bcd | ||
|
|
dad084b309 | ||
|
|
e7fea1b138 | ||
|
|
b24d7df789 | ||
|
|
c2f801b6a9 | ||
|
|
20112688ab | ||
|
|
2a19bdd8d1 | ||
|
|
2d95806feb | ||
|
|
40da48a106 | ||
|
|
df81d9fd5f | ||
|
|
1060775cad | ||
|
|
96cc9c681c | ||
|
|
b4200fba60 | ||
|
|
2ded5228cb | ||
|
|
7be58fb884 | ||
|
|
a29e9e11f7 | ||
|
|
18c89e4fa5 | ||
|
|
84dd370cfb | ||
|
|
b45c79d65b | ||
|
|
52a5086f7e | ||
|
|
3980dc7e84 | ||
|
|
ffd0608dda | ||
|
|
322bc90920 | ||
|
|
9685f2c2b3 | ||
|
|
342871a216 |
@@ -5,6 +5,7 @@ LABEL com.bitwarden.product="bitwarden"
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
gosu \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV ASPNETCORE_URLS http://+:5000
|
||||
@@ -14,4 +15,6 @@ COPY ./build .
|
||||
COPY entrypoint.sh /
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
HEALTHCHECK CMD curl -f http://localhost:5000 || exit 1
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
11
gulpfile.js
11
gulpfile.js
@@ -30,17 +30,8 @@ function version(cb) {
|
||||
cb();
|
||||
}
|
||||
|
||||
// ref: https://github.com/t4t5/sweetalert/issues/890
|
||||
function fixSweetAlert(cb) {
|
||||
fs.writeFileSync(paths.node_modules + 'sweetalert/typings/sweetalert.d.ts',
|
||||
'import swal, { SweetAlert } from "./core";export default swal;export as namespace swal;');
|
||||
cb();
|
||||
}
|
||||
|
||||
exports.clean = clean;
|
||||
exports.webfonts = gulp.series(clean, webfonts);
|
||||
exports.prebuild = gulp.series(clean, webfonts);
|
||||
exports.version = version;
|
||||
exports.postdist = version;
|
||||
exports.fixSweetAlert = fixSweetAlert;
|
||||
exports.postinstall = fixSweetAlert;
|
||||
exports.postdist = version;
|
||||
2
jslib
2
jslib
Submodule jslib updated: a884f77938...2858724f44
4254
package-lock.json
generated
4254
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
@@ -1,11 +1,16 @@
|
||||
{
|
||||
"name": "bitwarden-web",
|
||||
"version": "2.10.0",
|
||||
"version": "2.14.0",
|
||||
"license": "GPL-3.0",
|
||||
"repository": "https://github.com/bitwarden/web",
|
||||
"scripts": {
|
||||
"sub:init": "git submodule update --init --recursive",
|
||||
"sub:update": "git submodule update --remote",
|
||||
"sub:pull": "git submodule foreach git pull",
|
||||
"postinstall": "npm run sub:init && gulp postinstall",
|
||||
"sub:pull": "git submodule foreach git pull origin master",
|
||||
"postinstall": "npm run sub:init",
|
||||
"symlink:win": "rm -rf ./jslib && cmd /c mklink /J .\\jslib ..\\jslib",
|
||||
"symlink:mac": "npm run symlink:lin",
|
||||
"symlink:lin": "rm -rf ./jslib && ln -s ../jslib ./jslib",
|
||||
"build": "gulp prebuild && webpack",
|
||||
"build:watch": "gulp prebuild && webpack-dev-server",
|
||||
"build:prod": "gulp prebuild && cross-env NODE_ENV=production webpack",
|
||||
@@ -23,10 +28,10 @@
|
||||
"lint:fix": "tslint src/**/*.ts --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/compiler-cli": "^7.2.1",
|
||||
"@angular/compiler-cli": "^7.2.11",
|
||||
"@ngtools/webpack": "^7.2.2",
|
||||
"@types/jquery": "^3.3.6",
|
||||
"@types/lunr": "^2.1.6",
|
||||
"@types/lunr": "^2.3.3",
|
||||
"@types/node-forge": "^0.7.5",
|
||||
"@types/papaparse": "^4.5.3",
|
||||
"@types/webcrypto": "^0.0.28",
|
||||
@@ -38,16 +43,17 @@
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^1.0.0",
|
||||
"del": "^3.0.0",
|
||||
"extract-text-webpack-plugin": "next",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"file-loader": "^2.0.0",
|
||||
"gh-pages": "^1.2.0",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-google-webfonts": "^2.0.0",
|
||||
"html-loader": "^0.5.5",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"node-sass": "^4.9.3",
|
||||
"node-sass": "^4.13.1",
|
||||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.23.0",
|
||||
"terser-webpack-plugin": "^1.2.3",
|
||||
"ts-loader": "^5.3.3",
|
||||
"tslint": "^5.12.1",
|
||||
"tslint-loader": "^3.5.4",
|
||||
@@ -58,26 +64,26 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "7.2.1",
|
||||
"@angular/cdk": "7.2.1",
|
||||
"@angular/common": "7.2.1",
|
||||
"@angular/compiler": "7.2.1",
|
||||
"@angular/core": "7.2.1",
|
||||
"@angular/forms": "7.2.1",
|
||||
"@angular/http": "7.2.1",
|
||||
"@angular/platform-browser": "7.2.1",
|
||||
"@angular/platform-browser-dynamic": "7.2.1",
|
||||
"@angular/router": "7.2.1",
|
||||
"@angular/upgrade": "7.2.1",
|
||||
"@aspnet/signalr": "1.0.4",
|
||||
"@aspnet/signalr-protocol-msgpack": "1.0.4",
|
||||
"@microsoft/signalr": "3.1.0",
|
||||
"@microsoft/signalr-protocol-msgpack": "3.1.0",
|
||||
"angular2-toaster": "6.1.0",
|
||||
"angulartics2": "6.3.0",
|
||||
"big-integer": "1.6.36",
|
||||
"bootstrap": "4.1.3",
|
||||
"bootstrap": "4.3.1",
|
||||
"braintree-web-drop-in": "1.13.0",
|
||||
"core-js": "2.6.2",
|
||||
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
|
||||
"font-awesome": "4.7.0",
|
||||
"jquery": "3.3.1",
|
||||
"jquery": "3.4.1",
|
||||
"lunr": "2.3.3",
|
||||
"ngx-infinite-scroll": "7.0.1",
|
||||
"node-forge": "0.7.6",
|
||||
@@ -85,7 +91,7 @@
|
||||
"popper.js": "1.14.4",
|
||||
"qrious": "4.0.2",
|
||||
"rxjs": "6.3.3",
|
||||
"sweetalert": "2.1.2",
|
||||
"sweetalert2": "9.8.1",
|
||||
"web-animations-js": "2.3.1",
|
||||
"webcrypto-shim": "0.1.4",
|
||||
"whatwg-fetch": "3.0.0",
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<div>
|
||||
<img src="../../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden">
|
||||
<p class="text-center">
|
||||
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -44,6 +44,7 @@ export class AcceptOrganizationComponent implements OnInit {
|
||||
fired = true;
|
||||
await this.stateService.remove('orgInvitation');
|
||||
let error = qParams.organizationId == null || qParams.organizationUserId == null || qParams.token == null;
|
||||
let errorMessage: string = null;
|
||||
if (!error) {
|
||||
this.authed = await this.userService.isAuthenticated();
|
||||
if (this.authed) {
|
||||
@@ -61,8 +62,9 @@ export class AcceptOrganizationComponent implements OnInit {
|
||||
};
|
||||
this.toasterService.popAsync(toast);
|
||||
this.router.navigate(['/vault']);
|
||||
} catch {
|
||||
} catch (e) {
|
||||
error = true;
|
||||
errorMessage = e.message;
|
||||
}
|
||||
} else {
|
||||
await this.stateService.save('orgInvitation', qParams);
|
||||
@@ -76,7 +78,14 @@ export class AcceptOrganizationComponent implements OnInit {
|
||||
}
|
||||
|
||||
if (error) {
|
||||
this.toasterService.popAsync('error', null, this.i18nService.t('inviteAcceptFailed'));
|
||||
const toast: Toast = {
|
||||
type: 'error',
|
||||
title: null,
|
||||
body: errorMessage != null ? this.i18nService.t('inviteAcceptFailedShort', errorMessage) :
|
||||
this.i18nService.t('inviteAcceptFailed'),
|
||||
timeout: 10000,
|
||||
};
|
||||
this.toasterService.popAsync(toast);
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="d-flex">
|
||||
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading">
|
||||
<span [hidden]="form.loading">{{'submit' | i18n}}</span>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
{{'cancel' | i18n}}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<p class="text-center mb-4">
|
||||
<i class="fa fa-lock fa-4x text-muted"></i>
|
||||
<i class="fa fa-lock fa-4x text-muted" aria-hidden="true"></i>
|
||||
</p>
|
||||
<p class="lead text-center mx-4 mb-4">{{'yourVaultIsLocked' | i18n}}</p>
|
||||
<div class="card d-block">
|
||||
@@ -13,18 +13,20 @@
|
||||
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
|
||||
name="MasterPassword" class="text-monospace form-control" [(ngModel)]="masterPassword"
|
||||
required appAutofocus appInputVerbatim>
|
||||
<button type="button" class="ml-1 btn btn-link" title="{{'toggleVisibility' | i18n}}"
|
||||
<button type="button" class="ml-1 btn btn-link" appA11yTitle="{{'toggleVisibility' | i18n}}"
|
||||
(click)="togglePassword()">
|
||||
<i class="fa fa-lg"
|
||||
<i class="fa fa-lg" aria-hidden="true"
|
||||
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-muted form-text">{{'loggedInAsEmail' | i18n : email}}</small>
|
||||
<small class="text-muted form-text">
|
||||
{{'loggedInAsEmailOn' | i18n : email : webVaultHostname}}
|
||||
</small>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex">
|
||||
<button type="submit" class="btn btn-primary btn-block">
|
||||
<i class="fa fa-unlock-alt"></i>
|
||||
<i class="fa fa-unlock-alt" aria-hidden="true"></i>
|
||||
{{'unlock' | i18n}}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-block ml-2 mt-0" (click)="logOut()">
|
||||
|
||||
@@ -2,12 +2,14 @@ import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { LockService } from 'jslib/abstractions/lock.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { StateService } from 'jslib/abstractions/state.service';
|
||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
|
||||
|
||||
import { RouterService } from '../services/router.service';
|
||||
|
||||
@@ -21,10 +23,11 @@ export class LockComponent extends BaseLockComponent {
|
||||
constructor(router: Router, i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService, messagingService: MessagingService,
|
||||
userService: UserService, cryptoService: CryptoService,
|
||||
storageService: StorageService, lockService: LockService,
|
||||
private routerService: RouterService) {
|
||||
storageService: StorageService, vaultTimeoutService: VaultTimeoutService,
|
||||
environmentService: EnvironmentService, private routerService: RouterService,
|
||||
stateService: StateService) {
|
||||
super(router, i18nService, platformUtilsService, messagingService, userService, cryptoService,
|
||||
storageService, lockService);
|
||||
storageService, vaultTimeoutService, environmentService, stateService);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
|
||||
name="MasterPassword" class="text-monospace form-control" [(ngModel)]="masterPassword"
|
||||
required appInputVerbatim>
|
||||
<button type="button" class="ml-1 btn btn-link" title="{{'toggleVisibility' | i18n}}"
|
||||
<button type="button" class="ml-1 btn btn-link" appA11yTitle="{{'toggleVisibility' | i18n}}"
|
||||
(click)="togglePassword()">
|
||||
<i class="fa fa-lg"
|
||||
<i class="fa fa-lg" aria-hidden="true"
|
||||
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -35,13 +35,13 @@
|
||||
<div class="d-flex">
|
||||
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading">
|
||||
<span>
|
||||
<i class="fa fa-sign-in"></i> {{'logIn' | i18n}}
|
||||
<i class="fa fa-sign-in" aria-hidden="true"></i> {{'logIn' | i18n}}
|
||||
</span>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
<a routerLink="/register" [queryParams]="{email: email}"
|
||||
class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
<i class="fa fa-pencil-square-o"></i> {{'createAccount' | i18n}}
|
||||
<i class="fa fa-pencil-square-o" aria-hidden="true"></i> {{'createAccount' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,9 +19,9 @@ import { LoginComponent as BaseLoginComponent } from 'jslib/angular/components/l
|
||||
export class LoginComponent extends BaseLoginComponent {
|
||||
constructor(authService: AuthService, router: Router,
|
||||
i18nService: I18nService, private route: ActivatedRoute,
|
||||
storageService: StorageService, private stateService: StateService,
|
||||
storageService: StorageService, stateService: StateService,
|
||||
platformUtilsService: PlatformUtilsService) {
|
||||
super(authService, router, platformUtilsService, i18nService, storageService);
|
||||
super(authService, router, platformUtilsService, i18nService, storageService, stateService);
|
||||
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="d-flex">
|
||||
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading">
|
||||
<span>{{'submit' | i18n}}</span>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
{{'cancel' | i18n}}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<div class="d-flex">
|
||||
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading">
|
||||
<span>{{'submit' | i18n}}</span>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
{{'cancel' | i18n}}
|
||||
|
||||
@@ -21,6 +21,25 @@
|
||||
<small class="form-text text-muted">{{'yourNameDesc' | i18n}}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<app-callout type="info" *ngIf="enforcedPolicyOptions">
|
||||
{{'masterPasswordPolicyInEffect' | i18n}}
|
||||
<ul class="mb-0">
|
||||
<li *ngIf="enforcedPolicyOptions?.minComplexity > 0">
|
||||
{{'policyInEffectMinComplexity' | i18n : getPasswordScoreAlertDisplay()}}
|
||||
</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.minLength > 0">
|
||||
{{'policyInEffectMinLength' | i18n : enforcedPolicyOptions?.minLength.toString()}}
|
||||
</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireUpper">
|
||||
{{'policyInEffectUppercase' | i18n}}</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireLower">
|
||||
{{'policyInEffectLowercase' | i18n}}</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireNumbers">
|
||||
{{'policyInEffectNumbers' | i18n}}</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireSpecial">
|
||||
{{'policyInEffectSpecial' | i18n : '!@#$%^&*'}}</li>
|
||||
</ul>
|
||||
</app-callout>
|
||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
||||
<div class="d-flex">
|
||||
<div class="w-100">
|
||||
@@ -32,9 +51,9 @@
|
||||
</app-password-strength>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" class="ml-1 btn btn-link" title="{{'toggleVisibility' | i18n}}"
|
||||
(click)="togglePassword(false)">
|
||||
<i class="fa fa-lg"
|
||||
<button type="button" class="ml-1 btn btn-link"
|
||||
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)">
|
||||
<i class="fa fa-lg" aria-hidden="true"
|
||||
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
||||
</button>
|
||||
<div class="progress-bar invisible"></div>
|
||||
@@ -48,9 +67,9 @@
|
||||
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
|
||||
name="MasterPasswordRetype" class="text-monospace form-control"
|
||||
[(ngModel)]="confirmMasterPassword" required appInputVerbatim>
|
||||
<button type="button" class="ml-1 btn btn-link" title="{{'toggleVisibility' | i18n}}"
|
||||
<button type="button" class="ml-1 btn btn-link" appA11yTitle="{{'toggleVisibility' | i18n}}"
|
||||
(click)="togglePassword(true)">
|
||||
<i class="fa fa-lg"
|
||||
<i class="fa fa-lg" aria-hidden="true"
|
||||
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -64,7 +83,7 @@
|
||||
<div class="d-flex mb-2">
|
||||
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading">
|
||||
<span>{{'submit' | i18n}}</span>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
{{'cancel' | i18n}}
|
||||
|
||||
@@ -10,10 +10,16 @@ import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib/abstractions/policy.service';
|
||||
import { StateService } from 'jslib/abstractions/state.service';
|
||||
|
||||
import { RegisterComponent as BaseRegisterComponent } from 'jslib/angular/components/register.component';
|
||||
|
||||
import { MasterPasswordPolicyOptions } from 'jslib/models/domain/masterPasswordPolicyOptions';
|
||||
import { Policy } from 'jslib/models/domain/policy';
|
||||
|
||||
import { PolicyData } from 'jslib/models/data/policyData';
|
||||
|
||||
@Component({
|
||||
selector: 'app-register',
|
||||
templateUrl: 'register.component.html',
|
||||
@@ -21,18 +27,41 @@ import { RegisterComponent as BaseRegisterComponent } from 'jslib/angular/compon
|
||||
export class RegisterComponent extends BaseRegisterComponent {
|
||||
showCreateOrgMessage = false;
|
||||
showTerms = true;
|
||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
|
||||
private policies: Policy[];
|
||||
|
||||
constructor(authService: AuthService, router: Router,
|
||||
i18nService: I18nService, cryptoService: CryptoService,
|
||||
apiService: ApiService, private route: ActivatedRoute,
|
||||
stateService: StateService, platformUtilsService: PlatformUtilsService,
|
||||
passwordGenerationService: PasswordGenerationService) {
|
||||
passwordGenerationService: PasswordGenerationService, private policyService: PolicyService) {
|
||||
super(authService, router, i18nService, cryptoService, apiService, stateService, platformUtilsService,
|
||||
passwordGenerationService);
|
||||
this.showTerms = !platformUtilsService.isSelfHost();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
getPasswordScoreAlertDisplay() {
|
||||
if (this.enforcedPolicyOptions == null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let str: string;
|
||||
switch (this.enforcedPolicyOptions.minComplexity) {
|
||||
case 4:
|
||||
str = this.i18nService.t('strong');
|
||||
break;
|
||||
case 3:
|
||||
str = this.i18nService.t('good');
|
||||
break;
|
||||
default:
|
||||
str = this.i18nService.t('weak');
|
||||
break;
|
||||
}
|
||||
return str + ' (' + this.enforcedPolicyOptions.minComplexity + ')';
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
const queryParamsSub = this.route.queryParams.subscribe((qParams) => {
|
||||
if (qParams.email != null && qParams.email.indexOf('@') > -1) {
|
||||
this.email = qParams.email;
|
||||
@@ -48,5 +77,32 @@ export class RegisterComponent extends BaseRegisterComponent {
|
||||
queryParamsSub.unsubscribe();
|
||||
}
|
||||
});
|
||||
const invite = await this.stateService.get<any>('orgInvitation');
|
||||
if (invite != null) {
|
||||
try {
|
||||
const policies = await this.apiService.getPoliciesByToken(invite.organizationId, invite.token,
|
||||
invite.email, invite.organizationUserId);
|
||||
if (policies.data != null) {
|
||||
const policiesData = policies.data.map((p) => new PolicyData(p));
|
||||
this.policies = policiesData.map((p) => new Policy(p));
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
if (this.policies != null) {
|
||||
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(this.policies);
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (this.enforcedPolicyOptions != null &&
|
||||
!this.policyService.evaluateMasterPassword(this.masterPasswordScore, this.masterPassword,
|
||||
this.enforcedPolicyOptions)) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('masterPasswordPolicyRequirementsNotMet'));
|
||||
return;
|
||||
}
|
||||
|
||||
await super.submit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="twoStepOptionsTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">{{'twoStepOptions' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<h2 class="modal-title" id="twoStepOptionsTitle">{{'twoStepOptions' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,9 @@
|
||||
</ng-container>
|
||||
<ng-container *ngIf="selectedProviderType === providerType.U2f">
|
||||
<p class="text-center" *ngIf="!u2fReady">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</p>
|
||||
<ng-container *ngIf="u2fReady">
|
||||
<p class="text-center">{{'insertU2f' | i18n}}</p>
|
||||
@@ -49,7 +51,7 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
<i class="fa fa-spinner text-muted fa-spin pull-right" title="{{'loading' | i18n}}"
|
||||
*ngIf="form.loading && selectedProviderType === providerType.U2f"></i>
|
||||
*ngIf="form.loading && selectedProviderType === providerType.U2f" aria-hidden="true"></i>
|
||||
<div class="form-check" *ngIf="selectedProviderType != null">
|
||||
<input id="remember" type="checkbox" name="Remember" class="form-check-input"
|
||||
[(ngModel)]="remember">
|
||||
@@ -65,9 +67,9 @@
|
||||
*ngIf="selectedProviderType != null && selectedProviderType !== providerType.Duo &&
|
||||
selectedProviderType !== providerType.OrganizationDuo && selectedProviderType !== providerType.U2f">
|
||||
<span>
|
||||
<i class="fa fa-sign-in"></i> {{'continue' | i18n}}
|
||||
<i class="fa fa-sign-in" aria-hidden="true"></i> {{'continue' | i18n}}
|
||||
</span>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
{{'cancel' | i18n}}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { StateService } from 'jslib/abstractions/state.service';
|
||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||
|
||||
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib/angular/components/two-factor.component';
|
||||
|
||||
@@ -31,9 +32,11 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||
|
||||
constructor(authService: AuthService, router: Router,
|
||||
i18nService: I18nService, apiService: ApiService,
|
||||
platformUtilsService: PlatformUtilsService, private stateService: StateService,
|
||||
environmentService: EnvironmentService, private componentFactoryResolver: ComponentFactoryResolver) {
|
||||
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService);
|
||||
platformUtilsService: PlatformUtilsService, stateService: StateService,
|
||||
environmentService: EnvironmentService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||
storageService: StorageService) {
|
||||
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
|
||||
stateService, storageService);
|
||||
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<div>
|
||||
<img src="../../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden">
|
||||
<p class="text-center">
|
||||
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="d-flex">
|
||||
<button type="submit" class="btn btn-danger btn-block btn-submit" [disabled]="form.loading">
|
||||
<span>{{'deleteAccount' | i18n}}</span>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
{{'cancel' | i18n}}
|
||||
|
||||
@@ -24,6 +24,7 @@ import { EventsComponent as OrgEventsComponent } from './organizations/manage/ev
|
||||
import { GroupsComponent as OrgGroupsComponent } from './organizations/manage/groups.component';
|
||||
import { ManageComponent as OrgManageComponent } from './organizations/manage/manage.component';
|
||||
import { PeopleComponent as OrgPeopleComponent } from './organizations/manage/people.component';
|
||||
import { PoliciesComponent as OrgPoliciesComponent } from './organizations/manage/policies.component';
|
||||
|
||||
import { AccountComponent as OrgAccountComponent } from './organizations/settings/account.component';
|
||||
import { OrganizationBillingComponent } from './organizations/settings/organization-billing.component';
|
||||
@@ -264,6 +265,7 @@ const routes: Routes = [
|
||||
{ path: 'events', component: OrgEventsComponent, data: { titleId: 'eventLogs' } },
|
||||
{ path: 'groups', component: OrgGroupsComponent, data: { titleId: 'groups' } },
|
||||
{ path: 'people', component: OrgPeopleComponent, data: { titleId: 'people' } },
|
||||
{ path: 'policies', component: OrgPoliciesComponent, data: { titleId: 'policies' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
<toaster-container [toasterconfig]="toasterConfig"></toaster-container>
|
||||
<toaster-container [toasterconfig]="toasterConfig" aria-live="polite"></toaster-container>
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as jq from 'jquery';
|
||||
import * as _swal from 'sweetalert';
|
||||
import { SweetAlert } from 'sweetalert/typings/core';
|
||||
import Swal from 'sweetalert2/src/sweetalert2.js';
|
||||
|
||||
import {
|
||||
BodyOutputType,
|
||||
@@ -33,25 +32,26 @@ import { AuthService } from 'jslib/abstractions/auth.service';
|
||||
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||
import { CollectionService } from 'jslib/abstractions/collection.service';
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { EventService } from 'jslib/abstractions/event.service';
|
||||
import { FolderService } from 'jslib/abstractions/folder.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { LockService } from 'jslib/abstractions/lock.service';
|
||||
import { NotificationsService } from 'jslib/abstractions/notifications.service';
|
||||
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib/abstractions/policy.service';
|
||||
import { SearchService } from 'jslib/abstractions/search.service';
|
||||
import { SettingsService } from 'jslib/abstractions/settings.service';
|
||||
import { StateService } from 'jslib/abstractions/state.service';
|
||||
import { SyncService } from 'jslib/abstractions/sync.service';
|
||||
import { TokenService } from 'jslib/abstractions/token.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
|
||||
|
||||
import { ConstantsService } from 'jslib/services/constants.service';
|
||||
|
||||
import { RouterService } from './services/router.service';
|
||||
|
||||
const BroadcasterSubscriptionId = 'AppComponent';
|
||||
// Hack due to Angular 5.2 bug
|
||||
const swal: SweetAlert = _swal as any;
|
||||
const IdleTimeout = 60000 * 10; // 10 minutes
|
||||
|
||||
@Component({
|
||||
@@ -78,10 +78,12 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
private authService: AuthService, private router: Router, private analytics: Angulartics2,
|
||||
private toasterService: ToasterService, private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService, private ngZone: NgZone,
|
||||
private lockService: LockService, private storageService: StorageService,
|
||||
private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService,
|
||||
private cryptoService: CryptoService, private collectionService: CollectionService,
|
||||
private sanitizer: DomSanitizer, private searchService: SearchService,
|
||||
private notificationsService: NotificationsService, private routerService: RouterService) { }
|
||||
private notificationsService: NotificationsService, private routerService: RouterService,
|
||||
private stateService: StateService, private eventService: EventService,
|
||||
private policyService: PolicyService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.ngZone.runOutsideAngular(() => {
|
||||
@@ -101,11 +103,14 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
case 'unlocked':
|
||||
this.notificationsService.updateConnection(false);
|
||||
break;
|
||||
case 'authBlocked':
|
||||
this.router.navigate(['/']);
|
||||
break;
|
||||
case 'logout':
|
||||
this.logOut(!!message.expired);
|
||||
break;
|
||||
case 'lockVault':
|
||||
await this.lockService.lock();
|
||||
await this.vaultTimeoutService.lock();
|
||||
break;
|
||||
case 'locked':
|
||||
this.notificationsService.updateConnection(false);
|
||||
@@ -157,7 +162,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
if (document.querySelector('.swal-modal') != null) {
|
||||
swal.close(undefined);
|
||||
Swal.close(undefined);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -168,9 +173,11 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
private async logOut(expired: boolean) {
|
||||
await this.eventService.uploadEvents();
|
||||
const userId = await this.userService.getUserId();
|
||||
|
||||
await Promise.all([
|
||||
this.eventService.clearEvents(),
|
||||
this.syncService.setLastSync(new Date(0)),
|
||||
this.tokenService.clearToken(),
|
||||
this.cryptoService.clearKeys(),
|
||||
@@ -179,7 +186,9 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
this.cipherService.clear(userId),
|
||||
this.folderService.clear(userId),
|
||||
this.collectionService.clear(userId),
|
||||
this.policyService.clear(userId),
|
||||
this.passwordGenerationService.clear(),
|
||||
this.stateService.purge(),
|
||||
]);
|
||||
|
||||
this.searchService.clearIndex();
|
||||
|
||||
@@ -7,6 +7,7 @@ import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
@@ -18,7 +19,6 @@ import { AppComponent } from './app.component';
|
||||
import { ModalComponent } from './modal.component';
|
||||
|
||||
import { AvatarComponent } from './components/avatar.component';
|
||||
import { CalloutComponent } from './components/callout.component';
|
||||
import { PasswordStrengthComponent } from './components/password-strength.component';
|
||||
|
||||
import { FooterComponent } from './layouts/footer.component';
|
||||
@@ -50,6 +50,8 @@ import { GroupAddEditComponent as OrgGroupAddEditComponent } from './organizatio
|
||||
import { GroupsComponent as OrgGroupsComponent } from './organizations/manage/groups.component';
|
||||
import { ManageComponent as OrgManageComponent } from './organizations/manage/manage.component';
|
||||
import { PeopleComponent as OrgPeopleComponent } from './organizations/manage/people.component';
|
||||
import { PoliciesComponent as OrgPoliciesComponent } from './organizations/manage/policies.component';
|
||||
import { PolicyEditComponent as OrgPolicyEditComponent } from './organizations/manage/policy-edit.component';
|
||||
import { UserAddEditComponent as OrgUserAddEditComponent } from './organizations/manage/user-add-edit.component';
|
||||
import { UserConfirmComponent as OrgUserConfirmComponent } from './organizations/manage/user-confirm.component';
|
||||
import { UserGroupsComponent as OrgUserGroupsComponent } from './organizations/manage/user-groups.component';
|
||||
@@ -143,6 +145,7 @@ import { AddEditComponent } from './vault/add-edit.component';
|
||||
import { AttachmentsComponent } from './vault/attachments.component';
|
||||
import { BulkDeleteComponent } from './vault/bulk-delete.component';
|
||||
import { BulkMoveComponent } from './vault/bulk-move.component';
|
||||
import { BulkRestoreComponent } from './vault/bulk-restore.component';
|
||||
import { BulkShareComponent } from './vault/bulk-share.component';
|
||||
import { CiphersComponent } from './vault/ciphers.component';
|
||||
import { CollectionsComponent } from './vault/collections.component';
|
||||
@@ -151,15 +154,17 @@ import { GroupingsComponent } from './vault/groupings.component';
|
||||
import { ShareComponent } from './vault/share.component';
|
||||
import { VaultComponent } from './vault/vault.component';
|
||||
|
||||
import { CalloutComponent } from 'jslib/angular/components/callout.component';
|
||||
import { IconComponent } from 'jslib/angular/components/icon.component';
|
||||
|
||||
import { A11yTitleDirective } from 'jslib/angular/directives/a11y-title.directive';
|
||||
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 { FlexCopyDirective } from 'jslib/angular/directives/flex-copy.directive';
|
||||
import { InputVerbatimDirective } from 'jslib/angular/directives/input-verbatim.directive';
|
||||
import { SelectCopyDirective } from 'jslib/angular/directives/select-copy.directive';
|
||||
import { StopClickDirective } from 'jslib/angular/directives/stop-click.directive';
|
||||
import { StopPropDirective } from 'jslib/angular/directives/stop-prop.directive';
|
||||
import { TrueFalseValueDirective } from 'jslib/angular/directives/true-false-value.directive';
|
||||
@@ -178,8 +183,10 @@ import localeEnGb from '@angular/common/locales/en-GB';
|
||||
import localeEs from '@angular/common/locales/es';
|
||||
import localeEt from '@angular/common/locales/et';
|
||||
import localeFr from '@angular/common/locales/fr';
|
||||
import localeHe from '@angular/common/locales/he';
|
||||
import localeIt from '@angular/common/locales/it';
|
||||
import localeJa from '@angular/common/locales/ja';
|
||||
import localeKo from '@angular/common/locales/ko';
|
||||
import localeNb from '@angular/common/locales/nb';
|
||||
import localeNl from '@angular/common/locales/nl';
|
||||
import localePl from '@angular/common/locales/pl';
|
||||
@@ -200,8 +207,10 @@ registerLocaleData(localeEnGb, 'en-GB');
|
||||
registerLocaleData(localeEs, 'es');
|
||||
registerLocaleData(localeEt, 'et');
|
||||
registerLocaleData(localeFr, 'fr');
|
||||
registerLocaleData(localeHe, 'he');
|
||||
registerLocaleData(localeIt, 'it');
|
||||
registerLocaleData(localeJa, 'ja');
|
||||
registerLocaleData(localeKo, 'ko');
|
||||
registerLocaleData(localeNb, 'nb');
|
||||
registerLocaleData(localeNl, 'nl');
|
||||
registerLocaleData(localePl, 'pl');
|
||||
@@ -228,8 +237,10 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
}),
|
||||
ToasterModule.forRoot(),
|
||||
InfiniteScrollModule,
|
||||
DragDropModule,
|
||||
],
|
||||
declarations: [
|
||||
A11yTitleDirective,
|
||||
AcceptOrganizationComponent,
|
||||
AccountComponent,
|
||||
AddCreditComponent,
|
||||
@@ -247,6 +258,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
BreachReportComponent,
|
||||
BulkDeleteComponent,
|
||||
BulkMoveComponent,
|
||||
BulkRestoreComponent,
|
||||
BulkShareComponent,
|
||||
CalloutComponent,
|
||||
ChangeEmailComponent,
|
||||
@@ -265,7 +277,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
ExportComponent,
|
||||
ExposedPasswordsReportComponent,
|
||||
FallbackSrcDirective,
|
||||
FlexCopyDirective,
|
||||
FolderAddEditComponent,
|
||||
FooterComponent,
|
||||
FrontendLayoutComponent,
|
||||
@@ -304,6 +315,8 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
OrgManageCollectionsComponent,
|
||||
OrgManageComponent,
|
||||
OrgPeopleComponent,
|
||||
OrgPolicyEditComponent,
|
||||
OrgPoliciesComponent,
|
||||
OrgReusedPasswordsReportComponent,
|
||||
OrgRotateApiKeyComponent,
|
||||
OrgSettingComponent,
|
||||
@@ -330,6 +343,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
ReusedPasswordsReportComponent,
|
||||
SearchCiphersPipe,
|
||||
SearchPipe,
|
||||
SelectCopyDirective,
|
||||
SettingsComponent,
|
||||
ShareComponent,
|
||||
StopClickDirective,
|
||||
@@ -363,6 +377,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
AttachmentsComponent,
|
||||
BulkDeleteComponent,
|
||||
BulkMoveComponent,
|
||||
BulkRestoreComponent,
|
||||
BulkShareComponent,
|
||||
CollectionsComponent,
|
||||
DeauthorizeSessionsComponent,
|
||||
@@ -378,6 +393,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
OrgEntityEventsComponent,
|
||||
OrgEntityUsersComponent,
|
||||
OrgGroupAddEditComponent,
|
||||
OrgPolicyEditComponent,
|
||||
OrgRotateApiKeyComponent,
|
||||
OrgUserAddEditComponent,
|
||||
OrgUserConfirmComponent,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<div class="callout callout-{{calloutStyle}}" role="alert">
|
||||
<h3 class="callout-heading" *ngIf="title">
|
||||
<i class="fa {{icon}}" *ngIf="icon"></i>
|
||||
{{title}}
|
||||
</h3>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
@@ -1,53 +0,0 @@
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-callout',
|
||||
templateUrl: 'callout.component.html',
|
||||
})
|
||||
export class CalloutComponent implements OnInit {
|
||||
@Input() type = 'info';
|
||||
@Input() icon: string;
|
||||
@Input() title: string;
|
||||
|
||||
calloutStyle: string;
|
||||
|
||||
constructor(private i18nService: I18nService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.calloutStyle = this.type;
|
||||
|
||||
if (this.type === 'warning' || this.type === 'danger') {
|
||||
if (this.type === 'danger') {
|
||||
this.calloutStyle = 'danger';
|
||||
}
|
||||
if (this.title === undefined) {
|
||||
this.title = this.i18nService.t('warning');
|
||||
}
|
||||
if (this.icon === undefined) {
|
||||
this.icon = 'fa-warning';
|
||||
}
|
||||
} else if (this.type === 'error') {
|
||||
this.calloutStyle = 'danger';
|
||||
if (this.title === undefined) {
|
||||
this.title = this.i18nService.t('error');
|
||||
}
|
||||
if (this.icon === undefined) {
|
||||
this.icon = 'fa-bolt';
|
||||
}
|
||||
} else if (this.type === 'tip') {
|
||||
this.calloutStyle = 'success';
|
||||
if (this.title === undefined) {
|
||||
this.title = this.i18nService.t('tip');
|
||||
}
|
||||
if (this.icon === undefined) {
|
||||
this.icon = 'fa-lightbulb-o';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="container footer text-muted">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
© {{year}}, 8bit Solutions LLC
|
||||
© {{year}}, Bitwarden Inc.
|
||||
</div>
|
||||
<div class="col text-center"></div>
|
||||
<div class="col text-right">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<router-outlet></router-outlet>
|
||||
<div class="container my-5 text-muted text-center">
|
||||
© {{year}}, 8bit Solutions LLC
|
||||
© {{year}}, Bitwarden Inc.
|
||||
<br> {{'versionNumber' | i18n : version}}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<nav class="navbar navbar-expand navbar-dark bg-primary" [ngClass]="{'bg-secondary-alt': selfHosted}">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" routerLink="/" title="{{'pageTitle' | i18n : 'Bitwarden'}}">
|
||||
<i class="fa fa-shield"></i>
|
||||
<a class="navbar-brand" routerLink="/" appA11yTitle="{{'pageTitle' | i18n : 'Bitwarden'}}">
|
||||
<i class="fa fa-shield" aria-hidden="true"></i>
|
||||
</a>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav">
|
||||
@@ -20,7 +20,7 @@
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-item nav-link dropdown-toggle" href="#" id="nav-profile" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fa fa-user-circle fa-lg"></i>
|
||||
<i class="fa fa-user-circle fa-lg" aria-hidden="true"></i>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="nav-profile">
|
||||
<div class="dropdown-item-text d-flex align-items-center" *ngIf="name" appStopProp>
|
||||
@@ -32,24 +32,24 @@
|
||||
</div>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="#" routerLink="/settings/account">
|
||||
<i class="fa fa-fw fa-user"></i>
|
||||
<i class="fa fa-fw fa-user" aria-hidden="true"></i>
|
||||
{{'myAccount' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item" href="https://help.bitwarden.com" target="_blank" rel="noopener">
|
||||
<i class="fa fa-fw fa-question-circle"></i>
|
||||
<i class="fa fa-fw fa-question-circle" aria-hidden="true"></i>
|
||||
{{'getHelp' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item" href="https://bitwarden.com#download" target="_blank" rel="noopener">
|
||||
<i class="fa fa-fw fa-download"></i>
|
||||
<i class="fa fa-fw fa-download" aria-hidden="true"></i>
|
||||
{{'getApps' | i18n}}
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button type="button" class="dropdown-item" (click)="lock()">
|
||||
<i class="fa fa-fw fa-lock"></i>
|
||||
<i class="fa fa-fw fa-lock" aria-hidden="true"></i>
|
||||
{{'lockNow' | i18n}}
|
||||
</button>
|
||||
<button type="button" class="dropdown-item" (click)="logOut()">
|
||||
<i class="fa fa-fw fa-sign-out"></i>
|
||||
<i class="fa fa-fw fa-sign-out" aria-hidden="true"></i>
|
||||
{{'logOut' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</div>
|
||||
<div class="ml-auto card border-danger text-danger bg-transparent" *ngIf="!organization.enabled">
|
||||
<div class="card-body py-2">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
|
||||
{{'organizationIsDisabled' | i18n}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -17,25 +17,25 @@
|
||||
<ul class="nav nav-tabs" *ngIf="organization.isManager">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="vault" routerLinkActive="active">
|
||||
<i class="fa fa-lock"></i>
|
||||
<i class="fa fa-lock" aria-hidden="true"></i>
|
||||
{{'vault' | i18n}}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="manage" routerLinkActive="active">
|
||||
<i class="fa fa-sliders"></i>
|
||||
<i class="fa fa-sliders" aria-hidden="true"></i>
|
||||
{{'manage' | i18n}}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="organization.isAdmin">
|
||||
<a class="nav-link" routerLink="tools" routerLinkActive="active">
|
||||
<i class="fa fa-wrench"></i>
|
||||
<i class="fa fa-wrench" aria-hidden="true"></i>
|
||||
{{'tools' | i18n}}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="organization.isOwner">
|
||||
<a class="nav-link" routerLink="settings" routerLinkActive="active">
|
||||
<i class="fa fa-cogs"></i>
|
||||
<i class="fa fa-cogs" aria-hidden="true"></i>
|
||||
{{'settings' | i18n}}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
import { ModalComponent as BaseModalComponent } from 'jslib/angular/components/modal.component';
|
||||
import { Utils } from 'jslib/misc/utils';
|
||||
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-modal',
|
||||
template: `<ng-template #container></ng-template>`,
|
||||
@@ -17,18 +19,22 @@ import { Utils } from 'jslib/misc/utils';
|
||||
export class ModalComponent extends BaseModalComponent {
|
||||
el: any = null;
|
||||
|
||||
constructor(componentFactoryResolver: ComponentFactoryResolver) {
|
||||
super(componentFactoryResolver);
|
||||
constructor(componentFactoryResolver: ComponentFactoryResolver, messagingService: MessagingService) {
|
||||
super(componentFactoryResolver, messagingService);
|
||||
}
|
||||
|
||||
ngOnDestroy() { /* Nothing */ }
|
||||
|
||||
show<T>(type: Type<T>, parentContainer: ViewContainerRef, fade: boolean = true): T {
|
||||
show<T>(type: Type<T>, parentContainer: ViewContainerRef, fade: boolean = true,
|
||||
setComponentParameters: (component: T) => void = null): T {
|
||||
this.parentContainer = parentContainer;
|
||||
this.fade = fade;
|
||||
|
||||
const factory = this.componentFactoryResolver.resolveComponentFactory<T>(type);
|
||||
const componentRef = this.container.createComponent<T>(factory);
|
||||
if (setComponentParameters != null) {
|
||||
setComponentParameters(componentRef.instance);
|
||||
}
|
||||
|
||||
const modals = Array.from(document.querySelectorAll('.modal'));
|
||||
if (modals.length > 0) {
|
||||
@@ -37,18 +43,22 @@ export class ModalComponent extends BaseModalComponent {
|
||||
|
||||
this.el.on('show.bs.modal', () => {
|
||||
this.onShow.emit();
|
||||
this.messagingService.send('modalShow');
|
||||
});
|
||||
this.el.on('shown.bs.modal', () => {
|
||||
this.onShown.emit();
|
||||
this.messagingService.send('modalShown');
|
||||
if (!Utils.isMobileBrowser) {
|
||||
this.el.find('*[appAutoFocus]').focus();
|
||||
}
|
||||
});
|
||||
this.el.on('hide.bs.modal', () => {
|
||||
this.onClose.emit();
|
||||
this.messagingService.send('modalClose');
|
||||
});
|
||||
this.el.on('hidden.bs.modal', () => {
|
||||
this.onClosed.emit();
|
||||
this.messagingService.send('modalClosed');
|
||||
if (this.parentContainer != null) {
|
||||
this.parentContainer.clear();
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="collectionAddEditTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">{{title}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<h2 class="modal-title" id="collectionAddEditTitle">{{title}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="!loading">
|
||||
<div class="form-group">
|
||||
@@ -51,8 +52,11 @@
|
||||
</td>
|
||||
<td (click)="check(g)">
|
||||
{{g.name}}
|
||||
<i class="fa fa-th text-muted fa-fw" *ngIf="g.accessAll"
|
||||
title="This group can access all items"></i>
|
||||
<ng-container *ngIf="g.accessAll">
|
||||
<i class="fa fa-th text-muted fa-fw" title="{{'groupAccessAllItems' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'groupAccessAllItems' | i18n}}</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input type="checkbox" [(ngModel)]="g.readOnly" name="Groups[{{i}}].ReadOnly"
|
||||
@@ -65,18 +69,18 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||
<div class="ml-auto">
|
||||
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger"
|
||||
title="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
|
||||
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
|
||||
[appApiAction]="deletePromise">
|
||||
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"></i>
|
||||
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"
|
||||
title="{{'loading' | i18n}}"></i>
|
||||
title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,12 +7,15 @@
|
||||
[(ngModel)]="searchText">
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="add()">
|
||||
<i class="fa fa-plus fa-fw"></i>
|
||||
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
|
||||
{{'newCollection' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<i class="fa fa-spinner fa-spin text-muted" *ngIf="loading"></i>
|
||||
<ng-container *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!loading && (collections | search:searchText:'name':'id') as searchedCollections">
|
||||
<p *ngIf="!searchedCollections.length">{{'noCollectionsInList' | i18n}}</p>
|
||||
<table class="table table-hover table-list" *ngIf="searchedCollections.length">
|
||||
@@ -24,16 +27,16 @@
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown>
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fa fa-cog fa-lg"></i>
|
||||
aria-haspopup="true" aria-expanded="false" appA11yTitle="{{'options' | i18n}}">
|
||||
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="users(c)">
|
||||
<i class="fa fa-fw fa-users"></i>
|
||||
<i class="fa fa-fw fa-users" aria-hidden="true"></i>
|
||||
{{'users' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="delete(c)">
|
||||
<i class="fa fa-fw fa-trash-o"></i>
|
||||
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>
|
||||
{{'delete' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="eventLogsTitle">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">
|
||||
<h2 class="modal-title" id="eventLogsTitle">
|
||||
{{'eventLogs' | i18n}}
|
||||
<small class="text-muted" *ngIf="name">{{name}}</small>
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="!loaded">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="loaded">
|
||||
<div class="d-flex">
|
||||
@@ -27,7 +28,8 @@
|
||||
<button #refreshBtn [appApiAction]="refreshPromise" type="button"
|
||||
class="btn btn-sm btn-outline-primary ml-3" (click)="loadEvents(true)"
|
||||
[disabled]="loaded && refreshBtn.loading">
|
||||
<i class="fa fa-refresh fa-fw" [ngClass]="{'fa-spin': loaded && refreshBtn.loading}"></i>
|
||||
<i class="fa fa-refresh fa-fw" [ngClass]="{'fa-spin': loaded && refreshBtn.loading}"
|
||||
aria-hidden="true"></i>
|
||||
{{'refresh' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
@@ -50,10 +52,12 @@
|
||||
<tr *ngFor="let e of events">
|
||||
<td>{{e.date | date:'medium'}}</td>
|
||||
<td>
|
||||
<i class="text-muted fa fa-lg {{e.appIcon}}" title="{{e.appName}}, {{e.ip}}"></i>
|
||||
<i class="text-muted fa fa-lg {{e.appIcon}}" title="{{e.appName}}, {{e.ip}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{e.appName}}, {{e.ip}}</span>
|
||||
</td>
|
||||
<td *ngIf="showUser">
|
||||
<span title="{{e.userEmail}}">{{e.userName}}</span>
|
||||
<span appA11yTitle="{{e.userEmail}}">{{e.userName}}</span>
|
||||
</td>
|
||||
<td [innerHTML]="e.message"></td>
|
||||
</tr>
|
||||
@@ -61,7 +65,7 @@
|
||||
</table>
|
||||
<button #moreBtn [appApiAction]="morePromise" type="button" class="btn btn-block btn-link btn-submit"
|
||||
(click)="loadEvents(false)" [disabled]="loaded && moreBtn.loading" *ngIf="continuationToken">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'loadMore' | i18n}}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="userAccessTitle">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">
|
||||
<h2 class="modal-title" id="userAccessTitle">
|
||||
{{'userAccess' | i18n}}
|
||||
<small>{{entityName}}</small>
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="loading || !users">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</div>
|
||||
<div class="modal-body"
|
||||
*ngIf="!loading && users && (users | search:searchText:'name':'email':'id') as searchedUsers">
|
||||
@@ -72,7 +73,11 @@
|
||||
<small class="text-muted d-block" *ngIf="u.name">{{u.name}}</small>
|
||||
</td>
|
||||
<td *ngIf="entity === 'collection'">
|
||||
<i class="fa fa-th" *ngIf="u.accessAll" title="{{'userAccessAllItems' | i18n}}"></i>
|
||||
<ng-container *ngIf="u.accessAll">
|
||||
<i class="fa fa-th" title="{{'userAccessAllItems' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'userAccessAllItems' | i18n}}</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td>
|
||||
<span *ngIf="u.type === organizationUserType.Owner">{{'owner' | i18n}}</span>
|
||||
@@ -91,7 +96,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||
|
||||
@@ -12,12 +12,15 @@
|
||||
</div>
|
||||
<button #refreshBtn [appApiAction]="refreshPromise" type="button" class="btn btn-sm btn-outline-primary ml-3"
|
||||
(click)="loadEvents(true)" [disabled]="loaded && refreshBtn.loading">
|
||||
<i class="fa fa-refresh fa-fw" [ngClass]="{'fa-spin': loaded && refreshBtn.loading}"></i>
|
||||
<i class="fa fa-refresh fa-fw" aria-hidden="true" [ngClass]="{'fa-spin': loaded && refreshBtn.loading}"></i>
|
||||
{{'refresh' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<i class="fa fa-spinner fa-spin text-muted" *ngIf="!loaded" title="{{'loading' | i18n}}"></i>
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="loaded">
|
||||
<p *ngIf="!events || !events.length">{{'noEventsInList' | i18n}}</p>
|
||||
<table class="table table-hover" *ngIf="events && events.length">
|
||||
@@ -35,7 +38,8 @@
|
||||
<tr *ngFor="let e of events">
|
||||
<td>{{e.date | date:'medium'}}</td>
|
||||
<td>
|
||||
<i class="text-muted fa fa-lg {{e.appIcon}}" title="{{e.appName}}, {{e.ip}}"></i>
|
||||
<i class="text-muted fa fa-lg {{e.appIcon}}" title="{{e.appName}}, {{e.ip}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{e.appName}}, {{e.ip}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span title="{{e.userEmail}}">{{e.userName}}</span>
|
||||
@@ -46,7 +50,7 @@
|
||||
</table>
|
||||
<button #moreBtn [appApiAction]="morePromise" type="button" class="btn btn-block btn-link btn-submit"
|
||||
(click)="loadEvents(false)" [disabled]="loaded && moreBtn.loading" *ngIf="continuationToken">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'loadMore' | i18n}}</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="groupAddEditTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">{{title}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<h2 class="modal-title" id="groupAddEditTitle">{{title}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="!loading">
|
||||
<div class="form-group">
|
||||
@@ -81,17 +82,17 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||
<div class="ml-auto">
|
||||
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger"
|
||||
title="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
|
||||
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
|
||||
[appApiAction]="deletePromise">
|
||||
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"
|
||||
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading" aria-hidden="true"
|
||||
title="{{'loading' | i18n}}"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -7,12 +7,15 @@
|
||||
[(ngModel)]="searchText">
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="add()">
|
||||
<i class="fa fa-plus fa-fw"></i>
|
||||
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
|
||||
{{'newGroup' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<i class="fa fa-spinner fa-spin text-muted" *ngIf="loading" title="{{'loading' | i18n}}"></i>
|
||||
<ng-container *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!loading && (groups | search:searchText:'name':'id') as searchedGroups">
|
||||
<p *ngIf="!searchedGroups.length">{{'noGroupsInList' | i18n}}</p>
|
||||
<table class="table table-hover table-list" *ngIf="searchedGroups.length">
|
||||
@@ -24,16 +27,16 @@
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown>
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fa fa-cog fa-lg"></i>
|
||||
aria-haspopup="true" aria-expanded="false" appA11yTitle="{{'options' | i18n}}">
|
||||
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="users(g)">
|
||||
<i class="fa fa-fw fa-users"></i>
|
||||
<i class="fa fa-fw fa-users" aria-hidden="true"></i>
|
||||
{{'users' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="delete(g)">
|
||||
<i class="fa fa-fw fa-trash-o"></i>
|
||||
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>
|
||||
{{'delete' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
*ngIf="organization.isAdmin && accessGroups">
|
||||
{{'groups' | i18n}}
|
||||
</a>
|
||||
<a routerLink="policies" class="list-group-item" routerLinkActive="active"
|
||||
*ngIf="organization.isAdmin && accessPolicies">
|
||||
{{'policies' | i18n}}
|
||||
</a>
|
||||
<a routerLink="events" class="list-group-item" routerLinkActive="active"
|
||||
*ngIf="organization.isAdmin && accessEvents">
|
||||
{{'eventLogs' | i18n}}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Organization } from 'jslib/models/domain/organization';
|
||||
})
|
||||
export class ManageComponent implements OnInit {
|
||||
organization: Organization;
|
||||
accessPolicies = false;
|
||||
accessGroups = false;
|
||||
accessEvents = false;
|
||||
|
||||
@@ -22,6 +23,7 @@ export class ManageComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
this.route.parent.params.subscribe(async (params) => {
|
||||
this.organization = await this.userService.getOrganization(params.organizationId);
|
||||
this.accessPolicies = this.organization.usePolicies;
|
||||
this.accessEvents = this.organization.useEvents;
|
||||
this.accessGroups = this.organization.useGroups;
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<button type="button" class="btn btn-outline-secondary" [ngClass]="{active: status == null}"
|
||||
(click)="filter(null)">
|
||||
{{'all' | i18n}}
|
||||
<span class="badge badge-pill badge-info" *ngIf="allCount">{{allCount}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
[ngClass]="{active: status == organizationUserStatusType.Invited}"
|
||||
@@ -25,12 +26,15 @@
|
||||
[(ngModel)]="searchText">
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="invite()">
|
||||
<i class="fa fa-plus fa-fw"></i>
|
||||
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
|
||||
{{'inviteUser' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<i class="fa fa-spinner fa-spin text-muted" *ngIf="loading" title="{{'loading' | i18n}}"></i>
|
||||
<ng-container *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!loading && (users | search:searchText:'name':'email':'id') as searchedUsers">
|
||||
<p *ngIf="!searchedUsers.length">{{'noUsersInList' | i18n}}</p>
|
||||
<ng-container *ngIf="searchedUsers.length">
|
||||
@@ -53,7 +57,10 @@
|
||||
<small class="text-muted d-block" *ngIf="u.name">{{u.name}}</small>
|
||||
</td>
|
||||
<td>
|
||||
<i class="fa fa-lock" *ngIf="u.twoFactorEnabled" title="{{'userUsingTwoStep' | i18n}}"></i>
|
||||
<ng-container *ngIf="u.twoFactorEnabled">
|
||||
<i class="fa fa-lock" title="{{'userUsingTwoStep' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'userUsingTwoStep' | i18n}}</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td>
|
||||
<span *ngIf="u.type === organizationUserType.Owner">{{'owner' | i18n}}</span>
|
||||
@@ -64,31 +71,32 @@
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown>
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fa fa-cog fa-lg"></i>
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
|
||||
appA11yTitle="{{'options' | i18n}}">
|
||||
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="reinvite(u)"
|
||||
*ngIf="u.status === organizationUserStatusType.Invited">
|
||||
<i class="fa fa-fw fa-envelope-o"></i>
|
||||
<i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i>
|
||||
{{'resendInvitation' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item text-success" href="#" appStopClick (click)="confirm(u)"
|
||||
*ngIf="u.status === organizationUserStatusType.Accepted">
|
||||
<i class="fa fa-fw fa-check"></i>
|
||||
<i class="fa fa-fw fa-check" aria-hidden="true"></i>
|
||||
{{'confirm' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="groups(u)" *ngIf="accessGroups">
|
||||
<i class="fa fa-fw fa-sitemap"></i>
|
||||
<i class="fa fa-fw fa-sitemap" aria-hidden="true"></i>
|
||||
{{'groups' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="events(u)"
|
||||
*ngIf="accessEvents && u.status === organizationUserStatusType.Confirmed">
|
||||
<i class="fa fa-fw fa-file-text-o"></i>
|
||||
<i class="fa fa-fw fa-file-text-o" aria-hidden="true"></i>
|
||||
{{'eventLogs' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(u)">
|
||||
<i class="fa fa-fw fa-remove"></i>
|
||||
<i class="fa fa-fw fa-remove" aria-hidden="true"></i>
|
||||
{{'remove' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -121,6 +121,10 @@ export class PeopleComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
get allCount() {
|
||||
return this.allUsers != null ? this.allUsers.length : 0;
|
||||
}
|
||||
|
||||
get invitedCount() {
|
||||
return this.statusMap.has(OrganizationUserStatusType.Invited) ?
|
||||
this.statusMap.get(OrganizationUserStatusType.Invited).length : 0;
|
||||
|
||||
19
src/app/organizations/manage/policies.component.html
Normal file
19
src/app/organizations/manage/policies.component.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<div class="page-header d-flex">
|
||||
<h1>{{'policies' | i18n}}</h1>
|
||||
</div>
|
||||
<ng-container *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</ng-container>
|
||||
<table class="table table-hover table-list" *ngIf="!loading">
|
||||
<tbody>
|
||||
<tr *ngFor="let p of policies">
|
||||
<td>
|
||||
<a href="#" appStopClick (click)="edit(p)">{{p.name}}</a>
|
||||
<span class="badge badge-success" *ngIf="p.enabled">{{'enabled' | i18n}}</span>
|
||||
<small class="text-muted d-block">{{p.description}}</small>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<ng-template #editTemplate></ng-template>
|
||||
114
src/app/organizations/manage/policies.component.ts
Normal file
114
src/app/organizations/manage/policies.component.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import {
|
||||
Component,
|
||||
ComponentFactoryResolver,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
ActivatedRoute,
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
|
||||
import { PolicyType } from 'jslib/enums/policyType';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { PolicyResponse } from 'jslib/models/response/policyResponse';
|
||||
|
||||
import { ModalComponent } from '../../modal.component';
|
||||
|
||||
import { PolicyEditComponent } from './policy-edit.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-org-policies',
|
||||
templateUrl: 'policies.component.html',
|
||||
})
|
||||
export class PoliciesComponent implements OnInit {
|
||||
@ViewChild('editTemplate', { read: ViewContainerRef }) editModalRef: ViewContainerRef;
|
||||
|
||||
loading = true;
|
||||
organizationId: string;
|
||||
policies: any[];
|
||||
|
||||
private modal: ModalComponent = null;
|
||||
private orgPolicies: PolicyResponse[];
|
||||
private policiesEnabledMap: Map<PolicyType, boolean> = new Map<PolicyType, boolean>();
|
||||
|
||||
constructor(private apiService: ApiService, private route: ActivatedRoute,
|
||||
private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private platformUtilsService: PlatformUtilsService, private userService: UserService,
|
||||
private router: Router) {
|
||||
this.policies = [
|
||||
{
|
||||
name: i18nService.t('twoStepLogin'),
|
||||
description: i18nService.t('twoStepLoginPolicyDesc'),
|
||||
type: PolicyType.TwoFactorAuthentication,
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
name: i18nService.t('masterPass'),
|
||||
description: i18nService.t('masterPassPolicyDesc'),
|
||||
type: PolicyType.MasterPassword,
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
name: i18nService.t('passwordGenerator'),
|
||||
description: i18nService.t('passwordGeneratorPolicyDesc'),
|
||||
type: PolicyType.PasswordGenerator,
|
||||
enabled: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.parent.parent.params.subscribe(async (params) => {
|
||||
this.organizationId = params.organizationId;
|
||||
const organization = await this.userService.getOrganization(this.organizationId);
|
||||
if (organization == null || !organization.usePolicies) {
|
||||
this.router.navigate(['/organizations', this.organizationId]);
|
||||
return;
|
||||
}
|
||||
await this.load();
|
||||
});
|
||||
}
|
||||
|
||||
async load() {
|
||||
const response = await this.apiService.getPolicies(this.organizationId);
|
||||
this.orgPolicies = response.data != null && response.data.length > 0 ? response.data : [];
|
||||
this.orgPolicies.forEach((op) => {
|
||||
this.policiesEnabledMap.set(op.type, op.enabled);
|
||||
});
|
||||
this.policies.forEach((p) => {
|
||||
p.enabled = this.policiesEnabledMap.has(p.type) && this.policiesEnabledMap.get(p.type);
|
||||
});
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
edit(p: any) {
|
||||
if (this.modal != null) {
|
||||
this.modal.close();
|
||||
}
|
||||
|
||||
const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
|
||||
this.modal = this.editModalRef.createComponent(factory).instance;
|
||||
const childComponent = this.modal.show<PolicyEditComponent>(
|
||||
PolicyEditComponent, this.editModalRef);
|
||||
|
||||
childComponent.name = p.name;
|
||||
childComponent.description = p.description;
|
||||
childComponent.type = p.type;
|
||||
childComponent.organizationId = this.organizationId;
|
||||
childComponent.onSavedPolicy.subscribe(() => {
|
||||
this.modal.close();
|
||||
this.load();
|
||||
});
|
||||
|
||||
this.modal.onClosed.subscribe(() => {
|
||||
this.modal = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
143
src/app/organizations/manage/policy-edit.component.html
Normal file
143
src/app/organizations/manage/policy-edit.component.html
Normal file
@@ -0,0 +1,143 @@
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="policiesEditTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="policiesEditTitle">{{'editPolicy' | i18n}} - {{name}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="!loading">
|
||||
<p>{{description}}</p>
|
||||
<app-callout type="warning" *ngIf="type === policyType.TwoFactorAuthentication"
|
||||
title="{{'warning' | i18n}}" icon="fa-warning">
|
||||
{{'twoStepLoginPolicyWarning' | i18n}}
|
||||
</app-callout>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="enabled" [(ngModel)]="enabled"
|
||||
name="Enabled">
|
||||
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="type === policyType.MasterPassword">
|
||||
<div class="row">
|
||||
<div class="col-6 form-group">
|
||||
<label for="masterPassMinComplexity">{{'minComplexityScore' | i18n}}</label>
|
||||
<select id="masterPassMinComplexity" name="MasterPassMinComplexity"
|
||||
[(ngModel)]="masterPassMinComplexity" class="form-control">
|
||||
<option *ngFor="let o of passwordScores" [ngValue]="o.value">{{o.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6 form-group">
|
||||
<label for="masterPassMinLength">{{'minLength' | i18n}}</label>
|
||||
<input id="masterPassMinLength" class="form-control" type="number" min="8"
|
||||
name="MasterPassMinLength" [(ngModel)]="masterPassMinLength">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="masterPassRequireUpper"
|
||||
[(ngModel)]="masterPassRequireUpper" name="MasterPassRequireUpper">
|
||||
<label class="form-check-label" for="masterPassRequireUpper">A-Z</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="masterPassRequireLower"
|
||||
[(ngModel)]="masterPassRequireLower" name="MasterPassRequireLower">
|
||||
<label class="form-check-label" for="masterPassRequireLower">a-z</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="masterPassRequireNumbers"
|
||||
[(ngModel)]="masterPassRequireNumbers" name="MasterPassRequireNumbers">
|
||||
<label class="form-check-label" for="masterPassRequireNumbers">0-9</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="masterPassRequireSpecial"
|
||||
[(ngModel)]="masterPassRequireSpecial" name="MasterPassRequireSpecial">
|
||||
<label class="form-check-label" for="masterPassRequireSpecial">!@#$%^&*</label>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="type === policyType.PasswordGenerator">
|
||||
<div class="row">
|
||||
<div class="col-6 form-group mb-0">
|
||||
<label for="passGenDefaultType">{{'defaultType' | i18n}}</label>
|
||||
<select id="passGenDefaultType" name="PassGenDefaultType" [(ngModel)]="passGenDefaultType"
|
||||
class="form-control">
|
||||
<option *ngFor="let o of defaultTypes" [ngValue]="o.value">{{o.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="mt-4">{{'password' | i18n}}</h3>
|
||||
<div class="row">
|
||||
<div class="col-6 form-group">
|
||||
<label for="passGenMinLength">{{'minLength' | i18n}}</label>
|
||||
<input id="passGenMinLength" class="form-control" type="number" name="PassGenMinLength"
|
||||
min="5" max="128" [(ngModel)]="passGenMinLength">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 form-group">
|
||||
<label for="passGenMinNumbers">{{'minNumbers' | i18n}}</label>
|
||||
<input id="passGenMinNumbers" class="form-control" type="number" name="PassGenMinNumbers"
|
||||
min="0" max="9" [(ngModel)]="passGenMinNumbers">
|
||||
</div>
|
||||
<div class="col-6 form-group">
|
||||
<label for="passGenMinSpecial">{{'minSpecial' | i18n}}</label>
|
||||
<input id="passGenMinSpecial" class="form-control" type="number" name="PassGenMinSpecial"
|
||||
min="0" max="9" [(ngModel)]="passGenMinSpecial">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="passGenUseUpper"
|
||||
[(ngModel)]="passGenUseUpper" name="PassGenUseUpper">
|
||||
<label class="form-check-label" for="passGenUseUpper">A-Z</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="passGenUseLower"
|
||||
[(ngModel)]="passGenUseLower" name="PassGenUseLower">
|
||||
<label class="form-check-label" for="passGenUseLower">a-z</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="passGenUseNumbers"
|
||||
[(ngModel)]="passGenUseNumbers" name="PassGenUseNumbers">
|
||||
<label class="form-check-label" for="passGenUseNumbers">0-9</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="passGenUseSpecial"
|
||||
[(ngModel)]="passGenUseSpecial" name="PassGenUseSpecial">
|
||||
<label class="form-check-label" for="passGenUseSpecial">!@#$%^&*</label>
|
||||
</div>
|
||||
<h3 class="mt-4">{{'passphrase' | i18n}}</h3>
|
||||
<div class="row">
|
||||
<div class="col-6 form-group">
|
||||
<label for="passGenMinNumberWords">{{'minimumNumberOfWords' | i18n}}</label>
|
||||
<input id="passGenMinNumberWords" class="form-control" type="number"
|
||||
name="PassGenMinNumberWords" min="3" max="20" [(ngModel)]="passGenMinNumberWords">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="passGenCapitalize"
|
||||
[(ngModel)]="passGenCapitalize" name="PassGenCapitalize">
|
||||
<label class="form-check-label" for="passGenCapitalize">{{'capitalize' | i18n}}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="passGenIncludeNumber"
|
||||
[(ngModel)]="passGenIncludeNumber" name="PassGenIncludeNumber">
|
||||
<label class="form-check-label" for="passGenIncludeNumber">{{'includeNumber' | i18n}}</label>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
171
src/app/organizations/manage/policy-edit.component.ts
Normal file
171
src/app/organizations/manage/policy-edit.component.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
|
||||
import { PolicyType } from 'jslib/enums/policyType';
|
||||
|
||||
import { PolicyRequest } from 'jslib/models/request/policyRequest';
|
||||
|
||||
import { PolicyResponse } from 'jslib/models/response/policyResponse';
|
||||
|
||||
@Component({
|
||||
selector: 'app-policy-edit',
|
||||
templateUrl: 'policy-edit.component.html',
|
||||
})
|
||||
export class PolicyEditComponent implements OnInit {
|
||||
@Input() name: string;
|
||||
@Input() description: string;
|
||||
@Input() type: PolicyType;
|
||||
@Input() organizationId: string;
|
||||
@Output() onSavedPolicy = new EventEmitter();
|
||||
|
||||
policyType = PolicyType;
|
||||
loading = true;
|
||||
enabled = false;
|
||||
formPromise: Promise<any>;
|
||||
passwordScores: any[];
|
||||
defaultTypes: any[];
|
||||
|
||||
// Master password
|
||||
|
||||
masterPassMinComplexity?: number = null;
|
||||
masterPassMinLength?: number;
|
||||
masterPassRequireUpper?: number;
|
||||
masterPassRequireLower?: number;
|
||||
masterPassRequireNumbers?: number;
|
||||
masterPassRequireSpecial?: number;
|
||||
|
||||
// Password generator
|
||||
|
||||
passGenDefaultType?: string;
|
||||
passGenMinLength?: number;
|
||||
passGenUseUpper?: boolean;
|
||||
passGenUseLower?: boolean;
|
||||
passGenUseNumbers?: boolean;
|
||||
passGenUseSpecial?: boolean;
|
||||
passGenMinNumbers?: number;
|
||||
passGenMinSpecial?: number;
|
||||
passGenMinNumberWords?: number;
|
||||
passGenCapitalize?: boolean;
|
||||
passGenIncludeNumber?: boolean;
|
||||
|
||||
private policy: PolicyResponse;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService) {
|
||||
this.passwordScores = [
|
||||
{ name: '-- ' + i18nService.t('select') + ' --', value: null },
|
||||
{ name: i18nService.t('weak') + ' (0)', value: 0 },
|
||||
{ name: i18nService.t('weak') + ' (1)', value: 1 },
|
||||
{ name: i18nService.t('weak') + ' (2)', value: 2 },
|
||||
{ name: i18nService.t('good') + ' (3)', value: 3 },
|
||||
{ name: i18nService.t('strong') + ' (4)', value: 4 },
|
||||
];
|
||||
this.defaultTypes = [
|
||||
{ name: i18nService.t('userPreference'), value: null },
|
||||
{ name: i18nService.t('password'), value: 'password' },
|
||||
{ name: i18nService.t('passphrase'), value: 'passphrase' },
|
||||
];
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await this.load();
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async load() {
|
||||
try {
|
||||
this.policy = await this.apiService.getPolicy(this.organizationId, this.type);
|
||||
|
||||
if (this.policy != null) {
|
||||
this.enabled = this.policy.enabled;
|
||||
if (this.policy.data != null) {
|
||||
switch (this.type) {
|
||||
case PolicyType.PasswordGenerator:
|
||||
this.passGenDefaultType = this.policy.data.defaultType;
|
||||
this.passGenMinLength = this.policy.data.minLength;
|
||||
this.passGenUseUpper = this.policy.data.useUpper;
|
||||
this.passGenUseLower = this.policy.data.useLower;
|
||||
this.passGenUseNumbers = this.policy.data.useNumbers;
|
||||
this.passGenUseSpecial = this.policy.data.useSpecial;
|
||||
this.passGenMinNumbers = this.policy.data.minNumbers;
|
||||
this.passGenMinSpecial = this.policy.data.minSpecial;
|
||||
this.passGenMinNumberWords = this.policy.data.minNumberWords;
|
||||
this.passGenCapitalize = this.policy.data.capitalize;
|
||||
this.passGenIncludeNumber = this.policy.data.includeNumber;
|
||||
break;
|
||||
case PolicyType.MasterPassword:
|
||||
this.masterPassMinComplexity = this.policy.data.minComplexity;
|
||||
this.masterPassMinLength = this.policy.data.minLength;
|
||||
this.masterPassRequireUpper = this.policy.data.requireUpper;
|
||||
this.masterPassRequireLower = this.policy.data.requireLower;
|
||||
this.masterPassRequireNumbers = this.policy.data.requireNumbers;
|
||||
this.masterPassRequireSpecial = this.policy.data.requireSpecial;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.statusCode === 404) {
|
||||
this.enabled = false;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
const request = new PolicyRequest();
|
||||
request.enabled = this.enabled;
|
||||
request.type = this.type;
|
||||
request.data = null;
|
||||
switch (this.type) {
|
||||
case PolicyType.PasswordGenerator:
|
||||
request.data = {
|
||||
defaultType: this.passGenDefaultType,
|
||||
minLength: this.passGenMinLength || null,
|
||||
useUpper: this.passGenUseUpper,
|
||||
useLower: this.passGenUseLower,
|
||||
useNumbers: this.passGenUseNumbers,
|
||||
useSpecial: this.passGenUseSpecial,
|
||||
minNumbers: this.passGenMinNumbers || null,
|
||||
minSpecial: this.passGenMinSpecial || null,
|
||||
minNumberWords: this.passGenMinNumberWords || null,
|
||||
capitalize: this.passGenCapitalize,
|
||||
includeNumber: this.passGenIncludeNumber,
|
||||
};
|
||||
break;
|
||||
case PolicyType.MasterPassword:
|
||||
request.data = {
|
||||
minComplexity: this.masterPassMinComplexity || null,
|
||||
minLength: this.masterPassMinLength || null,
|
||||
requireUpper: this.masterPassRequireUpper,
|
||||
requireLower: this.masterPassRequireLower,
|
||||
requireNumbers: this.masterPassRequireNumbers,
|
||||
requireSpecial: this.masterPassRequireSpecial,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
try {
|
||||
this.formPromise = this.apiService.putPolicy(this.organizationId, this.type, request);
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: 'Edited Policy' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('editedPolicyId', this.name));
|
||||
this.onSavedPolicy.emit();
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog" [ngClass]="{'modal-lg': !editMode}">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="userAddEditTitle">
|
||||
<div class="modal-dialog" [ngClass]="{'modal-lg': !editMode}" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">
|
||||
<h2 class="modal-title" id="userAddEditTitle">
|
||||
{{title}}
|
||||
<small class="text-muted" *ngIf="name">{{name}}</small>
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="!loading">
|
||||
<ng-container *ngIf="!editMode">
|
||||
@@ -116,18 +117,18 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||
<div class="ml-auto">
|
||||
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger"
|
||||
title="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
|
||||
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
|
||||
[appApiAction]="deletePromise">
|
||||
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"></i>
|
||||
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading" aria-hidden="true"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"
|
||||
title="{{'loading' | i18n}}"></i>
|
||||
title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="confirmUserTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">
|
||||
<h2 class="modal-title" id="confirmUserTitle">
|
||||
{{'confirmUser' | i18n}}
|
||||
<small class="text-muted" *ngIf="name">{{name}}</small>
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'confirm' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="groupAccessTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">
|
||||
<h2 class="modal-title" id="groupAccessTitle">
|
||||
{{'groupAccess' | i18n}}
|
||||
<small class="text-muted" *ngIf="name">{{name}}</small>
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</div>
|
||||
<div class="modal-body" *ngIf="!loading">
|
||||
<p>{{'groupAccessUserDesc' | i18n}}</p>
|
||||
@@ -33,7 +34,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<h1>{{'myOrganization' | i18n}}</h1>
|
||||
</div>
|
||||
<div *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</div>
|
||||
<form *ngIf="org && !loading" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="row">
|
||||
@@ -27,7 +28,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<form #form class="card" (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="card-body">
|
||||
<button type="button" class="close" attr.aria-label="{{'cancel' | i18n}}" title="{{'cancel' | i18n}}"
|
||||
(click)="cancel()"><span aria-hidden="true">×</span></button>
|
||||
<button type="button" class="close" appA11yTitle="{{'cancel' | i18n}}" (click)="cancel()"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
<h3 class="card-body-header">{{(add ? 'addSeats' : 'removeSeats') | i18n}}</h3>
|
||||
<div class="row">
|
||||
<div class="form-group col-6">
|
||||
@@ -15,7 +15,7 @@
|
||||
| currency:'$'}} /{{interval | i18n}}
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'submit' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()">
|
||||
@@ -26,3 +26,4 @@
|
||||
</small>
|
||||
</div>
|
||||
</form>
|
||||
<app-payment [showMethods]="false"></app-payment>
|
||||
|
||||
@@ -3,8 +3,14 @@ import {
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
|
||||
import {
|
||||
ActivatedRoute,
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
@@ -13,6 +19,8 @@ import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
|
||||
import { SeatRequest } from 'jslib/models/request/seatRequest';
|
||||
|
||||
import { PaymentComponent } from '../../settings/payment.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-adjust-seats',
|
||||
templateUrl: 'adjust-seats.component.html',
|
||||
@@ -25,11 +33,14 @@ export class AdjustSeatsComponent {
|
||||
@Output() onAdjusted = new EventEmitter<number>();
|
||||
@Output() onCanceled = new EventEmitter();
|
||||
|
||||
@ViewChild(PaymentComponent) paymentComponent: PaymentComponent;
|
||||
|
||||
seatAdjustment = 0;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private router: Router, private activatedRoute: ActivatedRoute) { }
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
@@ -39,12 +50,32 @@ export class AdjustSeatsComponent {
|
||||
request.seatAdjustment *= -1;
|
||||
}
|
||||
|
||||
this.formPromise = this.apiService.postOrganizationSeat(this.organizationId, request);
|
||||
let paymentFailed = false;
|
||||
const action = async () => {
|
||||
const result = await this.apiService.postOrganizationSeat(this.organizationId, request);
|
||||
if (result != null && result.paymentIntentClientSecret != null) {
|
||||
try {
|
||||
await this.paymentComponent.handleStripeCardPayment(result.paymentIntentClientSecret, null);
|
||||
} catch {
|
||||
paymentFailed = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
this.formPromise = action();
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: this.add ? 'Added Seats' : 'Removed Seats' });
|
||||
this.toasterService.popAsync('success', null,
|
||||
this.i18nService.t('adjustedSeats', request.seatAdjustment.toString()));
|
||||
this.onAdjusted.emit(this.seatAdjustment);
|
||||
if (paymentFailed) {
|
||||
this.toasterService.popAsync({
|
||||
body: this.i18nService.t('couldNotChargeCardPayInvoice'),
|
||||
type: 'warning',
|
||||
timeout: 10000,
|
||||
});
|
||||
this.router.navigate(['../billing'], { relativeTo: this.activatedRoute });
|
||||
} else {
|
||||
this.toasterService.popAsync('success', null,
|
||||
this.i18nService.t('adjustedSeats', request.seatAdjustment.toString()));
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="apiKeyTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">{{'apiKey' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<h2 class="modal-title" id="apiKeyTitle">{{'apiKey' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -38,7 +38,7 @@
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"
|
||||
*ngIf="!clientSecret">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'viewApiKey' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="card card-org-plans">
|
||||
<div class="card-body">
|
||||
<button type="button" class="close" attr.aria-label="{{'cancel' | i18n}}" title="{{'cancel' | i18n}}"
|
||||
(click)="cancel()"><span aria-hidden="true">×</span></button>
|
||||
<button type="button" class="close" appA11yTitle="{{'cancel' | i18n}}" (click)="cancel()"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
<h2 class="card-body-header">{{'changeBillingPlan' | i18n}}</h2>
|
||||
<p class="mb-0">{{'changeBillingPlanUpgrade' | i18n}}</p>
|
||||
<app-organization-plans [showFree]="false" [showCancel]="true" plan="families" [organizationId]="organizationId"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="deleteOrganizationTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">{{'deleteOrganization' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<h2 class="modal-title" id="deleteOrganizationTitle">{{'deleteOrganization' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -16,7 +16,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-danger btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'deleteOrganization' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<form #form class="card" (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="card-body">
|
||||
<button type="button" class="close" attr.aria-label="{{'cancel' | i18n}}" title="{{'cancel' | i18n}}"
|
||||
(click)="cancel()"><span aria-hidden="true">×</span></button>
|
||||
<button type="button" class="close" appA11yTitle="{{'cancel' | i18n}}" (click)="cancel()"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
<h3 class="card-body-header">{{'downloadLicense' | i18n}}</h3>
|
||||
<div class="row">
|
||||
<div class="form-group col-6">
|
||||
<div class="d-flex">
|
||||
<label for="installationId">{{'enterInstallationId' | i18n}}</label>
|
||||
<a class="ml-auto" target="_blank" rel="noopener" title="{{'learnMore' | i18n}}"
|
||||
<a class="ml-auto" target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}"
|
||||
href="https://help.bitwarden.com/article/licensing-on-premise/#organization-account-sharing">
|
||||
<i class="fa fa-question-circle-o"></i>
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<input id="installationId" class="form-control" type="text" name="InstallationId"
|
||||
@@ -17,7 +17,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'submit' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()">
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
|
||||
import { UserBillingComponent } from '../../settings/user-billing.component';
|
||||
|
||||
@@ -19,8 +20,8 @@ import { UserBillingComponent } from '../../settings/user-billing.component';
|
||||
export class OrganizationBillingComponent extends UserBillingComponent implements OnInit {
|
||||
constructor(apiService: ApiService, i18nService: I18nService,
|
||||
analytics: Angulartics2, toasterService: ToasterService,
|
||||
private route: ActivatedRoute) {
|
||||
super(apiService, i18nService, analytics, toasterService);
|
||||
private route: ActivatedRoute, platformUtilsService: PlatformUtilsService) {
|
||||
super(apiService, i18nService, analytics, toasterService, platformUtilsService);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
<div class="page-header">
|
||||
<h1>
|
||||
{{'subscription' | i18n}}
|
||||
<small>
|
||||
<i class="fa fa-spinner fa-spin text-muted" *ngIf="firstLoaded && loading" title="{{'loading' | i18n}}"></i>
|
||||
<small *ngIf="firstLoaded && loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</small>
|
||||
</h1>
|
||||
</div>
|
||||
<i class="fa fa-spinner fa-spin text-muted" *ngIf="!firstLoaded && loading" title="{{'loading' | i18n}}"></i>
|
||||
<ng-container *ngIf="!firstLoaded && loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="sub">
|
||||
<app-callout type="warning" title="{{'canceled' | i18n}}" *ngIf="subscription && subscription.cancelled">
|
||||
{{'subscriptionCanceled' | i18n}}</app-callout>
|
||||
@@ -14,7 +18,7 @@
|
||||
<p>{{'subscriptionPendingCanceled' | i18n}}</p>
|
||||
<button #reinstateBtn type="button" class="btn btn-outline-secondary btn-submit" (click)="reinstate()"
|
||||
[appApiAction]="reinstatePromise" [disabled]="reinstateBtn.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'reinstateSubscription' | i18n}}</span>
|
||||
</button>
|
||||
</app-callout>
|
||||
@@ -25,7 +29,7 @@
|
||||
<dd *ngIf="sub.expiration">
|
||||
{{sub.expiration | date:'mediumDate'}}
|
||||
<span *ngIf="isExpired" class="text-danger ml-2">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
|
||||
{{'licenseIsExpired' | i18n}}
|
||||
</span>
|
||||
</dd>
|
||||
@@ -77,7 +81,7 @@
|
||||
</div>
|
||||
<div class="card mt-3" *ngIf="showUpdateLicense">
|
||||
<div class="card-body">
|
||||
<button type="button" class="close" attr.aria-label="{{'cancel' | i18n}}" title="{{'cancel' | i18n}}"
|
||||
<button type="button" class="close" appA11yTitle="{{'cancel' | i18n}}"
|
||||
(click)="closeUpdateLicense(false)"><span aria-hidden="true">×</span></button>
|
||||
<h3 class="card-body-header">{{'updateLicense' | i18n}}</h3>
|
||||
<app-update-license [organizationId]="organizationId" (onUpdated)="closeUpdateLicense(true)"
|
||||
@@ -97,7 +101,7 @@
|
||||
<button #cancelBtn type="button" class="btn btn-outline-danger btn-submit ml-auto" (click)="cancel()"
|
||||
[appApiAction]="cancelPromise" [disabled]="cancelBtn.loading"
|
||||
*ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'cancelSubscription' | i18n}}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -103,7 +103,7 @@ export class OrganizationSubscriptionComponent implements OnInit {
|
||||
}
|
||||
|
||||
async changePlan() {
|
||||
if (this.subscription == null) {
|
||||
if (this.subscription == null && this.sub.planType === PlanType.Free) {
|
||||
this.showChangePlan = !this.showChangePlan;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="rotateKeyTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">{{'rotateApiKey' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<h2 class="modal-title" id="rotateKeyTitle">{{'rotateApiKey' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -38,7 +38,7 @@
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading"
|
||||
*ngIf="!clientSecret">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'rotateApiKey' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { PolicyService } from 'jslib/abstractions/policy.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType';
|
||||
@@ -20,8 +21,8 @@ import { TwoFactorSetupComponent as BaseTwoFactorSetupComponent } from '../../se
|
||||
export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent {
|
||||
constructor(apiService: ApiService, userService: UserService,
|
||||
componentFactoryResolver: ComponentFactoryResolver, messagingService: MessagingService,
|
||||
private route: ActivatedRoute) {
|
||||
super(apiService, userService, componentFactoryResolver, messagingService);
|
||||
policyService: PolicyService, private route: ActivatedRoute) {
|
||||
super(apiService, userService, componentFactoryResolver, messagingService, policyService);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
|
||||
@@ -2,12 +2,15 @@ import { Component } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { EventService } from 'jslib/abstractions/event.service';
|
||||
import { ExportService } from 'jslib/abstractions/export.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
|
||||
import { ExportComponent as BaseExportComponent } from '../../tools/export.component';
|
||||
|
||||
import { EventType } from 'jslib/enums/eventType';
|
||||
|
||||
@Component({
|
||||
selector: 'app-org-export',
|
||||
templateUrl: '../../tools/export.component.html',
|
||||
@@ -17,8 +20,8 @@ export class ExportComponent extends BaseExportComponent {
|
||||
|
||||
constructor(cryptoService: CryptoService, i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService, exportService: ExportService,
|
||||
private route: ActivatedRoute) {
|
||||
super(cryptoService, i18nService, platformUtilsService, exportService);
|
||||
eventService: EventService, private route: ActivatedRoute) {
|
||||
super(cryptoService, i18nService, platformUtilsService, exportService, eventService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -34,4 +37,9 @@ export class ExportComponent extends BaseExportComponent {
|
||||
getFileName() {
|
||||
return super.getFileName('org');
|
||||
}
|
||||
|
||||
async collectEvent(): Promise<any> {
|
||||
// TODO
|
||||
// await this.eventService.collect(EventType.Organization_ClientExportedVault);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { AuditService } from 'jslib/abstractions/audit.service';
|
||||
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||
import { CollectionService } from 'jslib/abstractions/collection.service';
|
||||
import { EventService } from 'jslib/abstractions/event.service';
|
||||
import { FolderService } from 'jslib/abstractions/folder.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
@@ -34,9 +35,22 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
auditService: AuditService, stateService: StateService,
|
||||
userService: UserService, collectionService: CollectionService,
|
||||
totpService: TotpService, passwordGenerationService: PasswordGenerationService,
|
||||
private apiService: ApiService, messagingService: MessagingService) {
|
||||
private apiService: ApiService, messagingService: MessagingService,
|
||||
eventService: EventService) {
|
||||
super(cipherService, folderService, i18nService, platformUtilsService, auditService, stateService,
|
||||
userService, collectionService, totpService, passwordGenerationService, messagingService);
|
||||
userService, collectionService, totpService, passwordGenerationService, messagingService,
|
||||
eventService);
|
||||
}
|
||||
|
||||
protected allowOwnershipAssignment() {
|
||||
if (this.ownershipOptions != null && this.ownershipOptions.length > 1) {
|
||||
if (this.organization != null) {
|
||||
return this.cloneMode && this.organization.isAdmin;
|
||||
} else {
|
||||
return !this.editMode || this.cloneMode;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected loadCollections() {
|
||||
@@ -64,10 +78,10 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
}
|
||||
|
||||
protected async saveCipher(cipher: Cipher) {
|
||||
if (!this.organization.isAdmin) {
|
||||
if (!this.organization.isAdmin || cipher.organizationId == null) {
|
||||
return super.saveCipher(cipher);
|
||||
}
|
||||
if (this.editMode) {
|
||||
if (this.editMode && !this.cloneMode) {
|
||||
const request = new CipherRequest(cipher);
|
||||
return this.apiService.putCipherAdmin(this.cipherId, request);
|
||||
} else {
|
||||
@@ -80,6 +94,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
if (!this.organization.isAdmin) {
|
||||
return super.deleteCipher();
|
||||
}
|
||||
return this.apiService.deleteCipherAdmin(this.cipherId);
|
||||
return this.cipher.isDeleted ? this.apiService.deleteCipherAdmin(this.cipherId)
|
||||
: this.apiService.putDeleteCipherAdmin(this.cipherId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||
import { EventService } from 'jslib/abstractions/event.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { SearchService } from 'jslib/abstractions/search.service';
|
||||
@@ -33,13 +34,14 @@ export class CiphersComponent extends BaseCiphersComponent {
|
||||
constructor(searchService: SearchService, analytics: Angulartics2,
|
||||
toasterService: ToasterService, i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService, cipherService: CipherService,
|
||||
private apiService: ApiService) {
|
||||
super(searchService, analytics, toasterService, i18nService, platformUtilsService, cipherService);
|
||||
private apiService: ApiService, eventService: EventService) {
|
||||
super(searchService, analytics, toasterService, i18nService, platformUtilsService,
|
||||
cipherService, eventService);
|
||||
}
|
||||
|
||||
async load(filter: (cipher: CipherView) => boolean = null) {
|
||||
if (!this.organization.isAdmin) {
|
||||
await super.load(filter);
|
||||
await super.load(filter, this.deleted);
|
||||
return;
|
||||
}
|
||||
this.accessEvents = this.organization.useEvents;
|
||||
@@ -63,13 +65,19 @@ export class CiphersComponent extends BaseCiphersComponent {
|
||||
}
|
||||
this.searchPending = false;
|
||||
let filteredCiphers = this.allCiphers;
|
||||
if (this.filter != null) {
|
||||
filteredCiphers = filteredCiphers.filter(this.filter);
|
||||
}
|
||||
|
||||
if (this.searchText == null || this.searchText.trim().length < 2) {
|
||||
this.ciphers = filteredCiphers;
|
||||
this.ciphers = filteredCiphers.filter((c) => {
|
||||
if (c.isDeleted !== this.deleted) {
|
||||
return false;
|
||||
}
|
||||
return this.filter == null || this.filter(c);
|
||||
});
|
||||
} else {
|
||||
this.ciphers = this.searchService.searchCiphersBasic(filteredCiphers, this.searchText);
|
||||
if (this.filter != null) {
|
||||
filteredCiphers = filteredCiphers.filter(this.filter);
|
||||
}
|
||||
this.ciphers = this.searchService.searchCiphersBasic(filteredCiphers, this.searchText, this.deleted);
|
||||
}
|
||||
await this.resetPaging();
|
||||
}
|
||||
@@ -84,9 +92,9 @@ export class CiphersComponent extends BaseCiphersComponent {
|
||||
|
||||
protected deleteCipher(id: string) {
|
||||
if (!this.organization.isAdmin) {
|
||||
return super.deleteCipher(id);
|
||||
return super.deleteCipher(id, this.deleted);
|
||||
}
|
||||
return this.apiService.deleteCipherAdmin(id);
|
||||
return this.deleted ? this.apiService.deleteCipherAdmin(id) : this.apiService.putDeleteCipherAdmin(id);
|
||||
}
|
||||
|
||||
protected showFixOldAttachments(c: CipherView) {
|
||||
|
||||
@@ -24,6 +24,7 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
i18nService: I18nService, cipherService: CipherService,
|
||||
private apiService: ApiService) {
|
||||
super(collectionService, platformUtilsService, i18nService, cipherService);
|
||||
this.allowSelectNone = true;
|
||||
}
|
||||
|
||||
protected async loadCipher() {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<div class="container page-content">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<app-org-vault-groupings [showFolders]="false" [showFavorites]="false"
|
||||
<app-org-vault-groupings [showFolders]="false" [showFavorites]="false" [showTrash]="true"
|
||||
(onAllClicked)="clearGroupingFilters()" (onCipherTypeClicked)="filterCipherType($event)"
|
||||
(onCollectionClicked)="filterCollection($event.id)" (onSearchTextChanged)="filterSearchText($event)">
|
||||
(onCollectionClicked)="filterCollection($event.id)" (onSearchTextChanged)="filterSearchText($event)"
|
||||
(onTrashClicked)="filterDeleted()">
|
||||
</app-org-vault-groupings>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
@@ -11,17 +12,22 @@
|
||||
<h1>
|
||||
{{'vault' | i18n}}
|
||||
<small #actionSpinner [appApiAction]="ciphersComponent.actionPromise">
|
||||
<i *ngIf="actionSpinner.loading" class="fa fa-spinner fa-spin text-muted"
|
||||
title="{{'loading' | i18n}}"></i>
|
||||
<ng-container *ngIf="actionSpinner.loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</ng-container>
|
||||
</small>
|
||||
</h1>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm ml-auto" (click)="addCipher()">
|
||||
<i class="fa fa-plus fa-fw"></i>{{'addItem' | i18n}}
|
||||
<button type="button" class="btn btn-outline-primary btn-sm ml-auto" (click)="addCipher()"
|
||||
*ngIf="!deleted">
|
||||
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>{{'addItem' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
<app-org-vault-ciphers (onCipherClicked)="editCipher($event)"
|
||||
(onAttachmentsClicked)="editCipherAttachments($event)" (onAddCipher)="addCipher()"
|
||||
(onCollectionsClicked)="editCipherCollections($event)" (onEventsClicked)="viewEvents($event)">
|
||||
(onCollectionsClicked)="editCipherCollections($event)" (onEventsClicked)="viewEvents($event)"
|
||||
(onCloneClicked)="cloneCipher($event)">
|
||||
</app-org-vault-ciphers>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,8 +49,9 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
@ViewChild('eventsTemplate', { read: ViewContainerRef }) eventsModalRef: ViewContainerRef;
|
||||
|
||||
organization: Organization;
|
||||
collectionId: string;
|
||||
type: CipherType;
|
||||
collectionId: string = null;
|
||||
type: CipherType = null;
|
||||
deleted: boolean = false;
|
||||
|
||||
private modal: ModalComponent = null;
|
||||
|
||||
@@ -61,7 +62,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
private broadcasterService: BroadcasterService, private ngZone: NgZone) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.route.parent.params.subscribe(async (params) => {
|
||||
const queryParams = this.route.parent.params.subscribe(async (params) => {
|
||||
this.organization = await this.userService.getOrganization(params.organizationId);
|
||||
this.groupingsComponent.organization = this.organization;
|
||||
this.ciphersComponent.organization = this.organization;
|
||||
@@ -92,7 +93,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
this.groupingsComponent.selectedAll = true;
|
||||
await this.ciphersComponent.reload();
|
||||
} else {
|
||||
if (qParams.type) {
|
||||
if (qParams.deleted) {
|
||||
this.groupingsComponent.selectedTrash = true;
|
||||
await this.filterDeleted(true);
|
||||
} else if (qParams.type) {
|
||||
const t = parseInt(qParams.type, null);
|
||||
this.groupingsComponent.selectedType = t;
|
||||
await this.filterCipherType(t, true);
|
||||
@@ -116,6 +120,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
queryParamsSub.unsubscribe();
|
||||
}
|
||||
});
|
||||
|
||||
if (queryParams != null) {
|
||||
queryParams.unsubscribe();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -125,6 +133,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
async clearGroupingFilters() {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.ciphersComponent.deleted = false;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchVault');
|
||||
await this.ciphersComponent.applyFilter();
|
||||
this.clearFilters();
|
||||
@@ -133,6 +142,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
async filterCipherType(type: CipherType, load = false) {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.ciphersComponent.deleted = false;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchType');
|
||||
const filter = (c: CipherView) => c.type === type;
|
||||
if (load) {
|
||||
@@ -147,6 +157,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
async filterCollection(collectionId: string, load = false) {
|
||||
this.ciphersComponent.showAddNew = true;
|
||||
this.ciphersComponent.deleted = false;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchCollection');
|
||||
const filter = (c: CipherView) => {
|
||||
if (collectionId === 'unassigned') {
|
||||
@@ -165,6 +176,20 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterDeleted(load: boolean = false) {
|
||||
this.ciphersComponent.showAddNew = false;
|
||||
this.ciphersComponent.deleted = true;
|
||||
this.groupingsComponent.searchPlaceholder = this.i18nService.t('searchTrash');
|
||||
if (load) {
|
||||
await this.ciphersComponent.reload(null, true);
|
||||
} else {
|
||||
await this.ciphersComponent.applyFilter(null);
|
||||
}
|
||||
this.clearFilters();
|
||||
this.deleted = true;
|
||||
this.go();
|
||||
}
|
||||
|
||||
filterSearchText(searchText: string) {
|
||||
this.ciphersComponent.searchText = searchText;
|
||||
this.ciphersComponent.search(200);
|
||||
@@ -255,6 +280,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
this.modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
childComponent.onRestoredCipher.subscribe(async (c: CipherView) => {
|
||||
this.modal.close();
|
||||
await this.ciphersComponent.refresh();
|
||||
});
|
||||
|
||||
this.modal.onClosed.subscribe(() => {
|
||||
this.modal = null;
|
||||
@@ -263,6 +292,18 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
return childComponent;
|
||||
}
|
||||
|
||||
cloneCipher(cipher: CipherView) {
|
||||
const component = this.editCipher(cipher);
|
||||
component.cloneMode = true;
|
||||
component.organizationId = this.organization.id;
|
||||
if (this.organization.isAdmin) {
|
||||
component.collections = this.groupingsComponent.collections.filter((c) => !c.readOnly);
|
||||
}
|
||||
// Regardless of Admin state, the collection Ids need to passed manually as they are not assigned value
|
||||
// in the add-edit componenet
|
||||
component.collectionIds = cipher.collectionIds;
|
||||
}
|
||||
|
||||
async viewEvents(cipher: CipherView) {
|
||||
if (this.modal != null) {
|
||||
this.modal.close();
|
||||
@@ -287,6 +328,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
private clearFilters() {
|
||||
this.collectionId = null;
|
||||
this.type = null;
|
||||
this.deleted = false;
|
||||
}
|
||||
|
||||
private go(queryParams: any = null) {
|
||||
@@ -294,6 +336,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
queryParams = {
|
||||
type: this.type,
|
||||
collectionId: this.collectionId,
|
||||
deleted: this.deleted ? true : null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,9 @@ export class EventService {
|
||||
case EventType.User_FailedLogIn2fa:
|
||||
msg = this.i18nService.t('failedLogin2fa');
|
||||
break;
|
||||
case EventType.User_ClientExportedVault:
|
||||
msg = this.i18nService.t('exportedVault');
|
||||
break;
|
||||
// Cipher
|
||||
case EventType.Cipher_Created:
|
||||
msg = this.i18nService.t('createdItemId', this.formatCipherId(ev, options));
|
||||
@@ -70,8 +73,14 @@ export class EventService {
|
||||
msg = this.i18nService.t('editedItemId', this.formatCipherId(ev, options));
|
||||
break;
|
||||
case EventType.Cipher_Deleted:
|
||||
msg = this.i18nService.t('permanentlyDeletedItemId', this.formatCipherId(ev, options));
|
||||
break;
|
||||
case EventType.Cipher_SoftDeleted:
|
||||
msg = this.i18nService.t('deletedItemId', this.formatCipherId(ev, options));
|
||||
break;
|
||||
case EventType.Cipher_Restored:
|
||||
msg = this.i18nService.t('restoredItemId', this.formatCipherId(ev, options));
|
||||
break;
|
||||
case EventType.Cipher_AttachmentCreated:
|
||||
msg = this.i18nService.t('createdAttachmentForItem', this.formatCipherId(ev, options));
|
||||
break;
|
||||
@@ -81,6 +90,30 @@ export class EventService {
|
||||
case EventType.Cipher_Shared:
|
||||
msg = this.i18nService.t('sharedItemId', this.formatCipherId(ev, options));
|
||||
break;
|
||||
case EventType.Cipher_ClientViewed:
|
||||
msg = this.i18nService.t('viewedItemId', this.formatCipherId(ev, options));
|
||||
break;
|
||||
case EventType.Cipher_ClientToggledPasswordVisible:
|
||||
msg = this.i18nService.t('viewedPasswordItemId', this.formatCipherId(ev, options));
|
||||
break;
|
||||
case EventType.Cipher_ClientToggledHiddenFieldVisible:
|
||||
msg = this.i18nService.t('viewedHiddenFieldItemId', this.formatCipherId(ev, options));
|
||||
break;
|
||||
case EventType.Cipher_ClientToggledCardCodeVisible:
|
||||
msg = this.i18nService.t('viewedSecurityCodeItemId', this.formatCipherId(ev, options));
|
||||
break;
|
||||
case EventType.Cipher_ClientCopiedHiddenField:
|
||||
msg = this.i18nService.t('copiedHiddenFieldItemId', this.formatCipherId(ev, options));
|
||||
break;
|
||||
case EventType.Cipher_ClientCopiedPassword:
|
||||
msg = this.i18nService.t('copiedPasswordItemId', this.formatCipherId(ev, options));
|
||||
break;
|
||||
case EventType.Cipher_ClientCopiedCardCode:
|
||||
msg = this.i18nService.t('copiedSecurityCodeItemId', this.formatCipherId(ev, options));
|
||||
break;
|
||||
case EventType.Cipher_ClientAutofilled:
|
||||
msg = this.i18nService.t('autofilledItemId', this.formatCipherId(ev, options));
|
||||
break;
|
||||
case EventType.Cipher_UpdatedCollections:
|
||||
msg = this.i18nService.t('editedCollectionsForItem', this.formatCipherId(ev, options));
|
||||
break;
|
||||
@@ -127,6 +160,11 @@ export class EventService {
|
||||
case EventType.Organization_PurgedVault:
|
||||
msg = this.i18nService.t('purgedOrganizationVault');
|
||||
break;
|
||||
/*
|
||||
case EventType.Organization_ClientExportedVault:
|
||||
msg = this.i18nService.t('exportedOrganizationVault');
|
||||
break;
|
||||
*/
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -34,12 +34,13 @@ import { ConstantsService } from 'jslib/services/constants.service';
|
||||
import { ContainerService } from 'jslib/services/container.service';
|
||||
import { CryptoService } from 'jslib/services/crypto.service';
|
||||
import { EnvironmentService } from 'jslib/services/environment.service';
|
||||
import { EventService as EventLoggingService } from 'jslib/services/event.service';
|
||||
import { ExportService } from 'jslib/services/export.service';
|
||||
import { FolderService } from 'jslib/services/folder.service';
|
||||
import { ImportService } from 'jslib/services/import.service';
|
||||
import { LockService } from 'jslib/services/lock.service';
|
||||
import { NotificationsService } from 'jslib/services/notifications.service';
|
||||
import { PasswordGenerationService } from 'jslib/services/passwordGeneration.service';
|
||||
import { PolicyService } from 'jslib/services/policy.service';
|
||||
import { SearchService } from 'jslib/services/search.service';
|
||||
import { SettingsService } from 'jslib/services/settings.service';
|
||||
import { StateService } from 'jslib/services/state.service';
|
||||
@@ -47,6 +48,7 @@ import { SyncService } from 'jslib/services/sync.service';
|
||||
import { TokenService } from 'jslib/services/token.service';
|
||||
import { TotpService } from 'jslib/services/totp.service';
|
||||
import { UserService } from 'jslib/services/user.service';
|
||||
import { VaultTimeoutService } from 'jslib/services/vaultTimeout.service';
|
||||
import { WebCryptoFunctionService } from 'jslib/services/webCryptoFunction.service';
|
||||
|
||||
import { ApiService as ApiServiceAbstraction } from 'jslib/abstractions/api.service';
|
||||
@@ -58,11 +60,11 @@ import { CollectionService as CollectionServiceAbstraction } from 'jslib/abstrac
|
||||
import { CryptoService as CryptoServiceAbstraction } from 'jslib/abstractions/crypto.service';
|
||||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib/abstractions/cryptoFunction.service';
|
||||
import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib/abstractions/environment.service';
|
||||
import { EventService as EventLoggingServiceAbstraction } from 'jslib/abstractions/event.service';
|
||||
import { ExportService as ExportServiceAbstraction } from 'jslib/abstractions/export.service';
|
||||
import { FolderService as FolderServiceAbstraction } from 'jslib/abstractions/folder.service';
|
||||
import { I18nService as I18nServiceAbstraction } from 'jslib/abstractions/i18n.service';
|
||||
import { ImportService as ImportServiceAbstraction } from 'jslib/abstractions/import.service';
|
||||
import { LockService as LockServiceAbstraction } from 'jslib/abstractions/lock.service';
|
||||
import { LogService as LogServiceAbstraction } from 'jslib/abstractions/log.service';
|
||||
import { MessagingService as MessagingServiceAbstraction } from 'jslib/abstractions/messaging.service';
|
||||
import { NotificationsService as NotificationsServiceAbstraction } from 'jslib/abstractions/notifications.service';
|
||||
@@ -70,6 +72,7 @@ import {
|
||||
PasswordGenerationService as PasswordGenerationServiceAbstraction,
|
||||
} from 'jslib/abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib/abstractions/platformUtils.service';
|
||||
import { PolicyService as PolicyServiceAbstraction } from 'jslib/abstractions/policy.service';
|
||||
import { SearchService as SearchServiceAbstraction } from 'jslib/abstractions/search.service';
|
||||
import { SettingsService as SettingsServiceAbstraction } from 'jslib/abstractions/settings.service';
|
||||
import { StateService as StateServiceAbstraction } from 'jslib/abstractions/state.service';
|
||||
@@ -78,6 +81,7 @@ import { SyncService as SyncServiceAbstraction } from 'jslib/abstractions/sync.s
|
||||
import { TokenService as TokenServiceAbstraction } from 'jslib/abstractions/token.service';
|
||||
import { TotpService as TotpServiceAbstraction } from 'jslib/abstractions/totp.service';
|
||||
import { UserService as UserServiceAbstraction } from 'jslib/abstractions/user.service';
|
||||
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib/abstractions/vaultTimeout.service';
|
||||
|
||||
const i18nService = new I18nService(window.navigator.language, 'locales');
|
||||
const stateService = new StateService();
|
||||
@@ -103,12 +107,14 @@ const folderService = new FolderService(cryptoService, userService, apiService,
|
||||
i18nService, cipherService);
|
||||
const collectionService = new CollectionService(cryptoService, userService, storageService, i18nService);
|
||||
searchService = new SearchService(cipherService, platformUtilsService);
|
||||
const lockService = new LockService(cipherService, folderService, collectionService,
|
||||
cryptoService, platformUtilsService, storageService, messagingService, searchService, userService, null);
|
||||
const policyService = new PolicyService(userService, storageService);
|
||||
const vaultTimeoutService = new VaultTimeoutService(cipherService, folderService, collectionService,
|
||||
cryptoService, platformUtilsService, storageService, messagingService, searchService, userService, tokenService,
|
||||
null, async () => messagingService.send('logout', { expired: false }));
|
||||
const syncService = new SyncService(userService, apiService, settingsService,
|
||||
folderService, cipherService, cryptoService, collectionService, storageService, messagingService,
|
||||
folderService, cipherService, cryptoService, collectionService, storageService, messagingService, policyService,
|
||||
async (expired: boolean) => messagingService.send('logout', { expired: expired }));
|
||||
const passwordGenerationService = new PasswordGenerationService(cryptoService, storageService);
|
||||
const passwordGenerationService = new PasswordGenerationService(cryptoService, storageService, policyService);
|
||||
const totpService = new TotpService(storageService, cryptoFunctionService);
|
||||
const containerService = new ContainerService(cryptoService);
|
||||
const authService = new AuthService(cryptoService, apiService,
|
||||
@@ -116,9 +122,10 @@ const authService = new AuthService(cryptoService, apiService,
|
||||
const exportService = new ExportService(folderService, cipherService, apiService);
|
||||
const importService = new ImportService(cipherService, folderService, apiService, i18nService, collectionService);
|
||||
const notificationsService = new NotificationsService(userService, syncService, appIdService,
|
||||
apiService, lockService, async () => messagingService.send('logout', { expired: true }));
|
||||
apiService, vaultTimeoutService, async () => messagingService.send('logout', { expired: true }));
|
||||
const environmentService = new EnvironmentService(apiService, storageService, notificationsService);
|
||||
const auditService = new AuditService(cryptoFunctionService, apiService);
|
||||
const eventLoggingService = new EventLoggingService(storageService, apiService, userService, cipherService);
|
||||
|
||||
const analytics = new Analytics(window, () => platformUtilsService.isDev() || platformUtilsService.isSelfHost(),
|
||||
platformUtilsService, storageService, appIdService);
|
||||
@@ -138,6 +145,7 @@ export function initFactory(): Function {
|
||||
base: isDev ? null : window.location.origin,
|
||||
api: isDev ? 'http://localhost:4000' : null,
|
||||
identity: isDev ? 'http://localhost:33656' : null,
|
||||
events: isDev ? 'http://localhost:46273' : null,
|
||||
|
||||
// Uncomment these (and comment out the above) if you want to target production
|
||||
// servers for local development.
|
||||
@@ -145,12 +153,14 @@ export function initFactory(): Function {
|
||||
// base: null,
|
||||
// api: 'https://api.bitwarden.com',
|
||||
// identity: 'https://identity.bitwarden.com',
|
||||
// events: 'https://events.bitwarden.com',
|
||||
});
|
||||
setTimeout(() => notificationsService.init(environmentService), 3000);
|
||||
|
||||
lockService.init(true);
|
||||
vaultTimeoutService.init(true);
|
||||
const locale = await storageService.get<string>(ConstantsService.localeKey);
|
||||
await i18nService.init(locale);
|
||||
eventLoggingService.init(true);
|
||||
authService.init();
|
||||
const htmlEl = window.document.documentElement;
|
||||
htmlEl.classList.add('locale_' + i18nService.translationLocale);
|
||||
@@ -196,7 +206,7 @@ export function initFactory(): Function {
|
||||
{ provide: MessagingServiceAbstraction, useValue: messagingService },
|
||||
{ provide: BroadcasterService, useValue: broadcasterService },
|
||||
{ provide: SettingsServiceAbstraction, useValue: settingsService },
|
||||
{ provide: LockServiceAbstraction, useValue: lockService },
|
||||
{ provide: VaultTimeoutServiceAbstraction, useValue: vaultTimeoutService },
|
||||
{ provide: StorageServiceAbstraction, useValue: storageService },
|
||||
{ provide: StateServiceAbstraction, useValue: stateService },
|
||||
{ provide: ExportServiceAbstraction, useValue: exportService },
|
||||
@@ -204,6 +214,8 @@ export function initFactory(): Function {
|
||||
{ provide: ImportServiceAbstraction, useValue: importService },
|
||||
{ provide: NotificationsServiceAbstraction, useValue: notificationsService },
|
||||
{ provide: CryptoFunctionServiceAbstraction, useValue: cryptoFunctionService },
|
||||
{ provide: EventLoggingServiceAbstraction, useValue: eventLoggingService },
|
||||
{ provide: PolicyServiceAbstraction, useValue: policyService },
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: initFactory,
|
||||
|
||||
@@ -4,18 +4,18 @@ import {
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
|
||||
import { LockService } from 'jslib/abstractions/lock.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
|
||||
|
||||
@Injectable()
|
||||
export class UnauthGuardService implements CanActivate {
|
||||
constructor(private lockService: LockService, private userService: UserService,
|
||||
constructor(private vaultTimeoutService: VaultTimeoutService, private userService: UserService,
|
||||
private router: Router) { }
|
||||
|
||||
async canActivate() {
|
||||
const isAuthed = await this.userService.isAuthenticated();
|
||||
if (isAuthed) {
|
||||
const locked = await this.lockService.isLocked();
|
||||
const locked = await this.vaultTimeoutService.isLocked();
|
||||
if (locked) {
|
||||
this.router.navigate(['lock']);
|
||||
} else {
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<form #form class="card" (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="card-body">
|
||||
<button type="button" class="close" attr.aria-label="{{'cancel' | i18n}}" title="{{'cancel' | i18n}}"
|
||||
(click)="cancel()"><span aria-hidden="true">×</span></button>
|
||||
<button type="button" class="close" appA11yTitle="{{'cancel' | i18n}}" (click)="cancel()"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
<h3 class="card-body-header">{{'addCredit' | i18n}}</h3>
|
||||
<div class="mb-4 text-lg" *ngIf="showOptions">
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="Method" id="credit-method-paypal"
|
||||
[value]="paymentMethodType.PayPal" [(ngModel)]="method">
|
||||
<label class="form-check-label" for="credit-method-paypal">
|
||||
<i class="fa fa-fw fa-paypal"></i> PayPal</label>
|
||||
<i class="fa fa-fw fa-paypal" aria-hidden="true"></i> PayPal</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="Method" id="credit-method-bitcoin"
|
||||
[value]="paymentMethodType.BitPay" [(ngModel)]="method">
|
||||
<label class="form-check-label" for="credit-method-bitcoin">
|
||||
<i class="fa fa-fw fa-bitcoin"></i> Bitcoin</label>
|
||||
<i class="fa fa-fw fa-bitcoin" aria-hidden="true"></i> Bitcoin</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -31,7 +31,7 @@
|
||||
<small class="form-text text-muted">{{'creditDelayed' | i18n}}</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading || ppLoading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'submit' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<form #form class="card" (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="card-body">
|
||||
<button type="button" class="close" attr.aria-label="{{'cancel' | i18n}}" title="{{'cancel' | i18n}}"
|
||||
(click)="cancel()"><span aria-hidden="true">×</span></button>
|
||||
<button type="button" class="close" appA11yTitle="{{'cancel' | i18n}}" (click)="cancel()"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
<h3 class="card-body-header">{{(currentType != null ? 'changePaymentMethod' : 'addPaymentMethod') | i18n}}</h3>
|
||||
<app-payment [hideBank]="!organizationId" [hideCredit]="true"></app-payment>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'submit' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<form #form class="card" (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="card-body">
|
||||
<button type="button" class="close" attr.aria-label="{{'cancel' | i18n}}" title="{{'cancel' | i18n}}"
|
||||
(click)="cancel()"><span aria-hidden="true">×</span></button>
|
||||
<button type="button" class="close" appA11yTitle="{{'cancel' | i18n}}" (click)="cancel()"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
<h3 class="card-body-header">{{(add ? 'addStorage' : 'removeStorage') | i18n}}</h3>
|
||||
<div class="row">
|
||||
<div class="form-group col-6">
|
||||
@@ -16,7 +16,7 @@
|
||||
| currency:'$'}} /{{interval | i18n}}
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'submit' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()">
|
||||
@@ -27,3 +27,4 @@
|
||||
</small>
|
||||
</div>
|
||||
</form>
|
||||
<app-payment [showMethods]="false"></app-payment>
|
||||
|
||||
@@ -3,8 +3,14 @@ import {
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
|
||||
import {
|
||||
ActivatedRoute,
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
@@ -13,6 +19,10 @@ import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
|
||||
import { StorageRequest } from 'jslib/models/request/storageRequest';
|
||||
|
||||
import { PaymentResponse } from 'jslib/models/response/paymentResponse';
|
||||
|
||||
import { PaymentComponent } from './payment.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-adjust-storage',
|
||||
templateUrl: 'adjust-storage.component.html',
|
||||
@@ -25,11 +35,14 @@ export class AdjustStorageComponent {
|
||||
@Output() onAdjusted = new EventEmitter<number>();
|
||||
@Output() onCanceled = new EventEmitter();
|
||||
|
||||
@ViewChild(PaymentComponent) paymentComponent: PaymentComponent;
|
||||
|
||||
storageAdjustment = 0;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private router: Router, private activatedRoute: ActivatedRoute) { }
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
@@ -39,16 +52,38 @@ export class AdjustStorageComponent {
|
||||
request.storageGbAdjustment *= -1;
|
||||
}
|
||||
|
||||
if (this.organizationId == null) {
|
||||
this.formPromise = this.apiService.postAccountStorage(request);
|
||||
} else {
|
||||
this.formPromise = this.apiService.postOrganizationStorage(this.organizationId, request);
|
||||
}
|
||||
let paymentFailed = false;
|
||||
const action = async () => {
|
||||
let response: Promise<PaymentResponse>;
|
||||
if (this.organizationId == null) {
|
||||
response = this.formPromise = this.apiService.postAccountStorage(request);
|
||||
} else {
|
||||
response = this.formPromise = this.apiService.postOrganizationStorage(this.organizationId, request);
|
||||
}
|
||||
const result = await response;
|
||||
if (result != null && result.paymentIntentClientSecret != null) {
|
||||
try {
|
||||
await this.paymentComponent.handleStripeCardPayment(result.paymentIntentClientSecret, null);
|
||||
} catch {
|
||||
paymentFailed = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
this.formPromise = action();
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: this.add ? 'Added Storage' : 'Removed Storage' });
|
||||
this.toasterService.popAsync('success', null,
|
||||
this.i18nService.t('adjustedStorage', request.storageGbAdjustment.toString()));
|
||||
this.onAdjusted.emit(this.storageAdjustment);
|
||||
if (paymentFailed) {
|
||||
this.toasterService.popAsync({
|
||||
body: this.i18nService.t('couldNotChargeCardPayInvoice'),
|
||||
type: 'warning',
|
||||
timeout: 10000,
|
||||
});
|
||||
this.router.navigate(['../billing'], { relativeTo: this.activatedRoute });
|
||||
} else {
|
||||
this.toasterService.popAsync('success', null,
|
||||
this.i18nService.t('adjustedStorage', request.storageGbAdjustment.toString()));
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span *ngIf="!tokenSent">{{'continue' | i18n}}</span>
|
||||
<span *ngIf="tokenSent">{{'changeEmail' | i18n}}</span>
|
||||
</button>
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
<div class="form-group mb-0">
|
||||
<label for="kdf">{{'kdfAlgorithm' | i18n}}</label>
|
||||
<a class="ml-auto" href="https://en.wikipedia.org/wiki/Key_derivation_function" target="_blank"
|
||||
rel="noopener" title="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o"></i>
|
||||
rel="noopener" appA11yTitle="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
<select id="kdf" name="Kdf" [(ngModel)]="kdf" class="form-control" required>
|
||||
<option *ngFor="let o of kdfOptions" [ngValue]="o.value">{{o.name}}</option>
|
||||
@@ -26,10 +26,10 @@
|
||||
<div class="form-group mb-0">
|
||||
<label for="kdfIterations">{{'kdfIterations' | i18n}}</label>
|
||||
<a class="ml-auto" href="https://en.wikipedia.org/wiki/PBKDF2" target="_blank" rel="noopener"
|
||||
title="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o"></i>
|
||||
appA11yTitle="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
<input id="kdfIterations" type="number" min="5000" max="1000000" name="KdfIterations"
|
||||
<input id="kdfIterations" type="number" min="5000" max="2000000" name="KdfIterations"
|
||||
class="form-control" [(ngModel)]="kdfIterations" required>
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,7 +43,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'changeKdf' | i18n}}</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
<app-callout type="warning">{{'loggedOutWarning' | i18n}}</app-callout>
|
||||
<app-callout type="info" *ngIf="enforcedPolicyOptions">
|
||||
{{'masterPasswordPolicyInEffect' | i18n}}
|
||||
<ul class="mb-0">
|
||||
<li *ngIf="enforcedPolicyOptions?.minComplexity > 0">
|
||||
{{'policyInEffectMinComplexity' | i18n : getPasswordScoreAlertDisplay()}}
|
||||
</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.minLength > 0">
|
||||
{{'policyInEffectMinLength' | i18n : enforcedPolicyOptions?.minLength.toString()}}
|
||||
</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireUpper">{{'policyInEffectUppercase' | i18n}}</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireLower">{{'policyInEffectLowercase' | i18n}}</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireNumbers">{{'policyInEffectNumbers' | i18n}}</li>
|
||||
<li *ngIf="enforcedPolicyOptions?.requireSpecial">{{'policyInEffectSpecial' | i18n : '!@#$%^&*'}}</li>
|
||||
</ul>
|
||||
</app-callout>
|
||||
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
@@ -36,13 +52,13 @@
|
||||
{{'rotateAccountEncKey' | i18n}}
|
||||
</label>
|
||||
<a href="https://help.bitwarden.com/article/change-your-master-password/#rotating-your-accounts-encryption-key"
|
||||
target="_blank" rel="noopener" title="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o"></i>
|
||||
target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'changeMasterPassword' | i18n}}</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -14,10 +14,12 @@ import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib/abstractions/policy.service';
|
||||
import { SyncService } from 'jslib/abstractions/sync.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { CipherString } from 'jslib/models/domain/cipherString';
|
||||
import { MasterPasswordPolicyOptions } from 'jslib/models/domain/masterPasswordPolicyOptions';
|
||||
import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey';
|
||||
|
||||
import { CipherWithIdRequest } from 'jslib/models/request/cipherWithIdRequest';
|
||||
@@ -36,6 +38,7 @@ export class ChangePasswordComponent implements OnInit {
|
||||
formPromise: Promise<any>;
|
||||
masterPasswordScore: number;
|
||||
rotateEncKey = false;
|
||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
|
||||
private masterPasswordStrengthTimeout: any;
|
||||
private email: string;
|
||||
@@ -45,10 +48,32 @@ export class ChangePasswordComponent implements OnInit {
|
||||
private cryptoService: CryptoService, private messagingService: MessagingService,
|
||||
private userService: UserService, private passwordGenerationService: PasswordGenerationService,
|
||||
private platformUtilsService: PlatformUtilsService, private folderService: FolderService,
|
||||
private cipherService: CipherService, private syncService: SyncService) { }
|
||||
private cipherService: CipherService, private syncService: SyncService,
|
||||
private policyService: PolicyService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.email = await this.userService.getEmail();
|
||||
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
|
||||
}
|
||||
|
||||
getPasswordScoreAlertDisplay() {
|
||||
if (this.enforcedPolicyOptions == null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let str: string;
|
||||
switch (this.enforcedPolicyOptions.minComplexity) {
|
||||
case 4:
|
||||
str = this.i18nService.t('strong');
|
||||
break;
|
||||
case 3:
|
||||
str = this.i18nService.t('good');
|
||||
break;
|
||||
default:
|
||||
str = this.i18nService.t('weak');
|
||||
break;
|
||||
}
|
||||
return str + ' (' + this.enforcedPolicyOptions.minComplexity + ')';
|
||||
}
|
||||
|
||||
async submit() {
|
||||
@@ -77,6 +102,17 @@ export class ChangePasswordComponent implements OnInit {
|
||||
|
||||
const strengthResult = this.passwordGenerationService.passwordStrength(this.newMasterPassword,
|
||||
this.getPasswordStrengthUserInput());
|
||||
|
||||
if (this.enforcedPolicyOptions != null &&
|
||||
!this.policyService.evaluateMasterPassword(
|
||||
strengthResult.score,
|
||||
this.newMasterPassword,
|
||||
this.enforcedPolicyOptions)) {
|
||||
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
|
||||
this.i18nService.t('masterPasswordPolicyRequirementsNotMet'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (strengthResult != null && strengthResult.score < 3) {
|
||||
const result = await this.platformUtilsService.showDialog(this.i18nService.t('weakMasterPasswordDesc'),
|
||||
this.i18nService.t('weakMasterPassword'), this.i18nService.t('yes'), this.i18nService.t('no'),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="deAuthTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">{{'deauthorizeSessions' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<h2 class="modal-title" id="deAuthTitle">{{'deauthorizeSessions' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -16,7 +16,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-danger btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'deauthorizeSessions' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="deleteAccountTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">{{'deleteAccount' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<h2 class="modal-title" id="deleteAccountTitle">{{'deleteAccount' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -16,7 +16,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-danger btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'deleteAccount' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<h2>{{'customEqDomains' | i18n}}</h2>
|
||||
<p *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</p>
|
||||
<ng-container *ngIf="!loading">
|
||||
<div class="form-group d-flex" *ngFor="let d of custom; let i = index; trackBy: indexTrackBy">
|
||||
@@ -14,22 +15,24 @@
|
||||
<textarea class="form-control" name="CustomDomain[{{i}}]" id="customDomain_{{i}}"
|
||||
[(ngModel)]="custom[i]" placeholder="{{'ex' | i18n}} google.com, gmail.com" required></textarea>
|
||||
</div>
|
||||
<button type="button" class="btn btn-link text-danger ml-2" (click)="remove(i)" title="{{'remove' | i18n}}">
|
||||
<i class="fa fa-minus-circle fa-lg"></i>
|
||||
<button type="button" class="btn btn-link text-danger ml-2" (click)="remove(i)"
|
||||
appA11yTitle="{{'remove' | i18n}}">
|
||||
<i class="fa fa-minus-circle fa-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" (click)="add()" class="btn btn-outline-secondary btn-sm mb-2">
|
||||
<i class="fa fa-plus fa-fw"></i> {{'newCustomDomain' | i18n}}
|
||||
<i class="fa fa-plus fa-fw" aria-hidden="true"></i> {{'newCustomDomain' | i18n}}
|
||||
</button>
|
||||
<small class="text-muted d-block mb-3">{{'newCustomDomainDesc' | i18n}}</small>
|
||||
</ng-container>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<h2 class="spaced-header">{{'globalEqDomains' | i18n}}</h2>
|
||||
<p *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</p>
|
||||
<table class="table table-hover table-list" *ngIf="!loading && global.length > 0">
|
||||
<tbody>
|
||||
@@ -38,22 +41,22 @@
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown>
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fa fa-cog fa-lg"></i>
|
||||
aria-haspopup="true" aria-expanded="false" appA11yTitle="{{'options' | i18n}}">
|
||||
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="toggleExcluded(d)"
|
||||
*ngIf="!d.excluded">
|
||||
<i class="fa fa-fw fa-close"></i>
|
||||
<i class="fa fa-fw fa-close" aria-hidden="true"></i>
|
||||
{{'exclude' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="toggleExcluded(d)"
|
||||
*ngIf="d.excluded">
|
||||
<i class="fa fa-fw fa-plus"></i>
|
||||
<i class="fa fa-fw fa-plus" aria-hidden="true"></i>
|
||||
{{'include' | i18n}}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" appStopClick (click)="customize(d)">
|
||||
<i class="fa fa-fw fa-scissors"></i>
|
||||
<i class="fa fa-fw fa-scissors" aria-hidden="true"></i>
|
||||
{{'customize' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
@@ -63,7 +66,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -6,22 +6,41 @@
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="lockOption">{{'lockOptions' | i18n}}</label>
|
||||
<select id="lockOption" name="LockOption" [(ngModel)]="lockOption" class="form-control">
|
||||
<option *ngFor="let o of lockOptions" [ngValue]="o.value">{{o.name}}</option>
|
||||
<label for="vaultTimeout">{{'vaultTimeout' | i18n}}</label>
|
||||
<select id="vaultTimeout" name="VaultTimeout" [(ngModel)]="vaultTimeout" class="form-control">
|
||||
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{o.name}}</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">{{'lockOptionsDesc' | i18n}}</small>
|
||||
<small class="form-text text-muted">{{'vaultTimeoutDesc' | i18n}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{{'vaultTimeoutAction' | i18n}}</label>
|
||||
<div class="form-check form-check-block">
|
||||
<input class="form-check-input" type="radio" name="vaultTimeoutAction" id="vaultTimeoutActionLock"
|
||||
value="lock" [(ngModel)]="vaultTimeoutAction">
|
||||
<label class="form-check-label" for="vaultTimeoutActionLock">
|
||||
{{'lock' | i18n}}
|
||||
<small>{{'vaultTimeoutActionLockDesc' | i18n}}</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check mt-2 form-check-block">
|
||||
<input class="form-check-input" type="radio" name="vaultTimeoutAction" id="vaultTimeoutActionLogOut"
|
||||
value="logOut" [(ngModel)]="vaultTimeoutAction" (ngModelChange)="vaultTimeoutActionChanged($event)">
|
||||
<label class="form-check-label" for="vaultTimeoutActionLogOut">
|
||||
{{'logOut' | i18n}}
|
||||
<small>{{'vaultTimeoutActionLogOutDesc' | i18n}}</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<div class="d-flex">
|
||||
<label for="locale">{{'language' | i18n}}</label>
|
||||
<a class="ml-auto" href="https://help.bitwarden.com/article/localization/" target="_blank"
|
||||
rel="noopener" title="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o"></i>
|
||||
rel="noopener" appA11yTitle="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<select id="locale" name="Locale" [(ngModel)]="locale" class="form-control">
|
||||
@@ -39,8 +58,8 @@
|
||||
{{'disableIcons' | i18n}}
|
||||
</label>
|
||||
<a href="https://help.bitwarden.com/article/website-icons/" target="_blank" rel="noopener"
|
||||
title="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o"></i>
|
||||
appA11yTitle="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<small class="form-text text-muted">{{'disableIconsDesc' | i18n}}</small>
|
||||
@@ -52,8 +71,8 @@
|
||||
<label class="form-check-label" for="enableGravatars">
|
||||
{{'enableGravatars' | i18n}}
|
||||
</label>
|
||||
<a href="https://gravatar.com/" target="_blank" rel="noopener" title="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o"></i>
|
||||
<a href="https://gravatar.com/" target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<small class="form-text text-muted">{{'enableGravatarsDesc' | i18n}}</small>
|
||||
|
||||
@@ -7,10 +7,10 @@ import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { LockService } from 'jslib/abstractions/lock.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { StateService } from 'jslib/abstractions/state.service';
|
||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
|
||||
|
||||
import { ConstantsService } from 'jslib/services/constants.service';
|
||||
|
||||
@@ -21,20 +21,21 @@ import { Utils } from 'jslib/misc/utils';
|
||||
templateUrl: 'options.component.html',
|
||||
})
|
||||
export class OptionsComponent implements OnInit {
|
||||
lockOption: number = null;
|
||||
vaultTimeout: number = null;
|
||||
vaultTimeoutAction: string = 'lock';
|
||||
disableIcons: boolean;
|
||||
enableGravatars: boolean;
|
||||
locale: string;
|
||||
lockOptions: any[];
|
||||
vaultTimeouts: any[];
|
||||
localeOptions: any[];
|
||||
|
||||
private startingLocale: string;
|
||||
|
||||
constructor(private storageService: StorageService, private stateService: StateService,
|
||||
private analytics: Angulartics2, private i18nService: I18nService,
|
||||
private toasterService: ToasterService, private lockService: LockService,
|
||||
private toasterService: ToasterService, private vaultTimeoutService: VaultTimeoutService,
|
||||
private platformUtilsService: PlatformUtilsService) {
|
||||
this.lockOptions = [
|
||||
this.vaultTimeouts = [
|
||||
{ name: i18nService.t('oneMinute'), value: 1 },
|
||||
{ name: i18nService.t('fiveMinutes'), value: 5 },
|
||||
{ name: i18nService.t('fifteenMinutes'), value: 15 },
|
||||
@@ -44,12 +45,16 @@ export class OptionsComponent implements OnInit {
|
||||
{ name: i18nService.t('onRefresh'), value: -1 },
|
||||
];
|
||||
if (this.platformUtilsService.isDev()) {
|
||||
this.lockOptions.push({ name: i18nService.t('never'), value: null });
|
||||
this.vaultTimeouts.push({ name: i18nService.t('never'), value: null });
|
||||
}
|
||||
|
||||
const localeOptions: any[] = [];
|
||||
i18nService.supportedTranslationLocales.forEach((locale) => {
|
||||
localeOptions.push({ name: locale, value: locale });
|
||||
let name = locale;
|
||||
if (i18nService.localeNames.has(locale)) {
|
||||
name += (' - ' + i18nService.localeNames.get(locale));
|
||||
}
|
||||
localeOptions.push({ name: name, value: locale });
|
||||
});
|
||||
localeOptions.sort(Utils.getSortFunction(i18nService, 'name'));
|
||||
localeOptions.splice(0, 0, { name: i18nService.t('default'), value: null });
|
||||
@@ -57,14 +62,16 @@ export class OptionsComponent implements OnInit {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.lockOption = await this.storageService.get<number>(ConstantsService.lockOptionKey);
|
||||
this.vaultTimeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
|
||||
this.vaultTimeoutAction = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
|
||||
this.disableIcons = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
|
||||
this.enableGravatars = await this.storageService.get<boolean>('enableGravatars');
|
||||
this.locale = this.startingLocale = await this.storageService.get<string>(ConstantsService.localeKey);
|
||||
}
|
||||
|
||||
async submit() {
|
||||
await this.lockService.setLockOption(this.lockOption != null ? this.lockOption : null);
|
||||
await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout != null ? this.vaultTimeout : null,
|
||||
this.vaultTimeoutAction);
|
||||
await this.storageService.save(ConstantsService.disableFaviconKey, this.disableIcons);
|
||||
await this.stateService.save(ConstantsService.disableFaviconKey, this.disableIcons);
|
||||
await this.storageService.save('enableGravatars', this.enableGravatars);
|
||||
@@ -77,4 +84,18 @@ export class OptionsComponent implements OnInit {
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('optionsUpdated'));
|
||||
}
|
||||
}
|
||||
|
||||
async vaultTimeoutActionChanged(newValue: string) {
|
||||
if (newValue === 'logOut') {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t('vaultTimeoutLogOutConfirmation'),
|
||||
this.i18nService.t('vaultTimeoutLogOutConfirmationTitle'),
|
||||
this.i18nService.t('yes'), this.i18nService.t('cancel'), 'warning');
|
||||
if (!confirmed) {
|
||||
this.vaultTimeoutAction = 'lock';
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.vaultTimeoutAction = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
class="form-text text-muted">{{'licenseFileDesc' | i18n : 'bitwarden_organization_license.json'}}</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'submit' | i18n}}</span>
|
||||
</button>
|
||||
</form>
|
||||
@@ -203,12 +203,15 @@
|
||||
<h2 class="spaced-header mb-4">{{'paymentInformation' | i18n}}</h2>
|
||||
<app-payment [hideCredit]="true"></app-payment>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!createOrganization">
|
||||
<app-payment [showMethods]="false"></app-payment>
|
||||
</ng-container>
|
||||
<small class="text-muted font-italic mt-2 d-block" *ngIf="!createOrganization">
|
||||
{{'paymentCharged' | i18n : (interval | i18n) }}</small>
|
||||
</ng-container>
|
||||
<div [ngClass]="{'mt-4': !createOrganization || plans[plan].noPayment}">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'submit' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" *ngIf="showCancel">
|
||||
|
||||
@@ -169,7 +169,10 @@ export class OrganizationPlansComponent {
|
||||
} else {
|
||||
request.planType = this.plans[this.plan].annualPlanType;
|
||||
}
|
||||
await this.apiService.postOrganizationUpgrade(this.organizationId, request);
|
||||
const result = await this.apiService.postOrganizationUpgrade(this.organizationId, request);
|
||||
if (!result.success && result.paymentIntentClientSecret != null) {
|
||||
await this.paymentComponent.handleStripeCardPayment(result.paymentIntentClientSecret, null);
|
||||
}
|
||||
orgId = this.organizationId;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
<ng-container *ngIf="vault">
|
||||
<p *ngIf="!loaded" class="text-muted">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</p>
|
||||
<ng-container *ngIf="loaded">
|
||||
<ul class="fa-ul card-ul carets" *ngIf="organizations && organizations.length">
|
||||
<li *ngFor="let o of organizations">
|
||||
<a [routerLink]="['/organizations', o.id]" class="text-body">
|
||||
<i class="fa-li fa fa-caret-right"></i> {{o.name}}
|
||||
<i *ngIf="!o.enabled" class="fa fa-exclamation-triangle text-danger"
|
||||
title="{{'organizationIsDisabled' | i18n}}"></i>
|
||||
<i class="fa-li fa fa-caret-right" aria-hidden="true"></i> {{o.name}}
|
||||
<ng-container *ngIf="!o.enabled">
|
||||
<i class="fa fa-exclamation-triangle text-danger" title="{{'organizationIsDisabled' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'organizationIsDisabled' | i18n}}</span>
|
||||
</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p *ngIf="!organizations || !organizations.length">{{'noOrganizationsList' | i18n}}</p>
|
||||
</ng-container>
|
||||
<a href="#" routerLink="/settings/create-organization" class="btn btn-block btn-outline-primary">
|
||||
<i class="fa fa-plus fa-fw"></i>
|
||||
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
|
||||
{{'newOrganization' | i18n}}
|
||||
</a>
|
||||
</ng-container>
|
||||
@@ -24,21 +28,27 @@
|
||||
<h1>
|
||||
{{'organizations' | i18n}}
|
||||
<small [appApiAction]="actionPromise" #action>
|
||||
<i class="fa fa-spinner fa-spin text-muted" *ngIf="action.loading" title="{{'loading' | i18n}}"></i>
|
||||
<ng-container *ngIf="action.loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</ng-container>
|
||||
</small>
|
||||
</h1>
|
||||
<a href="#" routerLink="/settings/create-organization" class="btn btn-sm btn-outline-primary ml-auto"
|
||||
*ngIf="!loaded || (organizations && organizations.length)">
|
||||
<i class="fa fa-plus fa-fw"></i>
|
||||
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
|
||||
{{'newOrganization' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
<i class="fa fa-spinner fa-spin text-muted" *ngIf="!loaded" title="{{'loading' | i18n}}"></i>
|
||||
<ng-container *ngIf="!loaded">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="loaded">
|
||||
<ng-container *ngIf="!organizations || !organizations.length">
|
||||
<p>{{'noOrganizationsList' | i18n}}</p>
|
||||
<a href="#" routerLink="/settings/create-organization" class="btn btn-outline-primary">
|
||||
<i class="fa fa-plus fa-fw"></i>
|
||||
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
|
||||
{{'newOrganization' | i18n}}
|
||||
</a>
|
||||
</ng-container>
|
||||
@@ -50,18 +60,22 @@
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" [routerLink]="['/organizations', o.id]">{{o.name}}</a>
|
||||
<i *ngIf="!o.enabled" class="fa fa-exclamation-triangle text-danger"
|
||||
title="{{'organizationIsDisabled' | i18n}}"></i>
|
||||
<ng-container *ngIf="!o.enabled">
|
||||
<i class="fa fa-exclamation-triangle text-danger"
|
||||
title="{{'organizationIsDisabled' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'organizationIsDisabled' | i18n}}</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td class="table-list-options">
|
||||
<div class="dropdown" appListDropdown>
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fa fa-cog fa-lg"></i>
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
|
||||
appA11yTitle="{{'options' | i18n}}">
|
||||
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="leave(o)">
|
||||
<i class="fa fa-fw fa-sign-out"></i>
|
||||
<i class="fa fa-fw fa-sign-out" aria-hidden="true"></i>
|
||||
{{'leave' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
<div class="mb-4 text-lg" *ngIf="showOptions">
|
||||
<div class="mb-4 text-lg" *ngIf="showOptions && showMethods">
|
||||
<div class="form-check form-check-inline mr-4">
|
||||
<input class="form-check-input" type="radio" name="Method" id="method-card" [value]="paymentMethodType.Card"
|
||||
[(ngModel)]="method" (change)="changeMethod()">
|
||||
<label class="form-check-label" for="method-card">
|
||||
<i class="fa fa-fw fa-credit-card"></i> {{'creditCard' | i18n}}</label>
|
||||
<i class="fa fa-fw fa-credit-card" aria-hidden="true"></i> {{'creditCard' | i18n}}</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline mr-4" *ngIf="!hideBank">
|
||||
<input class="form-check-input" type="radio" name="Method" id="method-bank"
|
||||
[value]="paymentMethodType.BankAccount" [(ngModel)]="method" (change)="changeMethod()">
|
||||
<label class="form-check-label" for="method-bank">
|
||||
<i class="fa fa-fw fa-university"></i> {{'bankAccount' | i18n}}</label>
|
||||
<i class="fa fa-fw fa-university" aria-hidden="true"></i> {{'bankAccount' | i18n}}</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline" *ngIf="!hidePaypal">
|
||||
<input class="form-check-input" type="radio" name="Method" id="method-paypal" [value]="paymentMethodType.PayPal"
|
||||
[(ngModel)]="method" (change)="changeMethod()">
|
||||
<label class="form-check-label" for="method-paypal">
|
||||
<i class="fa fa-fw fa-paypal"></i> PayPal</label>
|
||||
<i class="fa fa-fw fa-paypal" aria-hidden="true"></i> PayPal</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline" *ngIf="!hideCredit">
|
||||
<input class="form-check-input" type="radio" name="Method" id="method-credit" [value]="paymentMethodType.Credit"
|
||||
[(ngModel)]="method" (change)="changeMethod()">
|
||||
<label class="form-check-label" for="method-credit">
|
||||
<i class="fa fa-fw fa-dollar"></i> {{'accountCredit' | i18n}}</label>
|
||||
<i class="fa fa-fw fa-dollar" aria-hidden="true"></i> {{'accountCredit' | i18n}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="method === paymentMethodType.Card">
|
||||
<ng-container *ngIf="showMethods && method === paymentMethodType.Card">
|
||||
<div class="row">
|
||||
<div class="form-group col-4">
|
||||
<label for="stripe-card-number-element">{{'number' | i18n}}</label>
|
||||
@@ -42,15 +42,15 @@
|
||||
<label for="stripe-card-cvc-element" class="d-flex">
|
||||
{{'securityCode' | i18n}}
|
||||
<a href="https://www.cvvnumber.com/cvv.html" tabindex="-1" target="_blank" rel="noopener noreferrer"
|
||||
class="ml-auto" title="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o"></i>
|
||||
class="ml-auto" appA11yTitle="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</a>
|
||||
</label>
|
||||
<div id="stripe-card-cvc-element" class="form-control stripe-form-control"></div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="method === paymentMethodType.BankAccount">
|
||||
<ng-container *ngIf="showMethods && method === paymentMethodType.BankAccount">
|
||||
<app-callout type="warning" title="{{'verifyBankAccount' | i18n}}">
|
||||
{{'verifyBankAccountInitialDesc' | i18n}} {{'verifyBankAccountFailureWarning' | i18n}}
|
||||
</app-callout>
|
||||
@@ -81,13 +81,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="method === paymentMethodType.PayPal">
|
||||
<ng-container *ngIf="showMethods && method === paymentMethodType.PayPal">
|
||||
<div class="mb-3">
|
||||
<div id="bt-dropin-container" class="mb-1"></div>
|
||||
<small class="text-muted">{{'paypalClickSubmit' | i18n}}</small>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="method === paymentMethodType.Credit">
|
||||
<ng-container *ngIf="showMethods && method === paymentMethodType.Credit">
|
||||
<app-callout type="note">
|
||||
{{'makeSureEnoughCredit' | i18n}}
|
||||
</app-callout>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
|
||||
import { PaymentMethodType } from 'jslib/enums/paymentMethodType';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
|
||||
import { WebConstants } from '../../services/webConstants';
|
||||
@@ -34,6 +35,7 @@ const StripeElementClasses = {
|
||||
templateUrl: 'payment.component.html',
|
||||
})
|
||||
export class PaymentComponent implements OnInit {
|
||||
@Input() showMethods = true;
|
||||
@Input() showOptions = true;
|
||||
@Input() method = PaymentMethodType.Card;
|
||||
@Input() hideBank = false;
|
||||
@@ -60,7 +62,7 @@ export class PaymentComponent implements OnInit {
|
||||
private stripeCardExpiryElement: any = null;
|
||||
private stripeCardCvcElement: any = null;
|
||||
|
||||
constructor(private platformUtilsService: PlatformUtilsService) {
|
||||
constructor(private platformUtilsService: PlatformUtilsService, private apiService: ApiService) {
|
||||
this.stripeScript = window.document.createElement('script');
|
||||
this.stripeScript.src = 'https://js.stripe.com/v3/';
|
||||
this.stripeScript.async = true;
|
||||
@@ -162,30 +164,60 @@ export class PaymentComponent implements OnInit {
|
||||
reject(err.message);
|
||||
});
|
||||
} else if (this.method === PaymentMethodType.Card || this.method === PaymentMethodType.BankAccount) {
|
||||
let sourceObj: any = null;
|
||||
let createObj: any = null;
|
||||
if (this.method === PaymentMethodType.Card) {
|
||||
sourceObj = this.stripeCardNumberElement;
|
||||
this.apiService.postSetupPayment().then((clientSecret) =>
|
||||
this.stripe.handleCardSetup(clientSecret, this.stripeCardNumberElement))
|
||||
.then((result: any) => {
|
||||
if (result.error) {
|
||||
reject(result.error.message);
|
||||
} else if (result.setupIntent && result.setupIntent.status === 'succeeded') {
|
||||
resolve([result.setupIntent.payment_method, this.method]);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
sourceObj = 'bank_account';
|
||||
createObj = this.bank;
|
||||
this.stripe.createToken('bank_account', this.bank).then((result: any) => {
|
||||
if (result.error) {
|
||||
reject(result.error.message);
|
||||
} else if (result.token && result.token.id != null) {
|
||||
resolve([result.token.id, this.method]);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
this.stripe.createToken(sourceObj, createObj).then((result: any) => {
|
||||
if (result.error) {
|
||||
reject(result.error.message);
|
||||
} else if (result.token && result.token.id != null) {
|
||||
resolve([result.token.id, this.method]);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleStripeCardPayment(clientSecret: string, successCallback: () => Promise<any>): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.showMethods && this.stripeCardNumberElement == null) {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
const handleCardPayment = () => this.showMethods ?
|
||||
this.stripe.handleCardPayment(clientSecret, this.stripeCardNumberElement) :
|
||||
this.stripe.handleCardPayment(clientSecret);
|
||||
return handleCardPayment().then(async (result: any) => {
|
||||
if (result.error) {
|
||||
reject(result.error.message);
|
||||
} else if (result.paymentIntent && result.paymentIntent.status === 'succeeded') {
|
||||
if (successCallback != null) {
|
||||
await successCallback();
|
||||
}
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private setStripeElement() {
|
||||
window.setTimeout(() => {
|
||||
if (this.method === PaymentMethodType.Card) {
|
||||
if (this.showMethods && this.method === PaymentMethodType.Card) {
|
||||
if (this.stripeCardNumberElement == null) {
|
||||
this.stripeCardNumberElement = this.stripeElements.create('cardNumber', {
|
||||
style: StripeElementStyle,
|
||||
|
||||
@@ -8,27 +8,27 @@
|
||||
<p>{{'premiumUpgradeUnlockFeatures' | i18n}}</p>
|
||||
<ul class="fa-ul">
|
||||
<li>
|
||||
<i class="fa fa-check text-success fa-li"></i>
|
||||
<i class="fa fa-check text-success fa-li" aria-hidden="true"></i>
|
||||
{{'premiumSignUpStorage' | i18n}}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fa fa-check text-success fa-li"></i>
|
||||
<i class="fa fa-check text-success fa-li" aria-hidden="true"></i>
|
||||
{{'premiumSignUpTwoStep' | i18n}}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fa fa-check text-success fa-li"></i>
|
||||
<i class="fa fa-check text-success fa-li" aria-hidden="true"></i>
|
||||
{{'premiumSignUpReports' | i18n}}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fa fa-check text-success fa-li"></i>
|
||||
<i class="fa fa-check text-success fa-li" aria-hidden="true"></i>
|
||||
{{'premiumSignUpTotp' | i18n}}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fa fa-check text-success fa-li"></i>
|
||||
<i class="fa fa-check text-success fa-li" aria-hidden="true"></i>
|
||||
{{'premiumSignUpSupport' | i18n}}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fa fa-check text-success fa-li"></i>
|
||||
<i class="fa fa-check text-success fa-li" aria-hidden="true"></i>
|
||||
{{'premiumSignUpFuture' | i18n}}
|
||||
</li>
|
||||
</ul>
|
||||
@@ -47,7 +47,7 @@
|
||||
<small class="form-text text-muted">{{'licenseFileDesc' | i18n : 'bitwarden_premium_license.json'}}</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'submit' | i18n}}</span>
|
||||
</button>
|
||||
</form>
|
||||
@@ -76,7 +76,7 @@
|
||||
<h2 class="spaced-header mb-4">{{'paymentInformation' | i18n}}</h2>
|
||||
<app-payment [hideBank]="true"></app-payment>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'submit' | i18n}}</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -84,8 +84,13 @@ export class PremiumComponent implements OnInit {
|
||||
}
|
||||
fd.append('additionalStorageGb', (this.additionalStorage || 0).toString());
|
||||
return this.apiService.postPremium(fd);
|
||||
}).then(() => {
|
||||
return this.finalizePremium();
|
||||
}).then((paymentResponse) => {
|
||||
if (!paymentResponse.success && paymentResponse.paymentIntentClientSecret != null) {
|
||||
return this.paymentComponent.handleStripeCardPayment(paymentResponse.paymentIntentClientSecret,
|
||||
() => this.finalizePremium());
|
||||
} else {
|
||||
return this.finalizePremium();
|
||||
}
|
||||
});
|
||||
}
|
||||
await this.formPromise;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<div *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</div>
|
||||
<form *ngIf="profile && !loading" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="row">
|
||||
@@ -27,14 +28,14 @@
|
||||
<p *ngIf="fingerprint">
|
||||
{{'yourAccountsFingerprint' | i18n}}:
|
||||
<a href="https://help.bitwarden.com/article/fingerprint-phrase/" target="_blank" rel="noopener"
|
||||
title="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o"></i></a><br>
|
||||
appA11yTitle="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i></a><br>
|
||||
<code>{{fingerprint}}</code>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'save' | i18n}}</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="purgeVaultTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">{{'purgeVault' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<h2 class="modal-title" id="purgeVaultTitle">{{'purgeVault' | i18n}}</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -16,7 +16,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-danger btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'purgeVault' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="2faAuthenticatorTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">
|
||||
<h2 class="modal-title" id="2faAuthenticatorTitle">
|
||||
{{'twoStepLogin' | i18n}}
|
||||
<small>{{'authenticatorAppTitle' | i18n}}</small>
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -54,7 +54,7 @@
|
||||
<hr *ngIf="enabled">
|
||||
<p class="text-center" [ngClass]="{'mb-0': enabled}">
|
||||
<canvas id="qr"></canvas><br>
|
||||
<code title="{{'key' | i18n}}">{{key}}</code>
|
||||
<code appA11yTitle="{{'key' | i18n}}">{{key}}</code>
|
||||
</p>
|
||||
<ng-container *ngIf="!enabled">
|
||||
<label for="token">3. {{'twoStepAuthenticatorEnterCode' | i18n}}</label>
|
||||
@@ -64,7 +64,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span *ngIf="!enabled">{{'enable' | i18n}}</span>
|
||||
<span *ngIf="enabled">{{'disable' | i18n}}</span>
|
||||
</button>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="2faDuoTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">
|
||||
<h2 class="modal-title" title="2faDuoTitle">
|
||||
{{'twoStepLogin' | i18n}}
|
||||
<small>Duo</small>
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -49,7 +49,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span *ngIf="!enabled">{{'enable' | i18n}}</span>
|
||||
<span *ngIf="enabled">{{'disable' | i18n}}</span>
|
||||
</button>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="2faEmailTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">
|
||||
<h2 class="modal-title" id="2faEmailTitle">
|
||||
{{'twoStepLogin' | i18n}}
|
||||
<small>{{'emailTitle' | i18n}}</small>
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -35,7 +35,7 @@
|
||||
<button #sendBtn type="button"
|
||||
class="btn btn-outline-primary btn-sm btn-submit align-self-start" (click)="sendEmail()"
|
||||
[appApiAction]="emailPromise" [disabled]="sendBtn.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'sendEmail' | i18n}}</span>
|
||||
</button>
|
||||
<span class="text-success ml-3" *ngIf="sentEmail">
|
||||
@@ -51,7 +51,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span *ngIf="!enabled">{{'enable' | i18n}}</span>
|
||||
<span *ngIf="enabled">{{'disable' | i18n}}</span>
|
||||
</button>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="2faRecoveryTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">
|
||||
<h2 class="modal-title" id="2faRecoveryTitle">
|
||||
{{'twoStepLogin' | i18n}}
|
||||
<small>{{'recoveryCodeTitle' | i18n}}</small>
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -11,9 +11,13 @@
|
||||
<h2 [ngClass]="{'mt-5':!organizationId}">
|
||||
{{'providers' | i18n}}
|
||||
<small *ngIf="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-fw text-muted" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-fw text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
</small>
|
||||
</h2>
|
||||
<app-callout type="warning" *ngIf="showPolicyWarning">
|
||||
{{'twoStepLoginPolicyUserWarning' | i18n}}
|
||||
</app-callout>
|
||||
<ul class="list-group list-group-2fa">
|
||||
<li *ngFor="let p of providers" class="list-group-item d-flex align-items-center">
|
||||
<div class="logo-2fa d-flex justify-content-center">
|
||||
@@ -22,8 +26,10 @@
|
||||
<div class="mx-4">
|
||||
<h3 class="mb-0">
|
||||
{{p.name}}
|
||||
<i class="fa fa-check text-success fa-fw" *ngIf="p.enabled && canAccessPremium"
|
||||
title="{{'enabled' | i18n}}"></i>
|
||||
<ng-container *ngIf="p.enabled">
|
||||
<i class="fa fa-check text-success fa-fw" title="{{'enabled' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'enabled' | i18n}}</span>
|
||||
</ng-container>
|
||||
<a href="#" appStopClick class="badge badge-primary" *ngIf="!canAccessPremium && p.premium"
|
||||
(click)="premiumRequired()">
|
||||
{{'premium' | i18n}}
|
||||
|
||||
@@ -9,10 +9,12 @@ import {
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { PolicyService } from 'jslib/abstractions/policy.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { TwoFactorProviders } from 'jslib/services/auth.service';
|
||||
|
||||
import { PolicyType } from 'jslib/enums/policyType';
|
||||
import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType';
|
||||
|
||||
import { ModalComponent } from '../modal.component';
|
||||
@@ -39,12 +41,14 @@ export class TwoFactorSetupComponent implements OnInit {
|
||||
organizationId: string;
|
||||
providers: any[] = [];
|
||||
canAccessPremium: boolean;
|
||||
showPolicyWarning = false;
|
||||
loading = true;
|
||||
|
||||
private modal: ModalComponent = null;
|
||||
|
||||
constructor(protected apiService: ApiService, protected userService: UserService,
|
||||
protected componentFactoryResolver: ComponentFactoryResolver, protected messagingService: MessagingService) { }
|
||||
protected componentFactoryResolver: ComponentFactoryResolver, protected messagingService: MessagingService,
|
||||
protected policyService: PolicyService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.canAccessPremium = await this.userService.canAccessPremium();
|
||||
@@ -83,6 +87,7 @@ export class TwoFactorSetupComponent implements OnInit {
|
||||
}
|
||||
});
|
||||
});
|
||||
this.evaluatePolicies();
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
@@ -166,5 +171,15 @@ export class TwoFactorSetupComponent implements OnInit {
|
||||
p.enabled = enabled;
|
||||
}
|
||||
});
|
||||
this.evaluatePolicies();
|
||||
}
|
||||
|
||||
private async evaluatePolicies() {
|
||||
if (this.organizationId == null && this.providers.filter((p) => p.enabled).length === 1) {
|
||||
const policies = await this.policyService.getAll(PolicyType.TwoFactorAuthentication);
|
||||
this.showPolicyWarning = policies != null && policies.some((p) => p.enabled);
|
||||
} else {
|
||||
this.showPolicyWarning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal fade" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="2faU2fTitle">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">
|
||||
<h2 class="modal-title" id="2faU2fTitle">
|
||||
{{'twoStepLogin' | i18n}}
|
||||
<small>FIDO U2F</small>
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -30,13 +30,21 @@
|
||||
<i class="fa-li fa fa-key"></i>
|
||||
<strong *ngIf="!k.configured || !k.name">{{'u2fkeyX' | i18n : i + 1}}</strong>
|
||||
<strong *ngIf="k.configured && k.name">{{k.name}}</strong>
|
||||
<i class="fa fa-fw"
|
||||
[ngClass]="{'fa-check text-success': !k.compromised, 'fa-exclamation-triangle text-warning': k.compromised}"
|
||||
*ngIf="k.configured && !removeKeyBtn.loading"
|
||||
title="{{(k.compromised ? 'keyCompromised' : 'enabled') | i18n}}"></i>
|
||||
<ng-container *ngIf="k.configured && !removeKeyBtn.loading">
|
||||
<ng-container *ngIf="k.compromised">
|
||||
<i class="fa fa-fw fa-exclamation-triangle text-warning" aria-hidden="true"
|
||||
title="{{'keyCompromised' | i18n}}"></i>
|
||||
<span class="sr-only">{{'keyCompromised' | i18n}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!k.compromised">
|
||||
<i class="fa fa-fw fa-check text-success" aria-hidden="true"
|
||||
title="{{'enabled' | i18n}}"></i>
|
||||
<span class="sr-only">{{'enabled' | i18n}}</span>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="keysConfiguredCount > 1 && k.configured">
|
||||
<i class="fa fa-spin fa-spinner text-muted fa-fw" title="{{'loading' | i18n}}"
|
||||
*ngIf="removeKeyBtn.loading"></i>
|
||||
*ngIf="removeKeyBtn.loading" aria-hidden="true"></i>
|
||||
-
|
||||
<a href="#" appStopClick (click)="remove(k)">{{'remove' | i18n}}</a>
|
||||
</ng-container>
|
||||
@@ -63,32 +71,33 @@
|
||||
{{'readKey' | i18n}}
|
||||
</button>
|
||||
<ng-container *ngIf="readKeyBtn.loading">
|
||||
<i class="fa fa-spinner fa-spin text-muted"></i>
|
||||
<i class="fa fa-spinner fa-spin text-muted" aria-hidden="true"></i>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!readKeyBtn.loading">
|
||||
<ng-container *ngIf="u2fListening">
|
||||
<i class="fa fa-spinner fa-spin text-muted"></i>
|
||||
<i class="fa fa-spinner fa-spin text-muted" aria-hidden="true"></i>
|
||||
{{'twoFactorU2fWaiting' | i18n}}...
|
||||
</ng-container>
|
||||
<ng-container *ngIf="u2fResponse">
|
||||
<i class="fa fa-check-circle text-success"></i>
|
||||
<i class="fa fa-check-circle text-success" aria-hidden="true"></i>
|
||||
{{'twoFactorU2fClickSave' | i18n}}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="u2fError">
|
||||
<i class="fa fa-warning text-danger"></i>
|
||||
<i class="fa fa-warning text-danger" aria-hidden="true"></i>
|
||||
{{'twoFactorU2fProblemReadingTryAgain' | i18n}}
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary" [disabled]="form.loading || !u2fResponse">
|
||||
<i class="fa fa-spinner fa-spin" *ngIf="form.loading" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" *ngIf="form.loading" title="{{'loading' | i18n}}"
|
||||
aria-hidden="true"></i>
|
||||
<span *ngIf="!form.loading">{{'save' | i18n}}</span>
|
||||
</button>
|
||||
<button #disableBtn type="button" class="btn btn-outline-secondary btn-submit"
|
||||
[appApiAction]="disablePromise" [disabled]="disableBtn.loading" (click)="disable()"
|
||||
*ngIf="enabled">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span>{{'disableAllKeys' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user