1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 16:23:44 +00:00

Apply Prettier (#1347)

This commit is contained in:
Oscar Hinton
2021-12-17 15:57:11 +01:00
committed by GitHub
parent 2b0a9d995e
commit 56477eb39c
414 changed files with 33390 additions and 26857 deletions

View File

@@ -1,50 +1,56 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="/404/bootstrap.min.css" rel="stylesheet" type="text/css"
integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l">
<link href="/404/font-awesome.min.css" rel="stylesheet" type="text/css"
integrity="sha512-SfTiTlX6kk+qitfevl/7LibUOeJWlt9rbyDn92a1DqWOw9vWG2MFoays0sgObmWazO5BQPiFucnnEAjpAB+/Sw==">
<link href="/404/styles.css" rel="stylesheet" type="text/css">
<link
href="/404/bootstrap.min.css"
rel="stylesheet"
type="text/css"
integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l"
/>
<link
href="/404/font-awesome.min.css"
rel="stylesheet"
type="text/css"
integrity="sha512-SfTiTlX6kk+qitfevl/7LibUOeJWlt9rbyDn92a1DqWOw9vWG2MFoays0sgObmWazO5BQPiFucnnEAjpAB+/Sw=="
/>
<link href="/404/styles.css" rel="stylesheet" type="text/css" />
<link rel="apple-touch-icon" sizes="180x180" href="/images/icons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/icons/favicon-16x16.png">
<link rel="mask-icon" href="/images/icons/safari-pinned-tab.svg" color="#175DDC">
<link rel="manifest" href="/manifest.json">
<link rel="apple-touch-icon" sizes="180x180" href="/images/icons/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/images/icons/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/images/icons/favicon-16x16.png" />
<link rel="mask-icon" href="/images/icons/safari-pinned-tab.svg" color="#175DDC" />
<link rel="manifest" href="/manifest.json" />
<title>Page not found!</title>
<meta name="description" content="404 Page Not Found">
</head>
<title>Page not found!</title>
<meta name="description" content="404 Page Not Found" />
</head>
<body>
<div class="banner">
<div class="container inner banner">
<div class="row align-items-center">
<div class="col brand">
<i class="fa fa-shield"></i>&nbsp;
<strong>bit</strong>warden
</div>
</div>
</div>
<body>
<div class="banner">
<div class="container inner banner">
<div class="row align-items-center">
<div class="col brand"><i class="fa fa-shield"></i>&nbsp; <strong>bit</strong>warden</div>
</div>
<div class="container inner content">
<h2>Page not found!</h2>
<p>Sorry, but the page you were looking for could not be found.</p>
<p>
<a href="/">
<img src="/images/404.png" class="img-fluid" alt="404 image" width="80%"/>
</a>
</p>
<p>You can <a href="/">return to the web vault</a>, check our <a href="https://status.bitwarden.com/">status page</a>
or <a href="https://bitwarden.com/contact/">contact us</a>.</p>
</div>
<div class="container footer text-muted content">
© Copyright 2021 Bitwarden, Inc.
</div>
</body>
</div>
</div>
<div class="container inner content">
<h2>Page not found!</h2>
<p>Sorry, but the page you were looking for could not be found.</p>
<p>
<a href="/">
<img src="/images/404.png" class="img-fluid" alt="404 image" width="80%" />
</a>
</p>
<p>
You can <a href="/">return to the web vault</a>, check our
<a href="https://status.bitwarden.com/">status page</a> or
<a href="https://bitwarden.com/contact/">contact us</a>.
</p>
</div>
<div class="container footer text-muted content">© Copyright 2021 Bitwarden, Inc.</div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@@ -1,119 +1,121 @@
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
src: url(../fonts/Open_Sans-italic-300.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: italic;
font-weight: 300;
src: url(../fonts/Open_Sans-italic-300.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
src: url(../fonts/Open_Sans-italic-400.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: italic;
font-weight: 400;
src: url(../fonts/Open_Sans-italic-400.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
src: url(../fonts/Open_Sans-italic-600.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: italic;
font-weight: 600;
src: url(../fonts/Open_Sans-italic-600.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
src: url(../fonts/Open_Sans-italic-700.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: italic;
font-weight: 700;
src: url(../fonts/Open_Sans-italic-700.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 800;
src: url(../fonts/Open_Sans-italic-800.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: italic;
font-weight: 800;
src: url(../fonts/Open_Sans-italic-800.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
src: url(../fonts/Open_Sans-normal-300.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: normal;
font-weight: 300;
src: url(../fonts/Open_Sans-normal-300.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: url(../fonts/Open_Sans-normal-400.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: normal;
font-weight: 400;
src: url(../fonts/Open_Sans-normal-400.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: url(../fonts/Open_Sans-normal-600.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: normal;
font-weight: 600;
src: url(../fonts/Open_Sans-normal-600.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
src: url(../fonts/Open_Sans-normal-700.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: normal;
font-weight: 700;
src: url(../fonts/Open_Sans-normal-700.woff) format("woff");
unicode-range: U+0-10FFFF;
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
src: url(../fonts/Open_Sans-normal-800.woff) format('woff');
unicode-range: U+0-10FFFF;
font-family: "Open Sans";
font-style: normal;
font-weight: 800;
src: url(../fonts/Open_Sans-normal-800.woff) format("woff");
unicode-range: U+0-10FFFF;
}
body {
font-family: 'Open Sans';
font-family: "Open Sans";
}
html, body, .row {
height: 100%;
-webkit-font-smoothing: antialiased;
html,
body,
.row {
height: 100%;
-webkit-font-smoothing: antialiased;
}
h2 {
font-size: 25px;
margin-bottom: 12.5px;
font-weight: 500;
line-height: 1.1;
font-size: 25px;
margin-bottom: 12.5px;
font-weight: 500;
line-height: 1.1;
}
.brand {
font-size: 23px;
line-height: 25px;
color: #fff;
font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.banner {
background-color: #175DDC;
height: 56px;
background-color: #175ddc;
height: 56px;
}
.content {
padding-top: 20px;
padding-bottom: 20px;
padding-left: 15px;
padding-right: 15px;
padding-top: 20px;
padding-bottom: 20px;
padding-left: 15px;
padding-right: 15px;
}
.footer {
padding: 40px 0 40px 0;
border-top: 1px solid #dee2e6;
padding: 40px 0 40px 0;
border-top: 1px solid #dee2e6;
}

View File

@@ -1,34 +1,41 @@
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
<div>
<img class="mb-4 logo logo-themed" alt="Bitwarden">
<p class="text-center">
<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>
<img class="mb-4 logo logo-themed" alt="Bitwarden" />
<p class="text-center">
<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>
<div class="container" *ngIf="!loading && !authed">
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{'emergencyAccess' | i18n}}</p>
<div class="card d-block">
<div class="card-body">
<p class="text-center">
{{name}}
</p>
<p>{{'acceptEmergencyAccess' | i18n}}</p>
<hr>
<div class="d-flex">
<a routerLink="/" [queryParams]="{email: email}" class="btn btn-primary btn-block">
{{'logIn' | i18n}}
</a>
<a routerLink="/register" [queryParams]="{email: email}"
class="btn btn-primary btn-block ml-2 mt-0">
{{'createAccount' | i18n}}
</a>
</div>
</div>
</div>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{ "emergencyAccess" | i18n }}</p>
<div class="card d-block">
<div class="card-body">
<p class="text-center">
{{ name }}
</p>
<p>{{ "acceptEmergencyAccess" | i18n }}</p>
<hr />
<div class="d-flex">
<a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
{{ "logIn" | i18n }}
</a>
<a
routerLink="/register"
[queryParams]="{ email: email }"
class="btn btn-primary btn-block ml-2 mt-0"
>
{{ "createAccount" | i18n }}
</a>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,60 +1,54 @@
import { Component } from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { EmergencyAccessAcceptRequest } from 'jslib-common/models/request/emergencyAccessAcceptRequest';
import { BaseAcceptComponent } from '../common/base.accept.component';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { EmergencyAccessAcceptRequest } from "jslib-common/models/request/emergencyAccessAcceptRequest";
import { BaseAcceptComponent } from "../common/base.accept.component";
@Component({
selector: 'app-accept-emergency',
templateUrl: 'accept-emergency.component.html',
selector: "app-accept-emergency",
templateUrl: "accept-emergency.component.html",
})
export class AcceptEmergencyComponent extends BaseAcceptComponent {
name: string;
name: string;
protected requiredParameters: string[] = ["id", "name", "email", "token"];
protected failedShortMessage = "emergencyInviteAcceptFailedShort";
protected failedMessage = "emergencyInviteAcceptFailed";
protected requiredParameters: string[] = ['id', 'name', 'email', 'token'];
protected failedShortMessage = 'emergencyInviteAcceptFailedShort';
protected failedMessage = 'emergencyInviteAcceptFailed';
constructor(
router: Router,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
route: ActivatedRoute,
private apiService: ApiService,
stateService: StateService
) {
super(router, platformUtilsService, i18nService, route, stateService);
}
constructor(
router: Router,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
route: ActivatedRoute,
private apiService: ApiService,
stateService: StateService
) {
super(
router,
platformUtilsService,
i18nService,
route,
stateService
);
}
async authedHandler(qParams: any): Promise<void> {
const request = new EmergencyAccessAcceptRequest();
request.token = qParams.token;
this.actionPromise = this.apiService.postEmergencyAccessAccept(qParams.id, request);
await this.actionPromise;
this.platformUtilService.showToast('success', this.i18nService.t('inviteAccepted'),
this.i18nService.t('emergencyInviteAcceptedDesc'), {timeout: 10000});
this.router.navigate(['/vault']);
}
async unauthedHandler(qParams: any): Promise<void> {
this.name = qParams.name;
if (this.name != null) {
// Fix URL encoding of space issue with Angular
this.name = this.name.replace(/\+/g, ' ');
}
async authedHandler(qParams: any): Promise<void> {
const request = new EmergencyAccessAcceptRequest();
request.token = qParams.token;
this.actionPromise = this.apiService.postEmergencyAccessAccept(qParams.id, request);
await this.actionPromise;
this.platformUtilService.showToast(
"success",
this.i18nService.t("inviteAccepted"),
this.i18nService.t("emergencyInviteAcceptedDesc"),
{ timeout: 10000 }
);
this.router.navigate(["/vault"]);
}
async unauthedHandler(qParams: any): Promise<void> {
this.name = qParams.name;
if (this.name != null) {
// Fix URL encoding of space issue with Angular
this.name = this.name.replace(/\+/g, " ");
}
}
}

View File

@@ -1,35 +1,42 @@
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
<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}}" aria-hidden="true"></i>
<span class="sr-only">{{'loading' | i18n}}</span>
</p>
</div>
<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 }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "loading" | i18n }}</span>
</p>
</div>
</div>
<div class="container" *ngIf="!loading && !authed">
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{'joinOrganization' | i18n}}</p>
<div class="card d-block">
<div class="card-body">
<p class="text-center">
{{orgName}}
<strong class="d-block mt-2">{{email}}</strong>
</p>
<p>{{'joinOrganizationDesc' | i18n}}</p>
<hr>
<div class="d-flex">
<a routerLink="/" [queryParams]="{email: email}" class="btn btn-primary btn-block">
{{'logIn' | i18n}}
</a>
<a routerLink="/register" [queryParams]="{email: email}"
class="btn btn-primary btn-block ml-2 mt-0">
{{'createAccount' | i18n}}
</a>
</div>
</div>
</div>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{ "joinOrganization" | i18n }}</p>
<div class="card d-block">
<div class="card-body">
<p class="text-center">
{{ orgName }}
<strong class="d-block mt-2">{{ email }}</strong>
</p>
<p>{{ "joinOrganizationDesc" | i18n }}</p>
<hr />
<div class="d-flex">
<a routerLink="/" [queryParams]="{ email: email }" class="btn btn-primary btn-block">
{{ "logIn" | i18n }}
</a>
<a
routerLink="/register"
[queryParams]="{ email: email }"
class="btn btn-primary btn-block ml-2 mt-0"
>
{{ "createAccount" | i18n }}
</a>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,116 +1,127 @@
import { Component } from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { OrganizationUserAcceptRequest } from 'jslib-common/models/request/organizationUserAcceptRequest';
import { OrganizationUserResetPasswordEnrollmentRequest } from 'jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest';
import { OrganizationUserAcceptRequest } from "jslib-common/models/request/organizationUserAcceptRequest";
import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest";
import { Utils } from 'jslib-common/misc/utils';
import { Policy } from 'jslib-common/models/domain/policy';
import { BaseAcceptComponent } from '../common/base.accept.component';
import { Utils } from "jslib-common/misc/utils";
import { Policy } from "jslib-common/models/domain/policy";
import { BaseAcceptComponent } from "../common/base.accept.component";
@Component({
selector: 'app-accept-organization',
templateUrl: 'accept-organization.component.html',
selector: "app-accept-organization",
templateUrl: "accept-organization.component.html",
})
export class AcceptOrganizationComponent extends BaseAcceptComponent {
orgName: string;
orgName: string;
protected requiredParameters: string[] = ['organizationId', 'organizationUserId', 'token'];
protected requiredParameters: string[] = ["organizationId", "organizationUserId", "token"];
constructor(
router: Router,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
route: ActivatedRoute,
private apiService: ApiService,
stateService: StateService,
private cryptoService: CryptoService,
private policyService: PolicyService,
private logService: LogService
) {
super(
router,
platformUtilsService,
i18nService,
route,
stateService
);
constructor(
router: Router,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
route: ActivatedRoute,
private apiService: ApiService,
stateService: StateService,
private cryptoService: CryptoService,
private policyService: PolicyService,
private logService: LogService
) {
super(router, platformUtilsService, i18nService, route, stateService);
}
async authedHandler(qParams: any): Promise<void> {
const request = new OrganizationUserAcceptRequest();
request.token = qParams.token;
if (await this.performResetPasswordAutoEnroll(qParams)) {
this.actionPromise = this.apiService
.postOrganizationUserAccept(qParams.organizationId, qParams.organizationUserId, request)
.then(() => {
// Retrieve Public Key
return this.apiService.getOrganizationKeys(qParams.organizationId);
})
.then(async (response) => {
if (response == null) {
throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
}
const publicKey = Utils.fromB64ToArray(response.publicKey);
// RSA Encrypt user's encKey.key with organization public key
const encKey = await this.cryptoService.getEncKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
// Create request and execute enrollment
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.resetPasswordKey = encryptedKey.encryptedString;
return this.apiService.putOrganizationUserResetPasswordEnrollment(
qParams.organizationId,
await this.stateService.getUserId(),
resetRequest
);
});
} else {
this.actionPromise = this.apiService.postOrganizationUserAccept(
qParams.organizationId,
qParams.organizationUserId,
request
);
}
async authedHandler(qParams: any): Promise<void> {
const request = new OrganizationUserAcceptRequest();
request.token = qParams.token;
if (await this.performResetPasswordAutoEnroll(qParams)) {
this.actionPromise = this.apiService.postOrganizationUserAccept(qParams.organizationId,
qParams.organizationUserId, request).then(() => {
// Retrieve Public Key
return this.apiService.getOrganizationKeys(qParams.organizationId);
}).then(async response => {
if (response == null) {
throw new Error(this.i18nService.t('resetPasswordOrgKeysError'));
}
await this.actionPromise;
this.platformUtilService.showToast(
"success",
this.i18nService.t("inviteAccepted"),
this.i18nService.t("inviteAcceptedDesc"),
{ timeout: 10000 }
);
const publicKey = Utils.fromB64ToArray(response.publicKey);
await this.stateService.setOrganizationInvitation(null);
this.router.navigate(["/vault"]);
}
// RSA Encrypt user's encKey.key with organization public key
const encKey = await this.cryptoService.getEncKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
async unauthedHandler(qParams: any): Promise<void> {
this.orgName = qParams.organizationName;
if (this.orgName != null) {
// Fix URL encoding of space issue with Angular
this.orgName = this.orgName.replace(/\+/g, " ");
}
await this.stateService.setOrganizationInvitation(qParams);
}
// Create request and execute enrollment
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.resetPasswordKey = encryptedKey.encryptedString;
return this.apiService.putOrganizationUserResetPasswordEnrollment(qParams.organizationId, await this.stateService.getUserId(), resetRequest);
});
} else {
this.actionPromise = this.apiService.postOrganizationUserAccept(qParams.organizationId,
qParams.organizationUserId, request);
}
await this.actionPromise;
this.platformUtilService.showToast('success', this.i18nService.t('inviteAccepted'),
this.i18nService.t('inviteAcceptedDesc'), {timeout: 10000});
await this.stateService.setOrganizationInvitation(null);
this.router.navigate(['/vault']);
private async performResetPasswordAutoEnroll(qParams: any): Promise<boolean> {
let policyList: Policy[] = null;
try {
const policies = await this.apiService.getPoliciesByToken(
qParams.organizationId,
qParams.token,
qParams.email,
qParams.organizationUserId
);
policyList = this.policyService.mapPoliciesFromToken(policies);
} catch (e) {
this.logService.error(e);
}
async unauthedHandler(qParams: any): Promise<void> {
this.orgName = qParams.organizationName;
if (this.orgName != null) {
// Fix URL encoding of space issue with Angular
this.orgName = this.orgName.replace(/\+/g, ' ');
}
await this.stateService.setOrganizationInvitation(qParams);
if (policyList != null) {
const result = this.policyService.getResetPasswordPolicyOptions(
policyList,
qParams.organizationId
);
// Return true if policy enabled and auto-enroll enabled
return result[1] && result[0].autoEnrollEnabled;
}
private async performResetPasswordAutoEnroll(qParams: any): Promise<boolean> {
let policyList: Policy[] = null;
try {
const policies = await this.apiService.getPoliciesByToken(qParams.organizationId, qParams.token,
qParams.email, qParams.organizationUserId);
policyList = this.policyService.mapPoliciesFromToken(policies);
} catch (e) {
this.logService.error(e);
}
if (policyList != null) {
const result = this.policyService.getResetPasswordPolicyOptions(policyList, qParams.organizationId);
// Return true if policy enabled and auto-enroll enabled
return result[1] && result[0].autoEnrollEnabled;
}
return false;
}
return false;
}
}

View File

@@ -1,27 +1,44 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{'passwordHint' | i18n}}</p>
<div class="card d-block">
<div class="card-body">
<div class="form-group">
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email" required
appAutofocus inputmode="email" appInputVerbatim="false">
<small class="form-text text-muted">{{'enterEmailToGetHint' | i18n}}</small>
</div>
<hr>
<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}}" aria-hidden="true"></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{'cancel' | i18n}}
</a>
</div>
</div>
</div>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{ "passwordHint" | i18n }}</p>
<div class="card d-block">
<div class="card-body">
<div class="form-group">
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
class="form-control"
type="text"
name="Email"
[(ngModel)]="email"
required
appAutofocus
inputmode="email"
appInputVerbatim="false"
/>
<small class="form-text text-muted">{{ "enterEmailToGetHint" | i18n }}</small>
</div>
<hr />
<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 }}"
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,21 +1,25 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { HintComponent as BaseHintComponent } from 'jslib-angular/components/hint.component';
import { HintComponent as BaseHintComponent } from "jslib-angular/components/hint.component";
@Component({
selector: 'app-hint',
templateUrl: 'hint.component.html',
selector: "app-hint",
templateUrl: "hint.component.html",
})
export class HintComponent extends BaseHintComponent {
constructor(router: Router, i18nService: I18nService,
apiService: ApiService, platformUtilsService: PlatformUtilsService,
logService: LogService) {
super(router, i18nService, apiService, platformUtilsService, logService);
}
constructor(
router: Router,
i18nService: I18nService,
apiService: ApiService,
platformUtilsService: PlatformUtilsService,
logService: LogService
) {
super(router, i18nService, apiService, platformUtilsService, logService);
}
}

View File

@@ -1,42 +1,68 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<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" aria-hidden="true"></i>
</p>
<p class="lead text-center mx-4 mb-4">{{'yourVaultIsLocked' | i18n}}</p>
<div class="card d-block">
<div class="card-body">
<div class="form-group">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<div class="d-flex">
<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" appA11yTitle="{{'toggleVisibility' | i18n}}"
(click)="togglePassword()">
<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">
{{'loggedInAsEmailOn' | i18n : email : webVaultHostname}}
</small>
</div>
<hr>
<div class="d-flex">
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading">
<span>
<i class="fa fa-unlock-alt" aria-hidden="true"></i> {{'unlock' | i18n}}
</span>
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
</button>
<button type="button" class="btn btn-outline-secondary btn-block ml-2 mt-0" (click)="logOut()">
{{'logOut' | i18n}}
</button>
</div>
</div>
<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" aria-hidden="true"></i>
</p>
<p class="lead text-center mx-4 mb-4">{{ "yourVaultIsLocked" | i18n }}</p>
<div class="card d-block">
<div class="card-body">
<div class="form-group">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<div class="d-flex">
<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"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<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">
{{ "loggedInAsEmailOn" | i18n: email:webVaultHostname }}
</small>
</div>
<hr />
<div class="d-flex">
<button
type="submit"
class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading"
>
<span>
<i class="fa fa-unlock-alt" aria-hidden="true"></i> {{ "unlock" | i18n }}
</span>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button>
<button
type="button"
class="btn btn-outline-secondary btn-block ml-2 mt-0"
(click)="logOut()"
>
{{ "logOut" | i18n }}
</button>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,48 +1,65 @@
import {
Component,
NgZone,
} from '@angular/core';
import { Router } from '@angular/router';
import { Component, NgZone } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { RouterService } from '../services/router.service';
import { RouterService } from "../services/router.service";
import { LockComponent as BaseLockComponent } from 'jslib-angular/components/lock.component';
import { LockComponent as BaseLockComponent } from "jslib-angular/components/lock.component";
@Component({
selector: 'app-lock',
templateUrl: 'lock.component.html',
selector: "app-lock",
templateUrl: "lock.component.html",
})
export class LockComponent extends BaseLockComponent {
constructor(router: Router, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, messagingService: MessagingService,
cryptoService: CryptoService, vaultTimeoutService: VaultTimeoutService,
environmentService: EnvironmentService, private routerService: RouterService,
stateService: StateService, apiService: ApiService, logService: LogService,
keyConnectorService: KeyConnectorService, ngZone: NgZone) {
super(router, i18nService, platformUtilsService, messagingService, cryptoService,
vaultTimeoutService, environmentService, stateService, apiService, logService,
keyConnectorService, ngZone);
}
constructor(
router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
messagingService: MessagingService,
cryptoService: CryptoService,
vaultTimeoutService: VaultTimeoutService,
environmentService: EnvironmentService,
private routerService: RouterService,
stateService: StateService,
apiService: ApiService,
logService: LogService,
keyConnectorService: KeyConnectorService,
ngZone: NgZone
) {
super(
router,
i18nService,
platformUtilsService,
messagingService,
cryptoService,
vaultTimeoutService,
environmentService,
stateService,
apiService,
logService,
keyConnectorService,
ngZone
);
}
async ngOnInit() {
await super.ngOnInit();
this.onSuccessfulSubmit = async () => {
const previousUrl = this.routerService.getPreviousUrl();
if (previousUrl !== '/' && previousUrl.indexOf('lock') === -1) {
this.successRoute = previousUrl;
}
this.router.navigate([this.successRoute]);
};
}
async ngOnInit() {
await super.ngOnInit();
this.onSuccessfulSubmit = async () => {
const previousUrl = this.routerService.getPreviousUrl();
if (previousUrl !== "/" && previousUrl.indexOf("lock") === -1) {
this.successRoute = previousUrl;
}
this.router.navigate([this.successRoute]);
};
}
}

View File

@@ -1,61 +1,101 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<img class="mb-2 logo logo-themed" alt="Bitwarden">
<p class="lead text-center mx-4 mb-4">{{'loginOrCreateNewAccount' | i18n}}</p>
<div class="card d-block">
<div class="card-body">
<app-callout type="warning" title="{{'resetPasswordPolicyAutoEnroll' | i18n}}"
*ngIf="showResetPasswordAutoEnrollWarning">
{{'resetPasswordAutoEnrollInviteWarning' | i18n}}
</app-callout>
<div class="form-group">
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email" required
inputmode="email" appInputVerbatim="false">
</div>
<div class="form-group">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<div class="d-flex">
<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" appA11yTitle="{{'toggleVisibility' | i18n}}"
(click)="togglePassword()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
</div>
<small class="form-text">
<a routerLink="/hint">{{'getMasterPasswordHint' | i18n}}</a>
</small>
</div>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="rememberEmail" name="RememberEmail"
[(ngModel)]="rememberEmail">
<label class="form-check-label" for="rememberEmail">{{'rememberEmail' | i18n}}</label>
</div>
<div class="mb-n3" [hidden]="!showCaptcha()"><iframe id="hcaptcha_iframe" height="80"></iframe></div>
<hr>
<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" aria-hidden="true"></i> {{'logIn' | i18n}}
</span>
<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" aria-hidden="true"></i> {{'createAccount' | i18n}}
</a>
</div>
<div class="d-flex">
<a routerLink="/sso" class="btn btn-outline-secondary btn-block mt-2">
<i class="fa fa-bank" aria-hidden="true"></i> {{'enterpriseSingleSignOn' | i18n}}
</a>
</div>
</div>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<img class="mb-2 logo logo-themed" alt="Bitwarden" />
<p class="lead text-center mx-4 mb-4">{{ "loginOrCreateNewAccount" | i18n }}</p>
<div class="card d-block">
<div class="card-body">
<app-callout
type="warning"
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
*ngIf="showResetPasswordAutoEnrollWarning"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</app-callout>
<div class="form-group">
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
class="form-control"
type="text"
name="Email"
[(ngModel)]="email"
required
inputmode="email"
appInputVerbatim="false"
/>
</div>
<div class="form-group">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<div class="d-flex">
<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"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button>
</div>
<small class="form-text">
<a routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</a>
</small>
</div>
<div class="form-check mb-3">
<input
type="checkbox"
class="form-check-input"
id="rememberEmail"
name="RememberEmail"
[(ngModel)]="rememberEmail"
/>
<label class="form-check-label" for="rememberEmail">{{ "rememberEmail" | i18n }}</label>
</div>
<div class="mb-n3" [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
<hr />
<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" aria-hidden="true"></i> {{ "logIn" | i18n }} </span>
<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" aria-hidden="true"></i> {{ "createAccount" | i18n }}
</a>
</div>
<div class="d-flex">
<a routerLink="/sso" class="btn btn-outline-secondary btn-block mt-2">
<i class="fa fa-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
</a>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,100 +1,118 @@
import {
Component,
NgZone,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { Component, NgZone } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from 'rxjs/operators';
import { first } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { LoginComponent as BaseLoginComponent } from 'jslib-angular/components/login.component';
import { LoginComponent as BaseLoginComponent } from "jslib-angular/components/login.component";
import { Policy } from 'jslib-common/models/domain/policy';
import { Policy } from "jslib-common/models/domain/policy";
@Component({
selector: 'app-login',
templateUrl: 'login.component.html',
selector: "app-login",
templateUrl: "login.component.html",
})
export class LoginComponent extends BaseLoginComponent {
showResetPasswordAutoEnrollWarning = false;
showResetPasswordAutoEnrollWarning = false;
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
private route: ActivatedRoute,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService,
cryptoFunctionService: CryptoFunctionService,
private apiService: ApiService,
private policyService: PolicyService,
logService: LogService,
ngZone: NgZone
) {
super(
authService,
router,
platformUtilsService,
i18nService,
stateService,
environmentService,
passwordGenerationService,
cryptoFunctionService,
logService,
ngZone
);
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
}
constructor(authService: AuthService, router: Router,
i18nService: I18nService, private route: ActivatedRoute, stateService: StateService,
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService, cryptoFunctionService: CryptoFunctionService,
private apiService: ApiService, private policyService: PolicyService, logService: LogService,
ngZone: NgZone) {
super(authService, router,
platformUtilsService, i18nService,
stateService, environmentService,
passwordGenerationService, cryptoFunctionService,
logService, ngZone);
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
}
async ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async qParams => {
if (qParams.email != null && qParams.email.indexOf('@') > -1) {
this.email = qParams.email;
}
if (qParams.premium != null) {
this.stateService.setLoginRedirect({ route: '/settings/premium' });
} else if (qParams.org != null) {
this.stateService.setLoginRedirect(
{ route: '/settings/create-organization', qParams: { plan: qParams.org } });
}
// Are they coming from an email for sponsoring a families organization
if (qParams.sponsorshipToken != null) {
// After logging in redirect them to setup the families sponsorship
this.stateService.setLoginRedirect({
route: '/setup/families-for-enterprise',
qParams: { token: qParams.sponsorshipToken },
});
}
await super.ngOnInit();
async ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.email != null && qParams.email.indexOf("@") > -1) {
this.email = qParams.email;
}
if (qParams.premium != null) {
this.stateService.setLoginRedirect({ route: "/settings/premium" });
} else if (qParams.org != null) {
this.stateService.setLoginRedirect({
route: "/settings/create-organization",
qParams: { plan: qParams.org },
});
}
const invite = await this.stateService.getOrganizationInvitation();
if (invite != null) {
let policyList: Policy[] = null;
try {
const policies = await this.apiService.getPoliciesByToken(invite.organizationId, invite.token,
invite.email, invite.organizationUserId);
policyList = this.policyService.mapPoliciesFromToken(policies);
} catch (e) {
this.logService.error(e);
}
// Are they coming from an email for sponsoring a families organization
if (qParams.sponsorshipToken != null) {
// After logging in redirect them to setup the families sponsorship
this.stateService.setLoginRedirect({
route: "/setup/families-for-enterprise",
qParams: { token: qParams.sponsorshipToken },
});
}
await super.ngOnInit();
});
if (policyList != null) {
const result = this.policyService.getResetPasswordPolicyOptions(policyList, invite.organizationId);
// Set to true if policy enabled and auto-enroll enabled
this.showResetPasswordAutoEnrollWarning = result[1] && result[0].autoEnrollEnabled;
}
}
const invite = await this.stateService.getOrganizationInvitation();
if (invite != null) {
let policyList: Policy[] = null;
try {
const policies = await this.apiService.getPoliciesByToken(
invite.organizationId,
invite.token,
invite.email,
invite.organizationUserId
);
policyList = this.policyService.mapPoliciesFromToken(policies);
} catch (e) {
this.logService.error(e);
}
if (policyList != null) {
const result = this.policyService.getResetPasswordPolicyOptions(
policyList,
invite.organizationId
);
// Set to true if policy enabled and auto-enroll enabled
this.showResetPasswordAutoEnrollWarning = result[1] && result[0].autoEnrollEnabled;
}
}
}
async goAfterLogIn() {
const loginRedirect = await this.stateService.getLoginRedirect();
if (loginRedirect != null) {
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
await this.stateService.setLoginRedirect(null);
} else {
this.router.navigate([this.successRoute]);
}
async goAfterLogIn() {
const loginRedirect = await this.stateService.getLoginRedirect();
if (loginRedirect != null) {
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
await this.stateService.setLoginRedirect(null);
} else {
this.router.navigate([this.successRoute]);
}
}
}

View File

@@ -1,27 +1,44 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{'deleteAccount' | i18n}}</p>
<div class="card">
<div class="card-body">
<p>{{'deleteRecoverDesc' | i18n}}</p>
<div class="form-group">
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email" required
appAutofocus inputmode="email" appInputVerbatim="false">
</div>
<hr>
<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}}" aria-hidden="true"></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{'cancel' | i18n}}
</a>
</div>
</div>
</div>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{ "deleteAccount" | i18n }}</p>
<div class="card">
<div class="card-body">
<p>{{ "deleteRecoverDesc" | i18n }}</p>
<div class="form-group">
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
class="form-control"
type="text"
name="Email"
[(ngModel)]="email"
required
appAutofocus
inputmode="email"
appInputVerbatim="false"
/>
</div>
<hr />
<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 }}"
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,36 +1,43 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { DeleteRecoverRequest } from 'jslib-common/models/request/deleteRecoverRequest';
import { DeleteRecoverRequest } from "jslib-common/models/request/deleteRecoverRequest";
@Component({
selector: 'app-recover-delete',
templateUrl: 'recover-delete.component.html',
selector: "app-recover-delete",
templateUrl: "recover-delete.component.html",
})
export class RecoverDeleteComponent {
email: string;
formPromise: Promise<any>;
email: string;
formPromise: Promise<any>;
constructor(private router: Router, private apiService: ApiService,
private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
private logService: LogService) {
}
constructor(
private router: Router,
private apiService: ApiService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private logService: LogService
) {}
async submit() {
try {
const request = new DeleteRecoverRequest();
request.email = this.email.trim().toLowerCase();
this.formPromise = this.apiService.postAccountRecoverDelete(request);
await this.formPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('deleteRecoverEmailSent'));
this.router.navigate(['/']);
} catch (e) {
this.logService.error(e);
}
async submit() {
try {
const request = new DeleteRecoverRequest();
request.email = this.email.trim().toLowerCase();
this.formPromise = this.apiService.postAccountRecoverDelete(request);
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("deleteRecoverEmailSent")
);
this.router.navigate(["/"]);
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -1,40 +1,76 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{'recoverAccountTwoStep' | i18n}}</p>
<div class="card">
<div class="card-body">
<p>{{'recoverAccountTwoStepDesc' | i18n}}
<a href="https://help.bitwarden.com/article/lost-two-step-device/" target="_blank"
rel="noopener">{{'learnMore' | i18n}}</a>
</p>
<div class="form-group">
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email" required
appAutofocus inputmode="email" appInputVerbatim="false">
</div>
<div class="form-group">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="password" name="MasterPassword" class="form-control"
[(ngModel)]="masterPassword" required appInputVerbatim>
</div>
<div class="form-group">
<label for="recoveryCode">{{'recoveryCodeTitle' | i18n}}</label>
<input id="recoveryCode" class="text-monospace form-control" type="text" name="RecoveryCode"
[(ngModel)]="recoveryCode" required appInputVerbatim>
</div>
<hr>
<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}}" aria-hidden="true"></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{'cancel' | i18n}}
</a>
</div>
</div>
</div>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{ "recoverAccountTwoStep" | i18n }}</p>
<div class="card">
<div class="card-body">
<p>
{{ "recoverAccountTwoStepDesc" | i18n }}
<a
href="https://help.bitwarden.com/article/lost-two-step-device/"
target="_blank"
rel="noopener"
>{{ "learnMore" | i18n }}</a
>
</p>
<div class="form-group">
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
class="form-control"
type="text"
name="Email"
[(ngModel)]="email"
required
appAutofocus
inputmode="email"
appInputVerbatim="false"
/>
</div>
<div class="form-group">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="password"
name="MasterPassword"
class="form-control"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div>
<div class="form-group">
<label for="recoveryCode">{{ "recoveryCodeTitle" | i18n }}</label>
<input
id="recoveryCode"
class="text-monospace form-control"
type="text"
name="RecoveryCode"
[(ngModel)]="recoveryCode"
required
appInputVerbatim
/>
</div>
<hr />
<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 }}"
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,43 +1,52 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { TwoFactorRecoveryRequest } from 'jslib-common/models/request/twoFactorRecoveryRequest';
import { TwoFactorRecoveryRequest } from "jslib-common/models/request/twoFactorRecoveryRequest";
@Component({
selector: 'app-recover-two-factor',
templateUrl: 'recover-two-factor.component.html',
selector: "app-recover-two-factor",
templateUrl: "recover-two-factor.component.html",
})
export class RecoverTwoFactorComponent {
email: string;
masterPassword: string;
recoveryCode: string;
formPromise: Promise<any>;
email: string;
masterPassword: string;
recoveryCode: string;
formPromise: Promise<any>;
constructor(private router: Router, private apiService: ApiService,
private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
private cryptoService: CryptoService, private authService: AuthService,
private logService: LogService) { }
constructor(
private router: Router,
private apiService: ApiService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private cryptoService: CryptoService,
private authService: AuthService,
private logService: LogService
) {}
async submit() {
try {
const request = new TwoFactorRecoveryRequest();
request.recoveryCode = this.recoveryCode.replace(/\s/g, '').toLowerCase();
request.email = this.email.trim().toLowerCase();
const key = await this.authService.makePreloginKey(this.masterPassword, request.email);
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
this.formPromise = this.apiService.postTwoFactorRecover(request);
await this.formPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('twoStepRecoverDisabled'));
this.router.navigate(['/']);
} catch (e) {
this.logService.error(e);
}
async submit() {
try {
const request = new TwoFactorRecoveryRequest();
request.recoveryCode = this.recoveryCode.replace(/\s/g, "").toLowerCase();
request.email = this.email.trim().toLowerCase();
const key = await this.authService.makePreloginKey(this.masterPassword, request.email);
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
this.formPromise = this.apiService.postTwoFactorRecover(request);
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("twoStepRecoverDisabled")
);
this.router.navigate(["/"]);
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -1,141 +1,216 @@
<div class="layout" [ngClass]="['layout', layout]">
<header class="header" *ngIf="layout === 'enterprise2'">
<div class="container">
<div class="row">
<div class="col-7">
<img alt="Bitwarden" class="logo mb-2" src="../../images/register-layout/logo-horizontal-white.png">
</div>
</div>
<header class="header" *ngIf="layout === 'enterprise2'">
<div class="container">
<div class="row">
<div class="col-7">
<img
alt="Bitwarden"
class="logo mb-2"
src="../../images/register-layout/logo-horizontal-white.png"
/>
</div>
</header>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row">
<div class="col-7" *ngIf="layout">
<div class="mt-5">
<div *ngIf="layout === 'enterprise2'">
<h2>Companies globally trust Bitwarden for password management.</h2>
<p>Start your 7-day free trial!</p>
<p class="highlight">Quickly deploy your <b>organization</b></p>
<p>Use Bitwarden across all platforms</p>
<p>Collaborate and share securely</p>
<figure>
<figcaption>
<cite>
<img src="../../images/register-layout/wired-logo.png" alt="Wired">
</cite>
</figcaption>
<blockquote>
"Bitwarden has become a popular choice among open-source software advocates. After using
it for a few months, I can see why." - February 2020
</blockquote>
</figure>
</div>
<div *ngIf="layout === 'enterprise3'">
<p>Enterprise 3 layout</p>
</div>
<div *ngIf="layout === 'enterprise4'">
<p>Enterprise 4 layout</p>
</div>
</div>
</div>
<div [ngClass]="{'col-5': layout, 'col-12': !layout}">
<div class="row justify-content-md-center mt-5">
<div [ngClass]="{'col-5': !layout, 'col-12': layout}">
<p class="lead text-center mb-4" *ngIf="!layout">{{'createAccount' | i18n}}</p>
<div class="card d-block">
<div class="card-body">
<app-callout title="{{'createOrganizationStep1' | i18n}}" type="info"
icon="fa-thumb-tack" *ngIf="showCreateOrgMessage">
{{'createOrganizationCreatePersonalAccount' | i18n}}
</app-callout>
<div class="form-group">
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" class="form-control" type="text" name="Email" [(ngModel)]="email"
required [appAutofocus]="email === ''" inputmode="email"
appInputVerbatim="false">
<small class="form-text text-muted">{{'emailAddressDesc' | i18n}}</small>
</div>
<div class="form-group">
<label for="name">{{'yourName' | i18n}}</label>
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="name"
[appAutofocus]="email !== ''">
<small class="form-text text-muted">{{'yourNameDesc' | i18n}}</small>
</div>
<div class="form-group">
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions">
</app-callout>
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<div class="d-flex">
<div class="w-100">
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPassword" class="text-monospace form-control mb-1"
[(ngModel)]="masterPassword" (input)="updatePasswordStrength()" required
appInputVerbatim>
<app-password-strength [score]="masterPasswordScore" [showText]="true">
</app-password-strength>
</div>
<div>
<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>
</div>
</div>
<small class="form-text text-muted">{{'masterPassDesc' | i18n}}</small>
</div>
<div class="form-group">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<div class="d-flex">
<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"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
</div>
</div>
<div class="form-group">
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint">
<small class="form-text text-muted">{{'masterPassHintDesc' | i18n}}</small>
</div>
<div [hidden]="!showCaptcha()"><iframe id="hcaptcha_iframe" height="80"></iframe></div>
<div class="form-group" *ngIf="showTerms">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="acceptPolicies"
[(ngModel)]="acceptPolicies" name="AcceptPolicies">
<label class="form-check-label small text-muted" for="acceptPolicies">
{{'acceptPolicies' | i18n}}<br>
<a href="https://bitwarden.com/terms/" target="_blank"
rel="noopener">{{'termsOfService' | i18n}}</a>,
<a href="https://bitwarden.com/privacy/" target="_blank"
rel="noopener">{{'privacyPolicy' | i18n}}</a>
</label>
</div>
</div>
<hr>
<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}}"
aria-hidden="true"></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{'cancel' | i18n}}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</header>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row">
<div class="col-7" *ngIf="layout">
<div class="mt-5">
<div *ngIf="layout === 'enterprise2'">
<h2>Companies globally trust Bitwarden for password management.</h2>
<p>Start your 7-day free trial!</p>
<p class="highlight">Quickly deploy your <b>organization</b></p>
<p>Use Bitwarden across all platforms</p>
<p>Collaborate and share securely</p>
<figure>
<figcaption>
<cite>
<img src="../../images/register-layout/wired-logo.png" alt="Wired" />
</cite>
</figcaption>
<blockquote>
"Bitwarden has become a popular choice among open-source software advocates. After
using it for a few months, I can see why." - February 2020
</blockquote>
</figure>
</div>
<div *ngIf="layout === 'enterprise3'">
<p>Enterprise 3 layout</p>
</div>
<div *ngIf="layout === 'enterprise4'">
<p>Enterprise 4 layout</p>
</div>
</div>
</form>
</div>
<div [ngClass]="{ 'col-5': layout, 'col-12': !layout }">
<div class="row justify-content-md-center mt-5">
<div [ngClass]="{ 'col-5': !layout, 'col-12': layout }">
<p class="lead text-center mb-4" *ngIf="!layout">{{ "createAccount" | i18n }}</p>
<div class="card d-block">
<div class="card-body">
<app-callout
title="{{ 'createOrganizationStep1' | i18n }}"
type="info"
icon="fa-thumb-tack"
*ngIf="showCreateOrgMessage"
>
{{ "createOrganizationCreatePersonalAccount" | i18n }}
</app-callout>
<div class="form-group">
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
class="form-control"
type="text"
name="Email"
[(ngModel)]="email"
required
[appAutofocus]="email === ''"
inputmode="email"
appInputVerbatim="false"
/>
<small class="form-text text-muted">{{ "emailAddressDesc" | i18n }}</small>
</div>
<div class="form-group">
<label for="name">{{ "yourName" | i18n }}</label>
<input
id="name"
class="form-control"
type="text"
name="Name"
[(ngModel)]="name"
[appAutofocus]="email !== ''"
/>
<small class="form-text text-muted">{{ "yourNameDesc" | i18n }}</small>
</div>
<div class="form-group">
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<div class="d-flex">
<div class="w-100">
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="text-monospace form-control mb-1"
[(ngModel)]="masterPassword"
(input)="updatePasswordStrength()"
required
appInputVerbatim
/>
<app-password-strength [score]="masterPasswordScore" [showText]="true">
</app-password-strength>
</div>
<div>
<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>
</div>
</div>
<small class="form-text text-muted">{{ "masterPassDesc" | i18n }}</small>
</div>
<div class="form-group">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<div class="d-flex">
<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"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<div class="form-group">
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input
id="hint"
class="form-control"
type="text"
name="Hint"
[(ngModel)]="hint"
/>
<small class="form-text text-muted">{{ "masterPassHintDesc" | i18n }}</small>
</div>
<div [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
<div class="form-group" *ngIf="showTerms">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="acceptPolicies"
[(ngModel)]="acceptPolicies"
name="AcceptPolicies"
/>
<label class="form-check-label small text-muted" for="acceptPolicies">
{{ "acceptPolicies" | i18n }}<br />
<a href="https://bitwarden.com/terms/" target="_blank" rel="noopener">{{
"termsOfService" | i18n
}}</a
>,
<a href="https://bitwarden.com/privacy/" target="_blank" rel="noopener">{{
"privacyPolicy" | i18n
}}</a>
</label>
</div>
</div>
<hr />
<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 }}"
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>

View File

@@ -1,115 +1,150 @@
import { Component } from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from 'rxjs/operators';
import { first } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { RegisterComponent as BaseRegisterComponent } from 'jslib-angular/components/register.component';
import { RegisterComponent as BaseRegisterComponent } from "jslib-angular/components/register.component";
import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions';
import { Policy } from 'jslib-common/models/domain/policy';
import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions";
import { Policy } from "jslib-common/models/domain/policy";
import { PolicyData } from 'jslib-common/models/data/policyData';
import { ReferenceEventRequest } from 'jslib-common/models/request/referenceEventRequest';
import { PolicyData } from "jslib-common/models/data/policyData";
import { ReferenceEventRequest } from "jslib-common/models/request/referenceEventRequest";
@Component({
selector: 'app-register',
templateUrl: 'register.component.html',
selector: "app-register",
templateUrl: "register.component.html",
})
export class RegisterComponent extends BaseRegisterComponent {
showCreateOrgMessage = false;
layout = '';
enforcedPolicyOptions: MasterPasswordPolicyOptions;
showCreateOrgMessage = false;
layout = "";
enforcedPolicyOptions: MasterPasswordPolicyOptions;
private policies: Policy[];
private policies: Policy[];
constructor(authService: AuthService, router: Router,
i18nService: I18nService, cryptoService: CryptoService,
apiService: ApiService, private route: ActivatedRoute,
stateService: StateService, platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService, private policyService: PolicyService,
environmentService: EnvironmentService, logService: LogService) {
super(authService, router, i18nService, cryptoService, apiService, stateService, platformUtilsService,
passwordGenerationService, environmentService, logService);
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
cryptoService: CryptoService,
apiService: ApiService,
private route: ActivatedRoute,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
private policyService: PolicyService,
environmentService: EnvironmentService,
logService: LogService
) {
super(
authService,
router,
i18nService,
cryptoService,
apiService,
stateService,
platformUtilsService,
passwordGenerationService,
environmentService,
logService
);
}
async ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(qParams => {
this.referenceData = new ReferenceEventRequest();
if (qParams.email != null && qParams.email.indexOf('@') > -1) {
this.email = qParams.email;
}
if (qParams.premium != null) {
this.stateService.setLoginRedirect({ route: '/settings/premium' });
} else if (qParams.org != null) {
this.showCreateOrgMessage = true;
this.referenceData.flow = qParams.org;
this.stateService.setLoginRedirect(
{ route: '/settings/create-organization', qParams: { plan: qParams.org } });
}
if (qParams.layout != null) {
this.layout = this.referenceData.layout = qParams.layout;
}
if (qParams.reference != null) {
this.referenceData.id = qParams.reference;
} else {
this.referenceData.id = ('; ' + document.cookie).split('; reference=').pop().split(';').shift();
}
// Are they coming from an email for sponsoring a families organization
if (qParams.sponsorshipToken != null) {
// After logging in redirect them to setup the families sponsorship
this.stateService.setLoginRedirect({
route: '/setup/families-for-enterprise',
qParams: { token: qParams.sponsorshipToken },
});
}
if (this.referenceData.id === '') {
this.referenceData.id = null;
}
async ngOnInit() {
this.route.queryParams.pipe(first()).subscribe((qParams) => {
this.referenceData = new ReferenceEventRequest();
if (qParams.email != null && qParams.email.indexOf("@") > -1) {
this.email = qParams.email;
}
if (qParams.premium != null) {
this.stateService.setLoginRedirect({ route: "/settings/premium" });
} else if (qParams.org != null) {
this.showCreateOrgMessage = true;
this.referenceData.flow = qParams.org;
this.stateService.setLoginRedirect({
route: "/settings/create-organization",
qParams: { plan: qParams.org },
});
const invite = await this.stateService.getOrganizationInvitation();
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 (e) {
this.logService.error(e);
}
}
if (qParams.layout != null) {
this.layout = this.referenceData.layout = qParams.layout;
}
if (qParams.reference != null) {
this.referenceData.id = qParams.reference;
} else {
this.referenceData.id = ("; " + document.cookie)
.split("; reference=")
.pop()
.split(";")
.shift();
}
// Are they coming from an email for sponsoring a families organization
if (qParams.sponsorshipToken != null) {
// After logging in redirect them to setup the families sponsorship
this.stateService.setLoginRedirect({
route: "/setup/families-for-enterprise",
qParams: { token: qParams.sponsorshipToken },
});
}
if (this.referenceData.id === "") {
this.referenceData.id = null;
}
});
const invite = await this.stateService.getOrganizationInvitation();
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));
}
if (this.policies != null) {
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(this.policies);
}
await super.ngOnInit();
} catch (e) {
this.logService.error(e);
}
}
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();
if (this.policies != null) {
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(
this.policies
);
}
await super.ngOnInit();
}
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();
}
}

View File

@@ -1,31 +1,55 @@
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
<div>
<img class="mb-4 logo logo-themed" alt="Bitwarden">
<p class="text-center">
<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>
<img class="mb-4 logo logo-themed" alt="Bitwarden" />
<p class="text-center">
<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>
<div class="container" *ngIf="!loading">
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{'removeMasterPassword' | i18n}}</p>
<hr>
<div class="card d-block">
<div class="card-body">
<p>{{'convertOrganizationEncryptionDesc' | i18n : organization.name}}</p>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{ "removeMasterPassword" | i18n }}</p>
<hr />
<div class="card d-block">
<div class="card-body">
<p>{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}</p>
<button type="button" class="btn btn-primary btn-block" (click)="convert()" [disabled]="actionPromise">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true" *ngIf="continuing"></i>
{{'removeMasterPassword' | i18n}}
</button>
<button type="button" class="btn btn-outline-secondary btn-block" (click)="leave()" [disabled]="actionPromise">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true" *ngIf="leaving"></i>
{{'leaveOrganization' | i18n}}
</button>
</div>
</div>
<button
type="button"
class="btn btn-primary btn-block"
(click)="convert()"
[disabled]="actionPromise"
>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
*ngIf="continuing"
></i>
{{ "removeMasterPassword" | i18n }}
</button>
<button
type="button"
class="btn btn-outline-secondary btn-block"
(click)="leave()"
[disabled]="actionPromise"
>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
*ngIf="leaving"
></i>
{{ "leaveOrganization" | i18n }}
</button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,10 +1,9 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { RemovePasswordComponent as BaseRemovePasswordComponent } from 'jslib-angular/components/remove-password.component';
import { RemovePasswordComponent as BaseRemovePasswordComponent } from "jslib-angular/components/remove-password.component";
@Component({
selector: 'app-remove-password',
templateUrl: 'remove-password.component.html',
selector: "app-remove-password",
templateUrl: "remove-password.component.html",
})
export class RemovePasswordComponent extends BaseRemovePasswordComponent {
}
export class RemovePasswordComponent extends BaseRemovePasswordComponent {}

View File

@@ -1,73 +1,117 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{'setMasterPassword' | i18n}}</p>
<div class="card d-block">
<div class="card-body text-center" *ngIf="syncLoading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
{{'loading' | i18n}}
</div>
<div class="card-body" *ngIf="!syncLoading">
<app-callout type="info">{{'ssoCompleteRegistration' | i18n}}</app-callout>
<app-callout type="warning" title="{{'resetPasswordPolicyAutoEnroll' | i18n}}"
*ngIf="resetPasswordAutoEnroll">
{{'resetPasswordAutoEnrollInviteWarning' | i18n}}
</app-callout>
<div class="form-group">
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions">
</app-callout>
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<div class="d-flex">
<div class="w-100">
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPasswordHash" class="text-monospace form-control mb-1"
[(ngModel)]="masterPassword" (input)="updatePasswordStrength()" required
appInputVerbatim>
<app-password-strength [score]="masterPasswordScore" [showText]="true">
</app-password-strength>
</div>
<div>
<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>
</div>
</div>
<small class="form-text text-muted">{{'masterPassDesc' | i18n}}</small>
</div>
<div class="form-group">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<div class="d-flex">
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPasswordRetype" class="text-monospace form-control"
[(ngModel)]="masterPasswordRetype" required appInputVerbatim>
<button type="button" class="ml-1 btn btn-link" appA11yTitle="{{'toggleVisibility' | i18n}}"
(click)="togglePassword(true)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
</div>
</div>
<div class="form-group">
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint">
<small class="form-text text-muted">{{'masterPassHintDesc' | i18n}}</small>
</div>
<hr>
<div class="d-flex">
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading">
<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 btn-block ml-2 mt-0" (click)="logOut()">
{{'logOut' | i18n}}
</button>
</div>
</div>
</div>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{ "setMasterPassword" | i18n }}</p>
<div class="card d-block">
<div class="card-body text-center" *ngIf="syncLoading">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
<div class="card-body" *ngIf="!syncLoading">
<app-callout type="info">{{ "ssoCompleteRegistration" | i18n }}</app-callout>
<app-callout
type="warning"
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
*ngIf="resetPasswordAutoEnroll"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</app-callout>
<div class="form-group">
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<div class="d-flex">
<div class="w-100">
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordHash"
class="text-monospace form-control mb-1"
[(ngModel)]="masterPassword"
(input)="updatePasswordStrength()"
required
appInputVerbatim
/>
<app-password-strength [score]="masterPasswordScore" [showText]="true">
</app-password-strength>
</div>
<div>
<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>
</div>
</div>
<small class="form-text text-muted">{{ "masterPassDesc" | i18n }}</small>
</div>
<div class="form-group">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<div class="d-flex">
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="text-monospace form-control"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
/>
<button
type="button"
class="ml-1 btn btn-link"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<div class="form-group">
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint" />
<small class="form-text text-muted">{{ "masterPassHintDesc" | i18n }}</small>
</div>
<hr />
<div class="d-flex">
<button
type="submit"
class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading"
>
<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 btn-block ml-2 mt-0"
(click)="logOut()"
>
{{ "logOut" | i18n }}
</button>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,34 +1,48 @@
import { Component } from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import {
SetPasswordComponent as BaseSetPasswordComponent,
} from 'jslib-angular/components/set-password.component';
import { SetPasswordComponent as BaseSetPasswordComponent } from "jslib-angular/components/set-password.component";
@Component({
selector: 'app-set-password',
templateUrl: 'set-password.component.html',
selector: "app-set-password",
templateUrl: "set-password.component.html",
})
export class SetPasswordComponent extends BaseSetPasswordComponent {
constructor(apiService: ApiService, i18nService: I18nService,
cryptoService: CryptoService, messagingService: MessagingService,
passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService,
policyService: PolicyService, router: Router,
syncService: SyncService, route: ActivatedRoute, stateService: StateService) {
super(i18nService, cryptoService, messagingService, passwordGenerationService,
platformUtilsService, policyService, router, apiService, syncService, route, stateService);
}
constructor(
apiService: ApiService,
i18nService: I18nService,
cryptoService: CryptoService,
messagingService: MessagingService,
passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
router: Router,
syncService: SyncService,
route: ActivatedRoute,
stateService: StateService
) {
super(
i18nService,
cryptoService,
messagingService,
passwordGenerationService,
platformUtilsService,
policyService,
router,
apiService,
syncService,
route,
stateService
);
}
}

View File

@@ -1,33 +1,52 @@
<form #form (ngSubmit)="submit()" class="container" [appApiAction]="initiateSsoFormPromise" ngNativeValidate>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<img class="logo mb-2 logo-themed" alt="Bitwarden">
<div class="card d-block mt-4">
<div class="card-body" *ngIf="loggingIn">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
{{'loading' | i18n}}
</div>
<div class="card-body" *ngIf="!loggingIn">
<p>{{'ssoLogInWithOrgIdentifier' | i18n}}</p>
<div class="form-group">
<label for="identifier">{{'organizationIdentifier' | i18n}}</label>
<input id="identifier" class="form-control" type="text" name="Identifier"
[(ngModel)]="identifier" required appAutofocus>
</div>
<hr>
<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" aria-hidden="true"></i> {{'logIn' | i18n}}
</span>
<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}}
</a>
</div>
</div>
</div>
<form
#form
(ngSubmit)="submit()"
class="container"
[appApiAction]="initiateSsoFormPromise"
ngNativeValidate
>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<img class="logo mb-2 logo-themed" alt="Bitwarden" />
<div class="card d-block mt-4">
<div class="card-body" *ngIf="loggingIn">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
<div class="card-body" *ngIf="!loggingIn">
<p>{{ "ssoLogInWithOrgIdentifier" | i18n }}</p>
<div class="form-group">
<label for="identifier">{{ "organizationIdentifier" | i18n }}</label>
<input
id="identifier"
class="form-control"
type="text"
name="Identifier"
[(ngModel)]="identifier"
required
appAutofocus
/>
</div>
<hr />
<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" aria-hidden="true"></i> {{ "logIn" | i18n }} </span>
<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 }}
</a>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,59 +1,74 @@
import { Component } from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from 'rxjs/operators';
import { first } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SsoComponent as BaseSsoComponent } from 'jslib-angular/components/sso.component';
import { SsoComponent as BaseSsoComponent } from "jslib-angular/components/sso.component";
@Component({
selector: 'app-sso',
templateUrl: 'sso.component.html',
selector: "app-sso",
templateUrl: "sso.component.html",
})
export class SsoComponent extends BaseSsoComponent {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, route: ActivatedRoute,
stateService: StateService, platformUtilsService: PlatformUtilsService,
apiService: ApiService, cryptoFunctionService: CryptoFunctionService,
environmentService: EnvironmentService, passwordGenerationService: PasswordGenerationService,
logService: LogService) {
super(authService, router, i18nService, route, stateService, platformUtilsService,
apiService, cryptoFunctionService, environmentService, passwordGenerationService, logService);
this.redirectUri = window.location.origin + '/sso-connector.html';
this.clientId = 'web';
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
route: ActivatedRoute,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
cryptoFunctionService: CryptoFunctionService,
environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationService,
logService: LogService
) {
super(
authService,
router,
i18nService,
route,
stateService,
platformUtilsService,
apiService,
cryptoFunctionService,
environmentService,
passwordGenerationService,
logService
);
this.redirectUri = window.location.origin + "/sso-connector.html";
this.clientId = "web";
}
async ngOnInit() {
super.ngOnInit();
this.route.queryParams.pipe(first()).subscribe(async qParams => {
if (qParams.identifier != null) {
this.identifier = qParams.identifier;
} else {
const storedIdentifier = await this.stateService.getSsoOrgIdentifier();
if (storedIdentifier != null) {
this.identifier = storedIdentifier;
}
}
});
}
async submit() {
await this.stateService.setSsoOrganizationIdentifier(this.identifier);
if (this.clientId === 'browser') {
document.cookie = `ssoHandOffMessage=${this.i18nService.t('ssoHandOff')};SameSite=strict`;
async ngOnInit() {
super.ngOnInit();
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.identifier != null) {
this.identifier = qParams.identifier;
} else {
const storedIdentifier = await this.stateService.getSsoOrgIdentifier();
if (storedIdentifier != null) {
this.identifier = storedIdentifier;
}
super.submit();
}
});
}
async submit() {
await this.stateService.setSsoOrganizationIdentifier(this.identifier);
if (this.clientId === "browser") {
document.cookie = `ssoHandOffMessage=${this.i18nService.t("ssoHandOff")};SameSite=strict`;
}
super.submit();
}
}

View File

@@ -1,54 +1,68 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="twoStepOptionsTitle">
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="twoStepOptionsTitle">{{'twoStepOptions' | i18n}}</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="twoStepOptionsTitle">{{ "twoStepOptions" | i18n }}</h2>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="list-group list-group-flush-2fa">
<div *ngFor="let p of providers" class="list-group-item list-group-item-action">
<div class="two-factor-content">
<div class="logo-col">
<img [class]="'mfaType' + p.type" [alt]="p.name + ' logo'" />
</div>
<div class="text-col">
<h3>{{ p.name }}</h3>
{{ p.description }}
</div>
<div class="btn-col">
<button
[attr.aria-describedby]="p.name"
type="button"
class="btn btn-outline-secondary btn-sm"
(click)="choose(p)"
>
{{ "select" | i18n }}
</button>
</div>
</div>
<div class="modal-body">
<div class="list-group list-group-flush-2fa">
<div *ngFor="let p of providers" class="list-group-item list-group-item-action">
<div class="two-factor-content">
<div class="logo-col">
<img [class]="'mfaType' + p.type" [alt]="p.name + ' logo'">
</div>
<div class="text-col">
<h3>{{p.name}}</h3>
{{p.description}}
</div>
<div class="btn-col">
<button [attr.aria-describedby]="p.name" type="button"
class="btn btn-outline-secondary btn-sm" (click)="choose(p)">
{{'select' | i18n}}
</button>
</div>
</div>
</div>
<div class="list-group-item list-group-item-action" (click)="recover()">
<div class="two-factor-content">
<div class="logo-col">
<img class="recovery-code-img" alt="rc logo">
</div>
<div class="text-col">
<h3>{{'recoveryCodeTitle' | i18n}}</h3>
{{'recoveryCodeDesc' | i18n}}
</div>
<div class="btn-col">
<button [attr.aria-descibedby]="'recoveryCodeTitle' | i18n" type="button"
class="btn btn-outline-secondary btn-sm" (click)="recover()">
{{'select' | i18n}}
</button>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' |
i18n}}</button>
</div>
<div class="list-group-item list-group-item-action" (click)="recover()">
<div class="two-factor-content">
<div class="logo-col">
<img class="recovery-code-img" alt="rc logo" />
</div>
<div class="text-col">
<h3>{{ "recoveryCodeTitle" | i18n }}</h3>
{{ "recoveryCodeDesc" | i18n }}
</div>
<div class="btn-col">
<button
[attr.aria-descibedby]="'recoveryCodeTitle' | i18n"
type="button"
class="btn btn-outline-secondary btn-sm"
(click)="recover()"
>
{{ "select" | i18n }}
</button>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "close" | i18n }}
</button>
</div>
</div>
</div>
</div>

View File

@@ -1,21 +1,23 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { AuthService } from "jslib-common/abstractions/auth.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import {
TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent,
} from 'jslib-angular/components/two-factor-options.component';
import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "jslib-angular/components/two-factor-options.component";
@Component({
selector: 'app-two-factor-options',
templateUrl: 'two-factor-options.component.html',
selector: "app-two-factor-options",
templateUrl: "two-factor-options.component.html",
})
export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, platformUtilsService: PlatformUtilsService) {
super(authService, router, i18nService, platformUtilsService, window);
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService
) {
super(authService, router, i18nService, platformUtilsService, window);
}
}

View File

@@ -1,80 +1,148 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate autocomplete="off">
<div class="row justify-content-md-center mt-5">
<div class="col-5"
[ngClass]="{'col-9': selectedProviderType === providerType.Duo || selectedProviderType === providerType.OrganizationDuo}">
<p class="lead text-center mb-4">{{title}}</p>
<div class="card d-block">
<div class="card-body">
<ng-container
*ngIf="selectedProviderType === providerType.Email || selectedProviderType === providerType.Authenticator">
<p *ngIf="selectedProviderType === providerType.Authenticator">
{{'enterVerificationCodeApp' | i18n}}</p>
<p *ngIf="selectedProviderType === providerType.Email">
{{'enterVerificationCodeEmail' | i18n : twoFactorEmail}}
</p>
<div class="form-group">
<label for="code" class="sr-only">{{'verificationCode' | i18n}}</label>
<input id="code" type="text" name="Code" class="form-control" [(ngModel)]="token" required
appAutofocus inputmode="tel" appInputVerbatim>
<small class="form-text" *ngIf="selectedProviderType === providerType.Email">
<a href="#" appStopClick (click)="sendEmail(true)" [appApiAction]="emailPromise"
*ngIf="selectedProviderType === providerType.Email">
{{'sendVerificationCodeEmailAgain' | i18n}}
</a>
</small>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
<p class="text-center">{{'insertYubiKey' | i18n}}</p>
<img src="../../images/yubikey.jpg" class="rounded img-fluid mb-3" alt="">
<div class="form-group">
<label for="code" class="sr-only">{{'verificationCode' | i18n}}</label>
<input id="code" type="password" name="Code" class="form-control" [(ngModel)]="token"
required appAutofocus appInputVerbatim autocomplete="new-password">
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
<div id="web-authn-frame" class="mb-3">
<iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo">
<div id="duo-frame" class="mb-3">
<iframe id="duo_iframe"></iframe>
</div>
</ng-container>
<i class="fa fa-spinner text-muted fa-spin pull-right" title="{{'loading' | i18n}}"
*ngIf="form.loading && selectedProviderType === providerType.WebAuthn" aria-hidden="true"></i>
<div class="form-check" *ngIf="selectedProviderType != null">
<input id="remember" type="checkbox" name="Remember" class="form-check-input"
[(ngModel)]="remember">
<label for="remember" class="form-check-label">{{'rememberMe' | i18n}}</label>
</div>
<ng-container *ngIf="selectedProviderType == null">
<p>{{'noTwoStepProviders' | i18n}}</p>
<p>{{'noTwoStepProviders2' | i18n}}</p>
</ng-container>
<hr>
<div class="d-flex mb-3">
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading"
*ngIf="selectedProviderType != null && selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo && selectedProviderType !== providerType.WebAuthn">
<span>
<i class="fa fa-sign-in" aria-hidden="true"></i> {{'continue' | i18n}}
</span>
<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}}
</a>
</div>
<div class="text-center">
<a href="#" appStopClick (click)="anotherMethod()">{{'useAnotherTwoStepMethod' | i18n}}</a>
</div>
</div>
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
class="container"
ngNativeValidate
autocomplete="off"
>
<div class="row justify-content-md-center mt-5">
<div
class="col-5"
[ngClass]="{
'col-9':
selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo
}"
>
<p class="lead text-center mb-4">{{ title }}</p>
<div class="card d-block">
<div class="card-body">
<ng-container
*ngIf="
selectedProviderType === providerType.Email ||
selectedProviderType === providerType.Authenticator
"
>
<p *ngIf="selectedProviderType === providerType.Authenticator">
{{ "enterVerificationCodeApp" | i18n }}
</p>
<p *ngIf="selectedProviderType === providerType.Email">
{{ "enterVerificationCodeEmail" | i18n: twoFactorEmail }}
</p>
<div class="form-group">
<label for="code" class="sr-only">{{ "verificationCode" | i18n }}</label>
<input
id="code"
type="text"
name="Code"
class="form-control"
[(ngModel)]="token"
required
appAutofocus
inputmode="tel"
appInputVerbatim
/>
<small class="form-text" *ngIf="selectedProviderType === providerType.Email">
<a
href="#"
appStopClick
(click)="sendEmail(true)"
[appApiAction]="emailPromise"
*ngIf="selectedProviderType === providerType.Email"
>
{{ "sendVerificationCodeEmailAgain" | i18n }}
</a>
</small>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
<p class="text-center">{{ "insertYubiKey" | i18n }}</p>
<img src="../../images/yubikey.jpg" class="rounded img-fluid mb-3" alt="" />
<div class="form-group">
<label for="code" class="sr-only">{{ "verificationCode" | i18n }}</label>
<input
id="code"
type="password"
name="Code"
class="form-control"
[(ngModel)]="token"
required
appAutofocus
appInputVerbatim
autocomplete="new-password"
/>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
<div id="web-authn-frame" class="mb-3">
<iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe>
</div>
</ng-container>
<ng-container
*ngIf="
selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo
"
>
<div id="duo-frame" class="mb-3">
<iframe id="duo_iframe"></iframe>
</div>
</ng-container>
<i
class="fa fa-spinner text-muted fa-spin pull-right"
title="{{ 'loading' | i18n }}"
*ngIf="form.loading && selectedProviderType === providerType.WebAuthn"
aria-hidden="true"
></i>
<div class="form-check" *ngIf="selectedProviderType != null">
<input
id="remember"
type="checkbox"
name="Remember"
class="form-check-input"
[(ngModel)]="remember"
/>
<label for="remember" class="form-check-label">{{ "rememberMe" | i18n }}</label>
</div>
<ng-container *ngIf="selectedProviderType == null">
<p>{{ "noTwoStepProviders" | i18n }}</p>
<p>{{ "noTwoStepProviders2" | i18n }}</p>
</ng-container>
<hr />
<div class="d-flex mb-3">
<button
type="submit"
class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading"
*ngIf="
selectedProviderType != null &&
selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo &&
selectedProviderType !== providerType.WebAuthn
"
>
<span>
<i class="fa fa-sign-in" aria-hidden="true"></i> {{ "continue" | i18n }}
</span>
<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 }}
</a>
</div>
<div class="text-center">
<a href="#" appStopClick (click)="anotherMethod()">{{
"useAnotherTwoStepMethod" | i18n
}}</a>
</div>
</div>
</div>
</div>
</div>
</form>
<ng-template #twoFactorOptions></ng-template>

View File

@@ -1,71 +1,86 @@
import {
Component,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ActivatedRoute, Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { ModalService } from 'jslib-angular/services/modal.service';
import { ModalService } from "jslib-angular/services/modal.service";
import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType';
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib-angular/components/two-factor.component';
import { TwoFactorComponent as BaseTwoFactorComponent } from "jslib-angular/components/two-factor.component";
import { TwoFactorOptionsComponent } from './two-factor-options.component';
import { TwoFactorOptionsComponent } from "./two-factor-options.component";
@Component({
selector: 'app-two-factor',
templateUrl: 'two-factor.component.html',
selector: "app-two-factor",
templateUrl: "two-factor.component.html",
})
export class TwoFactorComponent extends BaseTwoFactorComponent {
@ViewChild('twoFactorOptions', { read: ViewContainerRef, static: true }) twoFactorOptionsModal: ViewContainerRef;
@ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true })
twoFactorOptionsModal: ViewContainerRef;
constructor(authService: AuthService, router: Router,
i18nService: I18nService, apiService: ApiService,
platformUtilsService: PlatformUtilsService, stateService: StateService,
environmentService: EnvironmentService, private modalService: ModalService,
route: ActivatedRoute, logService: LogService) {
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
stateService, route, logService);
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
apiService: ApiService,
platformUtilsService: PlatformUtilsService,
stateService: StateService,
environmentService: EnvironmentService,
private modalService: ModalService,
route: ActivatedRoute,
logService: LogService
) {
super(
authService,
router,
i18nService,
apiService,
platformUtilsService,
window,
environmentService,
stateService,
route,
logService
);
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
}
async anotherMethod() {
const [modal] = await this.modalService.openViewRef(TwoFactorOptionsComponent, this.twoFactorOptionsModal, comp => {
comp.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
modal.close();
this.selectedProviderType = provider;
await this.init();
});
comp.onRecoverSelected.subscribe(() => {
modal.close();
});
async anotherMethod() {
const [modal] = await this.modalService.openViewRef(
TwoFactorOptionsComponent,
this.twoFactorOptionsModal,
(comp) => {
comp.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
modal.close();
this.selectedProviderType = provider;
await this.init();
});
}
comp.onRecoverSelected.subscribe(() => {
modal.close();
});
}
);
}
async goAfterLogIn() {
const loginRedirect = await this.stateService.getLoginRedirect();
if (loginRedirect != null) {
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
await this.stateService.setLoginRedirect(null);
} else {
this.router.navigate([this.successRoute], {
queryParams: {
identifier: this.identifier,
},
});
}
async goAfterLogIn() {
const loginRedirect = await this.stateService.getLoginRedirect();
if (loginRedirect != null) {
this.router.navigate([loginRedirect.route], { queryParams: loginRedirect.qParams });
await this.stateService.setLoginRedirect(null);
} else {
this.router.navigate([this.successRoute], {
queryParams: {
identifier: this.identifier,
},
});
}
}
}

View File

@@ -1,65 +1,105 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="row justify-content-md-center mt-5">
<div class="col-4">
<p class="lead text-center mb-4">{{'updateMasterPassword' | i18n}}</p>
<div class="card d-block">
<div class="card-body">
<app-callout type="warning">{{'updateMasterPasswordWarning' | i18n}}
</app-callout>
<div class="form-group">
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions">
</app-callout>
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<div class="d-flex">
<div class="w-100">
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPasswordHash" class="text-monospace form-control mb-1"
[(ngModel)]="masterPassword" (input)="updatePasswordStrength()" required
appInputVerbatim>
<app-password-strength [score]="masterPasswordScore" [showText]="true">
</app-password-strength>
</div>
<div>
<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>
</div>
</div>
</div>
<div class="form-group">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<div class="d-flex">
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPasswordRetype" class="text-monospace form-control"
[(ngModel)]="masterPasswordRetype" required appInputVerbatim>
<button type="button" class="ml-1 btn btn-link" appA11yTitle="{{'toggleVisibility' | i18n}}"
(click)="togglePassword(true)">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
</div>
</div>
<div class="form-group">
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint">
<small class="form-text text-muted">{{'masterPassHintDesc' | i18n}}</small>
</div>
<hr>
<div class="d-flex">
<button type="submit" class="btn btn-primary btn-block btn-submit" [disabled]="form.loading">
<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 btn-block ml-2 mt-0" (click)="logOut()">
{{'logOut' | i18n}}
</button>
</div>
</div>
<div class="row justify-content-md-center mt-5">
<div class="col-4">
<p class="lead text-center mb-4">{{ "updateMasterPassword" | i18n }}</p>
<div class="card d-block">
<div class="card-body">
<app-callout type="warning">{{ "updateMasterPasswordWarning" | i18n }} </app-callout>
<div class="form-group">
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<div class="d-flex">
<div class="w-100">
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordHash"
class="text-monospace form-control mb-1"
[(ngModel)]="masterPassword"
(input)="updatePasswordStrength()"
required
appInputVerbatim
/>
<app-password-strength [score]="masterPasswordScore" [showText]="true">
</app-password-strength>
</div>
<div>
<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>
</div>
</div>
</div>
<div class="form-group">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<div class="d-flex">
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="text-monospace form-control"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
/>
<button
type="button"
class="ml-1 btn btn-link"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<div class="form-group">
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint" />
<small class="form-text text-muted">{{ "masterPassHintDesc" | i18n }}</small>
</div>
<hr />
<div class="d-flex">
<button
type="submit"
class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading"
>
<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 btn-block ml-2 mt-0"
(click)="logOut()"
>
{{ "logOut" | i18n }}
</button>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,29 +1,46 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from 'jslib-angular/components/update-temp-password.component';
import { StateService } from 'jslib-common/abstractions/state.service';
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "jslib-angular/components/update-temp-password.component";
import { StateService } from "jslib-common/abstractions/state.service";
@Component({
selector: 'app-update-temp-password',
templateUrl: 'update-temp-password.component.html',
selector: "app-update-temp-password",
templateUrl: "update-temp-password.component.html",
})
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService, policyService: PolicyService,
cryptoService: CryptoService, messagingService: MessagingService,
apiService: ApiService, logService: LogService, stateService: StateService, syncService: SyncService) {
super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService,
messagingService, apiService, stateService, syncService, logService);
}
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
policyService: PolicyService,
cryptoService: CryptoService,
messagingService: MessagingService,
apiService: ApiService,
logService: LogService,
stateService: StateService,
syncService: SyncService
) {
super(
i18nService,
platformUtilsService,
passwordGenerationService,
policyService,
cryptoService,
messagingService,
apiService,
stateService,
syncService,
logService
);
}
}

View File

@@ -1,9 +1,13 @@
<div class="mt-5 d-flex justify-content-center">
<div>
<img class="mb-4 logo logo-themed" alt="Bitwarden">
<p class="text-center">
<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>
<img class="mb-4 logo logo-themed" alt="Bitwarden" />
<p class="text-center">
<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>

View File

@@ -1,55 +1,50 @@
import {
Component,
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from 'rxjs/operators';
import { first } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { VerifyEmailRequest } from 'jslib-common/models/request/verifyEmailRequest';
import { VerifyEmailRequest } from "jslib-common/models/request/verifyEmailRequest";
@Component({
selector: 'app-verify-email-token',
templateUrl: 'verify-email-token.component.html',
selector: "app-verify-email-token",
templateUrl: "verify-email-token.component.html",
})
export class VerifyEmailTokenComponent implements OnInit {
constructor(
private router: Router,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private route: ActivatedRoute,
private apiService: ApiService,
private logService: LogService,
private stateService: StateService
) { }
constructor(
private router: Router,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private route: ActivatedRoute,
private apiService: ApiService,
private logService: LogService,
private stateService: StateService
) {}
ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async qParams => {
if (qParams.userId != null && qParams.token != null) {
try {
await this.apiService.postAccountVerifyEmailToken(
new VerifyEmailRequest(qParams.userId, qParams.token));
if (await this.stateService.getIsAuthenticated()) {
await this.apiService.refreshIdentityToken();
}
this.platformUtilsService.showToast('success', null, this.i18nService.t('emailVerified'));
this.router.navigate(['/']);
return;
} catch (e) {
this.logService.error(e);
}
}
this.platformUtilsService.showToast('error', null, this.i18nService.t('emailVerifiedFailed'));
this.router.navigate(['/']);
});
}
ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.userId != null && qParams.token != null) {
try {
await this.apiService.postAccountVerifyEmailToken(
new VerifyEmailRequest(qParams.userId, qParams.token)
);
if (await this.stateService.getIsAuthenticated()) {
await this.apiService.refreshIdentityToken();
}
this.platformUtilsService.showToast("success", null, this.i18nService.t("emailVerified"));
this.router.navigate(["/"]);
return;
} catch (e) {
this.logService.error(e);
}
}
this.platformUtilsService.showToast("error", null, this.i18nService.t("emailVerifiedFailed"));
this.router.navigate(["/"]);
});
}
}

View File

@@ -1,26 +1,34 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{'deleteAccount' | i18n}}</p>
<div class="card">
<div class="card-body">
<app-callout type="warning">{{'deleteAccountWarning' | i18n}}</app-callout>
<p class="text-center">
<strong>{{email}}</strong>
</p>
<p>{{'deleteRecoverConfirmDesc' | i18n}}</p>
<hr>
<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}}" aria-hidden="true"></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{'cancel' | i18n}}
</a>
</div>
</div>
</div>
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{ "deleteAccount" | i18n }}</p>
<div class="card">
<div class="card-body">
<app-callout type="warning">{{ "deleteAccountWarning" | i18n }}</app-callout>
<p class="text-center">
<strong>{{ email }}</strong>
</p>
<p>{{ "deleteRecoverConfirmDesc" | i18n }}</p>
<hr />
<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 }}"
aria-hidden="true"
></i>
</button>
<a routerLink="/" class="btn btn-outline-secondary btn-block ml-2 mt-0">
{{ "cancel" | i18n }}
</a>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,59 +1,60 @@
import {
Component,
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from 'rxjs/operators';
import { first } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { VerifyDeleteRecoverRequest } from 'jslib-common/models/request/verifyDeleteRecoverRequest';
import { VerifyDeleteRecoverRequest } from "jslib-common/models/request/verifyDeleteRecoverRequest";
@Component({
selector: 'app-verify-recover-delete',
templateUrl: 'verify-recover-delete.component.html',
selector: "app-verify-recover-delete",
templateUrl: "verify-recover-delete.component.html",
})
export class VerifyRecoverDeleteComponent implements OnInit {
email: string;
formPromise: Promise<any>;
email: string;
formPromise: Promise<any>;
private userId: string;
private token: string;
private userId: string;
private token: string;
constructor(private router: Router, private apiService: ApiService,
private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
private route: ActivatedRoute, private logService: LogService) {
}
ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async qParams => {
if (qParams.userId != null && qParams.token != null && qParams.email != null) {
this.userId = qParams.userId;
this.token = qParams.token;
this.email = qParams.email;
} else {
this.router.navigate(['/']);
}
});
}
async submit() {
try {
const request = new VerifyDeleteRecoverRequest(this.userId, this.token);
this.formPromise = this.apiService.postAccountRecoverDeleteToken(request);
await this.formPromise;
this.platformUtilsService.showToast('success', this.i18nService.t('accountDeleted'),
this.i18nService.t('accountDeletedDesc'));
this.router.navigate(['/']);
} catch (e) {
this.logService.error(e);
}
constructor(
private router: Router,
private apiService: ApiService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private route: ActivatedRoute,
private logService: LogService
) {}
ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.userId != null && qParams.token != null && qParams.email != null) {
this.userId = qParams.userId;
this.token = qParams.token;
this.email = qParams.email;
} else {
this.router.navigate(["/"]);
}
});
}
async submit() {
try {
const request = new VerifyDeleteRecoverRequest(this.userId, this.token);
this.formPromise = this.apiService.postAccountRecoverDeleteToken(request);
await this.formPromise;
this.platformUtilsService.showToast(
"success",
this.i18nService.t("accountDeleted"),
this.i18nService.t("accountDeletedDesc")
);
this.router.navigate(["/"]);
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -1,300 +1,310 @@
import {
Component,
NgZone,
OnDestroy,
OnInit,
SecurityContext,
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import {
NavigationEnd,
Router,
} from '@angular/router';
import * as jq from 'jquery';
import { IndividualConfig, ToastrService } from 'ngx-toastr';
import Swal from 'sweetalert2';
import { Component, NgZone, OnDestroy, OnInit, SecurityContext } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
import { NavigationEnd, Router } from "@angular/router";
import * as jq from "jquery";
import { IndividualConfig, ToastrService } from "ngx-toastr";
import Swal from "sweetalert2";
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EventService } from 'jslib-common/abstractions/event.service';
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { NotificationsService } from 'jslib-common/abstractions/notifications.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { SettingsService } from 'jslib-common/abstractions/settings.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { TokenService } from 'jslib-common/abstractions/token.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EventService } from "jslib-common/abstractions/event.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { NotificationsService } from "jslib-common/abstractions/notifications.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { SettingsService } from "jslib-common/abstractions/settings.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { StorageService } from "jslib-common/abstractions/storage.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { PolicyListService } from './services/policy-list.service';
import { RouterService } from './services/router.service';
import { PolicyListService } from "./services/policy-list.service";
import { RouterService } from "./services/router.service";
import { DisableSendPolicy } from './organizations/policies/disable-send.component';
import { MasterPasswordPolicy } from './organizations/policies/master-password.component';
import { PasswordGeneratorPolicy } from './organizations/policies/password-generator.component';
import { PersonalOwnershipPolicy } from './organizations/policies/personal-ownership.component';
import { RequireSsoPolicy } from './organizations/policies/require-sso.component';
import { ResetPasswordPolicy } from './organizations/policies/reset-password.component';
import { SendOptionsPolicy } from './organizations/policies/send-options.component';
import { SingleOrgPolicy } from './organizations/policies/single-org.component';
import { TwoFactorAuthenticationPolicy } from './organizations/policies/two-factor-authentication.component';
import { DisableSendPolicy } from "./organizations/policies/disable-send.component";
import { MasterPasswordPolicy } from "./organizations/policies/master-password.component";
import { PasswordGeneratorPolicy } from "./organizations/policies/password-generator.component";
import { PersonalOwnershipPolicy } from "./organizations/policies/personal-ownership.component";
import { RequireSsoPolicy } from "./organizations/policies/require-sso.component";
import { ResetPasswordPolicy } from "./organizations/policies/reset-password.component";
import { SendOptionsPolicy } from "./organizations/policies/send-options.component";
import { SingleOrgPolicy } from "./organizations/policies/single-org.component";
import { TwoFactorAuthenticationPolicy } from "./organizations/policies/two-factor-authentication.component";
const BroadcasterSubscriptionId = 'AppComponent';
const BroadcasterSubscriptionId = "AppComponent";
const IdleTimeout = 60000 * 10; // 10 minutes
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
selector: "app-root",
templateUrl: "app.component.html",
})
export class AppComponent implements OnDestroy, OnInit {
private lastActivity: number = null;
private idleTimer: number = null;
private isIdle = false;
private lastActivity: number = null;
private idleTimer: number = null;
private isIdle = false;
constructor(
private broadcasterService: BroadcasterService,
private tokenService: TokenService,
private folderService: FolderService,
private settingsService: SettingsService,
private syncService: SyncService,
private passwordGenerationService: PasswordGenerationService,
private cipherService: CipherService,
private authService: AuthService,
private router: Router,
private toastrService: ToastrService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private ngZone: NgZone,
private vaultTimeoutService: VaultTimeoutService,
private cryptoService: CryptoService,
private collectionService: CollectionService,
private sanitizer: DomSanitizer,
private searchService: SearchService,
private notificationsService: NotificationsService,
private routerService: RouterService,
private stateService: StateService,
private eventService: EventService,
private policyService: PolicyService,
protected policyListService: PolicyListService,
private keyConnectorService: KeyConnectorService
) {}
constructor(
private broadcasterService: BroadcasterService,
private tokenService: TokenService,
private folderService: FolderService,
private settingsService: SettingsService,
private syncService: SyncService,
private passwordGenerationService: PasswordGenerationService,
private cipherService: CipherService,
private authService: AuthService,
private router: Router,
private toastrService: ToastrService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private ngZone: NgZone,
private vaultTimeoutService: VaultTimeoutService,
private cryptoService: CryptoService,
private collectionService: CollectionService,
private sanitizer: DomSanitizer,
private searchService: SearchService,
private notificationsService: NotificationsService,
private routerService: RouterService,
private stateService: StateService,
private eventService: EventService,
private policyService: PolicyService,
protected policyListService: PolicyListService,
private keyConnectorService: KeyConnectorService
) { }
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
window.onmousemove = () => this.recordActivity();
window.onmousedown = () => this.recordActivity();
window.ontouchstart = () => this.recordActivity();
window.onclick = () => this.recordActivity();
window.onscroll = () => this.recordActivity();
window.onkeypress = () => this.recordActivity();
});
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
window.onmousemove = () => this.recordActivity();
window.onmousedown = () => this.recordActivity();
window.ontouchstart = () => this.recordActivity();
window.onclick = () => this.recordActivity();
window.onscroll = () => this.recordActivity();
window.onkeypress = () => this.recordActivity();
});
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case 'loggedIn':
case 'loggedOut':
case 'unlocked':
this.notificationsService.updateConnection(false);
break;
case 'authBlocked':
this.router.navigate(['/']);
break;
case 'logout':
this.logOut(!!message.expired);
break;
case 'lockVault':
await this.vaultTimeoutService.lock();
break;
case 'locked':
this.notificationsService.updateConnection(false);
this.router.navigate(['lock']);
break;
case 'lockedUrl':
window.setTimeout(() => this.routerService.setPreviousUrl(message.url), 500);
break;
case 'syncStarted':
break;
case 'syncCompleted':
break;
case 'upgradeOrganization':
const upgradeConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('upgradeOrganizationDesc'), this.i18nService.t('upgradeOrganization'),
this.i18nService.t('upgradeOrganization'), this.i18nService.t('cancel'));
if (upgradeConfirmed) {
this.router.navigate(['organizations', message.organizationId, 'settings', 'billing']);
}
break;
case 'premiumRequired':
const premiumConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('premiumRequiredDesc'), this.i18nService.t('premiumRequired'),
this.i18nService.t('learnMore'), this.i18nService.t('cancel'));
if (premiumConfirmed) {
this.router.navigate(['settings/premium']);
}
break;
case 'emailVerificationRequired':
const emailVerificationConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('emailVerificationRequiredDesc'),
this.i18nService.t('emailVerificationRequired'),
this.i18nService.t('learnMore'), this.i18nService.t('cancel'));
if (emailVerificationConfirmed) {
this.platformUtilsService.launchUri('https://bitwarden.com/help/article/create-bitwarden-account/');
}
break;
case 'showToast':
this.showToast(message);
break;
case 'setFullWidth':
this.setFullWidth();
break;
case 'convertAccountToKeyConnector':
this.keyConnectorService.setConvertAccountRequired(true);
this.router.navigate(['/remove-password']);
break;
default:
break;
}
});
});
this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
const modals = Array.from(document.querySelectorAll('.modal'));
for (const modal of modals) {
(jq(modal) as any).modal('hide');
}
if (document.querySelector('.swal-modal') != null) {
Swal.close(undefined);
}
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case "loggedIn":
case "loggedOut":
case "unlocked":
this.notificationsService.updateConnection(false);
break;
case "authBlocked":
this.router.navigate(["/"]);
break;
case "logout":
this.logOut(!!message.expired);
break;
case "lockVault":
await this.vaultTimeoutService.lock();
break;
case "locked":
this.notificationsService.updateConnection(false);
this.router.navigate(["lock"]);
break;
case "lockedUrl":
window.setTimeout(() => this.routerService.setPreviousUrl(message.url), 500);
break;
case "syncStarted":
break;
case "syncCompleted":
break;
case "upgradeOrganization":
const upgradeConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("upgradeOrganizationDesc"),
this.i18nService.t("upgradeOrganization"),
this.i18nService.t("upgradeOrganization"),
this.i18nService.t("cancel")
);
if (upgradeConfirmed) {
this.router.navigate([
"organizations",
message.organizationId,
"settings",
"billing",
]);
}
});
this.policyListService.addPolicies([
new TwoFactorAuthenticationPolicy(),
new MasterPasswordPolicy(),
new PasswordGeneratorPolicy(),
new SingleOrgPolicy(),
new RequireSsoPolicy(),
new PersonalOwnershipPolicy(),
new DisableSendPolicy(),
new SendOptionsPolicy(),
new ResetPasswordPolicy(),
]);
this.setFullWidth();
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
private async logOut(expired: boolean) {
await this.eventService.uploadEvents();
const userId = await this.stateService.getUserId();
await Promise.all([
this.eventService.clearEvents(),
this.syncService.setLastSync(new Date(0)),
this.tokenService.clearToken(),
this.cryptoService.clearKeys(),
this.settingsService.clear(userId),
this.cipherService.clear(userId),
this.folderService.clear(userId),
this.collectionService.clear(userId),
this.policyService.clear(userId),
this.passwordGenerationService.clear(),
this.keyConnectorService.clear(),
]);
this.searchService.clearIndex();
this.authService.logOut(async () => {
if (expired) {
this.platformUtilsService.showToast('warning', this.i18nService.t('loggedOut'),
this.i18nService.t('loginExpired'));
break;
case "premiumRequired":
const premiumConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("premiumRequiredDesc"),
this.i18nService.t("premiumRequired"),
this.i18nService.t("learnMore"),
this.i18nService.t("cancel")
);
if (premiumConfirmed) {
this.router.navigate(["settings/premium"]);
}
await this.stateService.clean({ userId: userId });
Swal.close();
this.router.navigate(['/']);
});
}
private async recordActivity() {
const now = (new Date()).getTime();
if (this.lastActivity != null && now - this.lastActivity < 250) {
return;
}
this.lastActivity = now;
this.stateService.setLastActive(now);
// Idle states
if (this.isIdle) {
this.isIdle = false;
this.idleStateChanged();
}
if (this.idleTimer != null) {
window.clearTimeout(this.idleTimer);
this.idleTimer = null;
}
this.idleTimer = window.setTimeout(() => {
if (!this.isIdle) {
this.isIdle = true;
this.idleStateChanged();
break;
case "emailVerificationRequired":
const emailVerificationConfirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("emailVerificationRequiredDesc"),
this.i18nService.t("emailVerificationRequired"),
this.i18nService.t("learnMore"),
this.i18nService.t("cancel")
);
if (emailVerificationConfirmed) {
this.platformUtilsService.launchUri(
"https://bitwarden.com/help/article/create-bitwarden-account/"
);
}
}, IdleTimeout);
}
private showToast(msg: any) {
let message = '';
const options: Partial<IndividualConfig> = {};
if (typeof (msg.text) === 'string') {
message = msg.text;
} else if (msg.text.length === 1) {
message = msg.text[0];
} else {
msg.text.forEach((t: string) =>
message += ('<p>' + this.sanitizer.sanitize(SecurityContext.HTML, t) + '</p>'));
options.enableHtml = true;
break;
case "showToast":
this.showToast(message);
break;
case "setFullWidth":
this.setFullWidth();
break;
case "convertAccountToKeyConnector":
this.keyConnectorService.setConvertAccountRequired(true);
this.router.navigate(["/remove-password"]);
break;
default:
break;
}
if (msg.options != null) {
if (msg.options.trustedHtml === true) {
options.enableHtml = true;
}
if (msg.options.timeout != null && msg.options.timeout > 0) {
options.timeOut = msg.options.timeout;
}
});
});
this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
const modals = Array.from(document.querySelectorAll(".modal"));
for (const modal of modals) {
(jq(modal) as any).modal("hide");
}
this.toastrService.show(message, msg.title, options, 'toast-' + msg.type);
if (document.querySelector(".swal-modal") != null) {
Swal.close(undefined);
}
}
});
this.policyListService.addPolicies([
new TwoFactorAuthenticationPolicy(),
new MasterPasswordPolicy(),
new PasswordGeneratorPolicy(),
new SingleOrgPolicy(),
new RequireSsoPolicy(),
new PersonalOwnershipPolicy(),
new DisableSendPolicy(),
new SendOptionsPolicy(),
new ResetPasswordPolicy(),
]);
this.setFullWidth();
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
private async logOut(expired: boolean) {
await this.eventService.uploadEvents();
const userId = await this.stateService.getUserId();
await Promise.all([
this.eventService.clearEvents(),
this.syncService.setLastSync(new Date(0)),
this.tokenService.clearToken(),
this.cryptoService.clearKeys(),
this.settingsService.clear(userId),
this.cipherService.clear(userId),
this.folderService.clear(userId),
this.collectionService.clear(userId),
this.policyService.clear(userId),
this.passwordGenerationService.clear(),
this.keyConnectorService.clear(),
]);
this.searchService.clearIndex();
this.authService.logOut(async () => {
if (expired) {
this.platformUtilsService.showToast(
"warning",
this.i18nService.t("loggedOut"),
this.i18nService.t("loginExpired")
);
}
await this.stateService.clean({ userId: userId });
Swal.close();
this.router.navigate(["/"]);
});
}
private async recordActivity() {
const now = new Date().getTime();
if (this.lastActivity != null && now - this.lastActivity < 250) {
return;
}
private idleStateChanged() {
if (this.isIdle) {
this.notificationsService.disconnectFromInactivity();
} else {
this.notificationsService.reconnectFromActivity();
}
this.lastActivity = now;
this.stateService.setLastActive(now);
// Idle states
if (this.isIdle) {
this.isIdle = false;
this.idleStateChanged();
}
if (this.idleTimer != null) {
window.clearTimeout(this.idleTimer);
this.idleTimer = null;
}
this.idleTimer = window.setTimeout(() => {
if (!this.isIdle) {
this.isIdle = true;
this.idleStateChanged();
}
}, IdleTimeout);
}
private showToast(msg: any) {
let message = "";
const options: Partial<IndividualConfig> = {};
if (typeof msg.text === "string") {
message = msg.text;
} else if (msg.text.length === 1) {
message = msg.text[0];
} else {
msg.text.forEach(
(t: string) =>
(message += "<p>" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "</p>")
);
options.enableHtml = true;
}
if (msg.options != null) {
if (msg.options.trustedHtml === true) {
options.enableHtml = true;
}
if (msg.options.timeout != null && msg.options.timeout > 0) {
options.timeOut = msg.options.timeout;
}
}
private async setFullWidth() {
const enableFullWidth = await this.stateService.getEnableFullWidth();
if (enableFullWidth) {
document.body.classList.add('full-width');
} else {
document.body.classList.remove('full-width');
}
this.toastrService.show(message, msg.title, options, "toast-" + msg.type);
}
private idleStateChanged() {
if (this.isIdle) {
this.notificationsService.disconnectFromInactivity();
} else {
this.notificationsService.reconnectFromActivity();
}
}
private async setFullWidth() {
const enableFullWidth = await this.stateService.getEnableFullWidth();
if (enableFullWidth) {
document.body.classList.add("full-width");
} else {
document.body.classList.remove("full-width");
}
}
}

View File

@@ -1,37 +1,35 @@
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { InfiniteScrollModule } from "ngx-infinite-scroll";
import { DragDropModule } from '@angular/cdk/drag-drop';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { DragDropModule } from "@angular/cdk/drag-drop";
import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { BitwardenToastModule } from 'jslib-angular/components/toastr.component';
import { BitwardenToastModule } from "jslib-angular/components/toastr.component";
import { AppComponent } from './app.component';
import { OssRoutingModule } from './oss-routing.module';
import { OssModule } from './oss.module';
import { ServicesModule } from './services/services.module';
import { WildcardRoutingModule } from './wildcard-routing.module';
import { AppComponent } from "./app.component";
import { OssRoutingModule } from "./oss-routing.module";
import { OssModule } from "./oss.module";
import { ServicesModule } from "./services/services.module";
import { WildcardRoutingModule } from "./wildcard-routing.module";
@NgModule({
imports: [
OssModule,
BrowserAnimationsModule,
FormsModule,
ServicesModule,
BitwardenToastModule.forRoot({
maxOpened: 5,
autoDismiss: true,
closeButton: true,
}),
InfiniteScrollModule,
DragDropModule,
OssRoutingModule,
WildcardRoutingModule, // Needs to be last to catch all non-existing routes
],
declarations: [
AppComponent,
],
bootstrap: [AppComponent],
imports: [
OssModule,
BrowserAnimationsModule,
FormsModule,
ServicesModule,
BitwardenToastModule.forRoot({
maxOpened: 5,
autoDismiss: true,
closeButton: true,
}),
InfiniteScrollModule,
DragDropModule,
OssRoutingModule,
WildcardRoutingModule, // Needs to be last to catch all non-existing routes
],
declarations: [AppComponent],
bootstrap: [AppComponent],
})
export class AppModule { }
export class AppModule {}

View File

@@ -1,76 +1,78 @@
import {
Directive,
OnInit,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { Directive, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from 'rxjs/operators';
import { first } from "rxjs/operators";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
@Directive()
export abstract class BaseAcceptComponent implements OnInit {
loading = true;
authed = false;
email: string;
actionPromise: Promise<any>;
loading = true;
authed = false;
email: string;
actionPromise: Promise<any>;
protected requiredParameters: string[] = [];
protected failedShortMessage = 'inviteAcceptFailedShort';
protected failedMessage = 'inviteAcceptFailed';
protected requiredParameters: string[] = [];
protected failedShortMessage = "inviteAcceptFailedShort";
protected failedMessage = "inviteAcceptFailed";
constructor(protected router: Router, protected platformUtilService: PlatformUtilsService,
protected i18nService: I18nService, protected route: ActivatedRoute,
protected stateService: StateService) { }
constructor(
protected router: Router,
protected platformUtilService: PlatformUtilsService,
protected i18nService: I18nService,
protected route: ActivatedRoute,
protected stateService: StateService
) {}
abstract authedHandler(qParams: any): Promise<void>;
abstract unauthedHandler(qParams: any): Promise<void>;
abstract authedHandler(qParams: any): Promise<void>;
abstract unauthedHandler(qParams: any): Promise<void>;
ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async qParams => {
await this.stateService.setLoginRedirect(null);
let error = this.requiredParameters.some(e => qParams?.[e] == null || qParams[e] === '');
let errorMessage: string = null;
if (!error) {
this.authed = await this.stateService.getIsAuthenticated();
ngOnInit() {
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
await this.stateService.setLoginRedirect(null);
let error = this.requiredParameters.some((e) => qParams?.[e] == null || qParams[e] === "");
let errorMessage: string = null;
if (!error) {
this.authed = await this.stateService.getIsAuthenticated();
if (this.authed) {
try {
await this.authedHandler(qParams);
} catch (e) {
error = true;
errorMessage = e.message;
}
} else {
await this.stateService.setLoginRedirect({
route: this.getRedirectRoute(),
qParams: qParams,
});
if (this.authed) {
try {
await this.authedHandler(qParams);
} catch (e) {
error = true;
errorMessage = e.message;
}
} else {
await this.stateService.setLoginRedirect({
route: this.getRedirectRoute(),
qParams: qParams,
});
this.email = qParams.email;
await this.unauthedHandler(qParams);
}
}
this.email = qParams.email;
await this.unauthedHandler(qParams);
}
}
if (error) {
const message = errorMessage != null ? this.i18nService.t(this.failedShortMessage, errorMessage) :
this.i18nService.t(this.failedMessage);
this.platformUtilService.showToast('error', null, message, {timeout: 10000});
this.router.navigate(['/']);
}
this.loading = false;
if (error) {
const message =
errorMessage != null
? this.i18nService.t(this.failedShortMessage, errorMessage)
: this.i18nService.t(this.failedMessage);
this.platformUtilService.showToast("error", null, message, {
timeout: 10000,
});
}
this.router.navigate(["/"]);
}
getRedirectRoute() {
const urlTree = this.router.parseUrl(this.router.url);
urlTree.queryParams = {};
return urlTree.toString();
}
this.loading = false;
});
}
getRedirectRoute() {
const urlTree = this.router.parseUrl(this.router.url);
urlTree.queryParams = {};
return urlTree.toString();
}
}

View File

@@ -1,156 +1,193 @@
import { Directive } from '@angular/core';
import { Directive } from "@angular/core";
import { ExportService } from 'jslib-common/abstractions/export.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ExportService } from "jslib-common/abstractions/export.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { EventView } from 'jslib-common/models/view/eventView';
import { EventView } from "jslib-common/models/view/eventView";
import { EventResponse } from 'jslib-common/models/response/eventResponse';
import { ListResponse } from 'jslib-common/models/response/listResponse';
import { EventResponse } from "jslib-common/models/response/eventResponse";
import { ListResponse } from "jslib-common/models/response/listResponse";
import { EventService } from 'src/app/services/event.service';
import { EventService } from "src/app/services/event.service";
@Directive()
export abstract class BaseEventsComponent {
loading = true;
loaded = false;
events: EventView[];
start: string;
end: string;
dirtyDates: boolean = true;
continuationToken: string;
refreshPromise: Promise<any>;
exportPromise: Promise<any>;
morePromise: Promise<any>;
loading = true;
loaded = false;
events: EventView[];
start: string;
end: string;
dirtyDates: boolean = true;
continuationToken: string;
refreshPromise: Promise<any>;
exportPromise: Promise<any>;
morePromise: Promise<any>;
abstract readonly exportFileName: string;
abstract readonly exportFileName: string;
constructor(protected eventService: EventService, protected i18nService: I18nService,
protected exportService: ExportService, protected platformUtilsService: PlatformUtilsService,
protected logService: LogService) {
const defaultDates = this.eventService.getDefaultDateFilters();
this.start = defaultDates[0];
this.end = defaultDates[1];
constructor(
protected eventService: EventService,
protected i18nService: I18nService,
protected exportService: ExportService,
protected platformUtilsService: PlatformUtilsService,
protected logService: LogService
) {
const defaultDates = this.eventService.getDefaultDateFilters();
this.start = defaultDates[0];
this.end = defaultDates[1];
}
async exportEvents() {
if (this.appApiPromiseUnfulfilled() || this.dirtyDates) {
return;
}
async exportEvents() {
if (this.appApiPromiseUnfulfilled() || this.dirtyDates) {
return;
}
this.loading = true;
this.loading = true;
const dates = this.parseDates();
if (dates == null) {
return;
}
try {
this.exportPromise = this.export(dates[0], dates[1]);
await this.exportPromise;
} catch (e) {
this.logService.error(`Handled exception: ${e}`);
}
this.exportPromise = null;
this.loading = false;
const dates = this.parseDates();
if (dates == null) {
return;
}
async loadEvents(clearExisting: boolean) {
if (this.appApiPromiseUnfulfilled()) {
return;
}
try {
this.exportPromise = this.export(dates[0], dates[1]);
const dates = this.parseDates();
if (dates == null) {
return;
}
this.loading = true;
let events: EventView[] = [];
try {
const promise = this.loadAndParseEvents(dates[0], dates[1], clearExisting ? null : this.continuationToken);
if (clearExisting) {
this.refreshPromise = promise;
} else {
this.morePromise = promise;
}
const result = await promise;
this.continuationToken = result.continuationToken;
events = result.events;
} catch (e) {
this.logService.error(`Handled exception: ${e}`);
}
if (!clearExisting && this.events != null && this.events.length > 0) {
this.events = this.events.concat(events);
} else {
this.events = events;
}
this.dirtyDates = false;
this.loading = false;
this.morePromise = null;
this.refreshPromise = null;
await this.exportPromise;
} catch (e) {
this.logService.error(`Handled exception: ${e}`);
}
protected abstract requestEvents(startDate: string, endDate: string, continuationToken: string): Promise<ListResponse<EventResponse>>;
protected abstract getUserName(r: EventResponse, userId: string): { name: string, email: string };
this.exportPromise = null;
this.loading = false;
}
protected async loadAndParseEvents(startDate: string, endDate: string, continuationToken: string) {
const response = await this.requestEvents(startDate, endDate, continuationToken);
const events = await Promise.all(response.data.map(async r => {
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
const eventInfo = await this.eventService.getEventInfo(r);
const user = this.getUserName(r, userId);
return new EventView({
message: eventInfo.message,
humanReadableMessage: eventInfo.humanReadableMessage,
appIcon: eventInfo.appIcon,
appName: eventInfo.appName,
userId: userId,
userName: user != null ? user.name : this.i18nService.t('unknown'),
userEmail: user != null ? user.email : '',
date: r.date,
ip: r.ipAddress,
type: r.type,
});
}));
return { continuationToken: response.continuationToken, events: events };
async loadEvents(clearExisting: boolean) {
if (this.appApiPromiseUnfulfilled()) {
return;
}
protected parseDates() {
let dates: string[] = null;
try {
dates = this.eventService.formatDateFilters(this.start, this.end);
} catch (e) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidDateRange'));
return null;
}
return dates;
const dates = this.parseDates();
if (dates == null) {
return;
}
protected appApiPromiseUnfulfilled() {
return this.refreshPromise != null || this.morePromise != null || this.exportPromise != null;
this.loading = true;
let events: EventView[] = [];
try {
const promise = this.loadAndParseEvents(
dates[0],
dates[1],
clearExisting ? null : this.continuationToken
);
if (clearExisting) {
this.refreshPromise = promise;
} else {
this.morePromise = promise;
}
const result = await promise;
this.continuationToken = result.continuationToken;
events = result.events;
} catch (e) {
this.logService.error(`Handled exception: ${e}`);
}
private async export(start: string, end: string) {
let continuationToken = this.continuationToken;
let events = [].concat(this.events);
while (continuationToken != null) {
const result = await this.loadAndParseEvents(start, end, continuationToken);
continuationToken = result.continuationToken;
events = events.concat(result.events);
}
const data = await this.exportService.getEventExport(events);
const fileName = this.exportService.getFileName(this.exportFileName, 'csv');
this.platformUtilsService.saveFile(window, data, { type: 'text/plain' }, fileName);
if (!clearExisting && this.events != null && this.events.length > 0) {
this.events = this.events.concat(events);
} else {
this.events = events;
}
this.dirtyDates = false;
this.loading = false;
this.morePromise = null;
this.refreshPromise = null;
}
protected abstract requestEvents(
startDate: string,
endDate: string,
continuationToken: string
): Promise<ListResponse<EventResponse>>;
protected abstract getUserName(
r: EventResponse,
userId: string
): {
name: string;
email: string;
};
protected async loadAndParseEvents(
startDate: string,
endDate: string,
continuationToken: string
) {
const response = await this.requestEvents(startDate, endDate, continuationToken);
const events = await Promise.all(
response.data.map(async (r) => {
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
const eventInfo = await this.eventService.getEventInfo(r);
const user = this.getUserName(r, userId);
return new EventView({
message: eventInfo.message,
humanReadableMessage: eventInfo.humanReadableMessage,
appIcon: eventInfo.appIcon,
appName: eventInfo.appName,
userId: userId,
userName: user != null ? user.name : this.i18nService.t("unknown"),
userEmail: user != null ? user.email : "",
date: r.date,
ip: r.ipAddress,
type: r.type,
});
})
);
return {
continuationToken: response.continuationToken,
events: events,
};
}
protected parseDates() {
let dates: string[] = null;
try {
dates = this.eventService.formatDateFilters(this.start, this.end);
} catch (e) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidDateRange")
);
return null;
}
return dates;
}
protected appApiPromiseUnfulfilled() {
return this.refreshPromise != null || this.morePromise != null || this.exportPromise != null;
}
private async export(start: string, end: string) {
let continuationToken = this.continuationToken;
let events = [].concat(this.events);
while (continuationToken != null) {
const result = await this.loadAndParseEvents(start, end, continuationToken);
continuationToken = result.continuationToken;
events = events.concat(result.events);
}
const data = await this.exportService.getEventExport(events);
const fileName = this.exportService.getFileName(this.exportFileName, "csv");
this.platformUtilsService.saveFile(
window,
data,
{
type: "text/plain",
},
fileName
);
}
}

View File

@@ -1,320 +1,353 @@
import {
Directive,
ViewChild,
ViewContainerRef
} from '@angular/core';
import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { ModalService } from 'jslib-angular/services/modal.service';
import { ValidationService } from 'jslib-angular/services/validation.service';
import { ModalService } from "jslib-angular/services/modal.service";
import { ValidationService } from "jslib-angular/services/validation.service";
import { SearchPipe } from 'jslib-angular/pipes/search.pipe';
import { UserNamePipe } from 'jslib-angular/pipes/user-name.pipe';
import { SearchPipe } from "jslib-angular/pipes/search.pipe";
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType';
import { OrganizationUserType } from 'jslib-common/enums/organizationUserType';
import { ProviderUserStatusType } from 'jslib-common/enums/providerUserStatusType';
import { ProviderUserType } from 'jslib-common/enums/providerUserType';
import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType";
import { OrganizationUserType } from "jslib-common/enums/organizationUserType";
import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType";
import { ProviderUserType } from "jslib-common/enums/providerUserType";
import { ListResponse } from 'jslib-common/models/response/listResponse';
import { OrganizationUserUserDetailsResponse } from 'jslib-common/models/response/organizationUserResponse';
import { ProviderUserUserDetailsResponse } from 'jslib-common/models/response/provider/providerUserResponse';
import { ListResponse } from "jslib-common/models/response/listResponse";
import { OrganizationUserUserDetailsResponse } from "jslib-common/models/response/organizationUserResponse";
import { ProviderUserUserDetailsResponse } from "jslib-common/models/response/provider/providerUserResponse";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
import { UserConfirmComponent } from '../organizations/manage/user-confirm.component';
import { UserConfirmComponent } from "../organizations/manage/user-confirm.component";
type StatusType = OrganizationUserStatusType | ProviderUserStatusType;
const MaxCheckedCount = 500;
@Directive()
export abstract class BasePeopleComponent<UserType extends ProviderUserUserDetailsResponse | OrganizationUserUserDetailsResponse> {
export abstract class BasePeopleComponent<
UserType extends ProviderUserUserDetailsResponse | OrganizationUserUserDetailsResponse
> {
@ViewChild("confirmTemplate", {
read: ViewContainerRef,
static: true,
})
confirmModalRef: ViewContainerRef;
@ViewChild('confirmTemplate', { read: ViewContainerRef, static: true }) confirmModalRef: ViewContainerRef;
get allCount() {
return this.allUsers != null ? this.allUsers.length : 0;
}
get allCount() {
return this.allUsers != null ? this.allUsers.length : 0;
get invitedCount() {
return this.statusMap.has(this.userStatusType.Invited)
? this.statusMap.get(this.userStatusType.Invited).length
: 0;
}
get acceptedCount() {
return this.statusMap.has(this.userStatusType.Accepted)
? this.statusMap.get(this.userStatusType.Accepted).length
: 0;
}
get confirmedCount() {
return this.statusMap.has(this.userStatusType.Confirmed)
? this.statusMap.get(this.userStatusType.Confirmed).length
: 0;
}
get showConfirmUsers(): boolean {
return (
this.allUsers != null &&
this.statusMap != null &&
this.allUsers.length > 1 &&
this.confirmedCount > 0 &&
this.confirmedCount < 3 &&
this.acceptedCount > 0
);
}
get showBulkConfirmUsers(): boolean {
return this.acceptedCount > 0;
}
abstract userType: typeof OrganizationUserType | typeof ProviderUserType;
abstract userStatusType: typeof OrganizationUserStatusType | typeof ProviderUserStatusType;
loading = true;
statusMap = new Map<StatusType, UserType[]>();
status: StatusType;
users: UserType[] = [];
pagedUsers: UserType[] = [];
searchText: string;
actionPromise: Promise<any>;
protected allUsers: UserType[] = [];
protected didScroll = false;
protected pageSize = 100;
private pagedUsersCount = 0;
constructor(
protected apiService: ApiService,
private searchService: SearchService,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected cryptoService: CryptoService,
protected validationService: ValidationService,
protected modalService: ModalService,
private logService: LogService,
private searchPipe: SearchPipe,
protected userNamePipe: UserNamePipe,
protected stateService: StateService
) {}
abstract edit(user: UserType): void;
abstract getUsers(): Promise<ListResponse<UserType>>;
abstract deleteUser(id: string): Promise<any>;
abstract reinviteUser(id: string): Promise<any>;
abstract confirmUser(user: UserType, publicKey: Uint8Array): Promise<any>;
async load() {
const response = await this.getUsers();
this.statusMap.clear();
for (const status of Utils.iterateEnum(this.userStatusType)) {
this.statusMap.set(status, []);
}
get invitedCount() {
return this.statusMap.has(this.userStatusType.Invited) ?
this.statusMap.get(this.userStatusType.Invited).length : 0;
this.allUsers = response.data != null && response.data.length > 0 ? response.data : [];
this.allUsers.sort(Utils.getSortFunction(this.i18nService, "email"));
this.allUsers.forEach((u) => {
if (!this.statusMap.has(u.status)) {
this.statusMap.set(u.status, [u]);
} else {
this.statusMap.get(u.status).push(u);
}
});
this.filter(this.status);
this.loading = false;
}
filter(status: StatusType) {
this.status = status;
if (this.status != null) {
this.users = this.statusMap.get(this.status);
} else {
this.users = this.allUsers;
}
// Reset checkbox selecton
this.selectAll(false);
this.resetPaging();
}
loadMore() {
if (!this.users || this.users.length <= this.pageSize) {
return;
}
const pagedLength = this.pagedUsers.length;
let pagedSize = this.pageSize;
if (pagedLength === 0 && this.pagedUsersCount > this.pageSize) {
pagedSize = this.pagedUsersCount;
}
if (this.users.length > pagedLength) {
this.pagedUsers = this.pagedUsers.concat(
this.users.slice(pagedLength, pagedLength + pagedSize)
);
}
this.pagedUsersCount = this.pagedUsers.length;
this.didScroll = this.pagedUsers.length > this.pageSize;
}
checkUser(user: OrganizationUserUserDetailsResponse, select?: boolean) {
(user as any).checked = select == null ? !(user as any).checked : select;
}
selectAll(select: boolean) {
if (select) {
this.selectAll(false);
}
get acceptedCount() {
return this.statusMap.has(this.userStatusType.Accepted) ?
this.statusMap.get(this.userStatusType.Accepted).length : 0;
const filteredUsers = this.searchPipe.transform(
this.users,
this.searchText,
"name",
"email",
"id"
);
const selectCount =
select && filteredUsers.length > MaxCheckedCount ? MaxCheckedCount : filteredUsers.length;
for (let i = 0; i < selectCount; i++) {
this.checkUser(filteredUsers[i], select);
}
}
async resetPaging() {
this.pagedUsers = [];
this.loadMore();
}
invite() {
this.edit(null);
}
async remove(user: UserType) {
const confirmed = await this.platformUtilsService.showDialog(
this.deleteWarningMessage(user),
this.userNamePipe.transform(user),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
get confirmedCount() {
return this.statusMap.has(this.userStatusType.Confirmed) ?
this.statusMap.get(this.userStatusType.Confirmed).length : 0;
this.actionPromise = this.deleteUser(user.id);
try {
await this.actionPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("removedUserId", this.userNamePipe.transform(user))
);
this.removeUser(user);
} catch (e) {
this.validationService.showError(e);
}
this.actionPromise = null;
}
async reinvite(user: UserType) {
if (this.actionPromise != null) {
return;
}
get showConfirmUsers(): boolean {
return this.allUsers != null && this.statusMap != null && this.allUsers.length > 1 &&
this.confirmedCount > 0 && this.confirmedCount < 3 && this.acceptedCount > 0;
this.actionPromise = this.reinviteUser(user.id);
try {
await this.actionPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("hasBeenReinvited", this.userNamePipe.transform(user))
);
} catch (e) {
this.validationService.showError(e);
}
this.actionPromise = null;
}
async confirm(user: UserType) {
function updateUser(self: BasePeopleComponent<UserType>) {
user.status = self.userStatusType.Confirmed;
const mapIndex = self.statusMap.get(self.userStatusType.Accepted).indexOf(user);
if (mapIndex > -1) {
self.statusMap.get(self.userStatusType.Accepted).splice(mapIndex, 1);
self.statusMap.get(self.userStatusType.Confirmed).push(user);
}
}
get showBulkConfirmUsers(): boolean {
return this.acceptedCount > 0;
}
abstract userType: typeof OrganizationUserType | typeof ProviderUserType;
abstract userStatusType: typeof OrganizationUserStatusType | typeof ProviderUserStatusType;
loading = true;
statusMap = new Map<StatusType, UserType[]>();
status: StatusType;
users: UserType[] = [];
pagedUsers: UserType[] = [];
searchText: string;
actionPromise: Promise<any>;
protected allUsers: UserType[] = [];
protected didScroll = false;
protected pageSize = 100;
private pagedUsersCount = 0;
constructor(
protected apiService: ApiService,
private searchService: SearchService,
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
protected cryptoService: CryptoService,
protected validationService: ValidationService,
protected modalService: ModalService,
private logService: LogService,
private searchPipe: SearchPipe,
protected userNamePipe: UserNamePipe,
protected stateService: StateService,
) { }
abstract edit(user: UserType): void;
abstract getUsers(): Promise<ListResponse<UserType>>;
abstract deleteUser(id: string): Promise<any>;
abstract reinviteUser(id: string): Promise<any>;
abstract confirmUser(user: UserType, publicKey: Uint8Array): Promise<any>;
async load() {
const response = await this.getUsers();
this.statusMap.clear();
for (const status of Utils.iterateEnum(this.userStatusType)) {
this.statusMap.set(status, []);
}
this.allUsers = response.data != null && response.data.length > 0 ? response.data : [];
this.allUsers.sort(Utils.getSortFunction(this.i18nService, 'email'));
this.allUsers.forEach(u => {
if (!this.statusMap.has(u.status)) {
this.statusMap.set(u.status, [u]);
} else {
this.statusMap.get(u.status).push(u);
}
});
this.filter(this.status);
this.loading = false;
}
filter(status: StatusType) {
this.status = status;
if (this.status != null) {
this.users = this.statusMap.get(this.status);
} else {
this.users = this.allUsers;
}
// Reset checkbox selecton
this.selectAll(false);
this.resetPaging();
}
loadMore() {
if (!this.users || this.users.length <= this.pageSize) {
return;
}
const pagedLength = this.pagedUsers.length;
let pagedSize = this.pageSize;
if (pagedLength === 0 && this.pagedUsersCount > this.pageSize) {
pagedSize = this.pagedUsersCount;
}
if (this.users.length > pagedLength) {
this.pagedUsers = this.pagedUsers.concat(this.users.slice(pagedLength, pagedLength + pagedSize));
}
this.pagedUsersCount = this.pagedUsers.length;
this.didScroll = this.pagedUsers.length > this.pageSize;
}
checkUser(user: OrganizationUserUserDetailsResponse, select?: boolean) {
(user as any).checked = select == null ? !(user as any).checked : select;
}
selectAll(select: boolean) {
if (select) {
this.selectAll(false);
}
const filteredUsers = this.searchPipe.transform(this.users, this.searchText, 'name', 'email', 'id');
const selectCount = select && filteredUsers.length > MaxCheckedCount
? MaxCheckedCount
: filteredUsers.length;
for (let i = 0; i < selectCount; i++) {
this.checkUser(filteredUsers[i], select);
}
}
async resetPaging() {
this.pagedUsers = [];
this.loadMore();
}
invite() {
this.edit(null);
}
async remove(user: UserType) {
const confirmed = await this.platformUtilsService.showDialog(
this.deleteWarningMessage(user), this.userNamePipe.transform(user),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
this.actionPromise = this.deleteUser(user.id);
try {
await this.actionPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('removedUserId',
this.userNamePipe.transform(user)));
this.removeUser(user);
} catch (e) {
this.validationService.showError(e);
}
const confirmUser = async (publicKey: Uint8Array) => {
try {
this.actionPromise = this.confirmUser(user, publicKey);
await this.actionPromise;
updateUser(this);
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(user))
);
} catch (e) {
this.validationService.showError(e);
throw e;
} finally {
this.actionPromise = null;
}
};
if (this.actionPromise != null) {
return;
}
async reinvite(user: UserType) {
if (this.actionPromise != null) {
return;
}
try {
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
this.actionPromise = this.reinviteUser(user.id);
try {
await this.actionPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('hasBeenReinvited',
this.userNamePipe.transform(user)));
} catch (e) {
this.validationService.showError(e);
}
this.actionPromise = null;
}
async confirm(user: UserType) {
function updateUser(self: BasePeopleComponent<UserType>) {
user.status = self.userStatusType.Confirmed;
const mapIndex = self.statusMap.get(self.userStatusType.Accepted).indexOf(user);
if (mapIndex > -1) {
self.statusMap.get(self.userStatusType.Accepted).splice(mapIndex, 1);
self.statusMap.get(self.userStatusType.Confirmed).push(user);
}
}
const confirmUser = async (publicKey: Uint8Array) => {
try {
this.actionPromise = this.confirmUser(user, publicKey);
await this.actionPromise;
updateUser(this);
this.platformUtilsService.showToast('success', null, this.i18nService.t('hasBeenConfirmed',
this.userNamePipe.transform(user)));
} catch (e) {
this.validationService.showError(e);
throw e;
} finally {
this.actionPromise = null;
}
};
if (this.actionPromise != null) {
return;
}
try {
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
const autoConfirm = await this.stateService.getAutoConfirmFingerPrints();
if (autoConfirm == null || !autoConfirm) {
const [modal] = await this.modalService.openViewRef(UserConfirmComponent, this.confirmModalRef, comp => {
comp.name = this.userNamePipe.transform(user);
comp.userId = user != null ? user.userId : null;
comp.publicKey = publicKey;
comp.onConfirmedUser.subscribe(async () => {
try {
comp.formPromise = confirmUser(publicKey);
await comp.formPromise;
modal.close();
} catch (e) {
this.logService.error(e);
}
});
});
return;
}
try {
const fingerprint = await this.cryptoService.getFingerprint(user.userId, publicKey.buffer);
this.logService.info(`User's fingerprint: ${fingerprint.join('-')}`);
} catch (e) {
const autoConfirm = await this.stateService.getAutoConfirmFingerPrints();
if (autoConfirm == null || !autoConfirm) {
const [modal] = await this.modalService.openViewRef(
UserConfirmComponent,
this.confirmModalRef,
(comp) => {
comp.name = this.userNamePipe.transform(user);
comp.userId = user != null ? user.userId : null;
comp.publicKey = publicKey;
comp.onConfirmedUser.subscribe(async () => {
try {
comp.formPromise = confirmUser(publicKey);
await comp.formPromise;
modal.close();
} catch (e) {
this.logService.error(e);
}
await confirmUser(publicKey);
} catch (e) {
this.logService.error(`Handled exception: ${e}`);
}
}
}
});
}
);
return;
}
isSearching() {
return this.searchService.isSearchable(this.searchText);
try {
const fingerprint = await this.cryptoService.getFingerprint(user.userId, publicKey.buffer);
this.logService.info(`User's fingerprint: ${fingerprint.join("-")}`);
} catch (e) {
this.logService.error(e);
}
await confirmUser(publicKey);
} catch (e) {
this.logService.error(`Handled exception: ${e}`);
}
}
isPaging() {
const searching = this.isSearching();
if (searching && this.didScroll) {
this.resetPaging();
}
return !searching && this.users && this.users.length > this.pageSize;
isSearching() {
return this.searchService.isSearchable(this.searchText);
}
isPaging() {
const searching = this.isSearching();
if (searching && this.didScroll) {
this.resetPaging();
}
return !searching && this.users && this.users.length > this.pageSize;
}
protected deleteWarningMessage(user: UserType): string {
return this.i18nService.t('removeUserConfirmation');
protected deleteWarningMessage(user: UserType): string {
return this.i18nService.t("removeUserConfirmation");
}
protected getCheckedUsers() {
return this.users.filter((u) => (u as any).checked);
}
protected removeUser(user: UserType) {
let index = this.users.indexOf(user);
if (index > -1) {
this.users.splice(index, 1);
this.resetPaging();
}
protected getCheckedUsers() {
return this.users.filter(u => (u as any).checked);
if (this.statusMap.has(user.status)) {
index = this.statusMap.get(user.status).indexOf(user);
if (index > -1) {
this.statusMap.get(user.status).splice(index, 1);
}
}
protected removeUser(user: UserType) {
let index = this.users.indexOf(user);
if (index > -1) {
this.users.splice(index, 1);
this.resetPaging();
}
if (this.statusMap.has(user.status)) {
index = this.statusMap.get(user.status).indexOf(user);
if (index > -1) {
this.statusMap.get(user.status).splice(index, 1);
}
}
}
}
}

View File

@@ -1,18 +1,30 @@
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" [name]="pascalize(parentId)" [id]="parentId"
[(ngModel)]="parentChecked" [indeterminate]="parentIndeterminate">
<label class="form-check-label font-weight-normal" [for]="parentId">
{{parentId | i18n}}
</label>
</div>
<div class="form-group form-group-child-check mb-0">
<div class="form-check mt-1" *ngFor="let c of checkboxes">
<input class="form-check-input" type="checkbox" [name]="pascalize(c.id)" [id]="c.id" [ngModel]="c.get()"
(ngModelChange)="c.set($event)">
<label class="form-check-label font-weight-normal" [for]="c.id">
{{c.id | i18n}}
</label>
</div>
<div class="form-check mt-1 form-check-block">
<input
class="form-check-input"
type="checkbox"
[name]="pascalize(parentId)"
[id]="parentId"
[(ngModel)]="parentChecked"
[indeterminate]="parentIndeterminate"
/>
<label class="form-check-label font-weight-normal" [for]="parentId">
{{ parentId | i18n }}
</label>
</div>
<div class="form-group form-group-child-check mb-0">
<div class="form-check mt-1" *ngFor="let c of checkboxes">
<input
class="form-check-input"
type="checkbox"
[name]="pascalize(c.id)"
[id]="c.id"
[ngModel]="c.get()"
(ngModelChange)="c.set($event)"
/>
<label class="form-check-label font-weight-normal" [for]="c.id">
{{ c.id | i18n }}
</label>
</div>
</div>
</div>

View File

@@ -1,37 +1,39 @@
import {
Component,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { Utils } from 'jslib-common/misc/utils';
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { Utils } from "jslib-common/misc/utils";
@Component({
selector: 'app-nested-checkbox',
templateUrl: 'nested-checkbox.component.html',
selector: "app-nested-checkbox",
templateUrl: "nested-checkbox.component.html",
})
export class NestedCheckboxComponent {
@Input() parentId: string;
@Input() checkboxes: { id: string, get: () => boolean, set: (v: boolean) => void; }[];
@Output() onSavedUser = new EventEmitter();
@Output() onDeletedUser = new EventEmitter();
@Input()
parentId: string;
@Input()
checkboxes: {
id: string;
get: () => boolean;
set: (v: boolean) => void;
}[];
@Output()
onSavedUser = new EventEmitter();
@Output()
onDeletedUser = new EventEmitter();
get parentIndeterminate() {
return !this.parentChecked &&
this.checkboxes.some(c => c.get());
}
get parentIndeterminate() {
return !this.parentChecked && this.checkboxes.some((c) => c.get());
}
get parentChecked() {
return this.checkboxes.every(c => c.get());
}
get parentChecked() {
return this.checkboxes.every((c) => c.get());
}
set parentChecked(value: boolean) {
this.checkboxes.forEach(c => {
c.set(value);
});
}
set parentChecked(value: boolean) {
this.checkboxes.forEach((c) => {
c.set(value);
});
}
pascalize(s: string) {
return Utils.camelToPascalCase(s);
}
pascalize(s: string) {
return Utils.camelToPascalCase(s);
}
}

View File

@@ -1,39 +1,56 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="confirmUserTitle">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-header">
<h2 class="modal-title" id="confirmUserTitle">
{{'passwordConfirmation' | i18n}}
</h2>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
{{'passwordConfirmationDesc' | i18n}}
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-header">
<h2 class="modal-title" id="confirmUserTitle">
{{ "passwordConfirmation" | i18n }}
</h2>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
{{ "passwordConfirmationDesc" | i18n }}
<div class="form-group">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<div class="d-flex">
<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" appA11yTitle="{{'toggleVisibility' | i18n}}"
(click)="togglePassword()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{'ok' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{'cancel' | i18n}}
</button>
</div>
</form>
</div>
<div class="form-group">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<div class="d-flex">
<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"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{
'fa-eye': !showPassword,
'fa-eye-slash': showPassword
}"
></i>
</button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{ "ok" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@@ -1,8 +1,8 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { PasswordRepromptComponent as BasePasswordRepromptComponent } from 'jslib-angular/components/password-reprompt.component';
import { PasswordRepromptComponent as BasePasswordRepromptComponent } from "jslib-angular/components/password-reprompt.component";
@Component({
templateUrl: 'password-reprompt.component.html',
templateUrl: "password-reprompt.component.html",
})
export class PasswordRepromptComponent extends BasePasswordRepromptComponent {}

View File

@@ -1,8 +1,16 @@
<div class="progress">
<div class="progress-bar {{color}}" role="progressbar" [ngStyle]="{width: (scoreWidth + '%')}"
attr.aria-valuenow="{{scoreWidth}}" aria-valuemin="0" aria-valuemax="100">
<ng-container *ngIf="showText && text">
{{text}}
</ng-container>
</div>
<div
class="progress-bar {{ color }}"
role="progressbar"
[ngStyle]="{
width: scoreWidth + '%'
}"
attr.aria-valuenow="{{ scoreWidth }}"
aria-valuemin="0"
aria-valuemax="100"
>
<ng-container *ngIf="showText && text">
{{ text }}
</ng-container>
</div>
</div>

View File

@@ -1,44 +1,42 @@
import {
Component,
Input,
OnChanges,
} from '@angular/core';
import { Component, Input, OnChanges } from "@angular/core";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
@Component({
selector: 'app-password-strength',
templateUrl: 'password-strength.component.html',
selector: "app-password-strength",
templateUrl: "password-strength.component.html",
})
export class PasswordStrengthComponent implements OnChanges {
@Input() score?: number;
@Input() showText = false;
@Input()
score?: number;
@Input()
showText = false;
scoreWidth = 0;
color = 'bg-danger';
text: string;
scoreWidth = 0;
color = "bg-danger";
text: string;
constructor(private i18nService: I18nService) { }
constructor(private i18nService: I18nService) {}
ngOnChanges(): void {
this.scoreWidth = this.score == null ? 0 : (this.score + 1) * 20;
switch (this.score) {
case 4:
this.color = 'bg-success';
this.text = this.i18nService.t('strong');
break;
case 3:
this.color = 'bg-primary';
this.text = this.i18nService.t('good');
break;
case 2:
this.color = 'bg-warning';
this.text = this.i18nService.t('weak');
break;
default:
this.color = 'bg-danger';
this.text = this.score != null ? this.i18nService.t('weak') : null;
break;
}
ngOnChanges(): void {
this.scoreWidth = this.score == null ? 0 : (this.score + 1) * 20;
switch (this.score) {
case 4:
this.color = "bg-success";
this.text = this.i18nService.t("strong");
break;
case 3:
this.color = "bg-primary";
this.text = this.i18nService.t("good");
break;
case 2:
this.color = "bg-warning";
this.text = this.i18nService.t("weak");
break;
default:
this.color = "bg-danger";
this.text = this.score != null ? this.i18nService.t("weak") : null;
break;
}
}
}

View File

@@ -1,11 +1,12 @@
<div class="container footer text-muted">
<div class="row">
<div class="col">
&copy; {{year}}, Bitwarden Inc.
</div>
<div class="col text-center"></div>
<div class="col text-right">
{{'versionNumber' | i18n : version}}
</div>
<div class="row">
<div class="col">
&copy;
{{ year }}, Bitwarden Inc.
</div>
<div class="col text-center"></div>
<div class="col text-right">
{{ "versionNumber" | i18n: version }}
</div>
</div>
</div>

View File

@@ -1,22 +1,19 @@
import {
Component,
OnInit,
} from '@angular/core';
import { Component, OnInit } from "@angular/core";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
@Component({
selector: 'app-footer',
templateUrl: 'footer.component.html',
selector: "app-footer",
templateUrl: "footer.component.html",
})
export class FooterComponent implements OnInit {
version: string;
year: string = '2015';
version: string;
year: string = "2015";
constructor(private platformUtilsService: PlatformUtilsService) { }
constructor(private platformUtilsService: PlatformUtilsService) {}
async ngOnInit() {
this.year = new Date().getFullYear().toString();
this.version = await this.platformUtilsService.getApplicationVersion();
}
async ngOnInit() {
this.year = new Date().getFullYear().toString();
this.version = await this.platformUtilsService.getApplicationVersion();
}
}

View File

@@ -1,5 +1,7 @@
<router-outlet></router-outlet>
<div class="container my-5 text-muted text-center">
&copy; {{year}}, Bitwarden Inc.
<br> {{'versionNumber' | i18n : version}}
&copy;
{{ year }}, Bitwarden Inc.
<br />
{{ "versionNumber" | i18n: version }}
</div>

View File

@@ -1,28 +1,24 @@
import {
Component,
OnDestroy,
OnInit,
} from '@angular/core';
import { Component, OnDestroy, OnInit } from "@angular/core";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
@Component({
selector: 'app-frontend-layout',
templateUrl: 'frontend-layout.component.html',
selector: "app-frontend-layout",
templateUrl: "frontend-layout.component.html",
})
export class FrontendLayoutComponent implements OnInit, OnDestroy {
version: string;
year: string = '2015';
version: string;
year: string = "2015";
constructor(private platformUtilsService: PlatformUtilsService) { }
constructor(private platformUtilsService: PlatformUtilsService) {}
async ngOnInit() {
this.year = new Date().getFullYear().toString();
this.version = await this.platformUtilsService.getApplicationVersion();
document.body.classList.add('layout_frontend');
}
async ngOnInit() {
this.year = new Date().getFullYear().toString();
this.version = await this.platformUtilsService.getApplicationVersion();
document.body.classList.add("layout_frontend");
}
ngOnDestroy() {
document.body.classList.remove('layout_frontend');
}
ngOnDestroy() {
document.body.classList.remove("layout_frontend");
}
}

View File

@@ -1,70 +1,94 @@
<nav class="navbar navbar-expand navbar-dark" [ngClass]="{'nav-background-alt': selfHosted}">
<div class="container">
<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">
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/vault">{{'myVault' | i18n}}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/sends">{{'send' | i18n}}</a>
</li>
<ng-container *ngIf="providers.length >= 1">
<li class="nav-item" routerLinkActive="active" *ngIf="providers.length == 1">
<a class="nav-link" [routerLink]="['/providers', providers[0].id]">{{'provider' | i18n}}</a>
</li>
<li class="nav-item" routerLinkActive="active" *ngIf="providers.length > 1">
<a class="nav-link" routerLink="/providers">{{'provider' | i18n}}</a>
</li>
</ng-container>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/tools">{{'tools' | i18n}}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/settings">{{'settings' | i18n}}</a>
</li>
</ul>
</div>
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
<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" 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>
<app-avatar [data]="name" [email]="email" size="25" fontSize="14" [circle]="true"></app-avatar>
<div class="ml-2 overflow-hidden">
<span>{{'loggedInAs' | i18n}}</span>
<small class="text-muted">{{name}}</small>
</div>
</div>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" routerLink="/settings/account">
<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" 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" 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" aria-hidden="true"></i>
{{'lockNow' | i18n}}
</button>
<button type="button" class="dropdown-item" (click)="logOut()">
<i class="fa fa-fw fa-sign-out" aria-hidden="true"></i>
{{'logOut' | i18n}}
</button>
</div>
</li>
</ul>
<nav
class="navbar navbar-expand navbar-dark"
[ngClass]="{
'nav-background-alt': selfHosted
}"
>
<div class="container">
<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">
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/vault">{{ "myVault" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/sends">{{ "send" | i18n }}</a>
</li>
<ng-container *ngIf="providers.length >= 1">
<li class="nav-item" routerLinkActive="active" *ngIf="providers.length == 1">
<a class="nav-link" [routerLink]="['/providers', providers[0].id]">{{
"provider" | i18n
}}</a>
</li>
<li class="nav-item" routerLinkActive="active" *ngIf="providers.length > 1">
<a class="nav-link" routerLink="/providers">{{ "provider" | i18n }}</a>
</li>
</ng-container>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/tools">{{ "tools" | i18n }}</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/settings">{{ "settings" | i18n }}</a>
</li>
</ul>
</div>
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
<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" 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>
<app-avatar
[data]="name"
[email]="email"
size="25"
fontSize="14"
[circle]="true"
></app-avatar>
<div class="ml-2 overflow-hidden">
<span>{{ "loggedInAs" | i18n }}</span>
<small class="text-muted">{{ name }}</small>
</div>
</div>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" routerLink="/settings/account">
<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" 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" 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" aria-hidden="true"></i>
{{ "lockNow" | i18n }}
</button>
<button type="button" class="dropdown-item" (click)="logOut()">
<i class="fa fa-fw fa-sign-out" aria-hidden="true"></i>
{{ "logOut" | i18n }}
</button>
</div>
</li>
</ul>
</div>
</nav>

View File

@@ -1,50 +1,52 @@
import {
Component,
OnInit,
} from '@angular/core';
import { Component, OnInit } from "@angular/core";
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ProviderService } from 'jslib-common/abstractions/provider.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { TokenService } from 'jslib-common/abstractions/token.service';
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderService } from "jslib-common/abstractions/provider.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { Provider } from 'jslib-common/models/domain/provider';
import { Provider } from "jslib-common/models/domain/provider";
@Component({
selector: 'app-navbar',
templateUrl: 'navbar.component.html',
selector: "app-navbar",
templateUrl: "navbar.component.html",
})
export class NavbarComponent implements OnInit {
selfHosted = false;
name: string;
email: string;
providers: Provider[] = [];
selfHosted = false;
name: string;
email: string;
providers: Provider[] = [];
constructor(private messagingService: MessagingService, private platformUtilsService: PlatformUtilsService,
private tokenService: TokenService, private providerService: ProviderService, private syncService: SyncService) {
this.selfHosted = this.platformUtilsService.isSelfHost();
constructor(
private messagingService: MessagingService,
private platformUtilsService: PlatformUtilsService,
private tokenService: TokenService,
private providerService: ProviderService,
private syncService: SyncService
) {
this.selfHosted = this.platformUtilsService.isSelfHost();
}
async ngOnInit() {
this.name = await this.tokenService.getName();
this.email = await this.tokenService.getEmail();
if (this.name == null || this.name.trim() === "") {
this.name = this.email;
}
async ngOnInit() {
this.name = await this.tokenService.getName();
this.email = await this.tokenService.getEmail();
if (this.name == null || this.name.trim() === '') {
this.name = this.email;
}
// Ensure provides are loaded
if (await this.syncService.getLastSync() == null) {
await this.syncService.fullSync(false);
}
this.providers = await this.providerService.getAll();
// Ensure provides are loaded
if ((await this.syncService.getLastSync()) == null) {
await this.syncService.fullSync(false);
}
this.providers = await this.providerService.getAll();
}
lock() {
this.messagingService.send('lockVault');
}
lock() {
this.messagingService.send("lockVault");
}
logOut() {
this.messagingService.send('logout');
}
logOut() {
this.messagingService.send("logout");
}
}

View File

@@ -1,54 +1,60 @@
<app-navbar></app-navbar>
<div class="org-nav" *ngIf="organization">
<div class="container d-flex">
<div class="d-flex flex-column">
<div class="my-auto d-flex align-items-center pl-1">
<app-avatar [data]="organization.name" size="45" [circle]="true"></app-avatar>
<div class="org-name ml-3">
<span>{{organization.name}}</span>
<small class="text-muted">{{'organization' | i18n}}</small>
</div>
<div class="ml-3 card border-danger text-danger bg-transparent" *ngIf="!organization.enabled">
<div class="card-body py-2">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
{{'organizationIsDisabled' | i18n}}
</div>
</div>
<div class="ml-3 card border-info text-info bg-transparent" *ngIf="organization.isProviderUser">
<div class="card-body py-2">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
{{'accessingUsingProvider' | i18n : organization.providerName}}
</div>
</div>
</div>
<ul class="nav nav-tabs" *ngIf="showMenuBar">
<li class="nav-item">
<a class="nav-link" routerLink="vault" routerLinkActive="active">
<i class="fa fa-lock" aria-hidden="true"></i>
{{'vault' | i18n}}
</a>
</li>
<li class="nav-item" *ngIf="showManageTab">
<a class="nav-link" [routerLink]="manageRoute" routerLinkActive="active">
<i class="fa fa-sliders" aria-hidden="true"></i>
{{'manage' | i18n}}
</a>
</li>
<li class="nav-item" *ngIf="showToolsTab">
<a class="nav-link" [routerLink]="toolsRoute" routerLinkActive="active">
<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" aria-hidden="true"></i>
{{'settings' | i18n}}
</a>
</li>
</ul>
<div class="container d-flex">
<div class="d-flex flex-column">
<div class="my-auto d-flex align-items-center pl-1">
<app-avatar [data]="organization.name" size="45" [circle]="true"></app-avatar>
<div class="org-name ml-3">
<span>{{ organization.name }}</span>
<small class="text-muted">{{ "organization" | i18n }}</small>
</div>
<div
class="ml-3 card border-danger text-danger bg-transparent"
*ngIf="!organization.enabled"
>
<div class="card-body py-2">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
{{ "organizationIsDisabled" | i18n }}
</div>
</div>
<div
class="ml-3 card border-info text-info bg-transparent"
*ngIf="organization.isProviderUser"
>
<div class="card-body py-2">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
{{ "accessingUsingProvider" | i18n: organization.providerName }}
</div>
</div>
</div>
<ul class="nav nav-tabs" *ngIf="showMenuBar">
<li class="nav-item">
<a class="nav-link" routerLink="vault" routerLinkActive="active">
<i class="fa fa-lock" aria-hidden="true"></i>
{{ "vault" | i18n }}
</a>
</li>
<li class="nav-item" *ngIf="showManageTab">
<a class="nav-link" [routerLink]="manageRoute" routerLinkActive="active">
<i class="fa fa-sliders" aria-hidden="true"></i>
{{ "manage" | i18n }}
</a>
</li>
<li class="nav-item" *ngIf="showToolsTab">
<a class="nav-link" [routerLink]="toolsRoute" routerLinkActive="active">
<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" aria-hidden="true"></i>
{{ "settings" | i18n }}
</a>
</li>
</ul>
</div>
</div>
</div>
<router-outlet></router-outlet>
<app-footer></app-footer>

View File

@@ -1,98 +1,99 @@
import {
Component,
NgZone,
OnDestroy,
OnInit,
} from '@angular/core';
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute } from "@angular/router";
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { Organization } from 'jslib-common/models/domain/organization';
import { Organization } from "jslib-common/models/domain/organization";
const BroadcasterSubscriptionId = 'OrganizationLayoutComponent';
const BroadcasterSubscriptionId = "OrganizationLayoutComponent";
@Component({
selector: 'app-organization-layout',
templateUrl: 'organization-layout.component.html',
selector: "app-organization-layout",
templateUrl: "organization-layout.component.html",
})
export class OrganizationLayoutComponent implements OnInit, OnDestroy {
organization: Organization;
businessTokenPromise: Promise<any>;
private organizationId: string;
organization: Organization;
businessTokenPromise: Promise<any>;
private organizationId: string;
constructor(private route: ActivatedRoute, private organizationService: OrganizationService,
private broadcasterService: BroadcasterService, private ngZone: NgZone) { }
constructor(
private route: ActivatedRoute,
private organizationService: OrganizationService,
private broadcasterService: BroadcasterService,
private ngZone: NgZone
) {}
ngOnInit() {
document.body.classList.remove('layout_frontend');
this.route.params.subscribe(async params => {
this.organizationId = params.organizationId;
ngOnInit() {
document.body.classList.remove("layout_frontend");
this.route.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
await this.load();
});
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case "updatedOrgLicense":
await this.load();
});
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
this.ngZone.run(async () => {
switch (message.command) {
case 'updatedOrgLicense':
await this.load();
break;
}
});
});
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async load() {
this.organization = await this.organizationService.get(this.organizationId);
}
get showMenuBar() {
return this.showManageTab || this.showToolsTab || this.organization.isOwner;
}
get showManageTab(): boolean {
return this.organization.canManageUsers ||
this.organization.canViewAllCollections ||
this.organization.canViewAssignedCollections ||
this.organization.canManageGroups ||
this.organization.canManagePolicies ||
this.organization.canAccessEventLogs;
}
get showToolsTab(): boolean {
return this.organization.canAccessImportExport || this.organization.canAccessReports;
}
get toolsRoute(): string {
return this.organization.canAccessImportExport ?
'tools/import' :
'tools/exposed-passwords-report';
}
get manageRoute(): string {
let route: string;
switch (true) {
case this.organization.canManageUsers:
route = 'manage/people';
break;
case this.organization.canViewAssignedCollections || this.organization.canViewAllCollections:
route = 'manage/collections';
break;
case this.organization.canManageGroups:
route = 'manage/groups';
break;
case this.organization.canManagePolicies:
route = 'manage/policies';
break;
case this.organization.canAccessEventLogs:
route = 'manage/events';
break;
break;
}
return route;
});
});
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
async load() {
this.organization = await this.organizationService.get(this.organizationId);
}
get showMenuBar() {
return this.showManageTab || this.showToolsTab || this.organization.isOwner;
}
get showManageTab(): boolean {
return (
this.organization.canManageUsers ||
this.organization.canViewAllCollections ||
this.organization.canViewAssignedCollections ||
this.organization.canManageGroups ||
this.organization.canManagePolicies ||
this.organization.canAccessEventLogs
);
}
get showToolsTab(): boolean {
return this.organization.canAccessImportExport || this.organization.canAccessReports;
}
get toolsRoute(): string {
return this.organization.canAccessImportExport
? "tools/import"
: "tools/exposed-passwords-report";
}
get manageRoute(): string {
let route: string;
switch (true) {
case this.organization.canManageUsers:
route = "manage/people";
break;
case this.organization.canViewAssignedCollections || this.organization.canViewAllCollections:
route = "manage/collections";
break;
case this.organization.canManageGroups:
route = "manage/groups";
break;
case this.organization.canManagePolicies:
route = "manage/policies";
break;
case this.organization.canAccessEventLogs:
route = "manage/events";
break;
}
return route;
}
}

View File

@@ -1,14 +1,11 @@
import {
Component,
OnInit,
} from '@angular/core';
import { Component, OnInit } from "@angular/core";
@Component({
selector: 'app-user-layout',
templateUrl: 'user-layout.component.html',
selector: "app-user-layout",
templateUrl: "user-layout.component.html",
})
export class UserLayoutComponent implements OnInit {
ngOnInit() {
document.body.classList.remove('layout_frontend');
}
ngOnInit() {
document.body.classList.remove("layout_frontend");
}
}

View File

@@ -1,17 +1,17 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import 'bootstrap';
import 'jquery';
import 'popper.js';
import "bootstrap";
import "jquery";
import "popper.js";
// tslint:disable-next-line
require('../scss/styles.scss');
require("../scss/styles.scss");
import { AppModule } from './app.module';
import { AppModule } from "./app.module";
if (process.env.NODE_ENV === 'production') {
enableProdMode();
if (process.env.NODE_ENV === "production") {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true });

View File

@@ -1,100 +1,133 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="bulkTitle">
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="bulkTitle">
{{'confirmUsers' | i18n}}
</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="card-body text-center" *ngIf="loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
{{'loading' | i18n}}
</div>
<app-callout type="danger" *ngIf="filteredUsers.length <= 0">
{{'noSelectedUsersApplicable' | i18n}}
</app-callout>
<app-callout type="error" *ngIf="error">
{{error}}
</app-callout>
<ng-container *ngIf="!loading && !done">
<p>
{{'fingerprintEnsureIntegrityVerify' | i18n}}
<a href="https://help.bitwarden.com/article/fingerprint-phrase/" target="_blank" rel="noopener">
{{'learnMore' | i18n}}</a>
</p>
<table class="table table-hover table-list">
<thead>
<tr>
<th colspan="2">{{'user' | i18n}}</th>
<th>{{'fingerprint' | i18n}}</th>
</tr>
</thead>
<tr *ngFor="let user of filteredUsers">
<td width="30">
<app-avatar [data]="user | userName" [email]="user.email" size="25" [circle]="true"
[fontSize]="14"></app-avatar>
</td>
<td>
{{user.email}}
<small class="text-muted d-block" *ngIf="user.name">{{user.name}}</small>
</td>
<td>
{{fingerprints.get(user.id)}}
</td>
</tr>
<tr *ngFor="let user of excludedUsers">
<td width="30">
<app-avatar [data]="user | userName" [email]="user.email" size="25" [circle]="true"
[fontSize]="14"></app-avatar>
</td>
<td>
{{user.email}}
<small class="text-muted d-block" *ngIf="user.name">{{user.name}}</small>
</td>
<td>
{{'bulkFilteredMessage' | i18n}}
</td>
</tr>
</table>
</ng-container>
<ng-container *ngIf="!loading && done">
<table class="table table-hover table-list">
<thead>
<tr>
<th colspan="2">{{'user' | i18n}}</th>
<th>{{'status' | i18n}}</th>
</tr>
</thead>
<tr *ngFor="let user of filteredUsers">
<td width="30">
<app-avatar [data]="user | userName" [email]="user.email" size="25" [circle]="true"
[fontSize]="14"></app-avatar>
</td>
<td>
{{user.email}}
<small class="text-muted d-block" *ngIf="user.name">{{user.name}}</small>
</td>
<td *ngIf="statuses.has(user.id)">
{{statuses.get(user.id)}}
</td>
<td *ngIf="!statuses.has(user.id)">
{{'bulkFilteredMessage' | i18n}}
</td>
</tr>
</table>
</ng-container>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" *ngIf="!done" [disabled]="loading" (click)="submit()">
<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" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="bulkTitle">
{{ "confirmUsers" | i18n }}
</h2>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="card-body text-center" *ngIf="loading">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
<app-callout type="danger" *ngIf="filteredUsers.length <= 0">
{{ "noSelectedUsersApplicable" | i18n }}
</app-callout>
<app-callout type="error" *ngIf="error">
{{ error }}
</app-callout>
<ng-container *ngIf="!loading && !done">
<p>
{{ "fingerprintEnsureIntegrityVerify" | i18n }}
<a
href="https://help.bitwarden.com/article/fingerprint-phrase/"
target="_blank"
rel="noopener"
>
{{ "learnMore" | i18n }}</a
>
</p>
<table class="table table-hover table-list">
<thead>
<tr>
<th colspan="2">{{ "user" | i18n }}</th>
<th>{{ "fingerprint" | i18n }}</th>
</tr>
</thead>
<tr *ngFor="let user of filteredUsers">
<td width="30">
<app-avatar
[data]="user | userName"
[email]="user.email"
size="25"
[circle]="true"
[fontSize]="14"
></app-avatar>
</td>
<td>
{{ user.email }}
<small class="text-muted d-block" *ngIf="user.name">{{ user.name }}</small>
</td>
<td>
{{ fingerprints.get(user.id) }}
</td>
</tr>
<tr *ngFor="let user of excludedUsers">
<td width="30">
<app-avatar
[data]="user | userName"
[email]="user.email"
size="25"
[circle]="true"
[fontSize]="14"
></app-avatar>
</td>
<td>
{{ user.email }}
<small class="text-muted d-block" *ngIf="user.name">{{ user.name }}</small>
</td>
<td>
{{ "bulkFilteredMessage" | i18n }}
</td>
</tr>
</table>
</ng-container>
<ng-container *ngIf="!loading && done">
<table class="table table-hover table-list">
<thead>
<tr>
<th colspan="2">{{ "user" | i18n }}</th>
<th>{{ "status" | i18n }}</th>
</tr>
</thead>
<tr *ngFor="let user of filteredUsers">
<td width="30">
<app-avatar
[data]="user | userName"
[email]="user.email"
size="25"
[circle]="true"
[fontSize]="14"
></app-avatar>
</td>
<td>
{{ user.email }}
<small class="text-muted d-block" *ngIf="user.name">{{ user.name }}</small>
</td>
<td *ngIf="statuses.has(user.id)">
{{ statuses.get(user.id) }}
</td>
<td *ngIf="!statuses.has(user.id)">
{{ "bulkFilteredMessage" | i18n }}
</td>
</tr>
</table>
</ng-container>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary btn-submit"
*ngIf="!done"
[disabled]="loading"
(click)="submit()"
>
<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" data-dismiss="modal">
{{ "close" | i18n }}
</button>
</div>
</div>
</div>
</div>

View File

@@ -1,110 +1,108 @@
import {
Component,
Input,
OnInit,
} from '@angular/core';
import { Component, Input, OnInit } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { OrganizationUserBulkConfirmRequest } from 'jslib-common/models/request/organizationUserBulkConfirmRequest';
import { OrganizationUserBulkRequest } from 'jslib-common/models/request/organizationUserBulkRequest';
import { OrganizationUserBulkConfirmRequest } from "jslib-common/models/request/organizationUserBulkConfirmRequest";
import { OrganizationUserBulkRequest } from "jslib-common/models/request/organizationUserBulkRequest";
import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType';
import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType";
import { Utils } from 'jslib-common/misc/utils';
import { BulkUserDetails } from './bulk-status.component';
import { Utils } from "jslib-common/misc/utils";
import { BulkUserDetails } from "./bulk-status.component";
@Component({
selector: 'app-bulk-confirm',
templateUrl: 'bulk-confirm.component.html',
selector: "app-bulk-confirm",
templateUrl: "bulk-confirm.component.html",
})
export class BulkConfirmComponent implements OnInit {
@Input() organizationId: string;
@Input() users: BulkUserDetails[];
@Input() organizationId: string;
@Input() users: BulkUserDetails[];
excludedUsers: BulkUserDetails[];
filteredUsers: BulkUserDetails[];
publicKeys: Map<string, Uint8Array> = new Map();
fingerprints: Map<string, string> = new Map();
statuses: Map<string, string> = new Map();
excludedUsers: BulkUserDetails[];
filteredUsers: BulkUserDetails[];
publicKeys: Map<string, Uint8Array> = new Map();
fingerprints: Map<string, string> = new Map();
statuses: Map<string, string> = new Map();
loading: boolean = true;
done: boolean = false;
error: string;
loading: boolean = true;
done: boolean = false;
error: string;
constructor(
protected cryptoService: CryptoService,
protected apiService: ApiService,
private i18nService: I18nService
) {}
constructor(protected cryptoService: CryptoService, protected apiService: ApiService,
private i18nService: I18nService) { }
async ngOnInit() {
this.excludedUsers = this.users.filter((u) => !this.isAccepted(u));
this.filteredUsers = this.users.filter((u) => this.isAccepted(u));
async ngOnInit() {
this.excludedUsers = this.users.filter(u => !this.isAccepted(u));
this.filteredUsers = this.users.filter(u => this.isAccepted(u));
if (this.filteredUsers.length <= 0) {
this.done = true;
}
if (this.filteredUsers.length <= 0) {
this.done = true;
const response = await this.getPublicKeys();
for (const entry of response.data) {
const publicKey = Utils.fromB64ToArray(entry.key);
const fingerprint = await this.cryptoService.getFingerprint(entry.userId, publicKey.buffer);
if (fingerprint != null) {
this.publicKeys.set(entry.id, publicKey);
this.fingerprints.set(entry.id, fingerprint.join("-"));
}
}
this.loading = false;
}
async submit() {
this.loading = true;
try {
const key = await this.getCryptoKey();
const userIdsWithKeys: any[] = [];
for (const user of this.filteredUsers) {
const publicKey = this.publicKeys.get(user.id);
if (publicKey == null) {
continue;
}
const encryptedKey = await this.cryptoService.rsaEncrypt(key.key, publicKey.buffer);
userIdsWithKeys.push({
id: user.id,
key: encryptedKey.encryptedString,
});
}
const response = await this.postConfirmRequest(userIdsWithKeys);
const response = await this.getPublicKeys();
response.data.forEach((entry) => {
const error = entry.error !== "" ? entry.error : this.i18nService.t("bulkConfirmMessage");
this.statuses.set(entry.id, error);
});
for (const entry of response.data) {
const publicKey = Utils.fromB64ToArray(entry.key);
const fingerprint = await this.cryptoService.getFingerprint(entry.userId, publicKey.buffer);
if (fingerprint != null) {
this.publicKeys.set(entry.id, publicKey);
this.fingerprints.set(entry.id, fingerprint.join('-'));
}
}
this.loading = false;
this.done = true;
} catch (e) {
this.error = e.message;
}
this.loading = false;
}
async submit() {
this.loading = true;
try {
const key = await this.getCryptoKey();
const userIdsWithKeys: any[] = [];
for (const user of this.filteredUsers) {
const publicKey = this.publicKeys.get(user.id);
if (publicKey == null) {
continue;
}
const encryptedKey = await this.cryptoService.rsaEncrypt(key.key, publicKey.buffer);
userIdsWithKeys.push({
id: user.id,
key: encryptedKey.encryptedString,
});
}
const response = await this.postConfirmRequest(userIdsWithKeys);
protected isAccepted(user: BulkUserDetails) {
return user.status === OrganizationUserStatusType.Accepted;
}
response.data.forEach(entry => {
const error = entry.error !== '' ? entry.error : this.i18nService.t('bulkConfirmMessage');
this.statuses.set(entry.id, error);
});
protected async getPublicKeys() {
const request = new OrganizationUserBulkRequest(this.filteredUsers.map((user) => user.id));
return await this.apiService.postOrganizationUsersPublicKey(this.organizationId, request);
}
this.done = true;
} catch (e) {
this.error = e.message;
}
this.loading = false;
}
protected getCryptoKey() {
return this.cryptoService.getOrgKey(this.organizationId);
}
protected isAccepted(user: BulkUserDetails) {
return user.status === OrganizationUserStatusType.Accepted;
}
protected async getPublicKeys() {
const request = new OrganizationUserBulkRequest(this.filteredUsers.map(user => user.id));
return await this.apiService.postOrganizationUsersPublicKey(this.organizationId, request);
}
protected getCryptoKey() {
return this.cryptoService.getOrgKey(this.organizationId);
}
protected async postConfirmRequest(userIdsWithKeys: any[]) {
const request = new OrganizationUserBulkConfirmRequest(userIdsWithKeys);
return await this.apiService.postOrganizationUserBulkConfirm(this.organizationId, request);
}
protected async postConfirmRequest(userIdsWithKeys: any[]) {
const request = new OrganizationUserBulkConfirmRequest(userIdsWithKeys);
return await this.apiService.postOrganizationUserBulkConfirm(this.organizationId, request);
}
}

View File

@@ -1,77 +1,100 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="bulkTitle">
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="bulkTitle">
{{'removeUsers' | i18n}}
</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<app-callout type="danger" *ngIf="users.length <= 0">
{{'noSelectedUsersApplicable' | i18n}}
</app-callout>
<app-callout type="error" *ngIf="error">
{{error}}
</app-callout>
<ng-container *ngIf="!done">
<app-callout type="warning" *ngIf="users.length > 0 && !error">
{{'removeUsersWarning' | i18n}}
</app-callout>
<table class="table table-hover table-list">
<thead>
<tr>
<th colspan="2">{{'user' | i18n}}</th>
</tr>
</thead>
<tr *ngFor="let user of users">
<td width="30">
<app-avatar [data]="user | userName" [email]="user.email" size="25" [circle]="true"
[fontSize]="14"></app-avatar>
</td>
<td>
{{user.email}}
<small class="text-muted d-block" *ngIf="user.name">{{user.name}}</small>
</td>
</tr>
</table>
</ng-container>
<ng-container *ngIf="done">
<table class="table table-hover table-list">
<thead>
<tr>
<th colspan="2">{{'user' | i18n}}</th>
<th>{{'status' | i18n}}</th>
</tr>
</thead>
<tr *ngFor="let user of users">
<td width="30">
<app-avatar [data]="user | userName" [email]="user.email" size="25" [circle]="true"
[fontSize]="14"></app-avatar>
</td>
<td>
{{user.email}}
<small class="text-muted d-block" *ngIf="user.name">{{user.name}}</small>
</td>
<td *ngIf="statuses.has(user.id)">
{{statuses.get(user.id)}}
</td>
<td *ngIf="!statuses.has(user.id)">
{{'bulkFilteredMessage' | i18n}}
</td>
</tr>
</table>
</ng-container>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" *ngIf="!done && users.length > 0" [disabled]="loading" (click)="submit()">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
<span>{{'removeUsers' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
</div>
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="bulkTitle">
{{ "removeUsers" | i18n }}
</h2>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<app-callout type="danger" *ngIf="users.length <= 0">
{{ "noSelectedUsersApplicable" | i18n }}
</app-callout>
<app-callout type="error" *ngIf="error">
{{ error }}
</app-callout>
<ng-container *ngIf="!done">
<app-callout type="warning" *ngIf="users.length > 0 && !error">
{{ "removeUsersWarning" | i18n }}
</app-callout>
<table class="table table-hover table-list">
<thead>
<tr>
<th colspan="2">{{ "user" | i18n }}</th>
</tr>
</thead>
<tr *ngFor="let user of users">
<td width="30">
<app-avatar
[data]="user | userName"
[email]="user.email"
size="25"
[circle]="true"
[fontSize]="14"
></app-avatar>
</td>
<td>
{{ user.email }}
<small class="text-muted d-block" *ngIf="user.name">{{ user.name }}</small>
</td>
</tr>
</table>
</ng-container>
<ng-container *ngIf="done">
<table class="table table-hover table-list">
<thead>
<tr>
<th colspan="2">{{ "user" | i18n }}</th>
<th>{{ "status" | i18n }}</th>
</tr>
</thead>
<tr *ngFor="let user of users">
<td width="30">
<app-avatar
[data]="user | userName"
[email]="user.email"
size="25"
[circle]="true"
[fontSize]="14"
></app-avatar>
</td>
<td>
{{ user.email }}
<small class="text-muted d-block" *ngIf="user.name">{{ user.name }}</small>
</td>
<td *ngIf="statuses.has(user.id)">
{{ statuses.get(user.id) }}
</td>
<td *ngIf="!statuses.has(user.id)">
{{ "bulkFilteredMessage" | i18n }}
</td>
</tr>
</table>
</ng-container>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary btn-submit"
*ngIf="!done && users.length > 0"
[disabled]="loading"
(click)="submit()"
>
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "removeUsers" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "close" | i18n }}
</button>
</div>
</div>
</div>
</div>

View File

@@ -1,50 +1,46 @@
import {
Component,
Input,
} from '@angular/core';
import { Component, Input } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { OrganizationUserBulkRequest } from 'jslib-common/models/request/organizationUserBulkRequest';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { OrganizationUserBulkRequest } from "jslib-common/models/request/organizationUserBulkRequest";
import { BulkUserDetails } from './bulk-status.component';
import { BulkUserDetails } from "./bulk-status.component";
@Component({
selector: 'app-bulk-remove',
templateUrl: 'bulk-remove.component.html',
selector: "app-bulk-remove",
templateUrl: "bulk-remove.component.html",
})
export class BulkRemoveComponent {
@Input() organizationId: string;
@Input() users: BulkUserDetails[];
@Input() organizationId: string;
@Input() users: BulkUserDetails[];
statuses: Map<string, string> = new Map();
statuses: Map<string, string> = new Map();
loading: boolean = false;
done: boolean = false;
error: string;
loading: boolean = false;
done: boolean = false;
error: string;
constructor(protected apiService: ApiService, protected i18nService: I18nService) {}
constructor(protected apiService: ApiService, protected i18nService: I18nService) { }
async submit() {
this.loading = true;
try {
const response = await this.deleteUsers();
async submit() {
this.loading = true;
try {
const response = await this.deleteUsers();
response.data.forEach(entry => {
const error = entry.error !== '' ? entry.error : this.i18nService.t('bulkRemovedMessage');
this.statuses.set(entry.id, error);
});
this.done = true;
} catch (e) {
this.error = e.message;
}
this.loading = false;
response.data.forEach((entry) => {
const error = entry.error !== "" ? entry.error : this.i18nService.t("bulkRemovedMessage");
this.statuses.set(entry.id, error);
});
this.done = true;
} catch (e) {
this.error = e.message;
}
protected async deleteUsers() {
const request = new OrganizationUserBulkRequest(this.users.map(user => user.id));
return await this.apiService.deleteManyOrganizationUsers(this.organizationId, request);
}
this.loading = false;
}
protected async deleteUsers() {
const request = new OrganizationUserBulkRequest(this.users.map((user) => user.id));
return await this.apiService.deleteManyOrganizationUsers(this.organizationId, request);
}
}

View File

@@ -1,47 +1,59 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="bulkTitle">
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="bulkTitle">
{{'bulkConfirmStatus' | i18n}}
</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="card-body text-center" *ngIf="loading">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
{{'loading' | i18n}}
</div>
<table class="table table-hover table-list" *ngIf="!loading">
<thead>
<tr>
<th colspan="2">{{'user' | i18n}}</th>
<th>{{'status' | i18n}}</th>
</tr>
</thead>
<tr *ngFor="let item of users">
<td width="30">
<app-avatar [data]="item.user | userName" [email]="item.user.email" size="25" [circle]="true"
[fontSize]="14"></app-avatar>
</td>
<td>
{{item.user.email}}
<small class="text-muted d-block" *ngIf="item.user.name">{{item.user.name}}</small>
</td>
<td class="text-danger" *ngIf="item.error">
{{item.message}}
</td>
<td *ngIf="!item.error">
{{item.message}}
</td>
</tr>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="bulkTitle">
{{ "bulkConfirmStatus" | i18n }}
</h2>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="card-body text-center" *ngIf="loading">
<i class="fa fa-spinner fa-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
<table class="table table-hover table-list" *ngIf="!loading">
<thead>
<tr>
<th colspan="2">{{ "user" | i18n }}</th>
<th>{{ "status" | i18n }}</th>
</tr>
</thead>
<tr *ngFor="let item of users">
<td width="30">
<app-avatar
[data]="item.user | userName"
[email]="item.user.email"
size="25"
[circle]="true"
[fontSize]="14"
></app-avatar>
</td>
<td>
{{ item.user.email }}
<small class="text-muted d-block" *ngIf="item.user.name">{{ item.user.name }}</small>
</td>
<td class="text-danger" *ngIf="item.error">
{{ item.message }}
</td>
<td *ngIf="!item.error">
{{ item.message }}
</td>
</tr>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "close" | i18n }}
</button>
</div>
</div>
</div>
</div>

View File

@@ -1,27 +1,25 @@
import { Component } from '@angular/core';
import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType';
import { ProviderUserStatusType } from 'jslib-common/enums/providerUserStatusType';
import { Component } from "@angular/core";
import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType";
import { ProviderUserStatusType } from "jslib-common/enums/providerUserStatusType";
export interface BulkUserDetails {
id: string;
name: string;
email: string;
status: OrganizationUserStatusType | ProviderUserStatusType;
id: string;
name: string;
email: string;
status: OrganizationUserStatusType | ProviderUserStatusType;
}
type BulkStatusEntry = {
user: BulkUserDetails,
error: boolean,
message: string,
user: BulkUserDetails;
error: boolean;
message: string;
};
@Component({
selector: 'app-bulk-status',
templateUrl: 'bulk-status.component.html',
selector: "app-bulk-status",
templateUrl: "bulk-status.component.html",
})
export class BulkStatusComponent {
users: BulkStatusEntry[];
loading: boolean = false;
users: BulkStatusEntry[];
loading: boolean = false;
}

View File

@@ -1,97 +1,162 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="collectionAddEditTitle">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="modal-header">
<h2 class="modal-title" id="collectionAddEditTitle">{{title}}</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form
class="modal-content"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
>
<div class="modal-header">
<h2 class="modal-title" id="collectionAddEditTitle">{{ title }}</h2>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</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">
<div class="form-group">
<label for="name">{{ "name" | i18n }}</label>
<input
id="name"
class="form-control"
type="text"
name="Name"
[(ngModel)]="name"
required
appAutofocus
[disabled]="!this.canSave"
/>
</div>
<div class="form-group">
<label for="externalId">{{ "externalId" | i18n }}</label>
<input
id="externalId"
class="form-control"
type="text"
name="ExternalId"
[(ngModel)]="externalId"
[disabled]="!this.canSave"
/>
<small class="form-text text-muted">{{ "externalIdDesc" | i18n }}</small>
</div>
<ng-container *ngIf="accessGroups">
<h3 class="mt-4 d-flex mb-0">
{{ "groupAccess" | i18n }}
<div class="ml-auto" *ngIf="groups && groups.length && this.canSave">
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
{{ "selectAll" | i18n }}
</button>
<button type="button" (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
{{ "unselectAll" | i18n }}
</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">
<div class="form-group">
<label for="name">{{'name' | i18n}}</label>
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="name" required
appAutofocus [disabled]="!this.canSave">
</div>
<div class="form-group">
<label for="externalId">{{'externalId' | i18n}}</label>
<input id="externalId" class="form-control" type="text" name="ExternalId" [(ngModel)]="externalId"
[disabled]="!this.canSave">
<small class="form-text text-muted">{{'externalIdDesc' | i18n}}</small>
</div>
<ng-container *ngIf="accessGroups">
<h3 class="mt-4 d-flex mb-0">
{{'groupAccess' | i18n}}
<div class="ml-auto" *ngIf="groups && groups.length && this.canSave">
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
{{'selectAll' | i18n}}
</button>
<button type="button" (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
{{'unselectAll' | i18n}}
</button>
</div>
</h3>
<div *ngIf="!groups || !groups.length">
{{'noGroupsInList' | i18n}}
</div>
<table class="table table-hover table-list mb-0" *ngIf="groups && groups.length">
<thead>
<tr>
<th>&nbsp;</th>
<th>{{'name' | i18n}}</th>
<th width="100" class="text-center">{{'hidePasswords' | i18n}}</th>
<th width="100" class="text-center">{{'readOnly' | i18n}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let g of groups; let i = index">
<td class="table-list-checkbox" (click)="check(g)">
<input type="checkbox" [(ngModel)]="g.checked" name="Groups[{{i}}].Checked"
[disabled]="g.accessAll || !this.canSave" appStopProp>
</td>
<td (click)="check(g)">
{{g.name}}
<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.hidePasswords"
name="Groups[{{i}}].HidePasswords" [disabled]="!g.checked || g.accessAll || !this.canSave">
</td>
<td class="text-center">
<input type="checkbox" [(ngModel)]="g.readOnly" name="Groups[{{i}}].ReadOnly"
[disabled]="!g.checked || g.accessAll || !this.canSave">
</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading" *ngIf="this.canSave">
<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" *ngIf="this.canDelete">
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger"
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode"
[disabled]="deleteBtn.loading" [appApiAction]="deletePromise">
<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}}" aria-hidden="true"></i>
</button>
</div>
</div>
</form>
</div>
</h3>
<div *ngIf="!groups || !groups.length">
{{ "noGroupsInList" | i18n }}
</div>
<table class="table table-hover table-list mb-0" *ngIf="groups && groups.length">
<thead>
<tr>
<th>&nbsp;</th>
<th>{{ "name" | i18n }}</th>
<th width="100" class="text-center">{{ "hidePasswords" | i18n }}</th>
<th width="100" class="text-center">{{ "readOnly" | i18n }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let g of groups; let i = index">
<td class="table-list-checkbox" (click)="check(g)">
<input
type="checkbox"
[(ngModel)]="g.checked"
name="Groups[{{ i }}].Checked"
[disabled]="g.accessAll || !this.canSave"
appStopProp
/>
</td>
<td (click)="check(g)">
{{ g.name }}
<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.hidePasswords"
name="Groups[{{ i }}].HidePasswords"
[disabled]="!g.checked || g.accessAll || !this.canSave"
/>
</td>
<td class="text-center">
<input
type="checkbox"
[(ngModel)]="g.readOnly"
name="Groups[{{ i }}].ReadOnly"
[disabled]="!g.checked || g.accessAll || !this.canSave"
/>
</td>
</tr>
</tbody>
</table>
</ng-container>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary btn-submit"
[disabled]="form.loading"
*ngIf="this.canSave"
>
<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" *ngIf="this.canDelete">
<button
#deleteBtn
type="button"
(click)="delete()"
class="btn btn-outline-danger"
appA11yTitle="{{ 'delete' | i18n }}"
*ngIf="editMode"
[disabled]="deleteBtn.loading"
[appApiAction]="deletePromise"
>
<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 }}"
aria-hidden="true"
></i>
</button>
</div>
</div>
</form>
</div>
</div>

View File

@@ -1,162 +1,182 @@
import {
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { EncString } from 'jslib-common/models/domain/encString';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { CollectionRequest } from 'jslib-common/models/request/collectionRequest';
import { SelectionReadOnlyRequest } from 'jslib-common/models/request/selectionReadOnlyRequest';
import { GroupResponse } from 'jslib-common/models/response/groupResponse';
import { EncString } from "jslib-common/models/domain/encString";
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { CollectionRequest } from "jslib-common/models/request/collectionRequest";
import { SelectionReadOnlyRequest } from "jslib-common/models/request/selectionReadOnlyRequest";
import { GroupResponse } from "jslib-common/models/response/groupResponse";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
@Component({
selector: 'app-collection-add-edit',
templateUrl: 'collection-add-edit.component.html',
selector: "app-collection-add-edit",
templateUrl: "collection-add-edit.component.html",
})
export class CollectionAddEditComponent implements OnInit {
@Input() collectionId: string;
@Input() organizationId: string;
@Input() canSave: boolean;
@Input() canDelete: boolean;
@Output() onSavedCollection = new EventEmitter();
@Output() onDeletedCollection = new EventEmitter();
@Input() collectionId: string;
@Input() organizationId: string;
@Input() canSave: boolean;
@Input() canDelete: boolean;
@Output() onSavedCollection = new EventEmitter();
@Output() onDeletedCollection = new EventEmitter();
loading = true;
editMode: boolean = false;
accessGroups: boolean = false;
title: string;
name: string;
externalId: string;
groups: GroupResponse[] = [];
formPromise: Promise<any>;
deletePromise: Promise<any>;
loading = true;
editMode: boolean = false;
accessGroups: boolean = false;
title: string;
name: string;
externalId: string;
groups: GroupResponse[] = [];
formPromise: Promise<any>;
deletePromise: Promise<any>;
private orgKey: SymmetricCryptoKey;
private orgKey: SymmetricCryptoKey;
constructor(
private apiService: ApiService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private cryptoService: CryptoService,
private logService: LogService,
private organizationService: OrganizationService
) { }
constructor(
private apiService: ApiService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private cryptoService: CryptoService,
private logService: LogService,
private organizationService: OrganizationService
) {}
async ngOnInit() {
const organization = await this.organizationService.get(this.organizationId);
this.accessGroups = organization.useGroups;
this.editMode = this.loading = this.collectionId != null;
if (this.accessGroups) {
const groupsResponse = await this.apiService.getGroups(this.organizationId);
this.groups = groupsResponse.data.map(r => r).sort(Utils.getSortFunction(this.i18nService, 'name'));
}
this.orgKey = await this.cryptoService.getOrgKey(this.organizationId);
async ngOnInit() {
const organization = await this.organizationService.get(this.organizationId);
this.accessGroups = organization.useGroups;
this.editMode = this.loading = this.collectionId != null;
if (this.accessGroups) {
const groupsResponse = await this.apiService.getGroups(this.organizationId);
this.groups = groupsResponse.data
.map((r) => r)
.sort(Utils.getSortFunction(this.i18nService, "name"));
}
this.orgKey = await this.cryptoService.getOrgKey(this.organizationId);
if (this.editMode) {
this.editMode = true;
this.title = this.i18nService.t('editCollection');
try {
const collection = await this.apiService.getCollectionDetails(this.organizationId, this.collectionId);
this.name = await this.cryptoService.decryptToUtf8(new EncString(collection.name), this.orgKey);
this.externalId = collection.externalId;
if (collection.groups != null && this.groups.length > 0) {
collection.groups.forEach(s => {
const group = this.groups.filter(g => !g.accessAll && g.id === s.id);
if (group != null && group.length > 0) {
(group[0] as any).checked = true;
(group[0] as any).readOnly = s.readOnly;
(group[0] as any).hidePasswords = s.hidePasswords;
}
});
}
} catch (e) {
this.logService.error(e);
if (this.editMode) {
this.editMode = true;
this.title = this.i18nService.t("editCollection");
try {
const collection = await this.apiService.getCollectionDetails(
this.organizationId,
this.collectionId
);
this.name = await this.cryptoService.decryptToUtf8(
new EncString(collection.name),
this.orgKey
);
this.externalId = collection.externalId;
if (collection.groups != null && this.groups.length > 0) {
collection.groups.forEach((s) => {
const group = this.groups.filter((g) => !g.accessAll && g.id === s.id);
if (group != null && group.length > 0) {
(group[0] as any).checked = true;
(group[0] as any).readOnly = s.readOnly;
(group[0] as any).hidePasswords = s.hidePasswords;
}
} else {
this.title = this.i18nService.t('addCollection');
});
}
this.groups.forEach(g => {
if (g.accessAll) {
(g as any).checked = true;
}
});
this.loading = false;
} catch (e) {
this.logService.error(e);
}
} else {
this.title = this.i18nService.t("addCollection");
}
check(g: GroupResponse, select?: boolean) {
if (g.accessAll) {
return;
}
(g as any).checked = select == null ? !(g as any).checked : select;
if (!(g as any).checked) {
(g as any).readOnly = false;
(g as any).hidePasswords = false;
}
this.groups.forEach((g) => {
if (g.accessAll) {
(g as any).checked = true;
}
});
this.loading = false;
}
check(g: GroupResponse, select?: boolean) {
if (g.accessAll) {
return;
}
(g as any).checked = select == null ? !(g as any).checked : select;
if (!(g as any).checked) {
(g as any).readOnly = false;
(g as any).hidePasswords = false;
}
}
selectAll(select: boolean) {
this.groups.forEach((g) => this.check(g, select));
}
async submit() {
if (this.orgKey == null) {
throw new Error("No encryption key for this organization.");
}
selectAll(select: boolean) {
this.groups.forEach(g => this.check(g, select));
const request = new CollectionRequest();
request.name = (await this.cryptoService.encrypt(this.name, this.orgKey)).encryptedString;
request.externalId = this.externalId;
request.groups = this.groups
.filter((g) => (g as any).checked && !g.accessAll)
.map(
(g) => new SelectionReadOnlyRequest(g.id, !!(g as any).readOnly, !!(g as any).hidePasswords)
);
try {
if (this.editMode) {
this.formPromise = this.apiService.putCollection(
this.organizationId,
this.collectionId,
request
);
} else {
this.formPromise = this.apiService.postCollection(this.organizationId, request);
}
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t(this.editMode ? "editedCollectionId" : "createdCollectionId", this.name)
);
this.onSavedCollection.emit();
} catch (e) {
this.logService.error(e);
}
}
async delete() {
if (!this.editMode) {
return;
}
async submit() {
if (this.orgKey == null) {
throw new Error('No encryption key for this organization.');
}
const request = new CollectionRequest();
request.name = (await this.cryptoService.encrypt(this.name, this.orgKey)).encryptedString;
request.externalId = this.externalId;
request.groups = this.groups.filter(g => (g as any).checked && !g.accessAll)
.map(g => new SelectionReadOnlyRequest(g.id, !!(g as any).readOnly, !!(g as any).hidePasswords));
try {
if (this.editMode) {
this.formPromise = this.apiService.putCollection(this.organizationId, this.collectionId, request);
} else {
this.formPromise = this.apiService.postCollection(this.organizationId, request);
}
await this.formPromise;
this.platformUtilsService.showToast('success', null,
this.i18nService.t(this.editMode ? 'editedCollectionId' : 'createdCollectionId', this.name));
this.onSavedCollection.emit();
} catch (e) {
this.logService.error(e);
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("deleteCollectionConfirmation"),
this.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
async delete() {
if (!this.editMode) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('deleteCollectionConfirmation'), this.name,
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
try {
this.deletePromise = this.apiService.deleteCollection(this.organizationId, this.collectionId);
await this.deletePromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedCollectionId', this.name));
this.onDeletedCollection.emit();
} catch (e) {
this.logService.error(e);
}
try {
this.deletePromise = this.apiService.deleteCollection(this.organizationId, this.collectionId);
await this.deletePromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("deletedCollectionId", this.name)
);
this.onDeletedCollection.emit();
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -1,52 +1,92 @@
<div class="page-header d-flex">
<h1>{{'collections' | i18n}}</h1>
<div class="ml-auto d-flex">
<div>
<label class="sr-only" for="search">{{'search' | i18n}}</label>
<input type="search" class="form-control form-control-sm" id="search" placeholder="{{'search' | i18n}}"
[(ngModel)]="searchText">
</div>
<button type="button" *ngIf="this.canCreate" class="btn btn-sm btn-outline-primary ml-3" (click)="add()">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
{{'newCollection' | i18n}}
</button>
<h1>{{ "collections" | i18n }}</h1>
<div class="ml-auto d-flex">
<div>
<label class="sr-only" for="search">{{ "search" | i18n }}</label>
<input
type="search"
class="form-control form-control-sm"
id="search"
placeholder="{{ 'search' | i18n }}"
[(ngModel)]="searchText"
/>
</div>
<button
type="button"
*ngIf="this.canCreate"
class="btn btn-sm btn-outline-primary ml-3"
(click)="add()"
>
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
{{ "newCollection" | i18n }}
</button>
</div>
</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>
<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 && (isPaging() ? pagedCollections : collections | search:searchText:'name':'id') as searchedCollections">
<p *ngIf="!searchedCollections.length">{{'noCollectionsInList' | i18n}}</p>
<table class="table table-hover table-list" *ngIf="searchedCollections.length" infiniteScroll
[infiniteScrollDistance]="1" [infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()">
<tbody>
<tr *ngFor="let c of searchedCollections">
<td>
<a href="#" appStopClick (click)="edit(c)">{{c.name}}</a>
</td>
<td class="table-list-options">
<div class="dropdown" appListDropdown *ngIf="this.canEdit(c) || this.canDelete(c)">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" 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 *ngIf="this.canEdit(c)" (click)="users(c)">
<i class="fa fa-fw fa-users" aria-hidden="true"></i>
{{'users' | i18n}}
</a>
<a class="dropdown-item text-danger" href="#" appStopClick *ngIf="this.canDelete(c)" (click)="delete(c)">
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>
{{'delete' | i18n}}
</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
*ngIf="
!loading &&
(isPaging()
? pagedCollections
: (collections | search: searchText:'name':'id')) as searchedCollections
"
>
<p *ngIf="!searchedCollections.length">{{ "noCollectionsInList" | i18n }}</p>
<table
class="table table-hover table-list"
*ngIf="searchedCollections.length"
infiniteScroll
[infiniteScrollDistance]="1"
[infiniteScrollDisabled]="!isPaging()"
(scrolled)="loadMore()"
>
<tbody>
<tr *ngFor="let c of searchedCollections">
<td>
<a href="#" appStopClick (click)="edit(c)">{{ c.name }}</a>
</td>
<td class="table-list-options">
<div class="dropdown" appListDropdown *ngIf="this.canEdit(c) || this.canDelete(c)">
<button
class="btn btn-outline-secondary dropdown-toggle"
type="button"
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
*ngIf="this.canEdit(c)"
(click)="users(c)"
>
<i class="fa fa-fw fa-users" aria-hidden="true"></i>
{{ "users" | i18n }}
</a>
<a
class="dropdown-item text-danger"
href="#"
appStopClick
*ngIf="this.canDelete(c)"
(click)="delete(c)"
>
<i class="fa fa-fw fa-trash-o" aria-hidden="true"></i>
{{ "delete" | i18n }}
</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</ng-container>
<ng-template #addEdit></ng-template>
<ng-template #usersTemplate></ng-template>

View File

@@ -1,229 +1,249 @@
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { first } from "rxjs/operators";
import { ApiService } from "jslib-common/abstractions/api.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { ModalService } from "jslib-angular/services/modal.service";
import { CollectionData } from "jslib-common/models/data/collectionData";
import { Collection } from "jslib-common/models/domain/collection";
import { Organization } from "jslib-common/models/domain/organization";
import {
Component,
OnInit,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
CollectionDetailsResponse,
CollectionResponse,
} from "jslib-common/models/response/collectionResponse";
import { ListResponse } from "jslib-common/models/response/listResponse";
import { CollectionView } from "jslib-common/models/view/collectionView";
import { first } from 'rxjs/operators';
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { ModalService } from 'jslib-angular/services/modal.service';
import { CollectionData } from 'jslib-common/models/data/collectionData';
import { Collection } from 'jslib-common/models/domain/collection';
import { Organization } from 'jslib-common/models/domain/organization';
import {
CollectionDetailsResponse,
CollectionResponse,
} from 'jslib-common/models/response/collectionResponse';
import { ListResponse } from 'jslib-common/models/response/listResponse';
import { CollectionView } from 'jslib-common/models/view/collectionView';
import { CollectionAddEditComponent } from './collection-add-edit.component';
import { EntityUsersComponent } from './entity-users.component';
import { CollectionAddEditComponent } from "./collection-add-edit.component";
import { EntityUsersComponent } from "./entity-users.component";
@Component({
selector: 'app-org-manage-collections',
templateUrl: 'collections.component.html',
selector: "app-org-manage-collections",
templateUrl: "collections.component.html",
})
export class CollectionsComponent implements OnInit {
@ViewChild('addEdit', { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
@ViewChild('usersTemplate', { read: ViewContainerRef, static: true }) usersModalRef: ViewContainerRef;
@ViewChild("addEdit", { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
@ViewChild("usersTemplate", { read: ViewContainerRef, static: true })
usersModalRef: ViewContainerRef;
loading = true;
organization: Organization;
canCreate: boolean = false;
organizationId: string;
collections: CollectionView[];
assignedCollections: CollectionView[];
pagedCollections: CollectionView[];
searchText: string;
loading = true;
organization: Organization;
canCreate: boolean = false;
organizationId: string;
collections: CollectionView[];
assignedCollections: CollectionView[];
pagedCollections: CollectionView[];
searchText: string;
protected didScroll = false;
protected pageSize = 100;
protected didScroll = false;
protected pageSize = 100;
private pagedCollectionsCount = 0;
private pagedCollectionsCount = 0;
constructor(
private apiService: ApiService,
private route: ActivatedRoute,
private collectionService: CollectionService,
private modalService: ModalService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private searchService: SearchService,
private logService: LogService,
private organizationService: OrganizationService,
) { }
constructor(
private apiService: ApiService,
private route: ActivatedRoute,
private collectionService: CollectionService,
private modalService: ModalService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private searchService: SearchService,
private logService: LogService,
private organizationService: OrganizationService
) {}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
await this.load();
this.route.queryParams.pipe(first()).subscribe(async qParams => {
this.searchText = qParams.search;
});
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
await this.load();
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
this.searchText = qParams.search;
});
});
}
async load() {
this.organization = await this.organizationService.get(this.organizationId);
this.canCreate = this.organization.canCreateNewCollections;
const decryptCollections = async (r: ListResponse<CollectionResponse>) => {
const collections = r.data
.filter((c) => c.organizationId === this.organizationId)
.map((d) => new Collection(new CollectionData(d as CollectionDetailsResponse)));
return await this.collectionService.decryptMany(collections);
};
if (this.organization.canViewAssignedCollections) {
const response = await this.apiService.getUserCollections();
this.assignedCollections = await decryptCollections(response);
}
if (this.organization.canViewAllCollections) {
const response = await this.apiService.getCollections(this.organizationId);
this.collections = await decryptCollections(response);
} else {
this.collections = this.assignedCollections;
}
this.resetPaging();
this.loading = false;
}
loadMore() {
if (!this.collections || this.collections.length <= this.pageSize) {
return;
}
const pagedLength = this.pagedCollections.length;
let pagedSize = this.pageSize;
if (pagedLength === 0 && this.pagedCollectionsCount > this.pageSize) {
pagedSize = this.pagedCollectionsCount;
}
if (this.collections.length > pagedLength) {
this.pagedCollections = this.pagedCollections.concat(
this.collections.slice(pagedLength, pagedLength + pagedSize)
);
}
this.pagedCollectionsCount = this.pagedCollections.length;
this.didScroll = this.pagedCollections.length > this.pageSize;
}
async edit(collection: CollectionView) {
const canCreate = collection == null && this.canCreate;
const canEdit = collection != null && this.canEdit(collection);
const canDelete = collection != null && this.canDelete(collection);
if (!(canCreate || canEdit || canDelete)) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("missingPermissions"));
return;
}
const [modal] = await this.modalService.openViewRef(
CollectionAddEditComponent,
this.addEditModalRef,
(comp) => {
comp.organizationId = this.organizationId;
comp.collectionId = collection != null ? collection.id : null;
comp.canSave = canCreate || canEdit;
comp.canDelete = canDelete;
comp.onSavedCollection.subscribe(() => {
modal.close();
this.load();
});
}
async load() {
this.organization = await this.organizationService.get(this.organizationId);
this.canCreate = this.organization.canCreateNewCollections;
const decryptCollections = async (r: ListResponse<CollectionResponse>) => {
const collections = r.data.filter(c => c.organizationId === this.organizationId).map(d =>
new Collection(new CollectionData(d as CollectionDetailsResponse)));
return await this.collectionService.decryptMany(collections);
};
if (this.organization.canViewAssignedCollections) {
const response = await this.apiService.getUserCollections();
this.assignedCollections = await decryptCollections(response);
}
if (this.organization.canViewAllCollections) {
const response = await this.apiService.getCollections(this.organizationId);
this.collections = await decryptCollections(response);
} else {
this.collections = this.assignedCollections;
}
this.resetPaging();
this.loading = false;
}
loadMore() {
if (!this.collections || this.collections.length <= this.pageSize) {
return;
}
const pagedLength = this.pagedCollections.length;
let pagedSize = this.pageSize;
if (pagedLength === 0 && this.pagedCollectionsCount > this.pageSize) {
pagedSize = this.pagedCollectionsCount;
}
if (this.collections.length > pagedLength) {
this.pagedCollections =
this.pagedCollections.concat(this.collections.slice(pagedLength, pagedLength + pagedSize));
}
this.pagedCollectionsCount = this.pagedCollections.length;
this.didScroll = this.pagedCollections.length > this.pageSize;
}
async edit(collection: CollectionView) {
const canCreate = collection == null && this.canCreate;
const canEdit = collection != null && this.canEdit(collection);
const canDelete = collection != null && this.canDelete(collection);
if (!(canCreate || canEdit || canDelete)) {
this.platformUtilsService.showToast('error', null, this.i18nService.t('missingPermissions'));
return;
}
const [modal] = await this.modalService.openViewRef(CollectionAddEditComponent, this.addEditModalRef, comp => {
comp.organizationId = this.organizationId;
comp.collectionId = collection != null ? collection.id : null;
comp.canSave = canCreate || canEdit;
comp.canDelete = canDelete;
comp.onSavedCollection.subscribe(() => {
modal.close();
this.load();
});
comp.onDeletedCollection.subscribe(() => {
modal.close();
this.removeCollection(collection);
});
comp.onDeletedCollection.subscribe(() => {
modal.close();
this.removeCollection(collection);
});
}
);
}
add() {
this.edit(null);
}
async delete(collection: CollectionView) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("deleteCollectionConfirmation"),
collection.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
add() {
this.edit(null);
try {
await this.apiService.deleteCollection(this.organizationId, collection.id);
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("deletedCollectionId", collection.name)
);
this.removeCollection(collection);
} catch (e) {
this.logService.error(e);
this.platformUtilsService.showToast("error", null, this.i18nService.t("missingPermissions"));
}
}
async delete(collection: CollectionView) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('deleteCollectionConfirmation'), collection.name,
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
async users(collection: CollectionView) {
const [modal] = await this.modalService.openViewRef(
EntityUsersComponent,
this.usersModalRef,
(comp) => {
comp.organizationId = this.organizationId;
comp.entity = "collection";
comp.entityId = collection.id;
comp.entityName = collection.name;
try {
await this.apiService.deleteCollection(this.organizationId, collection.id);
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedCollectionId', collection.name));
this.removeCollection(collection);
} catch (e) {
this.logService.error(e);
this.platformUtilsService.showToast('error', null, this.i18nService.t('missingPermissions'));
}
}
async users(collection: CollectionView) {
const [modal] = await this.modalService.openViewRef(EntityUsersComponent, this.usersModalRef, comp => {
comp.organizationId = this.organizationId;
comp.entity = 'collection';
comp.entityId = collection.id;
comp.entityName = collection.name;
comp.onEditedUsers.subscribe(() => {
this.load();
modal.close();
});
comp.onEditedUsers.subscribe(() => {
this.load();
modal.close();
});
}
);
}
async resetPaging() {
this.pagedCollections = [];
this.loadMore();
}
isSearching() {
return this.searchService.isSearchable(this.searchText);
}
isPaging() {
const searching = this.isSearching();
if (searching && this.didScroll) {
this.resetPaging();
}
return !searching && this.collections && this.collections.length > this.pageSize;
}
canEdit(collection: CollectionView) {
if (this.organization.canEditAnyCollection) {
return true;
}
async resetPaging() {
this.pagedCollections = [];
this.loadMore();
if (
this.organization.canEditAssignedCollections &&
this.assignedCollections.some((c) => c.id === collection.id)
) {
return true;
}
return false;
}
canDelete(collection: CollectionView) {
if (this.organization.canDeleteAnyCollection) {
return true;
}
isSearching() {
return this.searchService.isSearchable(this.searchText);
if (
this.organization.canDeleteAssignedCollections &&
this.assignedCollections.some((c) => c.id === collection.id)
) {
return true;
}
return false;
}
isPaging() {
const searching = this.isSearching();
if (searching && this.didScroll) {
this.resetPaging();
}
return !searching && this.collections && this.collections.length > this.pageSize;
}
canEdit(collection: CollectionView) {
if (this.organization.canEditAnyCollection) {
return true;
}
if (this.organization.canEditAssignedCollections && this.assignedCollections.some(c => c.id === collection.id)) {
return true;
}
return false;
}
canDelete(collection: CollectionView) {
if (this.organization.canDeleteAnyCollection) {
return true;
}
if (this.organization.canDeleteAssignedCollections && this.assignedCollections.some(c => c.id === collection.id)) {
return true;
}
return false;
}
private removeCollection(collection: CollectionView) {
const index = this.collections.indexOf(collection);
if (index > -1) {
this.collections.splice(index, 1);
this.resetPaging();
}
private removeCollection(collection: CollectionView) {
const index = this.collections.indexOf(collection);
if (index > -1) {
this.collections.splice(index, 1);
this.resetPaging();
}
}
}

View File

@@ -1,77 +1,118 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="eventLogsTitle">
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<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" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" *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>
</div>
<div class="modal-body" *ngIf="loaded">
<div class="d-flex">
<div class="form-inline">
<label class="sr-only" for="start">{{'startDate' | i18n}}</label>
<input type="datetime-local" class="form-control form-control-sm" id="start"
placeholder="{{'startDate' | i18n}}" [(ngModel)]="start" placeholder="YYYY-MM-DDTHH:MM">
<span class="mx-2">-</span>
<label class="sr-only" for="end">{{'endDate' | i18n}}</label>
<input type="datetime-local" class="form-control form-control-sm" id="end"
placeholder="{{'endDate' | i18n}}" [(ngModel)]="end" placeholder="YYYY-MM-DDTHH:MM">
</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}"
aria-hidden="true"></i>
{{'refresh' | i18n}}
</button>
</div>
<hr>
<div *ngIf="!events || !events.length">
{{'noEventsInList' | i18n}}
</div>
<table class="table table-hover mb-0" *ngIf="events && events.length">
<thead>
<tr>
<th class="border-top-0" width="210">{{'timestamp' | i18n}}</th>
<th class="border-top-0" width="40">
<span class="sr-only">{{'device' | i18n}}</span>
</th>
<th class="border-top-0" width="150" *ngIf="showUser">{{'user' | i18n}}</th>
<th class="border-top-0">{{'event' | i18n}}</th>
</tr>
</thead>
<tbody>
<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}}"
aria-hidden="true"></i>
<span class="sr-only">{{e.appName}}, {{e.ip}}</span>
</td>
<td *ngIf="showUser">
<span appA11yTitle="{{e.userEmail}}">{{e.userName}}</span>
</td>
<td [innerHTML]="e.message"></td>
</tr>
</tbody>
</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}}" aria-hidden="true"></i>
<span>{{'loadMore' | i18n}}</span>
</button>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<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"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" *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>
</div>
<div class="modal-body" *ngIf="loaded">
<div class="d-flex">
<div class="form-inline">
<label class="sr-only" for="start">{{ "startDate" | i18n }}</label>
<input
type="datetime-local"
class="form-control form-control-sm"
id="start"
placeholder="{{ 'startDate' | i18n }}"
[(ngModel)]="start"
placeholder="YYYY-MM-DDTHH:MM"
/>
<span class="mx-2">-</span>
<label class="sr-only" for="end">{{ "endDate" | i18n }}</label>
<input
type="datetime-local"
class="form-control form-control-sm"
id="end"
placeholder="{{ 'endDate' | i18n }}"
[(ngModel)]="end"
placeholder="YYYY-MM-DDTHH:MM"
/>
</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 }"
aria-hidden="true"
></i>
{{ "refresh" | i18n }}
</button>
</div>
<hr />
<div *ngIf="!events || !events.length">
{{ "noEventsInList" | i18n }}
</div>
<table class="table table-hover mb-0" *ngIf="events && events.length">
<thead>
<tr>
<th class="border-top-0" width="210">{{ "timestamp" | i18n }}</th>
<th class="border-top-0" width="40">
<span class="sr-only">{{ "device" | i18n }}</span>
</th>
<th class="border-top-0" width="150" *ngIf="showUser">{{ "user" | i18n }}</th>
<th class="border-top-0">{{ "event" | i18n }}</th>
</tr>
</thead>
<tbody>
<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 }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ e.appName }}, {{ e.ip }}</span>
</td>
<td *ngIf="showUser">
<span appA11yTitle="{{ e.userEmail }}">{{ e.userName }}</span>
</td>
<td [innerHTML]="e.message"></td>
</tr>
</tbody>
</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 }}" aria-hidden="true"></i>
<span>{{ "loadMore" | i18n }}</span>
</button>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "close" | i18n }}
</button>
</div>
</div>
</div>
</div>

View File

@@ -1,134 +1,156 @@
import {
Component,
Input,
OnInit,
} from '@angular/core';
import { Component, Input, OnInit } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { EventService } from '../../services/event.service';
import { EventService } from "../../services/event.service";
import { UserNamePipe } from 'jslib-angular/pipes/user-name.pipe';
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { EventResponse } from 'jslib-common/models/response/eventResponse';
import { ListResponse } from 'jslib-common/models/response/listResponse';
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { EventResponse } from "jslib-common/models/response/eventResponse";
import { ListResponse } from "jslib-common/models/response/listResponse";
@Component({
selector: 'app-entity-events',
templateUrl: 'entity-events.component.html',
selector: "app-entity-events",
templateUrl: "entity-events.component.html",
})
export class EntityEventsComponent implements OnInit {
@Input() name: string;
@Input() entity: 'user' | 'cipher';
@Input() entityId: string;
@Input() organizationId: string;
@Input() providerId: string;
@Input() showUser = false;
@Input() name: string;
@Input() entity: "user" | "cipher";
@Input() entityId: string;
@Input() organizationId: string;
@Input() providerId: string;
@Input() showUser = false;
loading = true;
loaded = false;
events: any[];
start: string;
end: string;
continuationToken: string;
refreshPromise: Promise<any>;
morePromise: Promise<any>;
loading = true;
loaded = false;
events: any[];
start: string;
end: string;
continuationToken: string;
refreshPromise: Promise<any>;
morePromise: Promise<any>;
private orgUsersUserIdMap = new Map<string, any>();
private orgUsersIdMap = new Map<string, any>();
private orgUsersUserIdMap = new Map<string, any>();
private orgUsersIdMap = new Map<string, any>();
constructor(private apiService: ApiService, private i18nService: I18nService,
private eventService: EventService, private platformUtilsService: PlatformUtilsService,
private userNamePipe: UserNamePipe, private logService: LogService) { }
constructor(
private apiService: ApiService,
private i18nService: I18nService,
private eventService: EventService,
private platformUtilsService: PlatformUtilsService,
private userNamePipe: UserNamePipe,
private logService: LogService
) {}
async ngOnInit() {
const defaultDates = this.eventService.getDefaultDateFilters();
this.start = defaultDates[0];
this.end = defaultDates[1];
await this.load();
async ngOnInit() {
const defaultDates = this.eventService.getDefaultDateFilters();
this.start = defaultDates[0];
this.end = defaultDates[1];
await this.load();
}
async load() {
if (this.showUser) {
const response = await this.apiService.getOrganizationUsers(this.organizationId);
response.data.forEach((u) => {
const name = this.userNamePipe.transform(u);
this.orgUsersIdMap.set(u.id, { name: name, email: u.email });
this.orgUsersUserIdMap.set(u.userId, { name: name, email: u.email });
});
}
await this.loadEvents(true);
this.loaded = true;
}
async loadEvents(clearExisting: boolean) {
if (this.refreshPromise != null || this.morePromise != null) {
return;
}
async load() {
if (this.showUser) {
const response = await this.apiService.getOrganizationUsers(this.organizationId);
response.data.forEach(u => {
const name = this.userNamePipe.transform(u);
this.orgUsersIdMap.set(u.id, { name: name, email: u.email });
this.orgUsersUserIdMap.set(u.userId, { name: name, email: u.email });
});
}
await this.loadEvents(true);
this.loaded = true;
let dates: string[] = null;
try {
dates = this.eventService.formatDateFilters(this.start, this.end);
} catch (e) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidDateRange")
);
return;
}
async loadEvents(clearExisting: boolean) {
if (this.refreshPromise != null || this.morePromise != null) {
return;
}
let dates: string[] = null;
try {
dates = this.eventService.formatDateFilters(this.start, this.end);
} catch (e) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('invalidDateRange'));
return;
}
this.loading = true;
let response: ListResponse<EventResponse>;
try {
let promise: Promise<any>;
if (this.entity === 'user' && this.providerId) {
promise = this.apiService.getEventsProviderUser(this.providerId, this.entityId,
dates[0], dates[1], clearExisting ? null : this.continuationToken);
} else if (this.entity === 'user') {
promise = this.apiService.getEventsOrganizationUser(this.organizationId, this.entityId,
dates[0], dates[1], clearExisting ? null : this.continuationToken);
} else {
promise = this.apiService.getEventsCipher(this.entityId,
dates[0], dates[1], clearExisting ? null : this.continuationToken);
}
if (clearExisting) {
this.refreshPromise = promise;
} else {
this.morePromise = promise;
}
response = await promise;
} catch (e) {
this.logService.error(e);
}
this.continuationToken = response.continuationToken;
const events = await Promise.all(response.data.map(async r => {
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
const eventInfo = await this.eventService.getEventInfo(r);
const user = this.showUser && userId != null && this.orgUsersUserIdMap.has(userId) ?
this.orgUsersUserIdMap.get(userId) : null;
return {
message: eventInfo.message,
appIcon: eventInfo.appIcon,
appName: eventInfo.appName,
userId: userId,
userName: user != null ? user.name : this.showUser ? this.i18nService.t('unknown') : null,
userEmail: user != null ? user.email : this.showUser ? '' : null,
date: r.date,
ip: r.ipAddress,
type: r.type,
};
}));
if (!clearExisting && this.events != null && this.events.length > 0) {
this.events = this.events.concat(events);
} else {
this.events = events;
}
this.loading = false;
this.morePromise = null;
this.refreshPromise = null;
this.loading = true;
let response: ListResponse<EventResponse>;
try {
let promise: Promise<any>;
if (this.entity === "user" && this.providerId) {
promise = this.apiService.getEventsProviderUser(
this.providerId,
this.entityId,
dates[0],
dates[1],
clearExisting ? null : this.continuationToken
);
} else if (this.entity === "user") {
promise = this.apiService.getEventsOrganizationUser(
this.organizationId,
this.entityId,
dates[0],
dates[1],
clearExisting ? null : this.continuationToken
);
} else {
promise = this.apiService.getEventsCipher(
this.entityId,
dates[0],
dates[1],
clearExisting ? null : this.continuationToken
);
}
if (clearExisting) {
this.refreshPromise = promise;
} else {
this.morePromise = promise;
}
response = await promise;
} catch (e) {
this.logService.error(e);
}
this.continuationToken = response.continuationToken;
const events = await Promise.all(
response.data.map(async (r) => {
const userId = r.actingUserId == null ? r.userId : r.actingUserId;
const eventInfo = await this.eventService.getEventInfo(r);
const user =
this.showUser && userId != null && this.orgUsersUserIdMap.has(userId)
? this.orgUsersUserIdMap.get(userId)
: null;
return {
message: eventInfo.message,
appIcon: eventInfo.appIcon,
appName: eventInfo.appName,
userId: userId,
userName: user != null ? user.name : this.showUser ? this.i18nService.t("unknown") : null,
userEmail: user != null ? user.email : this.showUser ? "" : null,
date: r.date,
ip: r.ipAddress,
type: r.type,
};
})
);
if (!clearExisting && this.events != null && this.events.length > 0) {
this.events = this.events.concat(events);
} else {
this.events = events;
}
this.loading = false;
this.morePromise = null;
this.refreshPromise = null;
}
}

View File

@@ -1,114 +1,179 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="userAccessTitle">
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="modal-header">
<h2 class="modal-title" id="userAccessTitle">
{{'userAccess' | i18n}}
<small>{{entityName}}</small>
</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" *ngIf="loading || !users">
<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">
<div class="d-flex">
<div class="mr-3">
<label class="sr-only" for="search">{{'search' | i18n}}</label>
<input type="search" class="form-control form-control-sm" id="search"
placeholder="{{'search' | i18n}}" name="SearchText" [(ngModel)]="searchText">
</div>
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn btn-outline-secondary" [ngClass]="{active: !showSelected}"
(click)="filterSelected(false)">
{{'all' | i18n}}
</button>
<button type="button" class="btn btn-outline-secondary" [ngClass]="{active: showSelected}"
(click)="filterSelected(true)">
{{'selected' | i18n}}
<span class="badge badge-pill badge-info" *ngIf="selectedCount">{{selectedCount}}</span>
</button>
</div>
</div>
<ng-container *ngIf="!searchedUsers.length">
<hr>
{{'noUsersInList' | i18n}}
</ng-container>
<ng-container *ngIf="searchedUsers.length">
<table class="table table-hover table-list mb-0">
<thead>
<tr>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>{{'name' | i18n}}</th>
<th *ngIf="entity === 'collection'">&nbsp;</th>
<th>{{'userType' | i18n}}</th>
<th width="100" class="text-center" *ngIf="entity === 'collection'">{{'hidePasswords' |
i18n}}</th>
<th width="100" class="text-center" *ngIf="entity === 'collection'">{{'readOnly' |
i18n}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let u of searchedUsers">
<td class="table-list-checkbox" (click)="check(u)">
<input type="checkbox" [(ngModel)]="u.checked" name="{{u.id.substr(0,8)}}_Checked"
[disabled]="entity === 'collection' && u.accessAll"
(change)="selectedChanged(u)" appStopProp>
</td>
<td width="30" (click)="check(u)">
<app-avatar [data]="u | userName" [email]="u.email" size="25" [circle]="true"
[fontSize]="14"></app-avatar>
</td>
<td>
{{u.email}}
<span class="badge badge-secondary"
*ngIf="u.status === organizationUserStatusType.Invited">{{'invited'
| i18n}}</span>
<span class="badge badge-warning"
*ngIf="u.status === organizationUserStatusType.Accepted">{{'accepted'
| i18n}}</span>
<small class="text-muted d-block" *ngIf="u.name">{{u.name}}</small>
</td>
<td *ngIf="entity === 'collection'">
<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>
<span *ngIf="u.type === organizationUserType.Admin">{{'admin' | i18n}}</span>
<span *ngIf="u.type === organizationUserType.Manager">{{'manager' | i18n}}</span>
<span *ngIf="u.type === organizationUserType.User">{{'user' | i18n}}</span>
<span *ngIf="u.type === organizationUserType.Custom">{{'custom' | i18n}}</span>
</td>
<td class="text-center" *ngIf="entity === 'collection'">
<input type="checkbox" [(ngModel)]="u.hidePasswords"
name="{{u.id.substr(0,8)}}_HidePasswords"
[disabled]="u.accessAll || !u.checked">
</td>
<td class="text-center" *ngIf="entity === 'collection'">
<input type="checkbox" [(ngModel)]="u.readOnly" name="{{u.id.substr(0,8)}}_ReadOnly"
[disabled]="u.accessAll || !u.checked">
</td>
</tr>
</tbody>
</table>
</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">{{'close' | i18n}}</button>
</div>
</form>
</div>
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<form
class="modal-content"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
>
<div class="modal-header">
<h2 class="modal-title" id="userAccessTitle">
{{ "userAccess" | i18n }}
<small>{{ entityName }}</small>
</h2>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" *ngIf="loading || !users">
<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
"
>
<div class="d-flex">
<div class="mr-3">
<label class="sr-only" for="search">{{ "search" | i18n }}</label>
<input
type="search"
class="form-control form-control-sm"
id="search"
placeholder="{{ 'search' | i18n }}"
name="SearchText"
[(ngModel)]="searchText"
/>
</div>
<div class="btn-group btn-group-sm" role="group">
<button
type="button"
class="btn btn-outline-secondary"
[ngClass]="{ active: !showSelected }"
(click)="filterSelected(false)"
>
{{ "all" | i18n }}
</button>
<button
type="button"
class="btn btn-outline-secondary"
[ngClass]="{ active: showSelected }"
(click)="filterSelected(true)"
>
{{ "selected" | i18n }}
<span class="badge badge-pill badge-info" *ngIf="selectedCount">{{
selectedCount
}}</span>
</button>
</div>
</div>
<ng-container *ngIf="!searchedUsers.length">
<hr />
{{ "noUsersInList" | i18n }}
</ng-container>
<ng-container *ngIf="searchedUsers.length">
<table class="table table-hover table-list mb-0">
<thead>
<tr>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>{{ "name" | i18n }}</th>
<th *ngIf="entity === 'collection'">&nbsp;</th>
<th>{{ "userType" | i18n }}</th>
<th width="100" class="text-center" *ngIf="entity === 'collection'">
{{ "hidePasswords" | i18n }}
</th>
<th width="100" class="text-center" *ngIf="entity === 'collection'">
{{ "readOnly" | i18n }}
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let u of searchedUsers">
<td class="table-list-checkbox" (click)="check(u)">
<input
type="checkbox"
[(ngModel)]="u.checked"
name="{{ u.id.substr(0, 8) }}_Checked"
[disabled]="entity === 'collection' && u.accessAll"
(change)="selectedChanged(u)"
appStopProp
/>
</td>
<td width="30" (click)="check(u)">
<app-avatar
[data]="u | userName"
[email]="u.email"
size="25"
[circle]="true"
[fontSize]="14"
></app-avatar>
</td>
<td>
{{ u.email }}
<span
class="badge badge-secondary"
*ngIf="u.status === organizationUserStatusType.Invited"
>{{ "invited" | i18n }}</span
>
<span
class="badge badge-warning"
*ngIf="u.status === organizationUserStatusType.Accepted"
>{{ "accepted" | i18n }}</span
>
<small class="text-muted d-block" *ngIf="u.name">{{ u.name }}</small>
</td>
<td *ngIf="entity === 'collection'">
<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>
<span *ngIf="u.type === organizationUserType.Admin">{{ "admin" | i18n }}</span>
<span *ngIf="u.type === organizationUserType.Manager">{{
"manager" | i18n
}}</span>
<span *ngIf="u.type === organizationUserType.User">{{ "user" | i18n }}</span>
<span *ngIf="u.type === organizationUserType.Custom">{{ "custom" | i18n }}</span>
</td>
<td class="text-center" *ngIf="entity === 'collection'">
<input
type="checkbox"
[(ngModel)]="u.hidePasswords"
name="{{ u.id.substr(0, 8) }}_HidePasswords"
[disabled]="u.accessAll || !u.checked"
/>
</td>
<td class="text-center" *ngIf="entity === 'collection'">
<input
type="checkbox"
[(ngModel)]="u.readOnly"
name="{{ u.id.substr(0, 8) }}_ReadOnly"
[disabled]="u.accessAll || !u.checked"
/>
</td>
</tr>
</tbody>
</table>
</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">
{{ "close" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@@ -1,137 +1,147 @@
import {
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType';
import { OrganizationUserType } from 'jslib-common/enums/organizationUserType';
import { SelectionReadOnlyRequest } from 'jslib-common/models/request/selectionReadOnlyRequest';
import { OrganizationUserUserDetailsResponse } from 'jslib-common/models/response/organizationUserResponse';
import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType";
import { OrganizationUserType } from "jslib-common/enums/organizationUserType";
import { SelectionReadOnlyRequest } from "jslib-common/models/request/selectionReadOnlyRequest";
import { OrganizationUserUserDetailsResponse } from "jslib-common/models/response/organizationUserResponse";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { Utils } from 'jslib-common/misc/utils';
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { Utils } from "jslib-common/misc/utils";
@Component({
selector: 'app-entity-users',
templateUrl: 'entity-users.component.html',
selector: "app-entity-users",
templateUrl: "entity-users.component.html",
})
export class EntityUsersComponent implements OnInit {
@Input() entity: 'group' | 'collection';
@Input() entityId: string;
@Input() entityName: string;
@Input() organizationId: string;
@Output() onEditedUsers = new EventEmitter();
@Input() entity: "group" | "collection";
@Input() entityId: string;
@Input() entityName: string;
@Input() organizationId: string;
@Output() onEditedUsers = new EventEmitter();
organizationUserType = OrganizationUserType;
organizationUserStatusType = OrganizationUserStatusType;
organizationUserType = OrganizationUserType;
organizationUserStatusType = OrganizationUserStatusType;
showSelected = false;
loading = true;
formPromise: Promise<any>;
selectedCount = 0;
searchText: string;
showSelected = false;
loading = true;
formPromise: Promise<any>;
selectedCount = 0;
searchText: string;
private allUsers: OrganizationUserUserDetailsResponse[] = [];
private allUsers: OrganizationUserUserDetailsResponse[] = [];
constructor(private apiService: ApiService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private logService: LogService) { }
constructor(
private apiService: ApiService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private logService: LogService
) {}
async ngOnInit() {
await this.loadUsers();
this.loading = false;
async ngOnInit() {
await this.loadUsers();
this.loading = false;
}
get users() {
if (this.showSelected) {
return this.allUsers.filter((u) => (u as any).checked);
} else {
return this.allUsers;
}
}
get users() {
if (this.showSelected) {
return this.allUsers.filter(u => (u as any).checked);
} else {
return this.allUsers;
}
}
async loadUsers() {
const users = await this.apiService.getOrganizationUsers(this.organizationId);
this.allUsers = users.data.map(r => r).sort(Utils.getSortFunction(this.i18nService, 'email'));
if (this.entity === 'group') {
const response = await this.apiService.getGroupUsers(this.organizationId, this.entityId);
if (response != null && users.data.length > 0) {
response.forEach(s => {
const user = users.data.filter(u => u.id === s);
if (user != null && user.length > 0) {
(user[0] as any).checked = true;
}
});
}
} else if (this.entity === 'collection') {
const response = await this.apiService.getCollectionUsers(this.organizationId, this.entityId);
if (response != null && users.data.length > 0) {
response.forEach(s => {
const user = users.data.filter(u => !u.accessAll && u.id === s.id);
if (user != null && user.length > 0) {
(user[0] as any).checked = true;
(user[0] as any).readOnly = s.readOnly;
(user[0] as any).hidePasswords = s.hidePasswords;
}
});
}
}
this.allUsers.forEach(u => {
if (this.entity === 'collection' && u.accessAll) {
(u as any).checked = true;
}
if ((u as any).checked) {
this.selectedCount++;
}
async loadUsers() {
const users = await this.apiService.getOrganizationUsers(this.organizationId);
this.allUsers = users.data.map((r) => r).sort(Utils.getSortFunction(this.i18nService, "email"));
if (this.entity === "group") {
const response = await this.apiService.getGroupUsers(this.organizationId, this.entityId);
if (response != null && users.data.length > 0) {
response.forEach((s) => {
const user = users.data.filter((u) => u.id === s);
if (user != null && user.length > 0) {
(user[0] as any).checked = true;
}
});
}
} else if (this.entity === "collection") {
const response = await this.apiService.getCollectionUsers(this.organizationId, this.entityId);
if (response != null && users.data.length > 0) {
response.forEach((s) => {
const user = users.data.filter((u) => !u.accessAll && u.id === s.id);
if (user != null && user.length > 0) {
(user[0] as any).checked = true;
(user[0] as any).readOnly = s.readOnly;
(user[0] as any).hidePasswords = s.hidePasswords;
}
});
}
}
check(u: OrganizationUserUserDetailsResponse) {
if (this.entity === 'collection' && u.accessAll) {
return;
}
(u as any).checked = !(u as any).checked;
this.selectedChanged(u);
}
this.allUsers.forEach((u) => {
if (this.entity === "collection" && u.accessAll) {
(u as any).checked = true;
}
if ((u as any).checked) {
this.selectedCount++;
}
});
}
selectedChanged(u: OrganizationUserUserDetailsResponse) {
if ((u as any).checked) {
this.selectedCount++;
} else {
if (this.entity === 'collection') {
(u as any).readOnly = false;
(u as any).hidePasswords = false;
}
this.selectedCount--;
}
check(u: OrganizationUserUserDetailsResponse) {
if (this.entity === "collection" && u.accessAll) {
return;
}
(u as any).checked = !(u as any).checked;
this.selectedChanged(u);
}
filterSelected(showSelected: boolean) {
this.showSelected = showSelected;
selectedChanged(u: OrganizationUserUserDetailsResponse) {
if ((u as any).checked) {
this.selectedCount++;
} else {
if (this.entity === "collection") {
(u as any).readOnly = false;
(u as any).hidePasswords = false;
}
this.selectedCount--;
}
}
async submit() {
try {
if (this.entity === 'group') {
const selections = this.users.filter(u => (u as any).checked).map(u => u.id);
this.formPromise = this.apiService.putGroupUsers(this.organizationId, this.entityId, selections);
} else {
const selections = this.users.filter(u => (u as any).checked && !u.accessAll)
.map(u => new SelectionReadOnlyRequest(u.id, !!(u as any).readOnly, !!(u as any).hidePasswords));
this.formPromise = this.apiService.putCollectionUsers(this.organizationId, this.entityId, selections);
}
await this.formPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('updatedUsers'));
this.onEditedUsers.emit();
} catch (e) {
this.logService.error(e);
}
filterSelected(showSelected: boolean) {
this.showSelected = showSelected;
}
async submit() {
try {
if (this.entity === "group") {
const selections = this.users.filter((u) => (u as any).checked).map((u) => u.id);
this.formPromise = this.apiService.putGroupUsers(
this.organizationId,
this.entityId,
selections
);
} else {
const selections = this.users
.filter((u) => (u as any).checked && !u.accessAll)
.map(
(u) =>
new SelectionReadOnlyRequest(u.id, !!(u as any).readOnly, !!(u as any).hidePasswords)
);
this.formPromise = this.apiService.putCollectionUsers(
this.organizationId,
this.entityId,
selections
);
}
await this.formPromise;
this.platformUtilsService.showToast("success", null, this.i18nService.t("updatedUsers"));
this.onEditedUsers.emit();
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -1,68 +1,103 @@
<div class="page-header d-flex">
<h1>{{'eventLogs' | i18n}}</h1>
<div class="ml-auto d-flex">
<div class="form-inline">
<label class="sr-only" for="start">{{'startDate' | i18n}}</label>
<input type="datetime-local" class="form-control form-control-sm" id="start"
placeholder="{{'startDate' | i18n}}" [(ngModel)]="start" placeholder="YYYY-MM-DDTHH:MM"
(change)="dirtyDates = true">
<span class="mx-2">-</span>
<label class="sr-only" for="end">{{'endDate' | i18n}}</label>
<input type="datetime-local" class="form-control form-control-sm" id="end"
placeholder="{{'endDate' | i18n}}" [(ngModel)]="end" placeholder="YYYY-MM-DDTHH:MM"
(change)="dirtyDates = true">
</div>
<form #refreshForm [appApiAction]="refreshPromise" class="d-inline">
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="loadEvents(true)"
[disabled]="loaded && refreshForm.loading">
<i class="fa fa-refresh fa-fw" aria-hidden="true" [ngClass]="{'fa-spin': loaded && refreshForm.loading}"></i>
{{'refresh' | i18n}}
</button>
</form>
<form #exportForm [appApiAction]="exportPromise" class="d-inline">
<button type="button" class="btn btn-sm btn-outline-primary btn-submit manual ml-3"
[ngClass]="{loading:exportForm.loading}" (click)="exportEvents()"
[disabled]="loaded && exportForm.loading || dirtyDates">
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
<span>{{'export' | i18n}}</span>
</button>
</form>
<h1>{{ "eventLogs" | i18n }}</h1>
<div class="ml-auto d-flex">
<div class="form-inline">
<label class="sr-only" for="start">{{ "startDate" | i18n }}</label>
<input
type="datetime-local"
class="form-control form-control-sm"
id="start"
placeholder="{{ 'startDate' | i18n }}"
[(ngModel)]="start"
placeholder="YYYY-MM-DDTHH:MM"
(change)="dirtyDates = true"
/>
<span class="mx-2">-</span>
<label class="sr-only" for="end">{{ "endDate" | i18n }}</label>
<input
type="datetime-local"
class="form-control form-control-sm"
id="end"
placeholder="{{ 'endDate' | i18n }}"
[(ngModel)]="end"
placeholder="YYYY-MM-DDTHH:MM"
(change)="dirtyDates = true"
/>
</div>
<form #refreshForm [appApiAction]="refreshPromise" class="d-inline">
<button
type="button"
class="btn btn-sm btn-outline-primary ml-3"
(click)="loadEvents(true)"
[disabled]="loaded && refreshForm.loading"
>
<i
class="fa fa-refresh fa-fw"
aria-hidden="true"
[ngClass]="{ 'fa-spin': loaded && refreshForm.loading }"
></i>
{{ "refresh" | i18n }}
</button>
</form>
<form #exportForm [appApiAction]="exportPromise" class="d-inline">
<button
type="button"
class="btn btn-sm btn-outline-primary btn-submit manual ml-3"
[ngClass]="{ loading: exportForm.loading }"
(click)="exportEvents()"
[disabled]="(loaded && exportForm.loading) || dirtyDates"
>
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
<span>{{ "export" | i18n }}</span>
</button>
</form>
</div>
</div>
<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>
<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">
<thead>
<tr>
<th class="border-top-0" width="210">{{'timestamp' | i18n}}</th>
<th class="border-top-0" width="40">
<span class="sr-only">{{'device' | i18n}}</span>
</th>
<th class="border-top-0" width="150">{{'user' | i18n}}</th>
<th class="border-top-0">{{'event' | i18n}}</th>
</tr>
</thead>
<tbody>
<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}}" aria-hidden="true"></i>
<span class="sr-only">{{e.appName}}, {{e.ip}}</span>
</td>
<td>
<span title="{{e.userEmail}}">{{e.userName}}</span>
</td>
<td [innerHTML]="e.message"></td>
</tr>
</tbody>
</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}}" aria-hidden="true"></i>
<span>{{'loadMore' | i18n}}</span>
</button>
<p *ngIf="!events || !events.length">{{ "noEventsInList" | i18n }}</p>
<table class="table table-hover" *ngIf="events && events.length">
<thead>
<tr>
<th class="border-top-0" width="210">{{ "timestamp" | i18n }}</th>
<th class="border-top-0" width="40">
<span class="sr-only">{{ "device" | i18n }}</span>
</th>
<th class="border-top-0" width="150">{{ "user" | i18n }}</th>
<th class="border-top-0">{{ "event" | i18n }}</th>
</tr>
</thead>
<tbody>
<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 }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ e.appName }}, {{ e.ip }}</span>
</td>
<td>
<span title="{{ e.userEmail }}">{{ e.userName }}</span>
</td>
<td [innerHTML]="e.message"></td>
</tr>
</tbody>
</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 }}" aria-hidden="true"></i>
<span>{{ "loadMore" | i18n }}</span>
</button>
</ng-container>

View File

@@ -1,117 +1,121 @@
import {
Component,
OnInit,
} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { UserNamePipe } from 'jslib-angular/pipes/user-name.pipe';
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { ExportService } from 'jslib-common/abstractions/export.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ProviderService } from 'jslib-common/abstractions/provider.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { ExportService } from "jslib-common/abstractions/export.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ProviderService } from "jslib-common/abstractions/provider.service";
import { Organization } from 'jslib-common/models/domain/organization';
import { EventResponse } from 'jslib-common/models/response/eventResponse';
import { Organization } from "jslib-common/models/domain/organization";
import { EventResponse } from "jslib-common/models/response/eventResponse";
import { EventService } from '../../services/event.service';
import { EventService } from "../../services/event.service";
import { BaseEventsComponent } from '../../common/base.events.component';
import { BaseEventsComponent } from "../../common/base.events.component";
@Component({
selector: 'app-org-events',
templateUrl: 'events.component.html',
selector: "app-org-events",
templateUrl: "events.component.html",
})
export class EventsComponent extends BaseEventsComponent implements OnInit {
exportFileName: string = 'org-events';
organizationId: string;
organization: Organization;
exportFileName: string = "org-events";
organizationId: string;
organization: Organization;
private orgUsersUserIdMap = new Map<string, any>();
private orgUsersUserIdMap = new Map<string, any>();
constructor(
private apiService: ApiService,
private route: ActivatedRoute,
eventService: EventService,
i18nService: I18nService,
exportService: ExportService,
platformUtilsService: PlatformUtilsService,
private router: Router,
logService: LogService,
private userNamePipe: UserNamePipe,
private organizationService: OrganizationService,
private providerService: ProviderService,
) {
super(
eventService,
i18nService,
exportService,
platformUtilsService,
logService
);
}
constructor(
private apiService: ApiService,
private route: ActivatedRoute,
eventService: EventService,
i18nService: I18nService,
exportService: ExportService,
platformUtilsService: PlatformUtilsService,
private router: Router,
logService: LogService,
private userNamePipe: UserNamePipe,
private organizationService: OrganizationService,
private providerService: ProviderService
) {
super(eventService, i18nService, exportService, platformUtilsService, logService);
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
this.organization = await this.organizationService.get(this.organizationId);
if (this.organization == null || !this.organization.useEvents) {
this.router.navigate(['/organizations', this.organizationId]);
return;
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
this.organization = await this.organizationService.get(this.organizationId);
if (this.organization == null || !this.organization.useEvents) {
this.router.navigate(["/organizations", this.organizationId]);
return;
}
await this.load();
});
}
await this.load();
});
}
async load() {
const response = await this.apiService.getOrganizationUsers(this.organizationId);
response.data.forEach(u => {
async load() {
const response = await this.apiService.getOrganizationUsers(this.organizationId);
response.data.forEach((u) => {
const name = this.userNamePipe.transform(u);
this.orgUsersUserIdMap.set(u.userId, { name: name, email: u.email });
});
if (this.organization.providerId != null) {
try {
const provider = await this.providerService.get(this.organization.providerId);
if (
provider != null &&
(await this.providerService.get(this.organization.providerId)).canManageUsers
) {
const providerUsersResponse = await this.apiService.getProviderUsers(
this.organization.providerId
);
providerUsersResponse.data.forEach((u) => {
const name = this.userNamePipe.transform(u);
this.orgUsersUserIdMap.set(u.userId, { name: name, email: u.email });
});
if (this.organization.providerId != null) {
try {
const provider = await this.providerService.get(this.organization.providerId);
if (provider != null && (await this.providerService.get(this.organization.providerId)).canManageUsers) {
const providerUsersResponse = await this.apiService.getProviderUsers(this.organization.providerId);
providerUsersResponse.data.forEach(u => {
const name = this.userNamePipe.transform(u);
this.orgUsersUserIdMap.set(u.userId, { name: `${name} (${this.organization.providerName})`, email: u.email });
});
}
} catch (e) {
this.logService.warning(e);
}
this.orgUsersUserIdMap.set(u.userId, {
name: `${name} (${this.organization.providerName})`,
email: u.email,
});
});
}
await this.loadEvents(true);
this.loaded = true;
} catch (e) {
this.logService.warning(e);
}
}
protected requestEvents(startDate: string, endDate: string, continuationToken: string) {
return this.apiService.getEventsOrganization(this.organizationId, startDate, endDate, continuationToken);
await this.loadEvents(true);
this.loaded = true;
}
protected requestEvents(startDate: string, endDate: string, continuationToken: string) {
return this.apiService.getEventsOrganization(
this.organizationId,
startDate,
endDate,
continuationToken
);
}
protected getUserName(r: EventResponse, userId: string) {
if (userId == null) {
return null;
}
protected getUserName(r: EventResponse, userId: string) {
if (userId == null) {
return null;
}
if (this.orgUsersUserIdMap.has(userId)) {
return this.orgUsersUserIdMap.get(userId);
}
if (r.providerId != null && r.providerId === this.organization.providerId) {
return {
'name': this.organization.providerName,
};
}
return null;
if (this.orgUsersUserIdMap.has(userId)) {
return this.orgUsersUserIdMap.get(userId);
}
if (r.providerId != null && r.providerId === this.organization.providerId) {
return {
name: this.organization.providerName,
};
}
return null;
}
}

View File

@@ -1,111 +1,186 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="groupAddEditTitle">
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="modal-header">
<h2 class="modal-title" id="groupAddEditTitle">{{title}}</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</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">
<div class="form-group">
<label for="name">{{'name' | i18n}}</label>
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="name" required>
</div>
<div class="form-group">
<label for="externalId">{{'externalId' | i18n}}</label>
<input id="externalId" class="form-control" type="text" name="ExternalId" [(ngModel)]="externalId">
<small class="form-text text-muted">{{'externalIdDesc' | i18n}}</small>
</div>
<h3 class="mt-4 d-flex">
<div class="mb-2">
{{'accessControl' | i18n}}
<a target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}"
href="https://bitwarden.com/help/article/user-types-access-control/#access-control">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</div>
<div class="ml-auto" *ngIf="access === 'selected' && collections && collections.length">
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
{{'selectAll' | i18n}}
</button>
<button type="button" (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
{{'unselectAll' | i18n}}
</button>
</div>
</h3>
<div class="form-group" [ngClass]="{'mb-0': access !== 'selected'}">
<div class="form-check">
<input class="form-check-input" type="radio" name="access" id="accessAll" value="all"
[(ngModel)]="access">
<label class="form-check-label" for="accessAll">
{{'groupAccessAllItems' | i18n}}
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="access" id="accessSelected" value="selected"
[(ngModel)]="access">
<label class="form-check-label" for="accessSelected">
{{'groupAccessSelectedCollections' | i18n}}
</label>
</div>
</div>
<ng-container *ngIf="access === 'selected'">
<div *ngIf="!collections || !collections.length">
{{'noCollectionsInList' | i18n}}
</div>
<table class="table table-hover table-list mb-0" *ngIf="collections && collections.length">
<thead>
<tr>
<th>&nbsp;</th>
<th>{{'name' | i18n}}</th>
<th width="100" class="text-center">{{'hidePasswords' | i18n}}</th>
<th width="100" class="text-center">{{'readOnly' | i18n}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let c of collections; let i = index">
<td class="table-list-checkbox" (click)="check(c)">
<input type="checkbox" [(ngModel)]="c.checked" name="Collection[{{i}}].Checked"
appStopProp>
</td>
<td (click)="check(c)">
{{c.name}}
</td>
<td class="text-center">
<input type="checkbox" [(ngModel)]="c.hidePasswords"
name="Collection[{{i}}].HidePasswords" [disabled]="!c.checked">
</td>
<td class="text-center">
<input type="checkbox" [(ngModel)]="c.readOnly" name="Collection[{{i}}].ReadOnly"
[disabled]="!c.checked">
</td>
</tr>
</tbody>
</table>
</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 class="ml-auto">
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger"
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
[appApiAction]="deletePromise">
<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>
</div>
</form>
</div>
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<form
class="modal-content"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
>
<div class="modal-header">
<h2 class="modal-title" id="groupAddEditTitle">{{ title }}</h2>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</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">
<div class="form-group">
<label for="name">{{ "name" | i18n }}</label>
<input
id="name"
class="form-control"
type="text"
name="Name"
[(ngModel)]="name"
required
/>
</div>
<div class="form-group">
<label for="externalId">{{ "externalId" | i18n }}</label>
<input
id="externalId"
class="form-control"
type="text"
name="ExternalId"
[(ngModel)]="externalId"
/>
<small class="form-text text-muted">{{ "externalIdDesc" | i18n }}</small>
</div>
<h3 class="mt-4 d-flex">
<div class="mb-2">
{{ "accessControl" | i18n }}
<a
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
href="https://bitwarden.com/help/article/user-types-access-control/#access-control"
>
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</div>
<div class="ml-auto" *ngIf="access === 'selected' && collections && collections.length">
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
{{ "selectAll" | i18n }}
</button>
<button type="button" (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
{{ "unselectAll" | i18n }}
</button>
</div>
</h3>
<div class="form-group" [ngClass]="{ 'mb-0': access !== 'selected' }">
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="access"
id="accessAll"
value="all"
[(ngModel)]="access"
/>
<label class="form-check-label" for="accessAll">
{{ "groupAccessAllItems" | i18n }}
</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="access"
id="accessSelected"
value="selected"
[(ngModel)]="access"
/>
<label class="form-check-label" for="accessSelected">
{{ "groupAccessSelectedCollections" | i18n }}
</label>
</div>
</div>
<ng-container *ngIf="access === 'selected'">
<div *ngIf="!collections || !collections.length">
{{ "noCollectionsInList" | i18n }}
</div>
<table
class="table table-hover table-list mb-0"
*ngIf="collections && collections.length"
>
<thead>
<tr>
<th>&nbsp;</th>
<th>{{ "name" | i18n }}</th>
<th width="100" class="text-center">{{ "hidePasswords" | i18n }}</th>
<th width="100" class="text-center">{{ "readOnly" | i18n }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let c of collections; let i = index">
<td class="table-list-checkbox" (click)="check(c)">
<input
type="checkbox"
[(ngModel)]="c.checked"
name="Collection[{{ i }}].Checked"
appStopProp
/>
</td>
<td (click)="check(c)">
{{ c.name }}
</td>
<td class="text-center">
<input
type="checkbox"
[(ngModel)]="c.hidePasswords"
name="Collection[{{ i }}].HidePasswords"
[disabled]="!c.checked"
/>
</td>
<td class="text-center">
<input
type="checkbox"
[(ngModel)]="c.readOnly"
name="Collection[{{ i }}].ReadOnly"
[disabled]="!c.checked"
/>
</td>
</tr>
</tbody>
</table>
</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 class="ml-auto">
<button
#deleteBtn
type="button"
(click)="delete()"
class="btn btn-outline-danger"
appA11yTitle="{{ 'delete' | i18n }}"
*ngIf="editMode"
[disabled]="deleteBtn.loading"
[appApiAction]="deletePromise"
>
<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>
</div>
</form>
</div>
</div>

View File

@@ -1,142 +1,153 @@
import {
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { CollectionData } from 'jslib-common/models/data/collectionData';
import { Collection } from 'jslib-common/models/domain/collection';
import { GroupRequest } from 'jslib-common/models/request/groupRequest';
import { SelectionReadOnlyRequest } from 'jslib-common/models/request/selectionReadOnlyRequest';
import { CollectionDetailsResponse } from 'jslib-common/models/response/collectionResponse';
import { CollectionView } from 'jslib-common/models/view/collectionView';
import { CollectionData } from "jslib-common/models/data/collectionData";
import { Collection } from "jslib-common/models/domain/collection";
import { GroupRequest } from "jslib-common/models/request/groupRequest";
import { SelectionReadOnlyRequest } from "jslib-common/models/request/selectionReadOnlyRequest";
import { CollectionDetailsResponse } from "jslib-common/models/response/collectionResponse";
import { CollectionView } from "jslib-common/models/view/collectionView";
@Component({
selector: 'app-group-add-edit',
templateUrl: 'group-add-edit.component.html',
selector: "app-group-add-edit",
templateUrl: "group-add-edit.component.html",
})
export class GroupAddEditComponent implements OnInit {
@Input() groupId: string;
@Input() organizationId: string;
@Output() onSavedGroup = new EventEmitter();
@Output() onDeletedGroup = new EventEmitter();
@Input() groupId: string;
@Input() organizationId: string;
@Output() onSavedGroup = new EventEmitter();
@Output() onDeletedGroup = new EventEmitter();
loading = true;
editMode: boolean = false;
title: string;
name: string;
externalId: string;
access: 'all' | 'selected' = 'selected';
collections: CollectionView[] = [];
formPromise: Promise<any>;
deletePromise: Promise<any>;
loading = true;
editMode: boolean = false;
title: string;
name: string;
externalId: string;
access: "all" | "selected" = "selected";
collections: CollectionView[] = [];
formPromise: Promise<any>;
deletePromise: Promise<any>;
constructor(private apiService: ApiService, private i18nService: I18nService,
private collectionService: CollectionService,
private platformUtilsService: PlatformUtilsService, private logService: LogService) { }
constructor(
private apiService: ApiService,
private i18nService: I18nService,
private collectionService: CollectionService,
private platformUtilsService: PlatformUtilsService,
private logService: LogService
) {}
async ngOnInit() {
this.editMode = this.loading = this.groupId != null;
await this.loadCollections();
async ngOnInit() {
this.editMode = this.loading = this.groupId != null;
await this.loadCollections();
if (this.editMode) {
this.editMode = true;
this.title = this.i18nService.t('editGroup');
try {
const group = await this.apiService.getGroupDetails(this.organizationId, this.groupId);
this.access = group.accessAll ? 'all' : 'selected';
this.name = group.name;
this.externalId = group.externalId;
if (group.collections != null && this.collections != null) {
group.collections.forEach(s => {
const collection = this.collections.filter(c => c.id === s.id);
if (collection != null && collection.length > 0) {
(collection[0] as any).checked = true;
collection[0].readOnly = s.readOnly;
collection[0].hidePasswords = s.hidePasswords;
}
});
}
} catch (e) {
this.logService.error(e);
if (this.editMode) {
this.editMode = true;
this.title = this.i18nService.t("editGroup");
try {
const group = await this.apiService.getGroupDetails(this.organizationId, this.groupId);
this.access = group.accessAll ? "all" : "selected";
this.name = group.name;
this.externalId = group.externalId;
if (group.collections != null && this.collections != null) {
group.collections.forEach((s) => {
const collection = this.collections.filter((c) => c.id === s.id);
if (collection != null && collection.length > 0) {
(collection[0] as any).checked = true;
collection[0].readOnly = s.readOnly;
collection[0].hidePasswords = s.hidePasswords;
}
} else {
this.title = this.i18nService.t('addGroup');
});
}
this.loading = false;
} catch (e) {
this.logService.error(e);
}
} else {
this.title = this.i18nService.t("addGroup");
}
async loadCollections() {
const response = await this.apiService.getCollections(this.organizationId);
const collections = response.data.map(r =>
new Collection(new CollectionData(r as CollectionDetailsResponse)));
this.collections = await this.collectionService.decryptMany(collections);
this.loading = false;
}
async loadCollections() {
const response = await this.apiService.getCollections(this.organizationId);
const collections = response.data.map(
(r) => new Collection(new CollectionData(r as CollectionDetailsResponse))
);
this.collections = await this.collectionService.decryptMany(collections);
}
check(c: CollectionView, select?: boolean) {
(c as any).checked = select == null ? !(c as any).checked : select;
if (!(c as any).checked) {
c.readOnly = false;
}
}
selectAll(select: boolean) {
this.collections.forEach((c) => this.check(c, select));
}
async submit() {
const request = new GroupRequest();
request.name = this.name;
request.externalId = this.externalId;
request.accessAll = this.access === "all";
if (!request.accessAll) {
request.collections = this.collections
.filter((c) => (c as any).checked)
.map((c) => new SelectionReadOnlyRequest(c.id, !!c.readOnly, !!c.hidePasswords));
}
check(c: CollectionView, select?: boolean) {
(c as any).checked = select == null ? !(c as any).checked : select;
if (!(c as any).checked) {
c.readOnly = false;
}
try {
if (this.editMode) {
this.formPromise = this.apiService.putGroup(this.organizationId, this.groupId, request);
} else {
this.formPromise = this.apiService.postGroup(this.organizationId, request);
}
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t(this.editMode ? "editedGroupId" : "createdGroupId", this.name)
);
this.onSavedGroup.emit();
} catch (e) {
this.logService.error(e);
}
}
async delete() {
if (!this.editMode) {
return;
}
selectAll(select: boolean) {
this.collections.forEach(c => this.check(c, select));
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("deleteGroupConfirmation"),
this.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
async submit() {
const request = new GroupRequest();
request.name = this.name;
request.externalId = this.externalId;
request.accessAll = this.access === 'all';
if (!request.accessAll) {
request.collections = this.collections.filter(c => (c as any).checked)
.map(c => new SelectionReadOnlyRequest(c.id, !!c.readOnly, !!c.hidePasswords));
}
try {
if (this.editMode) {
this.formPromise = this.apiService.putGroup(this.organizationId, this.groupId, request);
} else {
this.formPromise = this.apiService.postGroup(this.organizationId, request);
}
await this.formPromise;
this.platformUtilsService.showToast('success', null,
this.i18nService.t(this.editMode ? 'editedGroupId' : 'createdGroupId', this.name));
this.onSavedGroup.emit();
} catch (e) {
this.logService.error(e);
}
}
async delete() {
if (!this.editMode) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('deleteGroupConfirmation'), this.name,
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
try {
this.deletePromise = this.apiService.deleteGroup(this.organizationId, this.groupId);
await this.deletePromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedGroupId', this.name));
this.onDeletedGroup.emit();
} catch (e) {
this.logService.error(e);
}
try {
this.deletePromise = this.apiService.deleteGroup(this.organizationId, this.groupId);
await this.deletePromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("deletedGroupId", this.name)
);
this.onDeletedGroup.emit();
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -1,51 +1,73 @@
<div class="page-header d-flex">
<h1>{{'groups' | i18n}}</h1>
<div class="ml-auto d-flex">
<div>
<label class="sr-only" for="search">{{'search' | i18n}}</label>
<input type="search" class="form-control form-control-sm" id="search" placeholder="{{'search' | i18n}}"
[(ngModel)]="searchText">
</div>
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="add()">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
{{'newGroup' | i18n}}
</button>
<h1>{{ "groups" | i18n }}</h1>
<div class="ml-auto d-flex">
<div>
<label class="sr-only" for="search">{{ "search" | i18n }}</label>
<input
type="search"
class="form-control form-control-sm"
id="search"
placeholder="{{ 'search' | i18n }}"
[(ngModel)]="searchText"
/>
</div>
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="add()">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
{{ "newGroup" | i18n }}
</button>
</div>
</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>
<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 && (isPaging() ? pagedGroups : groups | search:searchText:'name':'id') as searchedGroups">
<p *ngIf="!searchedGroups.length">{{'noGroupsInList' | i18n}}</p>
<table class="table table-hover table-list" *ngIf="searchedGroups.length" infiniteScroll
[infiniteScrollDistance]="1" [infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()">
<tbody>
<tr *ngFor="let g of searchedGroups">
<td>
<a href="#" appStopClick (click)="edit(g)">{{g.name}}</a>
</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" 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" 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" aria-hidden="true"></i>
{{'delete' | i18n}}
</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<ng-container
*ngIf="
!loading &&
(isPaging() ? pagedGroups : (groups | search: searchText:'name':'id')) as searchedGroups
"
>
<p *ngIf="!searchedGroups.length">{{ "noGroupsInList" | i18n }}</p>
<table
class="table table-hover table-list"
*ngIf="searchedGroups.length"
infiniteScroll
[infiniteScrollDistance]="1"
[infiniteScrollDisabled]="!isPaging()"
(scrolled)="loadMore()"
>
<tbody>
<tr *ngFor="let g of searchedGroups">
<td>
<a href="#" appStopClick (click)="edit(g)">{{ g.name }}</a>
</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"
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" 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" aria-hidden="true"></i>
{{ "delete" | i18n }}
</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</ng-container>
<ng-template #addEdit></ng-template>
<ng-template #usersTemplate></ng-template>

View File

@@ -1,174 +1,185 @@
import {
Component,
OnInit,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from 'rxjs/operators';
import { first } from "rxjs/operators";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { ModalService } from 'jslib-angular/services/modal.service';
import { ModalService } from "jslib-angular/services/modal.service";
import { GroupResponse } from 'jslib-common/models/response/groupResponse';
import { GroupResponse } from "jslib-common/models/response/groupResponse";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
import { EntityUsersComponent } from './entity-users.component';
import { GroupAddEditComponent } from './group-add-edit.component';
import { EntityUsersComponent } from "./entity-users.component";
import { GroupAddEditComponent } from "./group-add-edit.component";
@Component({
selector: 'app-org-groups',
templateUrl: 'groups.component.html',
selector: "app-org-groups",
templateUrl: "groups.component.html",
})
export class GroupsComponent implements OnInit {
@ViewChild('addEdit', { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
@ViewChild('usersTemplate', { read: ViewContainerRef, static: true }) usersModalRef: ViewContainerRef;
@ViewChild("addEdit", { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
@ViewChild("usersTemplate", { read: ViewContainerRef, static: true })
usersModalRef: ViewContainerRef;
loading = true;
organizationId: string;
groups: GroupResponse[];
pagedGroups: GroupResponse[];
searchText: string;
loading = true;
organizationId: string;
groups: GroupResponse[];
pagedGroups: GroupResponse[];
searchText: string;
protected didScroll = false;
protected pageSize = 100;
protected didScroll = false;
protected pageSize = 100;
private pagedGroupsCount = 0;
private pagedGroupsCount = 0;
constructor(
private apiService: ApiService,
private route: ActivatedRoute,
private i18nService: I18nService,
private modalService: ModalService,
private platformUtilsService: PlatformUtilsService,
private router: Router,
private searchService: SearchService,
private logService: LogService,
private organizationService: OrganizationService,
) { }
constructor(
private apiService: ApiService,
private route: ActivatedRoute,
private i18nService: I18nService,
private modalService: ModalService,
private platformUtilsService: PlatformUtilsService,
private router: Router,
private searchService: SearchService,
private logService: LogService,
private organizationService: OrganizationService
) {}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
const organization = await this.organizationService.get(this.organizationId);
if (organization == null || !organization.useGroups) {
this.router.navigate(['/organizations', this.organizationId]);
return;
}
await this.load();
this.route.queryParams.pipe(first()).subscribe(async qParams => {
this.searchText = qParams.search;
});
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
const organization = await this.organizationService.get(this.organizationId);
if (organization == null || !organization.useGroups) {
this.router.navigate(["/organizations", this.organizationId]);
return;
}
await this.load();
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
this.searchText = qParams.search;
});
});
}
async load() {
const response = await this.apiService.getGroups(this.organizationId);
const groups = response.data != null && response.data.length > 0 ? response.data : [];
groups.sort(Utils.getSortFunction(this.i18nService, "name"));
this.groups = groups;
this.resetPaging();
this.loading = false;
}
loadMore() {
if (!this.groups || this.groups.length <= this.pageSize) {
return;
}
const pagedLength = this.pagedGroups.length;
let pagedSize = this.pageSize;
if (pagedLength === 0 && this.pagedGroupsCount > this.pageSize) {
pagedSize = this.pagedGroupsCount;
}
if (this.groups.length > pagedLength) {
this.pagedGroups = this.pagedGroups.concat(
this.groups.slice(pagedLength, pagedLength + pagedSize)
);
}
this.pagedGroupsCount = this.pagedGroups.length;
this.didScroll = this.pagedGroups.length > this.pageSize;
}
async edit(group: GroupResponse) {
const [modal] = await this.modalService.openViewRef(
GroupAddEditComponent,
this.addEditModalRef,
(comp) => {
comp.organizationId = this.organizationId;
comp.groupId = group != null ? group.id : null;
comp.onSavedGroup.subscribe(() => {
modal.close();
this.load();
});
}
async load() {
const response = await this.apiService.getGroups(this.organizationId);
const groups = response.data != null && response.data.length > 0 ? response.data : [];
groups.sort(Utils.getSortFunction(this.i18nService, 'name'));
this.groups = groups;
this.resetPaging();
this.loading = false;
}
loadMore() {
if (!this.groups || this.groups.length <= this.pageSize) {
return;
}
const pagedLength = this.pagedGroups.length;
let pagedSize = this.pageSize;
if (pagedLength === 0 && this.pagedGroupsCount > this.pageSize) {
pagedSize = this.pagedGroupsCount;
}
if (this.groups.length > pagedLength) {
this.pagedGroups = this.pagedGroups.concat(this.groups.slice(pagedLength, pagedLength + pagedSize));
}
this.pagedGroupsCount = this.pagedGroups.length;
this.didScroll = this.pagedGroups.length > this.pageSize;
}
async edit(group: GroupResponse) {
const [modal] = await this.modalService.openViewRef(GroupAddEditComponent, this.addEditModalRef, comp => {
comp.organizationId = this.organizationId;
comp.groupId = group != null ? group.id : null;
comp.onSavedGroup.subscribe(() => {
modal.close();
this.load();
});
comp.onDeletedGroup.subscribe(() => {
modal.close();
this.removeGroup(group);
});
comp.onDeletedGroup.subscribe(() => {
modal.close();
this.removeGroup(group);
});
}
);
}
add() {
this.edit(null);
}
async delete(group: GroupResponse) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("deleteGroupConfirmation"),
group.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
add() {
this.edit(null);
try {
await this.apiService.deleteGroup(this.organizationId, group.id);
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("deletedGroupId", group.name)
);
this.removeGroup(group);
} catch (e) {
this.logService.error(e);
}
}
async delete(group: GroupResponse) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('deleteGroupConfirmation'), group.name,
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
async users(group: GroupResponse) {
const [modal] = await this.modalService.openViewRef(
EntityUsersComponent,
this.usersModalRef,
(comp) => {
comp.organizationId = this.organizationId;
comp.entity = "group";
comp.entityId = group.id;
comp.entityName = group.name;
try {
await this.apiService.deleteGroup(this.organizationId, group.id);
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedGroupId', group.name));
this.removeGroup(group);
} catch (e) {
this.logService.error(e);
}
}
async users(group: GroupResponse) {
const [modal] = await this.modalService.openViewRef(EntityUsersComponent, this.usersModalRef, comp => {
comp.organizationId = this.organizationId;
comp.entity = 'group';
comp.entityId = group.id;
comp.entityName = group.name;
comp.onEditedUsers.subscribe(() => {
modal.close();
});
comp.onEditedUsers.subscribe(() => {
modal.close();
});
}
}
);
}
async resetPaging() {
this.pagedGroups = [];
this.loadMore();
}
async resetPaging() {
this.pagedGroups = [];
this.loadMore();
}
isSearching() {
return this.searchService.isSearchable(this.searchText);
}
isSearching() {
return this.searchService.isSearchable(this.searchText);
}
isPaging() {
const searching = this.isSearching();
if (searching && this.didScroll) {
this.resetPaging();
}
return !searching && this.groups && this.groups.length > this.pageSize;
isPaging() {
const searching = this.isSearching();
if (searching && this.didScroll) {
this.resetPaging();
}
return !searching && this.groups && this.groups.length > this.pageSize;
}
private removeGroup(group: GroupResponse) {
const index = this.groups.indexOf(group);
if (index > -1) {
this.groups.splice(index, 1);
this.resetPaging();
}
private removeGroup(group: GroupResponse) {
const index = this.groups.indexOf(group);
if (index > -1) {
this.groups.splice(index, 1);
this.resetPaging();
}
}
}

View File

@@ -1,38 +1,62 @@
<div class="container page-content">
<div class="row">
<div class="col-3">
<div class="card" *ngIf="organization">
<div class="card-header">{{'manage' | i18n}}</div>
<div class="list-group list-group-flush">
<a routerLink="people" class="list-group-item" routerLinkActive="active"
*ngIf="organization.canManageUsers">
{{'people' | i18n}}
</a>
<a routerLink="collections" class="list-group-item" routerLinkActive="active"
*ngIf="organization.canViewAllCollections || organization.canViewAssignedCollections">
{{'collections' | i18n}}
</a>
<a routerLink="groups" class="list-group-item" routerLinkActive="active"
*ngIf="organization.canManageGroups && accessGroups">
{{'groups' | i18n}}
</a>
<a routerLink="policies" class="list-group-item" routerLinkActive="active"
*ngIf="organization.canManagePolicies && accessPolicies">
{{'policies' | i18n}}
</a>
<a routerLink="sso" class="list-group-item" routerLinkActive="active"
*ngIf="organization.canManageSso && accessSso">
{{'singleSignOn' | i18n}}
</a>
<a routerLink="events" class="list-group-item" routerLinkActive="active"
*ngIf="organization.canAccessEventLogs && accessEvents">
{{'eventLogs' | i18n}}
</a>
</div>
</div>
</div>
<div class="col-9">
<router-outlet></router-outlet>
<div class="row">
<div class="col-3">
<div class="card" *ngIf="organization">
<div class="card-header">{{ "manage" | i18n }}</div>
<div class="list-group list-group-flush">
<a
routerLink="people"
class="list-group-item"
routerLinkActive="active"
*ngIf="organization.canManageUsers"
>
{{ "people" | i18n }}
</a>
<a
routerLink="collections"
class="list-group-item"
routerLinkActive="active"
*ngIf="organization.canViewAllCollections || organization.canViewAssignedCollections"
>
{{ "collections" | i18n }}
</a>
<a
routerLink="groups"
class="list-group-item"
routerLinkActive="active"
*ngIf="organization.canManageGroups && accessGroups"
>
{{ "groups" | i18n }}
</a>
<a
routerLink="policies"
class="list-group-item"
routerLinkActive="active"
*ngIf="organization.canManagePolicies && accessPolicies"
>
{{ "policies" | i18n }}
</a>
<a
routerLink="sso"
class="list-group-item"
routerLinkActive="active"
*ngIf="organization.canManageSso && accessSso"
>
{{ "singleSignOn" | i18n }}
</a>
<a
routerLink="events"
class="list-group-item"
routerLinkActive="active"
*ngIf="organization.canAccessEventLogs && accessEvents"
>
{{ "eventLogs" | i18n }}
</a>
</div>
</div>
</div>
<div class="col-9">
<router-outlet></router-outlet>
</div>
</div>
</div>

View File

@@ -1,33 +1,30 @@
import {
Component,
OnInit,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { Organization } from 'jslib-common/models/domain/organization';
import { Organization } from "jslib-common/models/domain/organization";
@Component({
selector: 'app-org-manage',
templateUrl: 'manage.component.html',
selector: "app-org-manage",
templateUrl: "manage.component.html",
})
export class ManageComponent implements OnInit {
organization: Organization;
accessPolicies: boolean = false;
accessGroups: boolean = false;
accessEvents: boolean = false;
accessSso: boolean = false;
organization: Organization;
accessPolicies: boolean = false;
accessGroups: boolean = false;
accessEvents: boolean = false;
accessSso: boolean = false;
constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {}
constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {}
ngOnInit() {
this.route.parent.params.subscribe(async params => {
this.organization = await this.organizationService.get(params.organizationId);
this.accessPolicies = this.organization.usePolicies;
this.accessSso = this.organization.useSso;
this.accessEvents = this.organization.useEvents;
this.accessGroups = this.organization.useGroups;
});
}
ngOnInit() {
this.route.parent.params.subscribe(async (params) => {
this.organization = await this.organizationService.get(params.organizationId);
this.accessPolicies = this.organization.usePolicies;
this.accessSso = this.organization.useSso;
this.accessEvents = this.organization.useEvents;
this.accessGroups = this.organization.useGroups;
});
}
}

View File

@@ -1,156 +1,240 @@
<div class="page-header d-flex">
<h1>{{'people' | i18n}}</h1>
<div class="ml-auto d-flex">
<div class="btn-group btn-group-sm" role="group">
<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 == userStatusType.Invited}"
(click)="filter(userStatusType.Invited)">
{{'invited' | i18n}}
<span class="badge badge-pill badge-info" *ngIf="invitedCount">{{invitedCount}}</span>
</button>
<button type="button" class="btn btn-outline-secondary"
[ngClass]="{active: status == userStatusType.Accepted}"
(click)="filter(userStatusType.Accepted)">
{{'accepted' | i18n}}
<span class="badge badge-pill badge-warning" *ngIf="acceptedCount">{{acceptedCount}}</span>
</button>
</div>
<div class="ml-3">
<label class="sr-only" for="search">{{'search' | i18n}}</label>
<input type="search" class="form-control form-control-sm" id="search" placeholder="{{'search' | i18n}}"
[(ngModel)]="searchText">
</div>
<div class="dropdown ml-3" appListDropdown>
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" id="bulkActionsButton"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" appA11yTitle="{{'options' | i18n}}">
<i class="fa fa-cog" aria-hidden="true"></i>
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="bulkActionsButton">
<button class="dropdown-item" appStopClick (click)="bulkReinvite()">
<i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i>
{{'reinviteSelected' | i18n}}
</button>
<button class="dropdown-item text-success" appStopClick (click)="bulkConfirm()"
*ngIf="showBulkConfirmUsers">
<i class="fa fa-fw fa-check" aria-hidden="true"></i>
{{'confirmSelected' | i18n}}
</button>
<button class="dropdown-item text-danger" appStopClick (click)="bulkRemove()">
<i class="fa fa-fw fa-remove" aria-hidden="true"></i>
{{'remove' | i18n}}
</button>
<div class="dropdown-divider"></div>
<button class="dropdown-item" appStopClick (click)="selectAll(true)">
<i class="fa fa-fw fa-check-square-o" aria-hidden="true"></i>
{{'selectAll' | i18n}}
</button>
<button class="dropdown-item" appStopClick (click)="selectAll(false)">
<i class="fa fa-fw fa-minus-square-o" aria-hidden="true"></i>
{{'unselectAll' | i18n}}
</button>
</div>
</div>
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="invite()">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
{{'inviteUser' | i18n}}
</button>
<h1>{{ "people" | i18n }}</h1>
<div class="ml-auto d-flex">
<div class="btn-group btn-group-sm" role="group">
<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 == userStatusType.Invited }"
(click)="filter(userStatusType.Invited)"
>
{{ "invited" | i18n }}
<span class="badge badge-pill badge-info" *ngIf="invitedCount">{{ invitedCount }}</span>
</button>
<button
type="button"
class="btn btn-outline-secondary"
[ngClass]="{ active: status == userStatusType.Accepted }"
(click)="filter(userStatusType.Accepted)"
>
{{ "accepted" | i18n }}
<span class="badge badge-pill badge-warning" *ngIf="acceptedCount">{{
acceptedCount
}}</span>
</button>
</div>
<div class="ml-3">
<label class="sr-only" for="search">{{ "search" | i18n }}</label>
<input
type="search"
class="form-control form-control-sm"
id="search"
placeholder="{{ 'search' | i18n }}"
[(ngModel)]="searchText"
/>
</div>
<div class="dropdown ml-3" appListDropdown>
<button
class="btn btn-sm btn-outline-secondary dropdown-toggle"
type="button"
id="bulkActionsButton"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
appA11yTitle="{{ 'options' | i18n }}"
>
<i class="fa fa-cog" aria-hidden="true"></i>
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="bulkActionsButton">
<button class="dropdown-item" appStopClick (click)="bulkReinvite()">
<i class="fa fa-fw fa-envelope-o" aria-hidden="true"></i>
{{ "reinviteSelected" | i18n }}
</button>
<button
class="dropdown-item text-success"
appStopClick
(click)="bulkConfirm()"
*ngIf="showBulkConfirmUsers"
>
<i class="fa fa-fw fa-check" aria-hidden="true"></i>
{{ "confirmSelected" | i18n }}
</button>
<button class="dropdown-item text-danger" appStopClick (click)="bulkRemove()">
<i class="fa fa-fw fa-remove" aria-hidden="true"></i>
{{ "remove" | i18n }}
</button>
<div class="dropdown-divider"></div>
<button class="dropdown-item" appStopClick (click)="selectAll(true)">
<i class="fa fa-fw fa-check-square-o" aria-hidden="true"></i>
{{ "selectAll" | i18n }}
</button>
<button class="dropdown-item" appStopClick (click)="selectAll(false)">
<i class="fa fa-fw fa-minus-square-o" aria-hidden="true"></i>
{{ "unselectAll" | i18n }}
</button>
</div>
</div>
<button type="button" class="btn btn-sm btn-outline-primary ml-3" (click)="invite()">
<i class="fa fa-plus fa-fw" aria-hidden="true"></i>
{{ "inviteUser" | i18n }}
</button>
</div>
</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>
<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 && (isPaging() ? pagedUsers : users | search:searchText:'name':'email':'id') as searchedUsers">
<p *ngIf="!searchedUsers.length">{{'noUsersInList' | i18n}}</p>
<ng-container *ngIf="searchedUsers.length">
<app-callout type="info" title="{{'confirmUsers' | i18n}}" icon="fa-check-circle" *ngIf="showConfirmUsers">
{{'usersNeedConfirmed' | i18n}}
</app-callout>
<table class="table table-hover table-list" infiniteScroll [infiniteScrollDistance]="1"
[infiniteScrollDisabled]="!isPaging()" (scrolled)="loadMore()">
<tbody>
<tr *ngFor="let u of searchedUsers">
<td (click)="checkUser(u)" class="table-list-checkbox">
<input type="checkbox" [(ngModel)]="u.checked" appStopProp>
</td>
<td width="30">
<app-avatar [data]="u | userName" [email]="u.email" size="25" [circle]="true"
[fontSize]="14"></app-avatar>
</td>
<td>
<a href="#" appStopClick (click)="edit(u)">{{u.email}}</a>
<span class="badge badge-secondary"
*ngIf="u.status === userStatusType.Invited">{{'invited' | i18n}}</span>
<span class="badge badge-warning"
*ngIf="u.status === userStatusType.Accepted">{{'accepted' | i18n}}</span>
<small class="text-muted d-block" *ngIf="u.name">{{u.name}}</small>
</td>
<td>
<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>
<ng-container *ngIf="showEnrolledStatus(u)">
<i class="fa fa-key" title="{{'enrolledPasswordReset' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'enrolledPasswordReset' | i18n}}</span>
</ng-container>
</td>
<td>
<span *ngIf="u.type === userType.Owner">{{'owner' | i18n}}</span>
<span *ngIf="u.type === userType.Admin">{{'admin' | i18n}}</span>
<span *ngIf="u.type === userType.Manager">{{'manager' | i18n}}</span>
<span *ngIf="u.type === userType.User">{{'user' | i18n}}</span>
<span *ngIf="u.type === userType.Custom">{{'custom' | i18n}}</span>
</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"
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 === userStatusType.Invited">
<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 === userStatusType.Accepted">
<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" aria-hidden="true"></i>
{{'groups' | i18n}}
</a>
<a class="dropdown-item" href="#" appStopClick (click)="events(u)"
*ngIf="accessEvents && u.status === userStatusType.Confirmed">
<i class="fa fa-fw fa-file-text-o" aria-hidden="true"></i>
{{'eventLogs' | i18n}}
</a>
<a class="dropdown-item" href="#" appStopClick (click)="resetPassword(u)"
*ngIf="allowResetPassword(u)">
<i class="fa fa-fw fa-key" aria-hidden="true"></i>
{{'resetPassword' | i18n}}
</a>
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(u)">
<i class="fa fa-fw fa-remove" aria-hidden="true"></i>
{{'remove' | i18n}}
</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</ng-container>
*ngIf="
!loading &&
(isPaging() ? pagedUsers : (users | search: searchText:'name':'email':'id')) as searchedUsers
"
>
<p *ngIf="!searchedUsers.length">{{ "noUsersInList" | i18n }}</p>
<ng-container *ngIf="searchedUsers.length">
<app-callout
type="info"
title="{{ 'confirmUsers' | i18n }}"
icon="fa-check-circle"
*ngIf="showConfirmUsers"
>
{{ "usersNeedConfirmed" | i18n }}
</app-callout>
<table
class="table table-hover table-list"
infiniteScroll
[infiniteScrollDistance]="1"
[infiniteScrollDisabled]="!isPaging()"
(scrolled)="loadMore()"
>
<tbody>
<tr *ngFor="let u of searchedUsers">
<td (click)="checkUser(u)" class="table-list-checkbox">
<input type="checkbox" [(ngModel)]="u.checked" appStopProp />
</td>
<td width="30">
<app-avatar
[data]="u | userName"
[email]="u.email"
size="25"
[circle]="true"
[fontSize]="14"
></app-avatar>
</td>
<td>
<a href="#" appStopClick (click)="edit(u)">{{ u.email }}</a>
<span class="badge badge-secondary" *ngIf="u.status === userStatusType.Invited">{{
"invited" | i18n
}}</span>
<span class="badge badge-warning" *ngIf="u.status === userStatusType.Accepted">{{
"accepted" | i18n
}}</span>
<small class="text-muted d-block" *ngIf="u.name">{{ u.name }}</small>
</td>
<td>
<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>
<ng-container *ngIf="showEnrolledStatus(u)">
<i
class="fa fa-key"
title="{{ 'enrolledPasswordReset' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "enrolledPasswordReset" | i18n }}</span>
</ng-container>
</td>
<td>
<span *ngIf="u.type === userType.Owner">{{ "owner" | i18n }}</span>
<span *ngIf="u.type === userType.Admin">{{ "admin" | i18n }}</span>
<span *ngIf="u.type === userType.Manager">{{ "manager" | i18n }}</span>
<span *ngIf="u.type === userType.User">{{ "user" | i18n }}</span>
<span *ngIf="u.type === userType.Custom">{{ "custom" | i18n }}</span>
</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"
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 === userStatusType.Invited"
>
<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 === userStatusType.Accepted"
>
<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" aria-hidden="true"></i>
{{ "groups" | i18n }}
</a>
<a
class="dropdown-item"
href="#"
appStopClick
(click)="events(u)"
*ngIf="accessEvents && u.status === userStatusType.Confirmed"
>
<i class="fa fa-fw fa-file-text-o" aria-hidden="true"></i>
{{ "eventLogs" | i18n }}
</a>
<a
class="dropdown-item"
href="#"
appStopClick
(click)="resetPassword(u)"
*ngIf="allowResetPassword(u)"
>
<i class="fa fa-fw fa-key" aria-hidden="true"></i>
{{ "resetPassword" | i18n }}
</a>
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(u)">
<i class="fa fa-fw fa-remove" aria-hidden="true"></i>
{{ "remove" | i18n }}
</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</ng-container>
</ng-container>
<ng-template #addEdit></ng-template>
<ng-template #groupsTemplate></ng-template>

View File

@@ -1,364 +1,426 @@
import {
Component,
OnInit,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { first } from 'rxjs/operators';
import { first } from "rxjs/operators";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ActivatedRoute, Router } from "@angular/router";
import { ValidationService } from 'jslib-angular/services/validation.service';
import { ValidationService } from "jslib-angular/services/validation.service";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { ModalService } from 'jslib-angular/services/modal.service';
import { ModalService } from "jslib-angular/services/modal.service";
import { OrganizationKeysRequest } from 'jslib-common/models/request/organizationKeysRequest';
import { OrganizationUserBulkRequest } from 'jslib-common/models/request/organizationUserBulkRequest';
import { OrganizationUserConfirmRequest } from 'jslib-common/models/request/organizationUserConfirmRequest';
import { OrganizationKeysRequest } from "jslib-common/models/request/organizationKeysRequest";
import { OrganizationUserBulkRequest } from "jslib-common/models/request/organizationUserBulkRequest";
import { OrganizationUserConfirmRequest } from "jslib-common/models/request/organizationUserConfirmRequest";
import { ListResponse } from 'jslib-common/models/response/listResponse';
import { OrganizationUserBulkResponse } from 'jslib-common/models/response/organizationUserBulkResponse';
import { OrganizationUserUserDetailsResponse } from 'jslib-common/models/response/organizationUserResponse';
import { ListResponse } from "jslib-common/models/response/listResponse";
import { OrganizationUserBulkResponse } from "jslib-common/models/response/organizationUserBulkResponse";
import { OrganizationUserUserDetailsResponse } from "jslib-common/models/response/organizationUserResponse";
import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType';
import { OrganizationUserType } from 'jslib-common/enums/organizationUserType';
import { PolicyType } from 'jslib-common/enums/policyType';
import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType";
import { OrganizationUserType } from "jslib-common/enums/organizationUserType";
import { PolicyType } from "jslib-common/enums/policyType";
import { SearchPipe } from 'jslib-angular/pipes/search.pipe';
import { UserNamePipe } from 'jslib-angular/pipes/user-name.pipe';
import { SearchPipe } from "jslib-angular/pipes/search.pipe";
import { UserNamePipe } from "jslib-angular/pipes/user-name.pipe";
import { BasePeopleComponent } from '../../common/base.people.component';
import { BulkConfirmComponent } from './bulk/bulk-confirm.component';
import { BulkRemoveComponent } from './bulk/bulk-remove.component';
import { BulkStatusComponent } from './bulk/bulk-status.component';
import { EntityEventsComponent } from './entity-events.component';
import { ResetPasswordComponent } from './reset-password.component';
import { UserAddEditComponent } from './user-add-edit.component';
import { UserGroupsComponent } from './user-groups.component';
import { BasePeopleComponent } from "../../common/base.people.component";
import { BulkConfirmComponent } from "./bulk/bulk-confirm.component";
import { BulkRemoveComponent } from "./bulk/bulk-remove.component";
import { BulkStatusComponent } from "./bulk/bulk-status.component";
import { EntityEventsComponent } from "./entity-events.component";
import { ResetPasswordComponent } from "./reset-password.component";
import { UserAddEditComponent } from "./user-add-edit.component";
import { UserGroupsComponent } from "./user-groups.component";
@Component({
selector: 'app-org-people',
templateUrl: 'people.component.html',
selector: "app-org-people",
templateUrl: "people.component.html",
})
export class PeopleComponent extends BasePeopleComponent<OrganizationUserUserDetailsResponse> implements OnInit {
@ViewChild('addEdit', { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
@ViewChild('groupsTemplate', { read: ViewContainerRef, static: true }) groupsModalRef: ViewContainerRef;
@ViewChild('eventsTemplate', { read: ViewContainerRef, static: true }) eventsModalRef: ViewContainerRef;
@ViewChild('confirmTemplate', { read: ViewContainerRef, static: true }) confirmModalRef: ViewContainerRef;
@ViewChild('resetPasswordTemplate', { read: ViewContainerRef, static: true }) resetPasswordModalRef: ViewContainerRef;
@ViewChild('bulkStatusTemplate', { read: ViewContainerRef, static: true }) bulkStatusModalRef: ViewContainerRef;
@ViewChild('bulkConfirmTemplate', { read: ViewContainerRef, static: true }) bulkConfirmModalRef: ViewContainerRef;
@ViewChild('bulkRemoveTemplate', { read: ViewContainerRef, static: true }) bulkRemoveModalRef: ViewContainerRef;
export class PeopleComponent
extends BasePeopleComponent<OrganizationUserUserDetailsResponse>
implements OnInit
{
@ViewChild("addEdit", { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
@ViewChild("groupsTemplate", { read: ViewContainerRef, static: true })
groupsModalRef: ViewContainerRef;
@ViewChild("eventsTemplate", { read: ViewContainerRef, static: true })
eventsModalRef: ViewContainerRef;
@ViewChild("confirmTemplate", { read: ViewContainerRef, static: true })
confirmModalRef: ViewContainerRef;
@ViewChild("resetPasswordTemplate", { read: ViewContainerRef, static: true })
resetPasswordModalRef: ViewContainerRef;
@ViewChild("bulkStatusTemplate", { read: ViewContainerRef, static: true })
bulkStatusModalRef: ViewContainerRef;
@ViewChild("bulkConfirmTemplate", { read: ViewContainerRef, static: true })
bulkConfirmModalRef: ViewContainerRef;
@ViewChild("bulkRemoveTemplate", { read: ViewContainerRef, static: true })
bulkRemoveModalRef: ViewContainerRef;
userType = OrganizationUserType;
userStatusType = OrganizationUserStatusType;
userType = OrganizationUserType;
userStatusType = OrganizationUserStatusType;
organizationId: string;
status: OrganizationUserStatusType = null;
accessEvents = false;
accessGroups = false;
canResetPassword = false; // User permission (admin/custom)
orgUseResetPassword = false; // Org plan ability
orgHasKeys = false; // Org public/private keys
orgResetPasswordPolicyEnabled = false;
callingUserType: OrganizationUserType = null;
organizationId: string;
status: OrganizationUserStatusType = null;
accessEvents = false;
accessGroups = false;
canResetPassword = false; // User permission (admin/custom)
orgUseResetPassword = false; // Org plan ability
orgHasKeys = false; // Org public/private keys
orgResetPasswordPolicyEnabled = false;
callingUserType: OrganizationUserType = null;
constructor(
apiService: ApiService,
private route: ActivatedRoute,
i18nService: I18nService,
modalService: ModalService,
platformUtilsService: PlatformUtilsService,
cryptoService: CryptoService,
private router: Router,
searchService: SearchService,
validationService: ValidationService,
private policyService: PolicyService,
logService: LogService,
searchPipe: SearchPipe,
userNamePipe: UserNamePipe,
private syncService: SyncService,
stateService: StateService,
private organizationService: OrganizationService,
) {
super(
apiService,
searchService,
i18nService,
platformUtilsService,
cryptoService,
validationService,
modalService,
logService,
searchPipe,
userNamePipe,
stateService,
);
constructor(
apiService: ApiService,
private route: ActivatedRoute,
i18nService: I18nService,
modalService: ModalService,
platformUtilsService: PlatformUtilsService,
cryptoService: CryptoService,
private router: Router,
searchService: SearchService,
validationService: ValidationService,
private policyService: PolicyService,
logService: LogService,
searchPipe: SearchPipe,
userNamePipe: UserNamePipe,
private syncService: SyncService,
stateService: StateService,
private organizationService: OrganizationService
) {
super(
apiService,
searchService,
i18nService,
platformUtilsService,
cryptoService,
validationService,
modalService,
logService,
searchPipe,
userNamePipe,
stateService
);
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
const organization = await this.organizationService.get(this.organizationId);
if (!organization.canManageUsers) {
this.router.navigate(["../collections"], { relativeTo: this.route });
return;
}
this.accessEvents = organization.useEvents;
this.accessGroups = organization.useGroups;
this.canResetPassword = organization.canManageUsersPassword;
this.orgUseResetPassword = organization.useResetPassword;
this.callingUserType = organization.type;
this.orgHasKeys = organization.hasPublicAndPrivateKeys;
// Backfill pub/priv key if necessary
if (this.canResetPassword && !this.orgHasKeys) {
const orgShareKey = await this.cryptoService.getOrgKey(this.organizationId);
const orgKeys = await this.cryptoService.makeKeyPair(orgShareKey);
const request = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString);
const response = await this.apiService.postOrganizationKeys(this.organizationId, request);
if (response != null) {
this.orgHasKeys = response.publicKey != null && response.privateKey != null;
await this.syncService.fullSync(true); // Replace oganizations with new data
} else {
throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
}
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
const organization = await this.organizationService.get(this.organizationId);
if (!organization.canManageUsers) {
this.router.navigate(['../collections'], { relativeTo: this.route });
return;
}
this.accessEvents = organization.useEvents;
this.accessGroups = organization.useGroups;
this.canResetPassword = organization.canManageUsersPassword;
this.orgUseResetPassword = organization.useResetPassword;
this.callingUserType = organization.type;
this.orgHasKeys = organization.hasPublicAndPrivateKeys;
await this.load();
// Backfill pub/priv key if necessary
if (this.canResetPassword && !this.orgHasKeys) {
const orgShareKey = await this.cryptoService.getOrgKey(this.organizationId);
const orgKeys = await this.cryptoService.makeKeyPair(orgShareKey);
const request = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString);
const response = await this.apiService.postOrganizationKeys(this.organizationId, request);
if (response != null) {
this.orgHasKeys = response.publicKey != null && response.privateKey != null;
await this.syncService.fullSync(true); // Replace oganizations with new data
} else {
throw new Error(this.i18nService.t('resetPasswordOrgKeysError'));
}
}
await this.load();
this.route.queryParams.pipe(first()).subscribe(async qParams => {
this.searchText = qParams.search;
if (qParams.viewEvents != null) {
const user = this.users.filter(u => u.id === qParams.viewEvents);
if (user.length > 0 && user[0].status === OrganizationUserStatusType.Confirmed) {
this.events(user[0]);
}
}
});
});
}
async load() {
const resetPasswordPolicy = await this.policyService.getPolicyForOrganization(PolicyType.ResetPassword,
this.organizationId);
this.orgResetPasswordPolicyEnabled = resetPasswordPolicy?.enabled;
super.load();
}
getUsers(): Promise<ListResponse<OrganizationUserUserDetailsResponse>> {
return this.apiService.getOrganizationUsers(this.organizationId);
}
deleteUser(id: string): Promise<any> {
return this.apiService.deleteOrganizationUser(this.organizationId, id);
}
reinviteUser(id: string): Promise<any> {
return this.apiService.postOrganizationUserReinvite(this.organizationId, id);
}
async confirmUser(user: OrganizationUserUserDetailsResponse, publicKey: Uint8Array): Promise<any> {
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
const request = new OrganizationUserConfirmRequest();
request.key = key.encryptedString;
await this.apiService.postOrganizationUserConfirm(this.organizationId, user.id, request);
}
allowResetPassword(orgUser: OrganizationUserUserDetailsResponse): boolean {
// Hierarchy check
let callingUserHasPermission = false;
switch (this.callingUserType) {
case OrganizationUserType.Owner:
callingUserHasPermission = true;
break;
case OrganizationUserType.Admin:
callingUserHasPermission = orgUser.type !== OrganizationUserType.Owner;
break;
case OrganizationUserType.Custom:
callingUserHasPermission = orgUser.type !== OrganizationUserType.Owner
&& orgUser.type !== OrganizationUserType.Admin;
break;
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
this.searchText = qParams.search;
if (qParams.viewEvents != null) {
const user = this.users.filter((u) => u.id === qParams.viewEvents);
if (user.length > 0 && user[0].status === OrganizationUserStatusType.Confirmed) {
this.events(user[0]);
}
}
});
});
}
// Final
return this.canResetPassword && callingUserHasPermission && this.orgUseResetPassword && this.orgHasKeys
&& orgUser.resetPasswordEnrolled && this.orgResetPasswordPolicyEnabled
&& orgUser.status === OrganizationUserStatusType.Confirmed;
async load() {
const resetPasswordPolicy = await this.policyService.getPolicyForOrganization(
PolicyType.ResetPassword,
this.organizationId
);
this.orgResetPasswordPolicyEnabled = resetPasswordPolicy?.enabled;
super.load();
}
getUsers(): Promise<ListResponse<OrganizationUserUserDetailsResponse>> {
return this.apiService.getOrganizationUsers(this.organizationId);
}
deleteUser(id: string): Promise<any> {
return this.apiService.deleteOrganizationUser(this.organizationId, id);
}
reinviteUser(id: string): Promise<any> {
return this.apiService.postOrganizationUserReinvite(this.organizationId, id);
}
async confirmUser(
user: OrganizationUserUserDetailsResponse,
publicKey: Uint8Array
): Promise<any> {
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
const request = new OrganizationUserConfirmRequest();
request.key = key.encryptedString;
await this.apiService.postOrganizationUserConfirm(this.organizationId, user.id, request);
}
allowResetPassword(orgUser: OrganizationUserUserDetailsResponse): boolean {
// Hierarchy check
let callingUserHasPermission = false;
switch (this.callingUserType) {
case OrganizationUserType.Owner:
callingUserHasPermission = true;
break;
case OrganizationUserType.Admin:
callingUserHasPermission = orgUser.type !== OrganizationUserType.Owner;
break;
case OrganizationUserType.Custom:
callingUserHasPermission =
orgUser.type !== OrganizationUserType.Owner &&
orgUser.type !== OrganizationUserType.Admin;
break;
}
showEnrolledStatus(orgUser: OrganizationUserUserDetailsResponse): boolean {
return this.orgUseResetPassword && orgUser.resetPasswordEnrolled && this.orgResetPasswordPolicyEnabled;
}
// Final
return (
this.canResetPassword &&
callingUserHasPermission &&
this.orgUseResetPassword &&
this.orgHasKeys &&
orgUser.resetPasswordEnrolled &&
this.orgResetPasswordPolicyEnabled &&
orgUser.status === OrganizationUserStatusType.Confirmed
);
}
async edit(user: OrganizationUserUserDetailsResponse) {
const [modal] = await this.modalService.openViewRef(UserAddEditComponent, this.addEditModalRef, comp => {
comp.name = this.userNamePipe.transform(user);
comp.organizationId = this.organizationId;
comp.organizationUserId = user != null ? user.id : null;
comp.usesKeyConnector = user?.usesKeyConnector;
comp.onSavedUser.subscribe(() => {
modal.close();
this.load();
});
comp.onDeletedUser.subscribe(() => {
modal.close();
this.removeUser(user);
});
showEnrolledStatus(orgUser: OrganizationUserUserDetailsResponse): boolean {
return (
this.orgUseResetPassword &&
orgUser.resetPasswordEnrolled &&
this.orgResetPasswordPolicyEnabled
);
}
async edit(user: OrganizationUserUserDetailsResponse) {
const [modal] = await this.modalService.openViewRef(
UserAddEditComponent,
this.addEditModalRef,
(comp) => {
comp.name = this.userNamePipe.transform(user);
comp.organizationId = this.organizationId;
comp.organizationUserId = user != null ? user.id : null;
comp.usesKeyConnector = user?.usesKeyConnector;
comp.onSavedUser.subscribe(() => {
modal.close();
this.load();
});
}
async groups(user: OrganizationUserUserDetailsResponse) {
const [modal] = await this.modalService.openViewRef(UserGroupsComponent, this.groupsModalRef, comp => {
comp.name = this.userNamePipe.transform(user);
comp.organizationId = this.organizationId;
comp.organizationUserId = user != null ? user.id : null;
comp.onSavedUser.subscribe(() => {
modal.close();
});
comp.onDeletedUser.subscribe(() => {
modal.close();
this.removeUser(user);
});
}
}
);
}
async bulkRemove() {
if (this.actionPromise != null) {
return;
}
const [modal] = await this.modalService.openViewRef(BulkRemoveComponent, this.bulkRemoveModalRef, comp => {
comp.organizationId = this.organizationId;
comp.users = this.getCheckedUsers();
async groups(user: OrganizationUserUserDetailsResponse) {
const [modal] = await this.modalService.openViewRef(
UserGroupsComponent,
this.groupsModalRef,
(comp) => {
comp.name = this.userNamePipe.transform(user);
comp.organizationId = this.organizationId;
comp.organizationUserId = user != null ? user.id : null;
comp.onSavedUser.subscribe(() => {
modal.close();
});
}
);
}
await modal.onClosedPromise();
await this.load();
async bulkRemove() {
if (this.actionPromise != null) {
return;
}
async bulkReinvite() {
if (this.actionPromise != null) {
return;
}
const [modal] = await this.modalService.openViewRef(
BulkRemoveComponent,
this.bulkRemoveModalRef,
(comp) => {
comp.organizationId = this.organizationId;
comp.users = this.getCheckedUsers();
}
);
const users = this.getCheckedUsers();
const filteredUsers = users.filter(u => u.status === OrganizationUserStatusType.Invited);
await modal.onClosedPromise();
await this.load();
}
if (filteredUsers.length <= 0) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('noSelectedUsersApplicable'));
return;
}
try {
const request = new OrganizationUserBulkRequest(filteredUsers.map(user => user.id));
const response = this.apiService.postManyOrganizationUserReinvite(this.organizationId, request);
this.showBulkStatus(users, filteredUsers, response, this.i18nService.t('bulkReinviteMessage'));
} catch (e) {
this.validationService.showError(e);
}
this.actionPromise = null;
async bulkReinvite() {
if (this.actionPromise != null) {
return;
}
async bulkConfirm() {
if (this.actionPromise != null) {
return;
}
const users = this.getCheckedUsers();
const filteredUsers = users.filter((u) => u.status === OrganizationUserStatusType.Invited);
const [modal] = await this.modalService.openViewRef(BulkConfirmComponent, this.bulkConfirmModalRef, comp => {
comp.organizationId = this.organizationId;
comp.users = this.getCheckedUsers();
if (filteredUsers.length <= 0) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("noSelectedUsersApplicable")
);
return;
}
try {
const request = new OrganizationUserBulkRequest(filteredUsers.map((user) => user.id));
const response = this.apiService.postManyOrganizationUserReinvite(
this.organizationId,
request
);
this.showBulkStatus(
users,
filteredUsers,
response,
this.i18nService.t("bulkReinviteMessage")
);
} catch (e) {
this.validationService.showError(e);
}
this.actionPromise = null;
}
async bulkConfirm() {
if (this.actionPromise != null) {
return;
}
const [modal] = await this.modalService.openViewRef(
BulkConfirmComponent,
this.bulkConfirmModalRef,
(comp) => {
comp.organizationId = this.organizationId;
comp.users = this.getCheckedUsers();
}
);
await modal.onClosedPromise();
await this.load();
}
async events(user: OrganizationUserUserDetailsResponse) {
const [modal] = await this.modalService.openViewRef(
EntityEventsComponent,
this.eventsModalRef,
(comp) => {
comp.name = this.userNamePipe.transform(user);
comp.organizationId = this.organizationId;
comp.entityId = user.id;
comp.showUser = false;
comp.entity = "user";
}
);
}
async resetPassword(user: OrganizationUserUserDetailsResponse) {
const [modal] = await this.modalService.openViewRef(
ResetPasswordComponent,
this.resetPasswordModalRef,
(comp) => {
comp.name = this.userNamePipe.transform(user);
comp.email = user != null ? user.email : null;
comp.organizationId = this.organizationId;
comp.id = user != null ? user.id : null;
comp.onPasswordReset.subscribe(() => {
modal.close();
this.load();
});
}
);
}
await modal.onClosedPromise();
await this.load();
protected deleteWarningMessage(user: OrganizationUserUserDetailsResponse): string {
if (user.usesKeyConnector) {
return this.i18nService.t("removeUserConfirmationKeyConnector");
}
async events(user: OrganizationUserUserDetailsResponse) {
const [modal] = await this.modalService.openViewRef(EntityEventsComponent, this.eventsModalRef, comp => {
comp.name = this.userNamePipe.transform(user);
comp.organizationId = this.organizationId;
comp.entityId = user.id;
comp.showUser = false;
comp.entity = 'user';
return super.deleteWarningMessage(user);
}
private async showBulkStatus(
users: OrganizationUserUserDetailsResponse[],
filteredUsers: OrganizationUserUserDetailsResponse[],
request: Promise<ListResponse<OrganizationUserBulkResponse>>,
successfullMessage: string
) {
const [modal, childComponent] = await this.modalService.openViewRef(
BulkStatusComponent,
this.bulkStatusModalRef,
(comp) => {
comp.loading = true;
}
);
// Workaround to handle closing the modal shortly after it has been opened
let close = false;
modal.onShown.subscribe(() => {
if (close) {
modal.close();
}
});
try {
const response = await request;
if (modal) {
const keyedErrors: any = response.data
.filter((r) => r.error !== "")
.reduce((a, x) => ({ ...a, [x.id]: x.error }), {});
const keyedFilteredUsers: any = filteredUsers.reduce((a, x) => ({ ...a, [x.id]: x }), {});
childComponent.users = users.map((user) => {
let message = keyedErrors[user.id] ?? successfullMessage;
if (!keyedFilteredUsers.hasOwnProperty(user.id)) {
message = this.i18nService.t("bulkFilteredMessage");
}
return {
user: user,
error: keyedErrors.hasOwnProperty(user.id),
message: message,
};
});
childComponent.loading = false;
}
} catch {
close = true;
modal.close();
}
async resetPassword(user: OrganizationUserUserDetailsResponse) {
const [modal] = await this.modalService.openViewRef(ResetPasswordComponent, this.resetPasswordModalRef, comp => {
comp.name = this.userNamePipe.transform(user);
comp.email = user != null ? user.email : null;
comp.organizationId = this.organizationId;
comp.id = user != null ? user.id : null;
comp.onPasswordReset.subscribe(() => {
modal.close();
this.load();
});
});
}
protected deleteWarningMessage(user: OrganizationUserUserDetailsResponse): string {
if (user.usesKeyConnector) {
return this.i18nService.t('removeUserConfirmationKeyConnector');
}
return super.deleteWarningMessage(user);
}
private async showBulkStatus(users: OrganizationUserUserDetailsResponse[], filteredUsers: OrganizationUserUserDetailsResponse[],
request: Promise<ListResponse<OrganizationUserBulkResponse>>, successfullMessage: string) {
const [modal, childComponent] = await this.modalService.openViewRef(BulkStatusComponent, this.bulkStatusModalRef, comp => {
comp.loading = true;
});
// Workaround to handle closing the modal shortly after it has been opened
let close = false;
modal.onShown.subscribe(() => {
if (close) {
modal.close();
}
});
try {
const response = await request;
if (modal) {
const keyedErrors: any = response.data.filter(r => r.error !== '').reduce((a, x) => ({ ...a, [x.id]: x.error }), {});
const keyedFilteredUsers: any = filteredUsers.reduce((a, x) => ({ ...a, [x.id]: x }), {});
childComponent.users = users.map(user => {
let message = keyedErrors[user.id] ?? successfullMessage;
if (!keyedFilteredUsers.hasOwnProperty(user.id)) {
message = this.i18nService.t('bulkFilteredMessage');
}
return {
user: user,
error: keyedErrors.hasOwnProperty(user.id),
message: message,
};
});
childComponent.loading = false;
}
} catch {
close = true;
modal.close();
}
}
}
}

View File

@@ -1,19 +1,21 @@
<div class="page-header d-flex">
<h1>{{'policies' | i18n}}</h1>
<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>
<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 *ngIf="p.display(organization)">
<a href="#" appStopClick (click)="edit(p)">{{p.name | i18n}}</a>
<span class="badge badge-success" *ngIf="policiesEnabledMap.get(p.type)">{{'enabled' | i18n}}</span>
<small class="text-muted d-block">{{p.description | i18n}}</small>
</td>
</tr>
</tbody>
<tbody>
<tr *ngFor="let p of policies">
<td *ngIf="p.display(organization)">
<a href="#" appStopClick (click)="edit(p)">{{ p.name | i18n }}</a>
<span class="badge badge-success" *ngIf="policiesEnabledMap.get(p.type)">{{
"enabled" | i18n
}}</span>
<small class="text-muted d-block">{{ p.description | i18n }}</small>
</td>
</tr>
</tbody>
</table>
<ng-template #editTemplate></ng-template>

View File

@@ -1,103 +1,105 @@
import {
Component,
OnInit,
ViewChild,
ViewContainerRef,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from 'rxjs/operators';
import { first } from "rxjs/operators";
import { PolicyType } from 'jslib-common/enums/policyType';
import { PolicyType } from "jslib-common/enums/policyType";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { ModalService } from 'jslib-angular/services/modal.service';
import { ModalService } from "jslib-angular/services/modal.service";
import { PolicyResponse } from 'jslib-common/models/response/policyResponse';
import { PolicyResponse } from "jslib-common/models/response/policyResponse";
import { Organization } from 'jslib-common/models/domain/organization';
import { Organization } from "jslib-common/models/domain/organization";
import { PolicyEditComponent } from './policy-edit.component';
import { PolicyEditComponent } from "./policy-edit.component";
import { PolicyListService } from '../../services/policy-list.service';
import { BasePolicy } from '../policies/base-policy.component';
import { PolicyListService } from "../../services/policy-list.service";
import { BasePolicy } from "../policies/base-policy.component";
@Component({
selector: 'app-org-policies',
templateUrl: 'policies.component.html',
selector: "app-org-policies",
templateUrl: "policies.component.html",
})
export class PoliciesComponent implements OnInit {
@ViewChild('editTemplate', { read: ViewContainerRef, static: true }) editModalRef: ViewContainerRef;
@ViewChild("editTemplate", { read: ViewContainerRef, static: true })
editModalRef: ViewContainerRef;
loading = true;
organizationId: string;
policies: BasePolicy[];
organization: Organization;
loading = true;
organizationId: string;
policies: BasePolicy[];
organization: Organization;
private orgPolicies: PolicyResponse[];
private policiesEnabledMap: Map<PolicyType, boolean> = new Map<PolicyType, boolean>();
private orgPolicies: PolicyResponse[];
private policiesEnabledMap: Map<PolicyType, boolean> = new Map<PolicyType, boolean>();
constructor(private apiService: ApiService, private route: ActivatedRoute,
private modalService: ModalService, private organizationService: OrganizationService,
private policyListService: PolicyListService, private router: Router) { }
constructor(
private apiService: ApiService,
private route: ActivatedRoute,
private modalService: ModalService,
private organizationService: OrganizationService,
private policyListService: PolicyListService,
private router: Router
) {}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async params => {
this.organizationId = params.organizationId;
this.organization = await this.organizationService.get(this.organizationId);
if (this.organization == null || !this.organization.usePolicies) {
this.router.navigate(['/organizations', this.organizationId]);
return;
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
this.organization = await this.organizationService.get(this.organizationId);
if (this.organization == null || !this.organization.usePolicies) {
this.router.navigate(["/organizations", this.organizationId]);
return;
}
this.policies = this.policyListService.getPolicies();
this.policies = this.policyListService.getPolicies();
await this.load();
await this.load();
// Handle policies component launch from Event message
this.route.queryParams.pipe(first()).subscribe(async qParams => {
if (qParams.policyId != null) {
const policyIdFromEvents: string = qParams.policyId;
for (const orgPolicy of this.orgPolicies) {
if (orgPolicy.id === policyIdFromEvents) {
for (let i = 0; i < this.policies.length; i++) {
if (this.policies[i].type === orgPolicy.type) {
this.edit(this.policies[i]);
break;
}
}
break;
}
}
// Handle policies component launch from Event message
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.policyId != null) {
const policyIdFromEvents: string = qParams.policyId;
for (const orgPolicy of this.orgPolicies) {
if (orgPolicy.id === policyIdFromEvents) {
for (let i = 0; i < this.policies.length; i++) {
if (this.policies[i].type === orgPolicy.type) {
this.edit(this.policies[i]);
break;
}
});
});
}
}
break;
}
}
}
});
});
}
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);
});
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.loading = false;
}
this.loading = false;
}
async edit(policy: BasePolicy) {
const [modal] = await this.modalService.openViewRef(PolicyEditComponent, this.editModalRef, comp => {
comp.policy = policy;
comp.organizationId = this.organizationId;
comp.policiesEnabledMap = this.policiesEnabledMap;
comp.onSavedPolicy.subscribe(() => {
modal.close();
this.load();
});
async edit(policy: BasePolicy) {
const [modal] = await this.modalService.openViewRef(
PolicyEditComponent,
this.editModalRef,
(comp) => {
comp.policy = policy;
comp.organizationId = this.organizationId;
comp.policiesEnabledMap = this.policiesEnabledMap;
comp.onSavedPolicy.subscribe(() => {
modal.close();
this.load();
});
}
}
);
}
}

View File

@@ -1,32 +1,49 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="policiesEditTitle">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="modal-header">
<h2 class="modal-title" id="policiesEditTitle">{{'editPolicy' | i18n}} - {{policy.name | i18n}}</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<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 [hidden]="loading">
<p>{{policy.description | i18n}}</p>
<ng-template #policyForm></ng-template>
</div>
</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 class="modal-dialog modal-dialog-scrollable" role="document">
<form
class="modal-content"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
>
<div class="modal-header">
<h2 class="modal-title" id="policiesEditTitle">
{{ "editPolicy" | i18n }} - {{ policy.name | i18n }}
</h2>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<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 [hidden]="loading">
<p>{{ policy.description | i18n }}</p>
<ng-template #policyForm></ng-template>
</div>
</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>

View File

@@ -1,93 +1,103 @@
import {
ChangeDetectorRef,
Component,
ComponentFactoryResolver,
EventEmitter,
Input,
Output,
ViewChild,
ViewContainerRef,
} from '@angular/core';
ChangeDetectorRef,
Component,
ComponentFactoryResolver,
EventEmitter,
Input,
Output,
ViewChild,
ViewContainerRef,
} from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyType } from 'jslib-common/enums/policyType';
import { PolicyType } from "jslib-common/enums/policyType";
import { PolicyRequest } from 'jslib-common/models/request/policyRequest';
import { PolicyRequest } from "jslib-common/models/request/policyRequest";
import { PolicyResponse } from 'jslib-common/models/response/policyResponse';
import { PolicyResponse } from "jslib-common/models/response/policyResponse";
import { BasePolicy, BasePolicyComponent } from '../policies/base-policy.component';
import { BasePolicy, BasePolicyComponent } from "../policies/base-policy.component";
@Component({
selector: 'app-policy-edit',
templateUrl: 'policy-edit.component.html',
selector: "app-policy-edit",
templateUrl: "policy-edit.component.html",
})
export class PolicyEditComponent {
@Input() policy: BasePolicy;
@Input() organizationId: string;
@Input() policiesEnabledMap: Map<PolicyType, boolean> = new Map<PolicyType, boolean>();
@Output() onSavedPolicy = new EventEmitter();
@Input() policy: BasePolicy;
@Input() organizationId: string;
@Input() policiesEnabledMap: Map<PolicyType, boolean> = new Map<PolicyType, boolean>();
@Output() onSavedPolicy = new EventEmitter();
@ViewChild('policyForm', { read: ViewContainerRef, static: true }) policyFormRef: ViewContainerRef;
@ViewChild("policyForm", { read: ViewContainerRef, static: true })
policyFormRef: ViewContainerRef;
policyType = PolicyType;
loading = true;
enabled = false;
formPromise: Promise<any>;
defaultTypes: any[];
policyComponent: BasePolicyComponent;
policyType = PolicyType;
loading = true;
enabled = false;
formPromise: Promise<any>;
defaultTypes: any[];
policyComponent: BasePolicyComponent;
private policyResponse: PolicyResponse;
private policyResponse: PolicyResponse;
constructor(private apiService: ApiService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private componentFactoryResolver: ComponentFactoryResolver,
private cdr: ChangeDetectorRef, private logService: LogService) {
constructor(
private apiService: ApiService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private componentFactoryResolver: ComponentFactoryResolver,
private cdr: ChangeDetectorRef,
private logService: LogService
) {}
async ngAfterViewInit() {
await this.load();
this.loading = false;
const factory = this.componentFactoryResolver.resolveComponentFactory(this.policy.component);
this.policyComponent = this.policyFormRef.createComponent(factory)
.instance as BasePolicyComponent;
this.policyComponent.policy = this.policy;
this.policyComponent.policyResponse = this.policyResponse;
this.cdr.detectChanges();
}
async load() {
try {
this.policyResponse = await this.apiService.getPolicy(this.organizationId, this.policy.type);
} catch (e) {
if (e.statusCode === 404) {
this.policyResponse = new PolicyResponse({ Enabled: false });
} else {
throw e;
}
}
}
async submit() {
let request: PolicyRequest;
try {
request = await this.policyComponent.buildRequest(this.policiesEnabledMap);
} catch (e) {
this.platformUtilsService.showToast("error", null, e);
return;
}
async ngAfterViewInit() {
await this.load();
this.loading = false;
const factory = this.componentFactoryResolver.resolveComponentFactory(this.policy.component);
this.policyComponent = this.policyFormRef.createComponent(factory).instance as BasePolicyComponent;
this.policyComponent.policy = this.policy;
this.policyComponent.policyResponse = this.policyResponse;
this.cdr.detectChanges();
}
async load() {
try {
this.policyResponse = await this.apiService.getPolicy(this.organizationId, this.policy.type);
} catch (e) {
if (e.statusCode === 404) {
this.policyResponse = new PolicyResponse({Enabled: false});
} else {
throw e;
}
}
}
async submit() {
let request: PolicyRequest;
try {
request = await this.policyComponent.buildRequest(this.policiesEnabledMap);
} catch (e) {
this.platformUtilsService.showToast('error', null, e);
return;
}
try {
this.formPromise = this.apiService.putPolicy(this.organizationId, this.policy.type, request);
await this.formPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('editedPolicyId', this.i18nService.t(this.policy.name)));
this.onSavedPolicy.emit();
} catch (e) {
this.logService.error(e);
}
try {
this.formPromise = this.apiService.putPolicy(this.organizationId, this.policy.type, request);
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("editedPolicyId", this.i18nService.t(this.policy.name))
);
this.onSavedPolicy.emit();
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -1,63 +1,97 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="resetPasswordTitle">
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-header">
<h2 class="modal-title" id="resetPasswordTitle">
{{'resetPassword' | i18n}}
<small class="text-muted" *ngIf="name">{{name}}</small>
</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
<div class="modal-dialog" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-header">
<h2 class="modal-title" id="resetPasswordTitle">
{{ "resetPassword" | i18n }}
<small class="text-muted" *ngIf="name">{{ name }}</small>
</h2>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<app-callout type="warning"
>{{ "resetPasswordLoggedOutWarning" | i18n: loggedOutWarningName }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
enforcedPolicyMessage="{{ 'resetPasswordMasterPasswordPolicyInEffect' | i18n }}"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
<div class="row">
<div class="col form-group">
<div class="d-flex">
<label for="newPassword">{{ "newPassword" | i18n }}</label>
<div class="ml-auto d-flex">
<a
href="#"
class="d-block mr-2 fa-icon-above-input"
appStopClick
appA11yTitle="{{ 'generatePassword' | i18n }}"
(click)="generatePassword()"
>
<i class="fa fa-lg fa-fw fa-refresh" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="input-group mb-1">
<input
id="newPassword"
class="form-control text-monospace"
appAutofocus
type="{{ showPassword ? 'text' : 'password' }}"
name="NewPassword"
[(ngModel)]="newPassword"
required
appInputVerbatim
autocomplete="new-password"
(input)="updatePasswordStrength()"
/>
<div class="input-group-append">
<button
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button>
</div>
<div class="modal-body">
<app-callout type="warning">{{'resetPasswordLoggedOutWarning' | i18n: loggedOutWarningName}}
</app-callout>
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions"
enforcedPolicyMessage="{{'resetPasswordMasterPasswordPolicyInEffect' | i18n}}"
*ngIf="enforcedPolicyOptions">
</app-callout>
<div class="row">
<div class="col form-group">
<div class="d-flex">
<label for="newPassword">{{'newPassword' | i18n}}</label>
<div class="ml-auto d-flex">
<a href="#" class="d-block mr-2 fa-icon-above-input" appStopClick
appA11yTitle="{{'generatePassword' | i18n}}" (click)="generatePassword()">
<i class="fa fa-lg fa-fw fa-refresh" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="input-group mb-1">
<input id="newPassword" class="form-control text-monospace" appAutofocus
type="{{showPassword ? 'text' : 'password'}}" name="NewPassword"
[(ngModel)]="newPassword" required appInputVerbatim autocomplete="new-password"
(input)="updatePasswordStrength()">
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
<button type="button" class="btn btn-outline-secondary"
appA11yTitle="{{'copyPassword' | i18n}}" (click)="copy(newPassword)">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button>
</div>
</div>
<app-password-strength [score]="masterPasswordScore" [showText]="true">
</app-password-strength>
</div>
</div>
</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
type="button"
class="btn btn-outline-secondary"
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy(newPassword)"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'cancel' |
i18n}}</button>
</div>
</div>
</form>
</div>
<app-password-strength [score]="masterPasswordScore" [showText]="true">
</app-password-strength>
</div>
</div>
</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>

View File

@@ -1,174 +1,219 @@
import {
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { EncString } from 'jslib-common/models/domain/encString';
import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { EncString } from "jslib-common/models/domain/encString";
import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions";
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { OrganizationUserResetPasswordRequest } from 'jslib-common/models/request/organizationUserResetPasswordRequest';
import { OrganizationUserResetPasswordRequest } from "jslib-common/models/request/organizationUserResetPasswordRequest";
@Component({
selector: 'app-reset-password',
templateUrl: 'reset-password.component.html',
selector: "app-reset-password",
templateUrl: "reset-password.component.html",
})
export class ResetPasswordComponent implements OnInit {
@Input() name: string;
@Input() email: string;
@Input() id: string;
@Input() organizationId: string;
@Output() onPasswordReset = new EventEmitter();
@Input() name: string;
@Input() email: string;
@Input() id: string;
@Input() organizationId: string;
@Output() onPasswordReset = new EventEmitter();
enforcedPolicyOptions: MasterPasswordPolicyOptions;
newPassword: string = null;
showPassword: boolean = false;
masterPasswordScore: number;
formPromise: Promise<any>;
private newPasswordStrengthTimeout: any;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
newPassword: string = null;
showPassword: boolean = false;
masterPasswordScore: number;
formPromise: Promise<any>;
private newPasswordStrengthTimeout: any;
constructor(private apiService: ApiService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private passwordGenerationService: PasswordGenerationService,
private policyService: PolicyService, private cryptoService: CryptoService, private logService: LogService) { }
constructor(
private apiService: ApiService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private passwordGenerationService: PasswordGenerationService,
private policyService: PolicyService,
private cryptoService: CryptoService,
private logService: LogService
) {}
async ngOnInit() {
// Get Enforced Policy Options
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
async ngOnInit() {
// Get Enforced Policy Options
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
}
get loggedOutWarningName() {
return this.name != null ? this.name : this.i18nService.t("thisUser");
}
async generatePassword() {
const options = (await this.passwordGenerationService.getOptions())[0];
this.newPassword = await this.passwordGenerationService.generatePassword(options);
this.updatePasswordStrength();
}
togglePassword() {
this.showPassword = !this.showPassword;
document.getElementById("newPassword").focus();
}
copy(value: string) {
if (value == null) {
return;
}
get loggedOutWarningName() {
return this.name != null ? this.name : this.i18nService.t('thisUser');
this.platformUtilsService.copyToClipboard(value, { window: window });
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("valueCopied", this.i18nService.t("password"))
);
}
async submit() {
// Validation
if (this.newPassword == null || this.newPassword === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassRequired")
);
return false;
}
async generatePassword() {
const options = (await this.passwordGenerationService.getOptions())[0];
this.newPassword = await this.passwordGenerationService.generatePassword(options);
this.updatePasswordStrength();
if (this.newPassword.length < 8) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassLength")
);
return false;
}
togglePassword() {
this.showPassword = !this.showPassword;
document.getElementById('newPassword').focus();
if (
this.enforcedPolicyOptions != null &&
!this.policyService.evaluateMasterPassword(
this.masterPasswordScore,
this.newPassword,
this.enforcedPolicyOptions
)
) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPasswordPolicyRequirementsNotMet")
);
return;
}
copy(value: string) {
if (value == null) {
return;
}
this.platformUtilsService.copyToClipboard(value, { window: window });
this.platformUtilsService.showToast('info', null,
this.i18nService.t('valueCopied', this.i18nService.t('password')));
if (this.masterPasswordScore < 3) {
const result = await this.platformUtilsService.showDialog(
this.i18nService.t("weakMasterPasswordDesc"),
this.i18nService.t("weakMasterPassword"),
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!result) {
return false;
}
}
async submit() {
// Validation
if (this.newPassword == null || this.newPassword === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassRequired'));
return false;
}
// Get user Information (kdf type, kdf iterations, resetPasswordKey, private key) and change password
try {
this.formPromise = this.apiService
.getOrganizationUserResetPasswordDetails(this.organizationId, this.id)
.then(async (response) => {
if (response == null) {
throw new Error(this.i18nService.t("resetPasswordDetailsError"));
}
if (this.newPassword.length < 8) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassLength'));
return false;
}
const kdfType = response.kdf;
const kdfIterations = response.kdfIterations;
const resetPasswordKey = response.resetPasswordKey;
const encryptedPrivateKey = response.encryptedPrivateKey;
if (this.enforcedPolicyOptions != null &&
!this.policyService.evaluateMasterPassword(this.masterPasswordScore, this.newPassword,
this.enforcedPolicyOptions)) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPasswordPolicyRequirementsNotMet'));
return;
}
// Decrypt Organization's encrypted Private Key with org key
const orgSymKey = await this.cryptoService.getOrgKey(this.organizationId);
const decPrivateKey = await this.cryptoService.decryptToBytes(
new EncString(encryptedPrivateKey),
orgSymKey
);
if (this.masterPasswordScore < 3) {
const result = await this.platformUtilsService.showDialog(this.i18nService.t('weakMasterPasswordDesc'),
this.i18nService.t('weakMasterPassword'), this.i18nService.t('yes'), this.i18nService.t('no'),
'warning');
if (!result) {
return false;
}
}
// Decrypt User's Reset Password Key to get EncKey
const decValue = await this.cryptoService.rsaDecrypt(resetPasswordKey, decPrivateKey);
const userEncKey = new SymmetricCryptoKey(decValue);
// Get user Information (kdf type, kdf iterations, resetPasswordKey, private key) and change password
try {
this.formPromise = this.apiService.getOrganizationUserResetPasswordDetails(this.organizationId, this.id)
.then(async response => {
if (response == null) {
throw new Error(this.i18nService.t('resetPasswordDetailsError'));
}
// Create new key and hash new password
const newKey = await this.cryptoService.makeKey(
this.newPassword,
this.email.trim().toLowerCase(),
kdfType,
kdfIterations
);
const newPasswordHash = await this.cryptoService.hashPassword(this.newPassword, newKey);
const kdfType = response.kdf;
const kdfIterations = response.kdfIterations;
const resetPasswordKey = response.resetPasswordKey;
const encryptedPrivateKey = response.encryptedPrivateKey;
// Create new encKey for the User
const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey);
// Decrypt Organization's encrypted Private Key with org key
const orgSymKey = await this.cryptoService.getOrgKey(this.organizationId);
const decPrivateKey = await this.cryptoService.decryptToBytes(new EncString(encryptedPrivateKey), orgSymKey);
// Create request
const request = new OrganizationUserResetPasswordRequest();
request.key = newEncKey[1].encryptedString;
request.newMasterPasswordHash = newPasswordHash;
// Decrypt User's Reset Password Key to get EncKey
const decValue = await this.cryptoService.rsaDecrypt(resetPasswordKey, decPrivateKey);
const userEncKey = new SymmetricCryptoKey(decValue);
// Change user's password
return this.apiService.putOrganizationUserResetPassword(
this.organizationId,
this.id,
request
);
});
// Create new key and hash new password
const newKey = await this.cryptoService.makeKey(this.newPassword, this.email.trim().toLowerCase(),
kdfType, kdfIterations);
const newPasswordHash = await this.cryptoService.hashPassword(this.newPassword, newKey);
// Create new encKey for the User
const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey);
// Create request
const request = new OrganizationUserResetPasswordRequest();
request.key = newEncKey[1].encryptedString;
request.newMasterPasswordHash = newPasswordHash;
// Change user's password
return this.apiService.putOrganizationUserResetPassword(this.organizationId, this.id, request);
});
await this.formPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('resetPasswordSuccess'));
this.onPasswordReset.emit();
} catch (e) {
this.logService.error(e);
}
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("resetPasswordSuccess")
);
this.onPasswordReset.emit();
} catch (e) {
this.logService.error(e);
}
}
updatePasswordStrength() {
if (this.newPasswordStrengthTimeout != null) {
clearTimeout(this.newPasswordStrengthTimeout);
}
this.newPasswordStrengthTimeout = setTimeout(() => {
const strengthResult = this.passwordGenerationService.passwordStrength(this.newPassword,
this.getPasswordStrengthUserInput());
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
}, 300);
updatePasswordStrength() {
if (this.newPasswordStrengthTimeout != null) {
clearTimeout(this.newPasswordStrengthTimeout);
}
this.newPasswordStrengthTimeout = setTimeout(() => {
const strengthResult = this.passwordGenerationService.passwordStrength(
this.newPassword,
this.getPasswordStrengthUserInput()
);
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
}, 300);
}
private getPasswordStrengthUserInput() {
let userInput: string[] = [];
const atPosition = this.email.indexOf('@');
if (atPosition > -1) {
userInput = userInput.concat(this.email.substr(0, atPosition).trim().toLowerCase().split(/[^A-Za-z0-9]/));
}
if (this.name != null && this.name !== '') {
userInput = userInput.concat(this.name.trim().toLowerCase().split(' '));
}
return userInput;
private getPasswordStrengthUserInput() {
let userInput: string[] = [];
const atPosition = this.email.indexOf("@");
if (atPosition > -1) {
userInput = userInput.concat(
this.email
.substr(0, atPosition)
.trim()
.toLowerCase()
.split(/[^A-Za-z0-9]/)
);
}
if (this.name != null && this.name !== "") {
userInput = userInput.concat(this.name.trim().toLowerCase().split(" "));
}
return userInput;
}
}

View File

@@ -1,261 +1,407 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="userAddEditTitle">
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="modal-header">
<h2 class="modal-title" id="userAddEditTitle">
{{title}}
<small class="text-muted" *ngIf="name">{{name}}</small>
</h2>
<button type="button" class="close" data-dismiss="modal" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
<form
class="modal-content"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
>
<div class="modal-header">
<h2 class="modal-title" id="userAddEditTitle">
{{ title }}
<small class="text-muted" *ngIf="name">{{ name }}</small>
</h2>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</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">
<ng-container *ngIf="!editMode">
<p>{{ "inviteUserDesc" | i18n }}</p>
<div class="form-group mb-4">
<label for="emails">{{ "email" | i18n }}</label>
<input
id="emails"
class="form-control"
type="text"
name="Emails"
[(ngModel)]="emails"
required
appAutoFocus
/>
<small class="text-muted">{{ "inviteMultipleEmailDesc" | i18n: "20" }}</small>
</div>
</ng-container>
<h3>
{{ "userType" | i18n }}
<a
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
href="https://bitwarden.com/help/article/user-types-access-control/#user-types"
>
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</h3>
<div class="form-check mt-2 form-check-block">
<input
class="form-check-input"
type="radio"
name="userType"
id="userTypeUser"
[value]="organizationUserType.User"
[(ngModel)]="type"
/>
<label class="form-check-label" for="userTypeUser">
{{ "user" | i18n }}
<small>{{ "userDesc" | i18n }}</small>
</label>
</div>
<div class="form-check mt-2 form-check-block">
<input
class="form-check-input"
type="radio"
name="userType"
id="userTypeManager"
[value]="organizationUserType.Manager"
[(ngModel)]="type"
/>
<label class="form-check-label" for="userTypeManager">
{{ "manager" | i18n }}
<small>{{ "managerDesc" | i18n }}</small>
</label>
</div>
<div class="form-check mt-2 form-check-block">
<input
class="form-check-input"
type="radio"
name="userType"
id="userTypeAdmin"
[value]="organizationUserType.Admin"
[(ngModel)]="type"
/>
<label class="form-check-label" for="userTypeAdmin">
{{ "admin" | i18n }}
<small>{{ "adminDesc" | i18n }}</small>
</label>
</div>
<div class="form-check mt-2 form-check-block">
<input
class="form-check-input"
type="radio"
name="userType"
id="userTypeOwner"
[value]="organizationUserType.Owner"
[(ngModel)]="type"
/>
<label class="form-check-label" for="userTypeOwner">
{{ "owner" | i18n }}
<small>{{ "ownerDesc" | i18n }}</small>
</label>
</div>
<div class="form-check mt-2 form-check-block">
<input
class="form-check-input"
type="radio"
name="userType"
id="userTypeCustom"
[value]="organizationUserType.Custom"
[(ngModel)]="type"
/>
<label class="form-check-label" for="userTypeCustom">
{{ "custom" | i18n }}
<small>{{ "customDesc" | i18n }}</small>
</label>
</div>
<ng-container *ngIf="customUserTypeSelected">
<h3 class="mt-4 d-flex">
{{ "permissions" | i18n }}
</h3>
<div class="row">
<div class="col-6">
<div class="mb-3">
<label class="font-weight-bold mb-0">Manager Permissions</label>
<hr class="my-0 mr-2" />
<app-nested-checkbox
parentId="manageAssignedCollections"
[checkboxes]="manageAssignedCollectionsCheckboxes"
>
</app-nested-checkbox>
</div>
</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 class="col-6">
<div class="mb-3">
<label class="font-weight-bold mb-0">Admin Permissions</label>
<hr class="my-0 mr-2" />
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input
class="form-check-input"
type="checkbox"
name="accessEventLogs"
id="accessEventLogs"
[(ngModel)]="permissions.accessEventLogs"
/>
<label class="form-check-label font-weight-normal" for="accessEventLogs">
{{ "accessEventLogs" | i18n }}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input
class="form-check-input"
type="checkbox"
name="accessImportExport"
id="accessImportExport"
[(ngModel)]="permissions.accessImportExport"
/>
<label class="form-check-label font-weight-normal" for="accessImportExport">
{{ "accessImportExport" | i18n }}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input
class="form-check-input"
type="checkbox"
name="accessReports"
id="accessReports"
[(ngModel)]="permissions.accessReports"
/>
<label class="form-check-label font-weight-normal" for="accessReports">
{{ "accessReports" | i18n }}
</label>
</div>
</div>
<app-nested-checkbox
parentId="manageAllCollections"
[checkboxes]="manageAllCollectionsCheckboxes"
>
</app-nested-checkbox>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input
class="form-check-input"
type="checkbox"
name="manageGroups"
id="manageGroups"
[(ngModel)]="permissions.manageGroups"
/>
<label class="form-check-label font-weight-normal" for="manageGroups">
{{ "manageGroups" | i18n }}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input
class="form-check-input"
type="checkbox"
name="manageSso"
id="managePolicies"
[(ngModel)]="permissions.manageSso"
/>
<label class="form-check-label font-weight-normal" for="manageSso">
{{ "manageSso" | i18n }}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input
class="form-check-input"
type="checkbox"
name="managePolicies"
id="managePolicies"
[(ngModel)]="permissions.managePolicies"
/>
<label class="form-check-label font-weight-normal" for="managePolicies">
{{ "managePolicies" | i18n }}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input
class="form-check-input"
type="checkbox"
name="manageUsers"
id="manageUsers"
[(ngModel)]="permissions.manageUsers"
(change)="handleDependentPermissions()"
/>
<label class="form-check-label font-weight-normal" for="manageUsers">
{{ "manageUsers" | i18n }}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input
class="form-check-input"
type="checkbox"
name="manageResetPassword"
id="manageResetPassword"
[(ngModel)]="permissions.manageResetPassword"
(change)="handleDependentPermissions()"
/>
<label class="form-check-label font-weight-normal" for="manageResetPassword">
{{ "manageResetPassword" | i18n }}
</label>
</div>
</div>
</div>
</div>
<div class="modal-body" *ngIf="!loading">
<ng-container *ngIf="!editMode">
<p>{{'inviteUserDesc' | i18n}}</p>
<div class="form-group mb-4">
<label for="emails">{{'email' | i18n}}</label>
<input id="emails" class="form-control" type="text" name="Emails" [(ngModel)]="emails" required
appAutoFocus>
<small class="text-muted">{{'inviteMultipleEmailDesc' | i18n : '20'}}</small>
</div>
</ng-container>
<h3>
{{'userType' | i18n}}
<a target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}"
href="https://bitwarden.com/help/article/user-types-access-control/#user-types">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</h3>
<div class="form-check mt-2 form-check-block">
<input class="form-check-input" type="radio" name="userType" id="userTypeUser"
[value]="organizationUserType.User" [(ngModel)]="type">
<label class="form-check-label" for="userTypeUser">
{{'user' | i18n}}
<small>{{'userDesc' | i18n}}</small>
</label>
</div>
<div class="form-check mt-2 form-check-block">
<input class="form-check-input" type="radio" name="userType" id="userTypeManager"
[value]="organizationUserType.Manager" [(ngModel)]="type">
<label class="form-check-label" for="userTypeManager">
{{'manager' | i18n}}
<small>{{'managerDesc' | i18n}}</small>
</label>
</div>
<div class="form-check mt-2 form-check-block">
<input class="form-check-input" type="radio" name="userType" id="userTypeAdmin"
[value]="organizationUserType.Admin" [(ngModel)]="type">
<label class="form-check-label" for="userTypeAdmin">
{{'admin' | i18n}}
<small>{{'adminDesc' | i18n}}</small>
</label>
</div>
<div class="form-check mt-2 form-check-block">
<input class="form-check-input" type="radio" name="userType" id="userTypeOwner"
[value]="organizationUserType.Owner" [(ngModel)]="type">
<label class="form-check-label" for="userTypeOwner">
{{'owner' | i18n}}
<small>{{'ownerDesc' | i18n}}</small>
</label>
</div>
<div class="form-check mt-2 form-check-block">
<input class="form-check-input" type="radio" name="userType" id="userTypeCustom"
[value]="organizationUserType.Custom" [(ngModel)]="type">
<label class="form-check-label" for="userTypeCustom">
{{'custom' | i18n}}
<small>{{'customDesc' | i18n}}</small>
</label>
</div>
<ng-container *ngIf="customUserTypeSelected">
<h3 class="mt-4 d-flex">
{{'permissions' | i18n}}
</h3>
<div class="row">
<div class="col-6">
<div class="mb-3">
<label class="font-weight-bold mb-0">Manager Permissions</label>
<hr class="my-0 mr-2" />
<app-nested-checkbox parentId="manageAssignedCollections"
[checkboxes]="manageAssignedCollectionsCheckboxes">
</app-nested-checkbox>
</div>
</div>
<div class="col-6">
<div class="mb-3">
<label class="font-weight-bold mb-0">Admin Permissions</label>
<hr class="my-0 mr-2" />
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="accessEventLogs"
id="accessEventLogs" [(ngModel)]="permissions.accessEventLogs">
<label class="form-check-label font-weight-normal" for="accessEventLogs">
{{'accessEventLogs' | i18n}}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="accessImportExport"
id="accessImportExport" [(ngModel)]="permissions.accessImportExport">
<label class="form-check-label font-weight-normal" for="accessImportExport">
{{'accessImportExport' | i18n}}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="accessReports"
id="accessReports" [(ngModel)]="permissions.accessReports">
<label class="form-check-label font-weight-normal" for="accessReports">
{{'accessReports' | i18n}}
</label>
</div>
</div>
<app-nested-checkbox parentId="manageAllCollections"
[checkboxes]="manageAllCollectionsCheckboxes">
</app-nested-checkbox>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="manageGroups"
id="manageGroups" [(ngModel)]="permissions.manageGroups">
<label class="form-check-label font-weight-normal" for="manageGroups">
{{'manageGroups' | i18n}}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="manageSso"
id="managePolicies" [(ngModel)]="permissions.manageSso">
<label class="form-check-label font-weight-normal" for="manageSso">
{{'manageSso' | i18n}}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="managePolicies"
id="managePolicies" [(ngModel)]="permissions.managePolicies">
<label class="form-check-label font-weight-normal" for="managePolicies">
{{'managePolicies' | i18n}}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="manageUsers"
id="manageUsers" [(ngModel)]="permissions.manageUsers"
(change)="handleDependentPermissions()">
<label class="form-check-label font-weight-normal" for="manageUsers">
{{'manageUsers' | i18n}}
</label>
</div>
</div>
<div class="form-group mb-0">
<div class="form-check mt-1 form-check-block">
<input class="form-check-input" type="checkbox" name="manageResetPassword"
id="manageResetPassword" [(ngModel)]="permissions.manageResetPassword"
(change)="handleDependentPermissions()">
<label class="form-check-label font-weight-normal" for="manageResetPassword">
{{'manageResetPassword' | i18n}}
</label>
</div>
</div>
</div>
</div>
</div>
</ng-container>
<h3 class="mt-4 d-flex">
<div class="mb-3">
{{'accessControl' | i18n}}
<a target="_blank" rel="noopener" appA11yTitle="{{'learnMore' | i18n}}"
href="https://bitwarden.com/help/article/user-types-access-control/#access-control">
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</div>
<div class="ml-auto" *ngIf="access === 'selected' && collections && collections.length">
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
{{'selectAll' | i18n}}
</button>
<button type="button" (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
{{'unselectAll' | i18n}}
</button>
</div>
</h3>
<div class="form-group" [ngClass]="{'mb-0': access !== 'selected'}">
<div class="form-check">
<input class="form-check-input" type="radio" name="access" id="accessAll" value="all"
[(ngModel)]="access">
<label class="form-check-label" for="accessAll">
{{'userAccessAllItems' | i18n}}
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="access" id="accessSelected" value="selected"
[(ngModel)]="access">
<label class="form-check-label" for="accessSelected">
{{'userAccessSelectedCollections' | i18n}}
</label>
</div>
</div>
<ng-container *ngIf="access === 'selected'">
<div *ngIf="!collections || !collections.length">
{{'noCollectionsInList' | i18n}}
</div>
<table class="table table-hover table-list mb-0" *ngIf="collections && collections.length">
<thead>
<tr>
<th>&nbsp;</th>
<th>{{'name' | i18n}}</th>
<th width="100" class="text-center">{{'hidePasswords' | i18n}}</th>
<th width="100" class="text-center">{{'readOnly' | i18n}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let c of collections; let i = index">
<td class="table-list-checkbox" (click)="check(c)">
<input type="checkbox" [(ngModel)]="c.checked" name="Collection[{{i}}].Checked"
appStopProp>
</td>
<td (click)="check(c)">
{{c.name}}
</td>
<td class="text-center">
<input type="checkbox" [(ngModel)]="c.hidePasswords"
name="Collection[{{i}}].HidePasswords" [disabled]="!c.checked">
</td>
<td class="text-center">
<input type="checkbox" [(ngModel)]="c.readOnly" name="Collection[{{i}}].ReadOnly"
[disabled]="!c.checked">
</td>
</tr>
</tbody>
</table>
</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 class="ml-auto">
<button #deleteBtn type="button" (click)="delete()" class="btn btn-outline-danger"
appA11yTitle="{{'delete' | i18n}}" *ngIf="editMode" [disabled]="deleteBtn.loading"
[appApiAction]="deletePromise">
<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}}" aria-hidden="true"></i>
</button>
</div>
</div>
</form>
</div>
</div>
</ng-container>
<h3 class="mt-4 d-flex">
<div class="mb-3">
{{ "accessControl" | i18n }}
<a
target="_blank"
rel="noopener"
appA11yTitle="{{ 'learnMore' | i18n }}"
href="https://bitwarden.com/help/article/user-types-access-control/#access-control"
>
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
</a>
</div>
<div class="ml-auto" *ngIf="access === 'selected' && collections && collections.length">
<button type="button" (click)="selectAll(true)" class="btn btn-link btn-sm py-0">
{{ "selectAll" | i18n }}
</button>
<button type="button" (click)="selectAll(false)" class="btn btn-link btn-sm py-0">
{{ "unselectAll" | i18n }}
</button>
</div>
</h3>
<div class="form-group" [ngClass]="{ 'mb-0': access !== 'selected' }">
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="access"
id="accessAll"
value="all"
[(ngModel)]="access"
/>
<label class="form-check-label" for="accessAll">
{{ "userAccessAllItems" | i18n }}
</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="access"
id="accessSelected"
value="selected"
[(ngModel)]="access"
/>
<label class="form-check-label" for="accessSelected">
{{ "userAccessSelectedCollections" | i18n }}
</label>
</div>
</div>
<ng-container *ngIf="access === 'selected'">
<div *ngIf="!collections || !collections.length">
{{ "noCollectionsInList" | i18n }}
</div>
<table
class="table table-hover table-list mb-0"
*ngIf="collections && collections.length"
>
<thead>
<tr>
<th>&nbsp;</th>
<th>{{ "name" | i18n }}</th>
<th width="100" class="text-center">{{ "hidePasswords" | i18n }}</th>
<th width="100" class="text-center">{{ "readOnly" | i18n }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let c of collections; let i = index">
<td class="table-list-checkbox" (click)="check(c)">
<input
type="checkbox"
[(ngModel)]="c.checked"
name="Collection[{{ i }}].Checked"
appStopProp
/>
</td>
<td (click)="check(c)">
{{ c.name }}
</td>
<td class="text-center">
<input
type="checkbox"
[(ngModel)]="c.hidePasswords"
name="Collection[{{ i }}].HidePasswords"
[disabled]="!c.checked"
/>
</td>
<td class="text-center">
<input
type="checkbox"
[(ngModel)]="c.readOnly"
name="Collection[{{ i }}].ReadOnly"
[disabled]="!c.checked"
/>
</td>
</tr>
</tbody>
</table>
</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 class="ml-auto">
<button
#deleteBtn
type="button"
(click)="delete()"
class="btn btn-outline-danger"
appA11yTitle="{{ 'delete' | i18n }}"
*ngIf="editMode"
[disabled]="deleteBtn.loading"
[appApiAction]="deletePromise"
>
<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 }}"
aria-hidden="true"
></i>
</button>
</div>
</div>
</form>
</div>
</div>

View File

@@ -1,212 +1,244 @@
import {
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { CollectionData } from 'jslib-common/models/data/collectionData';
import { Collection } from 'jslib-common/models/domain/collection';
import { OrganizationUserInviteRequest } from 'jslib-common/models/request/organizationUserInviteRequest';
import { OrganizationUserUpdateRequest } from 'jslib-common/models/request/organizationUserUpdateRequest';
import { SelectionReadOnlyRequest } from 'jslib-common/models/request/selectionReadOnlyRequest';
import { CollectionDetailsResponse } from 'jslib-common/models/response/collectionResponse';
import { CollectionView } from 'jslib-common/models/view/collectionView';
import { CollectionData } from "jslib-common/models/data/collectionData";
import { Collection } from "jslib-common/models/domain/collection";
import { OrganizationUserInviteRequest } from "jslib-common/models/request/organizationUserInviteRequest";
import { OrganizationUserUpdateRequest } from "jslib-common/models/request/organizationUserUpdateRequest";
import { SelectionReadOnlyRequest } from "jslib-common/models/request/selectionReadOnlyRequest";
import { CollectionDetailsResponse } from "jslib-common/models/response/collectionResponse";
import { CollectionView } from "jslib-common/models/view/collectionView";
import { OrganizationUserType } from 'jslib-common/enums/organizationUserType';
import { PermissionsApi } from 'jslib-common/models/api/permissionsApi';
import { OrganizationUserType } from "jslib-common/enums/organizationUserType";
import { PermissionsApi } from "jslib-common/models/api/permissionsApi";
@Component({
selector: 'app-user-add-edit',
templateUrl: 'user-add-edit.component.html',
selector: "app-user-add-edit",
templateUrl: "user-add-edit.component.html",
})
export class UserAddEditComponent implements OnInit {
@Input() name: string;
@Input() organizationUserId: string;
@Input() organizationId: string;
@Input() usesKeyConnector: boolean = false;
@Output() onSavedUser = new EventEmitter();
@Output() onDeletedUser = new EventEmitter();
@Input() name: string;
@Input() organizationUserId: string;
@Input() organizationId: string;
@Input() usesKeyConnector: boolean = false;
@Output() onSavedUser = new EventEmitter();
@Output() onDeletedUser = new EventEmitter();
loading = true;
editMode: boolean = false;
title: string;
emails: string;
type: OrganizationUserType = OrganizationUserType.User;
permissions = new PermissionsApi();
showCustom = false;
access: 'all' | 'selected' = 'selected';
collections: CollectionView[] = [];
formPromise: Promise<any>;
deletePromise: Promise<any>;
organizationUserType = OrganizationUserType;
loading = true;
editMode: boolean = false;
title: string;
emails: string;
type: OrganizationUserType = OrganizationUserType.User;
permissions = new PermissionsApi();
showCustom = false;
access: "all" | "selected" = "selected";
collections: CollectionView[] = [];
formPromise: Promise<any>;
deletePromise: Promise<any>;
organizationUserType = OrganizationUserType;
manageAllCollectionsCheckboxes = [
{
id: 'createNewCollections',
get: () => this.permissions.createNewCollections,
set: (v: boolean) => this.permissions.createNewCollections = v,
},
{
id: 'editAnyCollection',
get: () => this.permissions.editAnyCollection,
set: (v: boolean) => this.permissions.editAnyCollection = v,
},
{
id: 'deleteAnyCollection',
get: () => this.permissions.deleteAnyCollection,
set: (v: boolean) => this.permissions.deleteAnyCollection = v,
},
];
manageAllCollectionsCheckboxes = [
{
id: "createNewCollections",
get: () => this.permissions.createNewCollections,
set: (v: boolean) => (this.permissions.createNewCollections = v),
},
{
id: "editAnyCollection",
get: () => this.permissions.editAnyCollection,
set: (v: boolean) => (this.permissions.editAnyCollection = v),
},
{
id: "deleteAnyCollection",
get: () => this.permissions.deleteAnyCollection,
set: (v: boolean) => (this.permissions.deleteAnyCollection = v),
},
];
manageAssignedCollectionsCheckboxes = [
{
id: 'editAssignedCollections',
get: () => this.permissions.editAssignedCollections,
set: (v: boolean) => this.permissions.editAssignedCollections = v,
},
{
id: 'deleteAssignedCollections',
get: () => this.permissions.deleteAssignedCollections,
set: (v: boolean) => this.permissions.deleteAssignedCollections = v,
},
];
manageAssignedCollectionsCheckboxes = [
{
id: "editAssignedCollections",
get: () => this.permissions.editAssignedCollections,
set: (v: boolean) => (this.permissions.editAssignedCollections = v),
},
{
id: "deleteAssignedCollections",
get: () => this.permissions.deleteAssignedCollections,
set: (v: boolean) => (this.permissions.deleteAssignedCollections = v),
},
];
get customUserTypeSelected(): boolean {
return this.type === OrganizationUserType.Custom;
}
get customUserTypeSelected(): boolean {
return this.type === OrganizationUserType.Custom;
}
constructor(private apiService: ApiService, private i18nService: I18nService,
private collectionService: CollectionService,
private platformUtilsService: PlatformUtilsService, private logService: LogService) { }
constructor(
private apiService: ApiService,
private i18nService: I18nService,
private collectionService: CollectionService,
private platformUtilsService: PlatformUtilsService,
private logService: LogService
) {}
async ngOnInit() {
this.editMode = this.loading = this.organizationUserId != null;
await this.loadCollections();
async ngOnInit() {
this.editMode = this.loading = this.organizationUserId != null;
await this.loadCollections();
if (this.editMode) {
this.editMode = true;
this.title = this.i18nService.t('editUser');
try {
const user = await this.apiService.getOrganizationUser(this.organizationId, this.organizationUserId);
this.access = user.accessAll ? 'all' : 'selected';
this.type = user.type;
if (user.type === OrganizationUserType.Custom) {
this.permissions = user.permissions;
}
if (user.collections != null && this.collections != null) {
user.collections.forEach(s => {
const collection = this.collections.filter(c => c.id === s.id);
if (collection != null && collection.length > 0) {
(collection[0] as any).checked = true;
collection[0].readOnly = s.readOnly;
collection[0].hidePasswords = s.hidePasswords;
}
});
}
} catch (e) {
this.logService.error(e);
}
} else {
this.title = this.i18nService.t('inviteUser');
}
this.loading = false;
}
async loadCollections() {
const response = await this.apiService.getCollections(this.organizationId);
const collections = response.data.map(r =>
new Collection(new CollectionData(r as CollectionDetailsResponse)));
this.collections = await this.collectionService.decryptMany(collections);
}
check(c: CollectionView, select?: boolean) {
(c as any).checked = select == null ? !(c as any).checked : select;
if (!(c as any).checked) {
c.readOnly = false;
}
}
selectAll(select: boolean) {
this.collections.forEach(c => this.check(c, select));
}
setRequestPermissions(p: PermissionsApi, clearPermissions: boolean) {
Object.assign(p, clearPermissions ? new PermissionsApi() : this.permissions);
return p;
}
handleDependentPermissions() {
// Manage Password Reset must have Manage Users enabled
if (this.permissions.manageResetPassword && !this.permissions.manageUsers) {
this.permissions.manageUsers = true;
(document.getElementById('manageUsers') as HTMLInputElement).checked = true;
this.platformUtilsService.showToast('info', null, this.i18nService.t('resetPasswordManageUsers'));
}
}
async submit() {
let collections: SelectionReadOnlyRequest[] = null;
if (this.access !== 'all') {
collections = this.collections.filter(c => (c as any).checked)
.map(c => new SelectionReadOnlyRequest(c.id, !!c.readOnly, !!c.hidePasswords));
}
try {
if (this.editMode) {
const request = new OrganizationUserUpdateRequest();
request.accessAll = this.access === 'all';
request.type = this.type;
request.collections = collections;
request.permissions = this.setRequestPermissions(request.permissions ?? new PermissionsApi(), request.type !== OrganizationUserType.Custom);
this.formPromise = this.apiService.putOrganizationUser(this.organizationId, this.organizationUserId,
request);
} else {
const request = new OrganizationUserInviteRequest();
request.emails = this.emails.trim().split(/\s*,\s*/);
request.accessAll = this.access === 'all';
request.type = this.type;
request.permissions = this.setRequestPermissions(request.permissions ?? new PermissionsApi(), request.type !== OrganizationUserType.Custom);
request.collections = collections;
this.formPromise = this.apiService.postOrganizationUserInvite(this.organizationId, request);
}
await this.formPromise;
this.platformUtilsService.showToast('success', null,
this.i18nService.t(this.editMode ? 'editedUserId' : 'invitedUsers', this.name));
this.onSavedUser.emit();
} catch (e) {
this.logService.error(e);
}
}
async delete() {
if (!this.editMode) {
return;
}
const message = this.usesKeyConnector ? 'removeUserConfirmationKeyConnector' : 'removeUserConfirmation';
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t(message), this.name, this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'
if (this.editMode) {
this.editMode = true;
this.title = this.i18nService.t("editUser");
try {
const user = await this.apiService.getOrganizationUser(
this.organizationId,
this.organizationUserId
);
if (!confirmed) {
return false;
this.access = user.accessAll ? "all" : "selected";
this.type = user.type;
if (user.type === OrganizationUserType.Custom) {
this.permissions = user.permissions;
}
try {
this.deletePromise = this.apiService.deleteOrganizationUser(this.organizationId, this.organizationUserId);
await this.deletePromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('removedUserId', this.name));
this.onDeletedUser.emit();
} catch (e) {
this.logService.error(e);
if (user.collections != null && this.collections != null) {
user.collections.forEach((s) => {
const collection = this.collections.filter((c) => c.id === s.id);
if (collection != null && collection.length > 0) {
(collection[0] as any).checked = true;
collection[0].readOnly = s.readOnly;
collection[0].hidePasswords = s.hidePasswords;
}
});
}
} catch (e) {
this.logService.error(e);
}
} else {
this.title = this.i18nService.t("inviteUser");
}
this.loading = false;
}
async loadCollections() {
const response = await this.apiService.getCollections(this.organizationId);
const collections = response.data.map(
(r) => new Collection(new CollectionData(r as CollectionDetailsResponse))
);
this.collections = await this.collectionService.decryptMany(collections);
}
check(c: CollectionView, select?: boolean) {
(c as any).checked = select == null ? !(c as any).checked : select;
if (!(c as any).checked) {
c.readOnly = false;
}
}
selectAll(select: boolean) {
this.collections.forEach((c) => this.check(c, select));
}
setRequestPermissions(p: PermissionsApi, clearPermissions: boolean) {
Object.assign(p, clearPermissions ? new PermissionsApi() : this.permissions);
return p;
}
handleDependentPermissions() {
// Manage Password Reset must have Manage Users enabled
if (this.permissions.manageResetPassword && !this.permissions.manageUsers) {
this.permissions.manageUsers = true;
(document.getElementById("manageUsers") as HTMLInputElement).checked = true;
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("resetPasswordManageUsers")
);
}
}
async submit() {
let collections: SelectionReadOnlyRequest[] = null;
if (this.access !== "all") {
collections = this.collections
.filter((c) => (c as any).checked)
.map((c) => new SelectionReadOnlyRequest(c.id, !!c.readOnly, !!c.hidePasswords));
}
try {
if (this.editMode) {
const request = new OrganizationUserUpdateRequest();
request.accessAll = this.access === "all";
request.type = this.type;
request.collections = collections;
request.permissions = this.setRequestPermissions(
request.permissions ?? new PermissionsApi(),
request.type !== OrganizationUserType.Custom
);
this.formPromise = this.apiService.putOrganizationUser(
this.organizationId,
this.organizationUserId,
request
);
} else {
const request = new OrganizationUserInviteRequest();
request.emails = this.emails.trim().split(/\s*,\s*/);
request.accessAll = this.access === "all";
request.type = this.type;
request.permissions = this.setRequestPermissions(
request.permissions ?? new PermissionsApi(),
request.type !== OrganizationUserType.Custom
);
request.collections = collections;
this.formPromise = this.apiService.postOrganizationUserInvite(this.organizationId, request);
}
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t(this.editMode ? "editedUserId" : "invitedUsers", this.name)
);
this.onSavedUser.emit();
} catch (e) {
this.logService.error(e);
}
}
async delete() {
if (!this.editMode) {
return;
}
const message = this.usesKeyConnector
? "removeUserConfirmationKeyConnector"
: "removeUserConfirmation";
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t(message),
this.name,
this.i18nService.t("yes"),
this.i18nService.t("no"),
"warning"
);
if (!confirmed) {
return false;
}
try {
this.deletePromise = this.apiService.deleteOrganizationUser(
this.organizationId,
this.organizationUserId
);
await this.deletePromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("removedUserId", this.name)
);
this.onDeletedUser.emit();
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -1,38 +1,56 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="confirmUserTitle">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-header">
<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" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>
{{'fingerprintEnsureIntegrityVerify' | i18n}}
<a href="https://help.bitwarden.com/article/fingerprint-phrase/" target="_blank" rel="noopener">
{{'learnMore' | i18n}}</a>
</p>
<p><code>{{fingerprint}}</code></p>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="dontAskAgain" name="DontAskAgain"
[(ngModel)]="dontAskAgain">
<label class="form-check-label" for="dontAskAgain">
{{'dontAskFingerprintAgain' | i18n}}
</label>
</div>
</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>{{'confirm' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary"
data-dismiss="modal">{{'cancel' | i18n}}</button>
</div>
</form>
</div>
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-header">
<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"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>
{{ "fingerprintEnsureIntegrityVerify" | i18n }}
<a
href="https://help.bitwarden.com/article/fingerprint-phrase/"
target="_blank"
rel="noopener"
>
{{ "learnMore" | i18n }}</a
>
</p>
<p>
<code>{{ fingerprint }}</code>
</p>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="dontAskAgain"
name="DontAskAgain"
[(ngModel)]="dontAskAgain"
/>
<label class="form-check-label" for="dontAskAgain">
{{ "dontAskFingerprintAgain" | i18n }}
</label>
</div>
</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>{{ "confirm" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@@ -1,56 +1,56 @@
import {
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { StateService } from "jslib-common/abstractions/state.service";
@Component({
selector: 'app-user-confirm',
templateUrl: 'user-confirm.component.html',
selector: "app-user-confirm",
templateUrl: "user-confirm.component.html",
})
export class UserConfirmComponent implements OnInit {
@Input() name: string;
@Input() userId: string;
@Input() publicKey: Uint8Array;
@Output() onConfirmedUser = new EventEmitter();
@Input() name: string;
@Input() userId: string;
@Input() publicKey: Uint8Array;
@Output() onConfirmedUser = new EventEmitter();
dontAskAgain = false;
loading = true;
fingerprint: string;
formPromise: Promise<any>;
dontAskAgain = false;
loading = true;
fingerprint: string;
formPromise: Promise<any>;
constructor(private cryptoService: CryptoService, private logService: LogService,
private stateService: StateService) { }
constructor(
private cryptoService: CryptoService,
private logService: LogService,
private stateService: StateService
) {}
async ngOnInit() {
try {
if (this.publicKey != null) {
const fingerprint = await this.cryptoService.getFingerprint(this.userId, this.publicKey.buffer);
if (fingerprint != null) {
this.fingerprint = fingerprint.join('-');
}
}
} catch (e) {
this.logService.error(e);
async ngOnInit() {
try {
if (this.publicKey != null) {
const fingerprint = await this.cryptoService.getFingerprint(
this.userId,
this.publicKey.buffer
);
if (fingerprint != null) {
this.fingerprint = fingerprint.join("-");
}
this.loading = false;
}
} catch (e) {
this.logService.error(e);
}
this.loading = false;
}
async submit() {
if (this.loading) {
return;
}
async submit() {
if (this.loading) {
return;
}
if (this.dontAskAgain) {
await this.stateService.setAutoConfirmFingerprints(true);
}
this.onConfirmedUser.emit();
if (this.dontAskAgain) {
await this.stateService.setAutoConfirmFingerprints(true);
}
this.onConfirmedUser.emit();
}
}

View File

@@ -1,45 +1,60 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="groupAccessTitle">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-header">
<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" appA11yTitle="{{'close' | i18n}}">
<span aria-hidden="true">&times;</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>{{'groupAccessUserDesc' | i18n}}</p>
<div *ngIf="!groups || !groups.length">
{{'noGroupsInList' | i18n}}
</div>
<table class="table table-hover table-list mb-0" *ngIf="groups && groups.length">
<tbody>
<tr *ngFor="let g of groups; let i = index">
<td class="table-list-checkbox" (click)="check(g)">
<input type="checkbox" [(ngModel)]="g.checked" name="Groups[{{i}}].Checked" appStopProp>
</td>
<td (click)="check(g)">
{{g.name}}
</td>
</tr>
</tbody>
</table>
</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 class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="modal-header">
<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"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</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>{{ "groupAccessUserDesc" | i18n }}</p>
<div *ngIf="!groups || !groups.length">
{{ "noGroupsInList" | i18n }}
</div>
<table class="table table-hover table-list mb-0" *ngIf="groups && groups.length">
<tbody>
<tr *ngFor="let g of groups; let i = index">
<td class="table-list-checkbox" (click)="check(g)">
<input
type="checkbox"
[(ngModel)]="g.checked"
name="Groups[{{ i }}].Checked"
appStopProp
/>
</td>
<td (click)="check(g)">
{{ g.name }}
</td>
</tr>
</tbody>
</table>
</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>

View File

@@ -1,85 +1,92 @@
import {
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { OrganizationUserUpdateGroupsRequest } from 'jslib-common/models/request/organizationUserUpdateGroupsRequest';
import { GroupResponse } from 'jslib-common/models/response/groupResponse';
import { OrganizationUserUpdateGroupsRequest } from "jslib-common/models/request/organizationUserUpdateGroupsRequest";
import { GroupResponse } from "jslib-common/models/response/groupResponse";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
@Component({
selector: 'app-user-groups',
templateUrl: 'user-groups.component.html',
selector: "app-user-groups",
templateUrl: "user-groups.component.html",
})
export class UserGroupsComponent implements OnInit {
@Input() name: string;
@Input() organizationUserId: string;
@Input() organizationId: string;
@Output() onSavedUser = new EventEmitter();
@Input() name: string;
@Input() organizationUserId: string;
@Input() organizationId: string;
@Output() onSavedUser = new EventEmitter();
loading = true;
groups: GroupResponse[] = [];
formPromise: Promise<any>;
loading = true;
groups: GroupResponse[] = [];
formPromise: Promise<any>;
constructor(private apiService: ApiService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private logService: LogService) { }
constructor(
private apiService: ApiService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private logService: LogService
) {}
async ngOnInit() {
const groupsResponse = await this.apiService.getGroups(this.organizationId);
const groups = groupsResponse.data.map(r => r);
groups.sort(Utils.getSortFunction(this.i18nService, 'name'));
this.groups = groups;
async ngOnInit() {
const groupsResponse = await this.apiService.getGroups(this.organizationId);
const groups = groupsResponse.data.map((r) => r);
groups.sort(Utils.getSortFunction(this.i18nService, "name"));
this.groups = groups;
try {
const userGroups = await this.apiService.getOrganizationUserGroups(
this.organizationId, this.organizationUserId);
if (userGroups != null && this.groups != null) {
userGroups.forEach(ug => {
const group = this.groups.filter(g => g.id === ug);
if (group != null && group.length > 0) {
(group[0] as any).checked = true;
}
});
}
} catch (e) {
this.logService.error(e);
}
this.loading = false;
try {
const userGroups = await this.apiService.getOrganizationUserGroups(
this.organizationId,
this.organizationUserId
);
if (userGroups != null && this.groups != null) {
userGroups.forEach((ug) => {
const group = this.groups.filter((g) => g.id === ug);
if (group != null && group.length > 0) {
(group[0] as any).checked = true;
}
});
}
} catch (e) {
this.logService.error(e);
}
check(g: GroupResponse, select?: boolean) {
(g as any).checked = select == null ? !(g as any).checked : select;
if (!(g as any).checked) {
(g as any).readOnly = false;
}
}
this.loading = false;
}
selectAll(select: boolean) {
this.groups.forEach(g => this.check(g, select));
check(g: GroupResponse, select?: boolean) {
(g as any).checked = select == null ? !(g as any).checked : select;
if (!(g as any).checked) {
(g as any).readOnly = false;
}
}
async submit() {
const request = new OrganizationUserUpdateGroupsRequest();
request.groupIds = this.groups.filter(g => (g as any).checked).map(g => g.id);
selectAll(select: boolean) {
this.groups.forEach((g) => this.check(g, select));
}
try {
this.formPromise = this.apiService.putOrganizationUserGroups(this.organizationId, this.organizationUserId,
request);
await this.formPromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('editedGroupsForUser', this.name));
this.onSavedUser.emit();
} catch (e) {
this.logService.error(e);
}
async submit() {
const request = new OrganizationUserUpdateGroupsRequest();
request.groupIds = this.groups.filter((g) => (g as any).checked).map((g) => g.id);
try {
this.formPromise = this.apiService.putOrganizationUserGroups(
this.organizationId,
this.organizationUserId,
request
);
await this.formPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("editedGroupsForUser", this.name)
);
this.onSavedUser.emit();
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -1,63 +1,59 @@
import {
Directive,
Input,
OnInit,
} from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Directive, Input, OnInit } from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms";
import { Organization } from 'jslib-common/models/domain/organization';
import { Organization } from "jslib-common/models/domain/organization";
import { PolicyType } from 'jslib-common/enums/policyType';
import { PolicyType } from "jslib-common/enums/policyType";
import { PolicyRequest } from 'jslib-common/models/request/policyRequest';
import { PolicyRequest } from "jslib-common/models/request/policyRequest";
import { PolicyResponse } from 'jslib-common/models/response/policyResponse';
import { PolicyResponse } from "jslib-common/models/response/policyResponse";
export abstract class BasePolicy {
abstract name: string;
abstract description: string;
abstract type: PolicyType;
abstract component: any;
abstract name: string;
abstract description: string;
abstract type: PolicyType;
abstract component: any;
display(organization: Organization) {
return true;
}
display(organization: Organization) {
return true;
}
}
@Directive()
export abstract class BasePolicyComponent implements OnInit {
@Input() policyResponse: PolicyResponse;
@Input() policy: BasePolicy;
@Input() policyResponse: PolicyResponse;
@Input() policy: BasePolicy;
enabled = new FormControl(false);
data: FormGroup = null;
enabled = new FormControl(false);
data: FormGroup = null;
ngOnInit(): void {
this.enabled.setValue(this.policyResponse.enabled);
ngOnInit(): void {
this.enabled.setValue(this.policyResponse.enabled);
if (this.policyResponse.data != null) {
this.loadData();
}
if (this.policyResponse.data != null) {
this.loadData();
}
}
loadData() {
this.data.patchValue(this.policyResponse.data ?? {});
}
buildRequestData() {
if (this.data != null) {
return this.data.value;
}
loadData() {
this.data.patchValue(this.policyResponse.data ?? {});
}
return null;
}
buildRequestData() {
if (this.data != null) {
return this.data.value;
}
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>) {
const request = new PolicyRequest();
request.enabled = this.enabled.value;
request.type = this.policy.type;
request.data = this.buildRequestData();
return null;
}
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>) {
const request = new PolicyRequest();
request.enabled = this.enabled.value;
request.type = this.policy.type;
request.data = this.buildRequestData();
return Promise.resolve(request);
}
return Promise.resolve(request);
}
}

View File

@@ -1,10 +1,16 @@
<app-callout type="warning">
{{'disableSendExemption' | i18n}}
{{ "disableSendExemption" | i18n }}
</app-callout>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled">
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="enabled"
[formControl]="enabled"
name="Enabled"
/>
<label class="form-check-label" for="enabled">{{ "enabled" | i18n }}</label>
</div>
</div>

View File

@@ -1,19 +1,18 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { PolicyType } from 'jslib-common/enums/policyType';
import { PolicyType } from "jslib-common/enums/policyType";
import { BasePolicy, BasePolicyComponent } from './base-policy.component';
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
export class DisableSendPolicy extends BasePolicy {
name = 'disableSend';
description = 'disableSendPolicyDesc';
type = PolicyType.DisableSend;
component = DisableSendPolicyComponent;
name = "disableSend";
description = "disableSendPolicyDesc";
type = PolicyType.DisableSend;
component = DisableSendPolicyComponent;
}
@Component({
selector: 'policy-disable-send',
templateUrl: 'disable-send.component.html',
selector: "policy-disable-send",
templateUrl: "disable-send.component.html",
})
export class DisableSendPolicyComponent extends BasePolicyComponent {
}
export class DisableSendPolicyComponent extends BasePolicyComponent {}

View File

@@ -1,46 +1,83 @@
<app-callout type="info" *ngIf="showKeyConnectorInfo">
{{'keyConnectorPolicyRestriction' | i18n}}
{{ "keyConnectorPolicyRestriction" | i18n }}
</app-callout>
<div [formGroup]="data">
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled">
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="enabled"
[formControl]="enabled"
name="Enabled"
/>
<label class="form-check-label" for="enabled">{{ "enabled" | i18n }}</label>
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="minComplexity">{{'minComplexityScore' | i18n}}</label>
<select id="minComplexity" name="minComplexity" formControlName="minComplexity" 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="minLength">{{'minLength' | i18n}}</label>
<input id="minLength" class="form-control" type="number" min="8" name="minLength"
formControlName="minLength">
</div>
<div class="row">
<div class="col-6 form-group">
<label for="minComplexity">{{ "minComplexityScore" | i18n }}</label>
<select
id="minComplexity"
name="minComplexity"
formControlName="minComplexity"
class="form-control"
>
<option *ngFor="let o of passwordScores" [ngValue]="o.value">{{ o.name }}</option>
</select>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="requireUpper" name="requireUpper"
formControlName="requireUpper">
<label class="form-check-label" for="requireUpper">A-Z</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="requireLower" name="requireLower"
formControlName="requireLower">
<label class="form-check-label" for="requireLower">a-z</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="requireNumbers" name="requireNumbers"
formControlName="requireNumbers">
<label class="form-check-label" for="requireNumbers">0-9</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="requireSpecial" name="requireSpecial"
formControlName="requireSpecial">
<label class="form-check-label" for="requireSpecial">!@#$%^&amp;*</label>
<div class="col-6 form-group">
<label for="minLength">{{ "minLength" | i18n }}</label>
<input
id="minLength"
class="form-control"
type="number"
min="8"
name="minLength"
formControlName="minLength"
/>
</div>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="requireUpper"
name="requireUpper"
formControlName="requireUpper"
/>
<label class="form-check-label" for="requireUpper">A-Z</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="requireLower"
name="requireLower"
formControlName="requireLower"
/>
<label class="form-check-label" for="requireLower">a-z</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="requireNumbers"
name="requireNumbers"
formControlName="requireNumbers"
/>
<label class="form-check-label" for="requireNumbers">0-9</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="requireSpecial"
name="requireSpecial"
formControlName="requireSpecial"
/>
<label class="form-check-label" for="requireSpecial">!@#$%^&amp;*</label>
</div>
</div>

View File

@@ -1,54 +1,57 @@
import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Component } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { OrganizationService } from "jslib-common/abstractions/organization.service";
import { PolicyType } from 'jslib-common/enums/policyType';
import { PolicyType } from "jslib-common/enums/policyType";
import { BasePolicy, BasePolicyComponent } from './base-policy.component';
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
export class MasterPasswordPolicy extends BasePolicy {
name = 'masterPass';
description = 'masterPassPolicyDesc';
type = PolicyType.MasterPassword;
component = MasterPasswordPolicyComponent;
name = "masterPass";
description = "masterPassPolicyDesc";
type = PolicyType.MasterPassword;
component = MasterPasswordPolicyComponent;
}
@Component({
selector: 'policy-master-password',
templateUrl: 'master-password.component.html',
selector: "policy-master-password",
templateUrl: "master-password.component.html",
})
export class MasterPasswordPolicyComponent extends BasePolicyComponent {
data = this.fb.group({
minComplexity: [null],
minLength: [null],
requireUpper: [null],
requireLower: [null],
requireNumbers: [null],
requireSpecial: [null],
});
data = this.fb.group({
minComplexity: [null],
minLength: [null],
requireUpper: [null],
requireLower: [null],
requireNumbers: [null],
requireSpecial: [null],
});
passwordScores: { name: string; value: number }[];
showKeyConnectorInfo: boolean = false;
passwordScores: { name: string; value: number; }[];
showKeyConnectorInfo: boolean = false;
constructor(
private fb: FormBuilder,
i18nService: I18nService,
private organizationService: OrganizationService
) {
super();
constructor(private fb: FormBuilder, i18nService: I18nService, private organizationService: OrganizationService) {
super();
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.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 },
];
}
async ngOnInit() {
super.ngOnInit();
const organization = await this.organizationService.get(this.policyResponse.organizationId);
this.showKeyConnectorInfo = organization.keyConnectorEnabled;
}
async ngOnInit() {
super.ngOnInit();
const organization = await this.organizationService.get(this.policyResponse.organizationId);
this.showKeyConnectorInfo = organization.keyConnectorEnabled;
}
}

View File

@@ -1,72 +1,144 @@
<div [formGroup]="data">
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled">
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="enabled"
[formControl]="enabled"
name="Enabled"
/>
<label class="form-check-label" for="enabled">{{ "enabled" | i18n }}</label>
</div>
</div>
<div class="row">
<div class="col-6 form-group mb-0">
<label for="defaultType">{{'defaultType' | i18n}}</label>
<select id="defaultType" name="defaultType" formControlName="defaultType" class="form-control">
<option *ngFor="let o of defaultTypes" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="row">
<div class="col-6 form-group mb-0">
<label for="defaultType">{{ "defaultType" | i18n }}</label>
<select
id="defaultType"
name="defaultType"
formControlName="defaultType"
class="form-control"
>
<option *ngFor="let o of defaultTypes" [ngValue]="o.value">{{ o.name }}</option>
</select>
</div>
<h3 class="mt-4">{{'password' | i18n}}</h3>
<div class="row">
<div class="col-6 form-group">
<label for="minLength">{{'minLength' | i18n}}</label>
<input id="minLength" class="form-control" type="number" name="minLength" min="5" max="128"
formControlName="minLength">
</div>
</div>
<h3 class="mt-4">{{ "password" | i18n }}</h3>
<div class="row">
<div class="col-6 form-group">
<label for="minLength">{{ "minLength" | i18n }}</label>
<input
id="minLength"
class="form-control"
type="number"
name="minLength"
min="5"
max="128"
formControlName="minLength"
/>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="minNumbers">{{'minNumbers' | i18n}}</label>
<input id="minNumbers" class="form-control" type="number" name="minNumbers" min="0" max="9"
formControlName="minNumbers">
</div>
<div class="col-6 form-group">
<label for="minSpecial">{{'minSpecial' | i18n}}</label>
<input id="minSpecial" class="form-control" type="number" name="minSpecial" min="0" max="9"
formControlName="minSpecial">
</div>
</div>
<div class="row">
<div class="col-6 form-group">
<label for="minNumbers">{{ "minNumbers" | i18n }}</label>
<input
id="minNumbers"
class="form-control"
type="number"
name="minNumbers"
min="0"
max="9"
formControlName="minNumbers"
/>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="useUpper"
formControlName="useUpper" name="useUpper">
<label class="form-check-label" for="useUpper">A-Z</label>
<div class="col-6 form-group">
<label for="minSpecial">{{ "minSpecial" | i18n }}</label>
<input
id="minSpecial"
class="form-control"
type="number"
name="minSpecial"
min="0"
max="9"
formControlName="minSpecial"
/>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="useLower" name="useLower" formControlName="useLower">
<label class="form-check-label" for="useLower">a-z</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="useNumbers" name="useNumbers" formControlName="useNumbers">
<label class="form-check-label" for="useNumbers">0-9</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="useSpecial" name="useSpecial" formControlName="useSpecial">
<label class="form-check-label" for="useSpecial">!@#$%^&amp;*</label>
</div>
<h3 class="mt-4">{{'passphrase' | i18n}}</h3>
<div class="row">
<div class="col-6 form-group">
<label for="minNumberWords">{{'minimumNumberOfWords' | i18n}}</label>
<input id="minNumberWords" class="form-control" type="number" name="minNumberWords" min="3" max="20"
formControlName="minNumberWords">
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="capitalize" name="capitalize"
formControlName="capitalize">
<label class="form-check-label" for="capitalize">{{'capitalize' | i18n}}</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="includeNumber" name="includeNumber"
formControlName="includeNumber">
<label class="form-check-label" for="includeNumber">{{'includeNumber' | i18n}}</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="useUpper"
formControlName="useUpper"
name="useUpper"
/>
<label class="form-check-label" for="useUpper">A-Z</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="useLower"
name="useLower"
formControlName="useLower"
/>
<label class="form-check-label" for="useLower">a-z</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="useNumbers"
name="useNumbers"
formControlName="useNumbers"
/>
<label class="form-check-label" for="useNumbers">0-9</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="useSpecial"
name="useSpecial"
formControlName="useSpecial"
/>
<label class="form-check-label" for="useSpecial">!@#$%^&amp;*</label>
</div>
<h3 class="mt-4">{{ "passphrase" | i18n }}</h3>
<div class="row">
<div class="col-6 form-group">
<label for="minNumberWords">{{ "minimumNumberOfWords" | i18n }}</label>
<input
id="minNumberWords"
class="form-control"
type="number"
name="minNumberWords"
min="3"
max="20"
formControlName="minNumberWords"
/>
</div>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="capitalize"
name="capitalize"
formControlName="capitalize"
/>
<label class="form-check-label" for="capitalize">{{ "capitalize" | i18n }}</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="includeNumber"
name="includeNumber"
formControlName="includeNumber"
/>
<label class="form-check-label" for="includeNumber">{{ "includeNumber" | i18n }}</label>
</div>
</div>

View File

@@ -1,48 +1,47 @@
import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Component } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PolicyType } from 'jslib-common/enums/policyType';
import { PolicyType } from "jslib-common/enums/policyType";
import { BasePolicy, BasePolicyComponent } from './base-policy.component';
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
export class PasswordGeneratorPolicy extends BasePolicy {
name = 'passwordGenerator';
description = 'passwordGeneratorPolicyDesc';
type = PolicyType.PasswordGenerator;
component = PasswordGeneratorPolicyComponent;
name = "passwordGenerator";
description = "passwordGeneratorPolicyDesc";
type = PolicyType.PasswordGenerator;
component = PasswordGeneratorPolicyComponent;
}
@Component({
selector: 'policy-password-generator',
templateUrl: 'password-generator.component.html',
selector: "policy-password-generator",
templateUrl: "password-generator.component.html",
})
export class PasswordGeneratorPolicyComponent extends BasePolicyComponent {
data = this.fb.group({
defaultType: [null],
minLength: [null],
useUpper: [null],
useLower: [null],
useNumbers: [null],
useSpecial: [null],
minNumbers: [null],
minSpecial: [null],
minNumberWords: [null],
capitalize: [null],
includeNumber: [null],
});
data = this.fb.group({
defaultType: [null],
minLength: [null],
useUpper: [null],
useLower: [null],
useNumbers: [null],
useSpecial: [null],
minNumbers: [null],
minSpecial: [null],
minNumberWords: [null],
capitalize: [null],
includeNumber: [null],
});
defaultTypes: { name: string; value: string }[];
defaultTypes: { name: string; value: string; }[];
constructor(private fb: FormBuilder, i18nService: I18nService) {
super();
constructor(private fb: FormBuilder, i18nService: I18nService) {
super();
this.defaultTypes = [
{ name: i18nService.t('userPreference'), value: null },
{ name: i18nService.t('password'), value: 'password' },
{ name: i18nService.t('passphrase'), value: 'passphrase' },
];
}
this.defaultTypes = [
{ name: i18nService.t("userPreference"), value: null },
{ name: i18nService.t("password"), value: "password" },
{ name: i18nService.t("passphrase"), value: "passphrase" },
];
}
}

View File

@@ -1,10 +1,18 @@
<app-callout type="warning">
{{'personalOwnershipExemption' | i18n}}
{{ "personalOwnershipExemption" | i18n }}
</app-callout>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled">
<label class="form-check-label" for="enabled">{{'personalOwnershipCheckboxDesc' | i18n}}</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="enabled"
[formControl]="enabled"
name="Enabled"
/>
<label class="form-check-label" for="enabled">{{
"personalOwnershipCheckboxDesc" | i18n
}}</label>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More