mirror of
https://github.com/bitwarden/browser
synced 2026-02-12 06:23:38 +00:00
Merge branch 'PS55-6-22' of https://github.com/bitwarden/clients into PS55-6-22
This commit is contained in:
@@ -9,5 +9,6 @@
|
||||
"dev": {
|
||||
"port": 8080,
|
||||
"allowedHosts": "auto"
|
||||
}
|
||||
},
|
||||
"flags": {}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"urls": {
|
||||
"icons": "https://icons.bitwarden.net",
|
||||
"notifications": "https://notifications.bitwarden.com"
|
||||
"notifications": "https://notifications.bitwarden.com",
|
||||
"scim": "https://scim.bitwarden.com"
|
||||
},
|
||||
"stripeKey": "pk_live_bpN0P37nMxrMQkcaHXtAybJk",
|
||||
"braintreeKey": "production_qfbsv8kc_njj2zjtyngtjmbjd",
|
||||
@@ -13,5 +14,9 @@
|
||||
"proxyApi": "https://api.bitwarden.com",
|
||||
"proxyIdentity": "https://identity.bitwarden.com",
|
||||
"proxyEvents": "https://events.bitwarden.com"
|
||||
},
|
||||
"flags": {
|
||||
"showTrial": false,
|
||||
"scim": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
{
|
||||
"urls": {
|
||||
"notifications": "http://localhost:61840"
|
||||
"notifications": "http://localhost:61840",
|
||||
"scim": "http://localhost:44559"
|
||||
},
|
||||
"dev": {
|
||||
"proxyApi": "http://localhost:4000",
|
||||
"proxyIdentity": "http://localhost:33656",
|
||||
"proxyEvents": "http://localhost:46273",
|
||||
"proxyNotifications": "http://localhost:61840"
|
||||
},
|
||||
"flags": {
|
||||
"showTrial": true,
|
||||
"scim": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
{
|
||||
"urls": {
|
||||
"icons": "https://icons.qa.bitwarden.pw",
|
||||
"notifications": "https://notifications.qa.bitwarden.pw"
|
||||
"notifications": "https://notifications.qa.bitwarden.pw",
|
||||
"scim": "https://scim.qa.bitwarden.pw"
|
||||
},
|
||||
"dev": {
|
||||
"proxyApi": "https://api.qa.bitwarden.pw",
|
||||
"proxyIdentity": "https://identity.qa.bitwarden.pw",
|
||||
"proxyEvents": "https://events.qa.bitwarden.pw"
|
||||
},
|
||||
"flags": {
|
||||
"showTrial": true,
|
||||
"scim": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,9 @@
|
||||
"proxyEvents": "http://localhost:46274",
|
||||
"proxyNotifications": "http://localhost:61841",
|
||||
"port": 8081
|
||||
},
|
||||
"flags": {
|
||||
"showTrial": false,
|
||||
"scim": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@bitwarden/web-vault",
|
||||
"version": "2022.6.0",
|
||||
"version": "2022.6.2",
|
||||
"scripts": {
|
||||
"build:oss": "webpack",
|
||||
"build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-7" *ngIf="layout">
|
||||
<div class="mt-5">
|
||||
@@ -112,156 +112,10 @@
|
||||
>
|
||||
{{ "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="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{
|
||||
'bwi-eye': !showPassword,
|
||||
'bwi-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="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-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="bwi bwi-spinner bwi-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
<a routerLink="/login" class="btn btn-outline-secondary btn-block ml-2 mt-0">
|
||||
{{ "cancel" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
<app-register-form
|
||||
[queryParamEmail]="email"
|
||||
[enforcedPolicyOptions]="enforcedPolicyOptions"
|
||||
></app-register-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -351,5 +205,5 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
@@ -7,6 +8,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { FormValidationErrorsService } from "@bitwarden/common/abstractions/formValidationErrors.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
@@ -25,6 +27,7 @@ import { RouterService } from "../services/router.service";
|
||||
templateUrl: "register.component.html",
|
||||
})
|
||||
export class RegisterComponent extends BaseRegisterComponent {
|
||||
email = "";
|
||||
showCreateOrgMessage = false;
|
||||
layout = "";
|
||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
@@ -32,6 +35,8 @@ export class RegisterComponent extends BaseRegisterComponent {
|
||||
private policies: Policy[];
|
||||
|
||||
constructor(
|
||||
formValidationErrorService: FormValidationErrorsService,
|
||||
formBuilder: FormBuilder,
|
||||
authService: AuthService,
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
@@ -47,6 +52,8 @@ export class RegisterComponent extends BaseRegisterComponent {
|
||||
private routerService: RouterService
|
||||
) {
|
||||
super(
|
||||
formValidationErrorService,
|
||||
formBuilder,
|
||||
authService,
|
||||
router,
|
||||
i18nService,
|
||||
@@ -126,24 +133,4 @@ export class RegisterComponent extends BaseRegisterComponent {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { NavigationEnd, Router } from "@angular/router";
|
||||
import * as jq from "jquery";
|
||||
import { IndividualConfig, ToastrService } from "ngx-toastr";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||
@@ -12,7 +13,7 @@ import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { EventService } from "@bitwarden/common/abstractions/event.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder.service";
|
||||
import { InternalFolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
|
||||
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
||||
@@ -23,7 +24,6 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { SyncService } from "@bitwarden/common/abstractions/sync.service";
|
||||
import { TokenService } from "@bitwarden/common/abstractions/token.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout.service";
|
||||
|
||||
import { DisableSendPolicy } from "./organizations/policies/disable-send.component";
|
||||
@@ -49,12 +49,12 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
private lastActivity: number = null;
|
||||
private idleTimer: number = null;
|
||||
private isIdle = false;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
@Inject(DOCUMENT) private document: Document,
|
||||
private broadcasterService: BroadcasterService,
|
||||
private tokenService: TokenService,
|
||||
private folderService: FolderService,
|
||||
private folderService: InternalFolderService,
|
||||
private settingsService: SettingsService,
|
||||
private syncService: SyncService,
|
||||
private passwordGenerationService: PasswordGenerationService,
|
||||
@@ -80,7 +80,9 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.document.documentElement.lang = this.i18nService.locale;
|
||||
this.i18nService.locale$.pipe(takeUntil(this.destroy$)).subscribe((locale) => {
|
||||
this.document.documentElement.lang = locale;
|
||||
});
|
||||
|
||||
this.ngZone.runOutsideAngular(() => {
|
||||
window.onmousemove = () => this.recordActivity();
|
||||
@@ -183,7 +185,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
});
|
||||
});
|
||||
|
||||
this.router.events.subscribe((event) => {
|
||||
this.router.events.pipe(takeUntil(this.destroy$)).subscribe((event) => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
const modals = Array.from(document.querySelectorAll(".modal"));
|
||||
for (const modal of modals) {
|
||||
@@ -199,13 +201,13 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
this.policyListService.addPolicies([
|
||||
new TwoFactorAuthenticationPolicy(),
|
||||
new MasterPasswordPolicy(),
|
||||
new ResetPasswordPolicy(),
|
||||
new PasswordGeneratorPolicy(),
|
||||
new SingleOrgPolicy(),
|
||||
new RequireSsoPolicy(),
|
||||
new PersonalOwnershipPolicy(),
|
||||
new DisableSendPolicy(),
|
||||
new SendOptionsPolicy(),
|
||||
new ResetPasswordPolicy(),
|
||||
]);
|
||||
|
||||
this.setFullWidth();
|
||||
@@ -213,6 +215,8 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
this.destroy$.next();
|
||||
this.destroy$.unsubscribe();
|
||||
}
|
||||
|
||||
private async logOut(expired: boolean) {
|
||||
|
||||
@@ -204,15 +204,18 @@ export abstract class BasePeopleComponent<
|
||||
this.edit(null);
|
||||
}
|
||||
|
||||
async remove(user: UserType) {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.deleteWarningMessage(user),
|
||||
protected async removeUserConfirmationDialog(user: UserType) {
|
||||
return this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("removeUserConfirmation"),
|
||||
this.userNamePipe.transform(user),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
}
|
||||
|
||||
async remove(user: UserType) {
|
||||
const confirmed = await this.removeUserConfirmationDialog(user);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
@@ -235,8 +238,8 @@ export abstract class BasePeopleComponent<
|
||||
async deactivate(user: UserType) {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.deactivateWarningMessage(),
|
||||
this.i18nService.t("deactivateUserId", this.userNamePipe.transform(user)),
|
||||
this.i18nService.t("deactivate"),
|
||||
this.i18nService.t("revokeUserId", this.userNamePipe.transform(user)),
|
||||
this.i18nService.t("revokeAccess"),
|
||||
this.i18nService.t("cancel"),
|
||||
"warning"
|
||||
);
|
||||
@@ -251,7 +254,7 @@ export abstract class BasePeopleComponent<
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("deactivatedUserId", this.userNamePipe.transform(user))
|
||||
this.i18nService.t("revokedUserId", this.userNamePipe.transform(user))
|
||||
);
|
||||
await this.load();
|
||||
} catch (e) {
|
||||
@@ -261,25 +264,13 @@ export abstract class BasePeopleComponent<
|
||||
}
|
||||
|
||||
async activate(user: UserType) {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.activateWarningMessage(),
|
||||
this.i18nService.t("activateUserId", this.userNamePipe.transform(user)),
|
||||
this.i18nService.t("activate"),
|
||||
this.i18nService.t("cancel"),
|
||||
"warning"
|
||||
);
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.actionPromise = this.activateUser(user.id);
|
||||
try {
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("activatedUserId", this.userNamePipe.transform(user))
|
||||
this.i18nService.t("restoredUserId", this.userNamePipe.transform(user))
|
||||
);
|
||||
await this.load();
|
||||
} catch (e) {
|
||||
@@ -395,7 +386,7 @@ export abstract class BasePeopleComponent<
|
||||
}
|
||||
|
||||
protected deactivateWarningMessage(): string {
|
||||
return this.i18nService.t("deactivateUserConfirmation");
|
||||
return this.i18nService.t("revokeUserConfirmation");
|
||||
}
|
||||
|
||||
protected activateWarningMessage(): string {
|
||||
|
||||
@@ -23,7 +23,6 @@ import { FilePasswordPromptComponent } from "../components/file-password-prompt.
|
||||
import { NestedCheckboxComponent } from "../components/nested-checkbox.component";
|
||||
import { OrganizationSwitcherComponent } from "../components/organization-switcher.component";
|
||||
import { PasswordRepromptComponent } from "../components/password-reprompt.component";
|
||||
import { PasswordStrengthComponent } from "../components/password-strength.component";
|
||||
import { PremiumBadgeComponent } from "../components/premium-badge.component";
|
||||
import { UserVerificationPromptComponent } from "../components/user-verification-prompt.component";
|
||||
import { FooterComponent } from "../layouts/footer.component";
|
||||
@@ -117,7 +116,6 @@ import { EmergencyAccessComponent } from "../settings/emergency-access.component
|
||||
import { EmergencyAddEditComponent } from "../settings/emergency-add-edit.component";
|
||||
import { OrganizationPlansComponent } from "../settings/organization-plans.component";
|
||||
import { PaymentMethodComponent } from "../settings/payment-method.component";
|
||||
import { PaymentComponent } from "../settings/payment.component";
|
||||
import { PreferencesComponent } from "../settings/preferences.component";
|
||||
import { PremiumComponent } from "../settings/premium.component";
|
||||
import { ProfileComponent } from "../settings/profile.component";
|
||||
@@ -128,7 +126,6 @@ import { SettingsComponent } from "../settings/settings.component";
|
||||
import { SponsoredFamiliesComponent } from "../settings/sponsored-families.component";
|
||||
import { SponsoringOrgRowComponent } from "../settings/sponsoring-org-row.component";
|
||||
import { SubscriptionComponent } from "../settings/subscription.component";
|
||||
import { TaxInfoComponent } from "../settings/tax-info.component";
|
||||
import { TwoFactorAuthenticatorComponent } from "../settings/two-factor-authenticator.component";
|
||||
import { TwoFactorDuoComponent } from "../settings/two-factor-duo.component";
|
||||
import { TwoFactorEmailComponent } from "../settings/two-factor-email.component";
|
||||
@@ -159,7 +156,9 @@ import { CollectionsComponent } from "../vault/collections.component";
|
||||
import { FolderAddEditComponent } from "../vault/folder-add-edit.component";
|
||||
import { ShareComponent } from "../vault/share.component";
|
||||
|
||||
import { OrganizationCreateModule } from "./organizations/create/organization-create.module";
|
||||
import { PipesModule } from "./pipes/pipes.module";
|
||||
import { RegisterFormModule } from "./register-form/register-form.module";
|
||||
import { SharedModule } from "./shared.module";
|
||||
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
|
||||
import { OrganizationBadgeModule } from "./vault/modules/organization-badge/organization-badge.module";
|
||||
@@ -167,7 +166,14 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga
|
||||
// Please do not add to this list of declarations - we should refactor these into modules when doing so makes sense until there are none left.
|
||||
// If you are building new functionality, please create or extend a feature module instead.
|
||||
@NgModule({
|
||||
imports: [SharedModule, VaultFilterModule, OrganizationBadgeModule, PipesModule],
|
||||
imports: [
|
||||
SharedModule,
|
||||
VaultFilterModule,
|
||||
OrganizationBadgeModule,
|
||||
PipesModule,
|
||||
OrganizationCreateModule,
|
||||
RegisterFormModule,
|
||||
],
|
||||
declarations: [
|
||||
PremiumBadgeComponent,
|
||||
AcceptEmergencyComponent,
|
||||
@@ -267,8 +273,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga
|
||||
PasswordRepromptComponent,
|
||||
FilePasswordPromptComponent,
|
||||
UserVerificationPromptComponent,
|
||||
PasswordStrengthComponent,
|
||||
PaymentComponent,
|
||||
PaymentMethodComponent,
|
||||
PersonalOwnershipPolicyComponent,
|
||||
PreferencesComponent,
|
||||
@@ -301,7 +305,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga
|
||||
SponsoringOrgRowComponent,
|
||||
SsoComponent,
|
||||
SubscriptionComponent,
|
||||
TaxInfoComponent,
|
||||
ToolsComponent,
|
||||
TwoFactorAuthenticationPolicyComponent,
|
||||
TwoFactorAuthenticatorComponent,
|
||||
@@ -423,8 +426,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga
|
||||
PasswordGeneratorPolicyComponent,
|
||||
PasswordRepromptComponent,
|
||||
FilePasswordPromptComponent,
|
||||
PasswordStrengthComponent,
|
||||
PaymentComponent,
|
||||
PaymentMethodComponent,
|
||||
PersonalOwnershipPolicyComponent,
|
||||
PreferencesComponent,
|
||||
@@ -457,7 +458,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga
|
||||
SponsoringOrgRowComponent,
|
||||
SsoComponent,
|
||||
SubscriptionComponent,
|
||||
TaxInfoComponent,
|
||||
ToolsComponent,
|
||||
TwoFactorAuthenticationPolicyComponent,
|
||||
TwoFactorAuthenticatorComponent,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { SharedModule } from "../../shared.module";
|
||||
|
||||
import { OrganizationInformationComponent } from "./organization-information.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule],
|
||||
declarations: [OrganizationInformationComponent],
|
||||
exports: [OrganizationInformationComponent],
|
||||
})
|
||||
export class OrganizationCreateModule {}
|
||||
@@ -0,0 +1,38 @@
|
||||
<form #form [formGroup]="formGroup" *ngIf="nameOnly">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "organizationName" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="name" />
|
||||
</bit-form-field>
|
||||
</form>
|
||||
<form #form [formGroup]="formGroup" *ngIf="!nameOnly">
|
||||
<h2>{{ "generalInformation" | i18n }}</h2>
|
||||
<div class="tw-flex tw-w-full tw-space-x-4" *ngIf="createOrganization">
|
||||
<bit-form-field class="tw-w-1/2">
|
||||
<bit-label>{{ "organizationName" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="name" />
|
||||
</bit-form-field>
|
||||
<bit-form-field class="tw-w-1/2">
|
||||
<bit-label>{{ "billingEmail" | i18n }}</bit-label>
|
||||
<input bitInput type="email" formControlName="billingEmail" />
|
||||
</bit-form-field>
|
||||
<bit-form-field class="tw-w-1/2" *ngIf="isProvider">
|
||||
<bit-label>{{ "clientOwnerEmail" | i18n }}</bit-label>
|
||||
<input bitInput type="email" formControlName="clientOwnerEmail" />
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<div *ngIf="!isProvider && !acceptingSponsorship">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="businessOwned"
|
||||
formControlName="businessOwned"
|
||||
(change)="changedBusinessOwned.emit()"
|
||||
/>
|
||||
<bit-label for="businessOwned" class="tw-mb-3">{{ "accountOwnedBusiness" | i18n }}</bit-label>
|
||||
<div class="tw-mt-4" *ngIf="formGroup.controls['businessOwned'].value">
|
||||
<bit-form-field class="tw-w-1/2">
|
||||
<bit-label>{{ "businessName" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="businessName" />
|
||||
</bit-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { FormGroup } from "@angular/forms";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-info",
|
||||
templateUrl: "organization-information.component.html",
|
||||
})
|
||||
export class OrganizationInformationComponent {
|
||||
@Input() nameOnly = false;
|
||||
@Input() createOrganization = true;
|
||||
@Input() isProvider = false;
|
||||
@Input() acceptingSponsorship = false;
|
||||
@Input() formGroup: FormGroup;
|
||||
@Output() changedBusinessOwned = new EventEmitter<void>();
|
||||
}
|
||||
@@ -64,9 +64,7 @@
|
||||
(click)="filterSelected(true)"
|
||||
>
|
||||
{{ "selected" | i18n }}
|
||||
<span class="badge badge-pill badge-info" *ngIf="selectedCount">{{
|
||||
selectedCount
|
||||
}}</span>
|
||||
<span bitBadge badgeType="info" *ngIf="selectedCount">{{ selectedCount }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -115,12 +113,14 @@
|
||||
<td>
|
||||
{{ u.email }}
|
||||
<span
|
||||
class="badge badge-secondary"
|
||||
bitBadge
|
||||
badgeType="secondary"
|
||||
*ngIf="u.status === organizationUserStatusType.Invited"
|
||||
>{{ "invited" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
class="badge badge-warning"
|
||||
bitBadge
|
||||
badgeType="warning"
|
||||
*ngIf="u.status === organizationUserStatusType.Accepted"
|
||||
>{{ "accepted" | i18n }}</span
|
||||
>
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
<form
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
class="tw-container tw-mx-auto"
|
||||
[formGroup]="formGroup"
|
||||
>
|
||||
<div>
|
||||
<div class="tw-mb-3">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "emailAddress" | i18n }}</bit-label>
|
||||
<input bitInput type="email" formControlName="email" />
|
||||
<bit-hint>{{ "emailAddressDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "name" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="name" />
|
||||
<bit-hint>{{ "yourNameDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3">
|
||||
<app-callout
|
||||
type="info"
|
||||
[enforcedPolicyOptions]="enforcedPolicyOptions"
|
||||
*ngIf="enforcedPolicyOptions"
|
||||
>
|
||||
</app-callout>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "masterPass" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
(input)="updatePasswordStrength()"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
formControlName="masterPassword"
|
||||
/>
|
||||
<button type="button" bitSuffix bitButton (click)="togglePassword()">
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="bwi bwi-lg bwi-eye"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
<bit-hint>
|
||||
<span class="tw-font-semibold">Important:</span>
|
||||
{{ "masterPassImportant" | i18n }}
|
||||
</bit-hint>
|
||||
</bit-form-field>
|
||||
<app-password-strength [score]="masterPasswordScore" [showText]="true">
|
||||
</app-password-strength>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "reTypeMasterPass" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
formControlName="confirmMasterPassword"
|
||||
/>
|
||||
<button type="button" bitSuffix bitButton (click)="togglePassword()">
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="bwi bwi-lg bwi-eye"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "masterPassHint" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="hint" />
|
||||
<bit-hint>{{ "masterPassHintDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
<div [hidden]="!showCaptcha()">
|
||||
<iframe id="hcaptcha_iframe" height="80"></iframe>
|
||||
</div>
|
||||
|
||||
<div class="tw-flex tw-items-start tw-mb-3" *ngIf="showTerms">
|
||||
<div class="tw-flex tw-items-center tw-h-6">
|
||||
<input
|
||||
class="tw-w-4 tw-rounded tw-border tw-border-gray-300"
|
||||
bitInput
|
||||
type="checkbox"
|
||||
formControlName="acceptPolicies"
|
||||
/>
|
||||
</div>
|
||||
<bit-label class="ml-2">
|
||||
{{ "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>
|
||||
</bit-label>
|
||||
</div>
|
||||
|
||||
<div class="tw-flex tw-mb-3">
|
||||
<bit-submit-button [loading]="form.loading">{{ "createAccount" | i18n }}</bit-submit-button>
|
||||
<a
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
routerLink="/login"
|
||||
class="tw-inline-flex tw-items-center tw-ml-3 tw-px-3"
|
||||
>
|
||||
<i class="bwi bwi-sign-in tw-mr-2"></i>
|
||||
{{ "logIn" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
<bit-error-summary *ngIf="showErrorSummary" [formGroup]="formGroup"></bit-error-summary>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/components/register.component";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { FormValidationErrorsService } from "@bitwarden/common/abstractions/formValidationErrors.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions";
|
||||
|
||||
@Component({
|
||||
selector: "app-register-form",
|
||||
templateUrl: "./register-form.component.html",
|
||||
})
|
||||
export class RegisterFormComponent extends BaseRegisterComponent {
|
||||
@Input() queryParamEmail: string;
|
||||
@Input() enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
|
||||
showErrorSummary = false;
|
||||
|
||||
constructor(
|
||||
formValidationErrorService: FormValidationErrorsService,
|
||||
formBuilder: FormBuilder,
|
||||
authService: AuthService,
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
stateService: StateService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
passwordGenerationService: PasswordGenerationService,
|
||||
private policyService: PolicyService,
|
||||
environmentService: EnvironmentService,
|
||||
logService: LogService
|
||||
) {
|
||||
super(
|
||||
formValidationErrorService,
|
||||
formBuilder,
|
||||
authService,
|
||||
router,
|
||||
i18nService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
stateService,
|
||||
platformUtilsService,
|
||||
passwordGenerationService,
|
||||
environmentService,
|
||||
logService
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
|
||||
if (this.queryParamEmail) {
|
||||
this.formGroup.get("email")?.setValue(this.queryParamEmail);
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (
|
||||
this.enforcedPolicyOptions != null &&
|
||||
!this.policyService.evaluateMasterPassword(
|
||||
this.masterPasswordScore,
|
||||
this.formGroup.get("masterPassword")?.value,
|
||||
this.enforcedPolicyOptions
|
||||
)
|
||||
) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("masterPasswordPolicyRequirementsNotMet")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await super.submit(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { SharedModule } from "../shared.module";
|
||||
|
||||
import { RegisterFormComponent } from "./register-form.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule],
|
||||
declarations: [RegisterFormComponent],
|
||||
exports: [RegisterFormComponent],
|
||||
})
|
||||
export class RegisterFormModule {}
|
||||
@@ -62,10 +62,14 @@ import {
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
FormFieldModule,
|
||||
MenuModule,
|
||||
SubmitButtonModule,
|
||||
MenuModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
import { PasswordStrengthComponent } from "../components/password-strength.component";
|
||||
import { PaymentComponent } from "../settings/payment.component";
|
||||
import { TaxInfoComponent } from "../settings/tax-info.component";
|
||||
|
||||
registerLocaleData(localeAf, "af");
|
||||
registerLocaleData(localeAz, "az");
|
||||
registerLocaleData(localeBe, "be");
|
||||
@@ -118,6 +122,7 @@ registerLocaleData(localeZhCn, "zh-CN");
|
||||
registerLocaleData(localeZhTw, "zh-TW");
|
||||
|
||||
@NgModule({
|
||||
declarations: [PasswordStrengthComponent, PaymentComponent, TaxInfoComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
DragDropModule,
|
||||
@@ -153,6 +158,9 @@ registerLocaleData(localeZhTw, "zh-TW");
|
||||
MenuModule,
|
||||
FormFieldModule,
|
||||
SubmitButtonModule,
|
||||
PasswordStrengthComponent,
|
||||
PaymentComponent,
|
||||
TaxInfoComponent,
|
||||
],
|
||||
providers: [DatePipe],
|
||||
bootstrap: [],
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<form #form [formGroup]="formGroup" [appApiAction]="formPromise" (ngSubmit)="submit()">
|
||||
<div class="tw-container tw-mb-3">
|
||||
<div class="tw-mb-6">
|
||||
<h2 class="tw-text-base tw-font-semibold tw-mb-3">{{ "billingPlanLabel" | i18n }}</h2>
|
||||
<div class="tw-items-center tw-mb-1" *ngFor="let selectablePlan of selectablePlans">
|
||||
<label class="tw-block tw- tw-text-main" for="interval{{ selectablePlan.type }}">
|
||||
<input
|
||||
checked
|
||||
class="tw-w-4 tw-h-4 tw-align-middle"
|
||||
id="interval{{ selectablePlan.type }}"
|
||||
name="plan"
|
||||
type="radio"
|
||||
[value]="selectablePlan.type"
|
||||
formControlName="plan"
|
||||
/>
|
||||
<ng-container *ngIf="selectablePlan.isAnnual">
|
||||
{{ "annual" | i18n }} -
|
||||
{{
|
||||
(selectablePlan.basePrice === 0 ? selectablePlan.seatPrice : selectablePlan.basePrice)
|
||||
| currency: "$"
|
||||
}}
|
||||
/{{ "yr" | i18n }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!selectablePlan.isAnnual">
|
||||
{{ "monthly" | i18n }} -
|
||||
{{
|
||||
(selectablePlan.basePrice === 0 ? selectablePlan.seatPrice : selectablePlan.basePrice)
|
||||
| currency: "$"
|
||||
}}
|
||||
/{{ "monthAbbr" | i18n }}
|
||||
</ng-container>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-4">
|
||||
<h2 class="tw-text-base tw-mb-3 tw-font-semibold">{{ "paymentType" | i18n }}</h2>
|
||||
<app-payment [hideCredit]="true" [trialFlow]="true"></app-payment>
|
||||
<app-tax-info [trialFlow]="true" (onCountryChanged)="changedCountry()"></app-tax-info>
|
||||
</div>
|
||||
|
||||
<div class="tw-flex tw-space-x-2">
|
||||
<bit-submit-button [loading]="form.loading">{{ "startTrial" | i18n }}</bit-submit-button>
|
||||
|
||||
<button bitButton type="button" buttonType="secondary" (click)="stepBack()">Back</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { FormBuilder, FormGroup } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy.service";
|
||||
import { SyncService } from "@bitwarden/common/abstractions/sync.service";
|
||||
|
||||
import { OrganizationPlansComponent } from "src/app/settings/organization-plans.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-billing",
|
||||
templateUrl: "./billing.component.html",
|
||||
})
|
||||
export class BillingComponent extends OrganizationPlansComponent {
|
||||
@Input() orgInfoForm: FormGroup;
|
||||
@Output() previousStep = new EventEmitter();
|
||||
|
||||
constructor(
|
||||
apiService: ApiService,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
cryptoService: CryptoService,
|
||||
router: Router,
|
||||
syncService: SyncService,
|
||||
policyService: PolicyService,
|
||||
organizationService: OrganizationService,
|
||||
logService: LogService,
|
||||
messagingService: MessagingService,
|
||||
formBuilder: FormBuilder
|
||||
) {
|
||||
super(
|
||||
apiService,
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
cryptoService,
|
||||
router,
|
||||
syncService,
|
||||
policyService,
|
||||
organizationService,
|
||||
logService,
|
||||
messagingService,
|
||||
formBuilder
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.formGroup.patchValue({
|
||||
name: this.orgInfoForm.get("name")?.value,
|
||||
billingEmail: this.orgInfoForm.get("email")?.value,
|
||||
additionalSeats: 1,
|
||||
plan: this.plan,
|
||||
product: this.product,
|
||||
});
|
||||
this.isInTrialFlow = true;
|
||||
await super.ngOnInit();
|
||||
}
|
||||
|
||||
stepBack() {
|
||||
this.previousStep.emit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<div class="tw-pl-6 tw-pb-6">
|
||||
<p class="tw-text-xl">{{ "trialThankYou" | i18n: orgLabel }}</p>
|
||||
<ul class="tw-list-disc">
|
||||
<li>
|
||||
<p>
|
||||
{{ "trialConfirmationEmail" | i18n }}
|
||||
<span class="tw-font-bold">{{ email }}</span
|
||||
>.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
{{ "trialPaidInfoMessage" | i18n: orgLabel }}
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-trial-confirmation-details",
|
||||
templateUrl: "confirmation-details.component.html",
|
||||
})
|
||||
export class ConfirmationDetailsComponent {
|
||||
@Input() email: string;
|
||||
@Input() orgLabel: string;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<h1 class="!tw-text-alt2">You've chosen Bitwarden for Enterprise</h1>
|
||||
<div class="tw-pt-24">
|
||||
<h2>What you can do with Bitwarden for Enterprise</h2>
|
||||
</div>
|
||||
|
||||
<div class="tw-text-3xl tw-text-main tw-mt-12">
|
||||
<p class="tw-mt-2.5 tw-mb-20">Collaborate and share securely</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Deploy and manage quickly and easily</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Access anywhere on any device</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Create your account to get started</p>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-enterprise-content",
|
||||
templateUrl: "enterprise-content.component.html",
|
||||
})
|
||||
export class EnterpriseContentComponent {}
|
||||
@@ -0,0 +1,13 @@
|
||||
<h1 class="!tw-text-alt2">You've chosen Bitwarden for Families</h1>
|
||||
<div class="tw-pt-24">
|
||||
<h2>
|
||||
Trusted by millions of individuals, teams, and organizations worldwide for secure password
|
||||
storage and sharing.
|
||||
</h2>
|
||||
</div>
|
||||
<div class="tw-text-3xl tw-text-main tw-mt-12">
|
||||
<p class="tw-mt-2.5 tw-mb-20">Collaborate and share securely</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Deploy and manage quickly and easily</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Access anywhere on any device</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Create your account to get started</p>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-families-content",
|
||||
templateUrl: "families-content.component.html",
|
||||
})
|
||||
export class FamiliesContentComponent {}
|
||||
@@ -0,0 +1,10 @@
|
||||
<h1 class="!tw-text-alt2">You've chosen Bitwarden for Teams</h1>
|
||||
<div class="tw-pt-24">
|
||||
<h2>What you can do with Btiwarden for Teams</h2>
|
||||
</div>
|
||||
<div class="tw-text-3xl tw-text-main tw-mt-12">
|
||||
<p class="tw-mt-2.5 tw-mb-20">Collaborate and share securely</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Deploy and manage quickly and easily</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Access anywhere on any device</p>
|
||||
<p class="tw-mt-2.5 tw-mb-20">Create your account to get started</p>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-teams-content",
|
||||
templateUrl: "teams-content.component.html",
|
||||
})
|
||||
export class TeamsContentComponent {}
|
||||
@@ -0,0 +1,94 @@
|
||||
<div *ngIf="accountCreateOnly" class="">
|
||||
<h1 class="tw-text-xl tw-text-center tw-mt-12" *ngIf="!layout">{{ "createAccount" | i18n }}</h1>
|
||||
<div
|
||||
class="tw-m-auto tw-rounded tw-border tw-border-solid tw-bg-background tw-border-secondary-300 tw-min-w-xl tw-max-w-xl tw-p-8"
|
||||
>
|
||||
<app-register-form
|
||||
[queryParamEmail]="email"
|
||||
[enforcedPolicyOptions]="enforcedPolicyOptions"
|
||||
></app-register-form>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!accountCreateOnly">
|
||||
<div
|
||||
class="tw-bg-background-alt2 tw-h-96 tw--mt-48 tw-absolute tw--skew-y-3 tw-w-full tw--z-10"
|
||||
></div>
|
||||
<div class="tw-flex tw-max-w-screen-xl tw-min-w-4xl tw-mx-auto tw-px-4">
|
||||
<div class="tw-w-1/2">
|
||||
<img
|
||||
alt="Bitwarden"
|
||||
style="height: 50px; width: 335px"
|
||||
class="tw-mt-6"
|
||||
src="../../images/register-layout/logo-horizontal-white.svg"
|
||||
/>
|
||||
<!-- This is to for illustrative purposes and content will be replaced by marketing -->
|
||||
<div class="tw-pt-12">
|
||||
<!-- Teams Body -->
|
||||
<app-teams-content *ngIf="org === 'teams'"></app-teams-content>
|
||||
<!-- Enterprise Body -->
|
||||
<app-enterprise-content *ngIf="org === 'enterprise'"></app-enterprise-content>
|
||||
<!-- Families Body -->
|
||||
<app-families-content *ngIf="org === 'families'"></app-families-content>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tw-w-1/2">
|
||||
<div class="tw-pt-56">
|
||||
<div class="tw-rounded tw-border tw-border-solid tw-bg-background tw-border-secondary-300">
|
||||
<div class="tw-h-12 tw-flex tw-items-center tw-rounded-t tw-bg-secondary-100 tw-w-full">
|
||||
<h2 class="tw-uppercase tw-pl-4 tw-text-base tw-mb-0 tw-font-bold">
|
||||
Start your 7-Day free trial of Bitwarden for {{ org }}
|
||||
</h2>
|
||||
</div>
|
||||
<app-vertical-stepper #stepper linear (selectionChange)="stepSelectionChange($event)">
|
||||
<app-vertical-step label="Create Account" [editable]="false" [subLabel]="email">
|
||||
<app-register-form
|
||||
[isInTrialFlow]="true"
|
||||
(createdAccount)="createdAccount($event)"
|
||||
></app-register-form>
|
||||
</app-vertical-step>
|
||||
<app-vertical-step label="Organization Information" [subLabel]="orgInfoSubLabel">
|
||||
<app-org-info [nameOnly]="true" [formGroup]="orgInfoFormGroup"></app-org-info>
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
[disabled]="orgInfoFormGroup.get('name').hasError('required')"
|
||||
cdkStepperNext
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</app-vertical-step>
|
||||
<app-vertical-step label="Billing" [subLabel]="billingSubLabel">
|
||||
<app-billing
|
||||
*ngIf="stepper.selectedIndex === 2"
|
||||
[plan]="plan"
|
||||
[product]="product"
|
||||
[orgInfoForm]="orgInfoFormGroup"
|
||||
(previousStep)="previousStep()"
|
||||
(onTrialBillingSuccess)="billingSuccess($event)"
|
||||
></app-billing>
|
||||
</app-vertical-step>
|
||||
<app-vertical-step label="Confirmation Details" [applyBorder]="false">
|
||||
<app-trial-confirmation-details
|
||||
[email]="email"
|
||||
[orgLabel]="orgLabel"
|
||||
></app-trial-confirmation-details>
|
||||
<div class="tw-flex tw-mb-3">
|
||||
<button bitButton buttonType="primary" (click)="navigateToOrgVault()">
|
||||
Get Started
|
||||
</button>
|
||||
<button
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
(click)="navigateToOrgInvite()"
|
||||
class="tw-inline-flex tw-items-center tw-ml-3 tw-px-3"
|
||||
>
|
||||
Invite Users
|
||||
</button>
|
||||
</div>
|
||||
</app-vertical-step>
|
||||
</app-vertical-stepper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,145 @@
|
||||
import { StepperSelectionEvent } from "@angular/cdk/stepper";
|
||||
import { TitleCasePipe } from "@angular/common";
|
||||
import { Component, OnInit, ViewChild } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { PlanType } from "@bitwarden/common/enums/planType";
|
||||
import { ProductType } from "@bitwarden/common/enums/productType";
|
||||
import { PolicyData } from "@bitwarden/common/models/data/policyData";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions";
|
||||
import { Policy } from "@bitwarden/common/models/domain/policy";
|
||||
|
||||
import { VerticalStepperComponent } from "../vertical-stepper/vertical-stepper.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-trial",
|
||||
templateUrl: "trial-initiation.component.html",
|
||||
})
|
||||
export class TrialInitiationComponent implements OnInit {
|
||||
email = "";
|
||||
org = "teams";
|
||||
orgInfoSubLabel = "";
|
||||
orgId = "";
|
||||
orgLabel = "";
|
||||
billingSubLabel = "";
|
||||
plan: PlanType;
|
||||
product: ProductType;
|
||||
accountCreateOnly = true;
|
||||
policies: Policy[];
|
||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
@ViewChild("stepper", { static: false }) verticalStepper: VerticalStepperComponent;
|
||||
|
||||
orgInfoFormGroup = this.formBuilder.group({
|
||||
name: ["", [Validators.required]],
|
||||
email: [""],
|
||||
});
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
protected router: Router,
|
||||
private formBuilder: FormBuilder,
|
||||
private titleCasePipe: TitleCasePipe,
|
||||
private stateService: StateService,
|
||||
private apiService: ApiService,
|
||||
private logService: LogService,
|
||||
private policyService: PolicyService,
|
||||
private i18nService: I18nService
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.route.queryParams.pipe(first()).subscribe((qParams) => {
|
||||
if (qParams.email != null && qParams.email.indexOf("@") > -1) {
|
||||
this.email = qParams.email;
|
||||
}
|
||||
|
||||
if (!qParams.org) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.org = qParams.org;
|
||||
this.orgLabel = this.titleCasePipe.transform(this.org);
|
||||
this.accountCreateOnly = false;
|
||||
|
||||
if (qParams.org === "families") {
|
||||
this.plan = PlanType.FamiliesAnnually;
|
||||
this.product = ProductType.Families;
|
||||
} else if (qParams.org === "teams") {
|
||||
this.plan = PlanType.TeamsAnnually;
|
||||
this.product = ProductType.Teams;
|
||||
} else if (qParams.org === "enterprise") {
|
||||
this.plan = PlanType.EnterpriseAnnually;
|
||||
this.product = ProductType.Enterprise;
|
||||
}
|
||||
});
|
||||
|
||||
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 (this.policies != null) {
|
||||
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(
|
||||
this.policies
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
stepSelectionChange(event: StepperSelectionEvent) {
|
||||
// Set org info sub label
|
||||
if (event.selectedIndex === 1 && this.orgInfoFormGroup.controls.name.value === "") {
|
||||
this.orgInfoSubLabel =
|
||||
"Enter your " + this.titleCasePipe.transform(this.org) + " organization information";
|
||||
} else if (event.previouslySelectedIndex === 1) {
|
||||
this.orgInfoSubLabel = this.orgInfoFormGroup.controls.name.value;
|
||||
}
|
||||
|
||||
//set billing sub label
|
||||
if (event.selectedIndex === 2) {
|
||||
this.billingSubLabel = this.i18nService.t("billingTrialSubLabel");
|
||||
}
|
||||
}
|
||||
|
||||
createdAccount(email: string) {
|
||||
this.email = email;
|
||||
this.orgInfoFormGroup.get("email")?.setValue(email);
|
||||
this.verticalStepper.next();
|
||||
}
|
||||
|
||||
billingSuccess(event: any) {
|
||||
this.orgId = event?.orgId;
|
||||
this.billingSubLabel = event?.subLabelText;
|
||||
this.verticalStepper.next();
|
||||
}
|
||||
|
||||
navigateToOrgVault() {
|
||||
this.router.navigate(["organizations", this.orgId, "vault"]);
|
||||
}
|
||||
|
||||
navigateToOrgInvite() {
|
||||
this.router.navigate(["organizations", this.orgId, "manage", "people"]);
|
||||
}
|
||||
|
||||
previousStep() {
|
||||
this.verticalStepper.previous();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { CdkStepperModule } from "@angular/cdk/stepper";
|
||||
import { TitleCasePipe } from "@angular/common";
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { FormFieldModule } from "@bitwarden/components";
|
||||
|
||||
import { OrganizationCreateModule } from "../organizations/create/organization-create.module";
|
||||
import { RegisterFormModule } from "../register-form/register-form.module";
|
||||
import { SharedModule } from "../shared.module";
|
||||
import { VerticalStepperModule } from "../vertical-stepper/vertical-stepper.module";
|
||||
|
||||
import { BillingComponent } from "./billing.component";
|
||||
import { ConfirmationDetailsComponent } from "./confirmation-details.component";
|
||||
import { EnterpriseContentComponent } from "./enterprise-content.component";
|
||||
import { FamiliesContentComponent } from "./families-content.component";
|
||||
import { TeamsContentComponent } from "./teams-content.component";
|
||||
import { TrialInitiationComponent } from "./trial-initiation.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
CdkStepperModule,
|
||||
VerticalStepperModule,
|
||||
FormFieldModule,
|
||||
RegisterFormModule,
|
||||
OrganizationCreateModule,
|
||||
],
|
||||
declarations: [
|
||||
TrialInitiationComponent,
|
||||
EnterpriseContentComponent,
|
||||
FamiliesContentComponent,
|
||||
TeamsContentComponent,
|
||||
ConfirmationDetailsComponent,
|
||||
BillingComponent,
|
||||
],
|
||||
exports: [TrialInitiationComponent],
|
||||
providers: [TitleCasePipe],
|
||||
})
|
||||
export class TrialInitiationModule {}
|
||||
@@ -23,7 +23,7 @@
|
||||
<li
|
||||
*ngFor="let c of collections"
|
||||
[ngClass]="{
|
||||
active: c.node.id === activeFilter.selectedCollectionId
|
||||
active: c.node.id === activeFilter.selectedCollectionId && activeFilter.selectedCollection
|
||||
}"
|
||||
class="filter-option"
|
||||
>
|
||||
|
||||
@@ -129,6 +129,12 @@
|
||||
<button class="filter-button" (click)="applyOrganizationFilter(organization)">
|
||||
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
|
||||
{{ organization.name }}
|
||||
<i
|
||||
*ngIf="!organization.enabled"
|
||||
class="bwi bwi-fw bwi-exclamation-triangle text-danger"
|
||||
aria-label="{{ 'organizationIsDisabled' | i18n }}"
|
||||
appA11yTitle="{{ 'organizationIsDisabled' | i18n }}"
|
||||
></i>
|
||||
</button>
|
||||
<ng-container>
|
||||
<button [bitMenuTriggerFor]="orgMenu" class="org-options ml-auto">
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { OrganizationFilterComponent as BaseOrganizationFilterComponent } from "@bitwarden/angular/modules/vault-filter/components/organization-filter.component";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
|
||||
@Component({
|
||||
selector: "app-organization-filter",
|
||||
@@ -8,4 +11,24 @@ import { OrganizationFilterComponent as BaseOrganizationFilterComponent } from "
|
||||
})
|
||||
export class OrganizationFilterComponent extends BaseOrganizationFilterComponent {
|
||||
displayText = "allVaults";
|
||||
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async applyOrganizationFilter(organization: Organization) {
|
||||
if (organization.enabled) {
|
||||
//proceed with default behaviour for enabled organizations
|
||||
super.applyOrganizationFilter(organization);
|
||||
} else {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
null,
|
||||
this.i18nService.t("disabledOrganizationFilterError")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ export class OrganizationOptionsComponent {
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// Remove reset password
|
||||
const request = new OrganizationUserResetPasswordEnrollmentRequest();
|
||||
request.masterPasswordHash = "ignored";
|
||||
request.resetPasswordKey = null;
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
[hide]="hideFolders"
|
||||
[activeFilter]="activeFilter"
|
||||
[collapsedFilterNodes]="collapsedFilterNodes"
|
||||
[folderNodes]="folders"
|
||||
[folderNodes]="folders$ | async"
|
||||
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
|
||||
(onFilterChange)="applyFilter($event)"
|
||||
(onAddFolder)="addFolder()"
|
||||
|
||||
@@ -6,7 +6,8 @@ import { VaultFilterService as BaseVaultFilterService } from "@bitwarden/angular
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
@@ -27,6 +28,7 @@ export class VaultFilterService extends BaseVaultFilterService {
|
||||
cipherService: CipherService,
|
||||
collectionService: CollectionService,
|
||||
policyService: PolicyService,
|
||||
private i18nService: I18nService,
|
||||
protected apiService: ApiService
|
||||
) {
|
||||
super(
|
||||
@@ -69,6 +71,11 @@ export class VaultFilterService extends BaseVaultFilterService {
|
||||
result = await this.collectionService.decryptMany(collectionDomains);
|
||||
}
|
||||
|
||||
const noneCollection = new CollectionView();
|
||||
noneCollection.name = this.i18nService.t("unassigned");
|
||||
noneCollection.organizationId = organizationId;
|
||||
result.push(noneCollection);
|
||||
|
||||
const nestedCollections = await this.collectionService.getAllNested(result);
|
||||
return new DynamicTreeNode<CollectionView>({
|
||||
fullList: result,
|
||||
|
||||
@@ -173,7 +173,10 @@ export class IndividualVaultComponent implements OnInit, OnDestroy {
|
||||
async applyVaultFilter(vaultFilter: VaultFilter) {
|
||||
this.ciphersComponent.showAddNew = vaultFilter.status !== "trash";
|
||||
this.activeFilter = vaultFilter;
|
||||
await this.ciphersComponent.reload(this.buildFilter(), vaultFilter.status === "trash");
|
||||
await this.ciphersComponent.reload(
|
||||
this.activeFilter.buildFilter(),
|
||||
vaultFilter.status === "trash"
|
||||
);
|
||||
this.filterComponent.searchPlaceholder = this.vaultService.calculateSearchBarLocalizationString(
|
||||
this.activeFilter
|
||||
);
|
||||
@@ -196,40 +199,6 @@ export class IndividualVaultComponent implements OnInit, OnDestroy {
|
||||
this.ciphersComponent.search(200);
|
||||
}
|
||||
|
||||
private buildFilter(): (cipher: CipherView) => boolean {
|
||||
return (cipher) => {
|
||||
let cipherPassesFilter = true;
|
||||
if (this.activeFilter.status === "favorites" && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.favorite;
|
||||
}
|
||||
if (this.activeFilter.status === "trash" && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.isDeleted;
|
||||
}
|
||||
if (this.activeFilter.cipherType != null && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.type === this.activeFilter.cipherType;
|
||||
}
|
||||
if (
|
||||
this.activeFilter.selectedFolder &&
|
||||
this.activeFilter.selectedFolderId != "none" &&
|
||||
cipherPassesFilter
|
||||
) {
|
||||
cipherPassesFilter = cipher.folderId === this.activeFilter.selectedFolderId;
|
||||
}
|
||||
if (this.activeFilter.selectedCollectionId != null && cipherPassesFilter) {
|
||||
cipherPassesFilter =
|
||||
cipher.collectionIds != null &&
|
||||
cipher.collectionIds.indexOf(this.activeFilter.selectedCollectionId) > -1;
|
||||
}
|
||||
if (this.activeFilter.selectedOrganizationId != null && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.organizationId === this.activeFilter.selectedOrganizationId;
|
||||
}
|
||||
if (this.activeFilter.myVaultOnly && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.organizationId === null;
|
||||
}
|
||||
return cipherPassesFilter;
|
||||
};
|
||||
}
|
||||
|
||||
async editCipherAttachments(cipher: CipherView) {
|
||||
const canAccessPremium = await this.stateService.getCanAccessPremium();
|
||||
if (cipher.organizationId == null && !canAccessPremium) {
|
||||
|
||||
@@ -162,46 +162,15 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
|
||||
async applyVaultFilter(vaultFilter: VaultFilter) {
|
||||
this.ciphersComponent.showAddNew = vaultFilter.status !== "trash";
|
||||
this.activeFilter = vaultFilter;
|
||||
await this.ciphersComponent.reload(this.buildFilter(), vaultFilter.status === "trash");
|
||||
await this.ciphersComponent.reload(
|
||||
this.activeFilter.buildFilter(),
|
||||
vaultFilter.status === "trash"
|
||||
);
|
||||
this.vaultFilterComponent.searchPlaceholder =
|
||||
this.vaultService.calculateSearchBarLocalizationString(this.activeFilter);
|
||||
this.go();
|
||||
}
|
||||
|
||||
private buildFilter(): (cipher: CipherView) => boolean {
|
||||
return (cipher) => {
|
||||
let cipherPassesFilter = true;
|
||||
if (this.activeFilter.status === "favorites" && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.favorite;
|
||||
}
|
||||
if (this.deleted && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.isDeleted;
|
||||
}
|
||||
if (this.activeFilter.cipherType != null && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.type === this.activeFilter.cipherType;
|
||||
}
|
||||
if (
|
||||
this.activeFilter.selectedFolder != null &&
|
||||
this.activeFilter.selectedFolderId != "none" &&
|
||||
cipherPassesFilter
|
||||
) {
|
||||
cipherPassesFilter = cipher.folderId === this.activeFilter.selectedFolderId;
|
||||
}
|
||||
if (this.activeFilter.selectedCollectionId != null && cipherPassesFilter) {
|
||||
cipherPassesFilter =
|
||||
cipher.collectionIds != null &&
|
||||
cipher.collectionIds.indexOf(this.activeFilter.selectedCollectionId) > -1;
|
||||
}
|
||||
if (this.activeFilter.selectedOrganizationId != null && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.organizationId === this.activeFilter.selectedOrganizationId;
|
||||
}
|
||||
if (this.activeFilter.myVaultOnly && cipherPassesFilter) {
|
||||
cipherPassesFilter = cipher.organizationId === null;
|
||||
}
|
||||
return cipherPassesFilter;
|
||||
};
|
||||
}
|
||||
|
||||
filterSearchText(searchText: string) {
|
||||
this.ciphersComponent.searchText = searchText;
|
||||
this.ciphersComponent.search(200);
|
||||
@@ -242,7 +211,7 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
|
||||
if (this.organization.canEditAnyCollection) {
|
||||
comp.collectionIds = cipher.collectionIds;
|
||||
comp.collections = this.vaultFilterComponent.collections.fullList.filter(
|
||||
(c) => !c.readOnly
|
||||
(c) => !c.readOnly && c.id != null
|
||||
);
|
||||
}
|
||||
comp.organization = this.organization;
|
||||
@@ -261,7 +230,7 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
|
||||
component.type = this.type;
|
||||
if (this.organization.canEditAnyCollection) {
|
||||
component.collections = this.vaultFilterComponent.collections.fullList.filter(
|
||||
(c) => !c.readOnly
|
||||
(c) => !c.readOnly && c.id != null
|
||||
);
|
||||
}
|
||||
if (this.collectionId != null) {
|
||||
@@ -316,7 +285,7 @@ export class OrganizationVaultComponent implements OnInit, OnDestroy {
|
||||
component.organizationId = this.organization.id;
|
||||
if (this.organization.canEditAnyCollection) {
|
||||
component.collections = this.vaultFilterComponent.collections.fullList.filter(
|
||||
(c) => !c.readOnly
|
||||
(c) => !c.readOnly && c.id != null
|
||||
);
|
||||
}
|
||||
// Regardless of Admin state, the collection Ids need to passed manually as they are not assigned value
|
||||
|
||||
@@ -14,7 +14,7 @@ export class VaultService {
|
||||
if (vaultFilter.selectedFolderId != null && vaultFilter.selectedFolderId != "none") {
|
||||
return "searchFolder";
|
||||
}
|
||||
if (vaultFilter.selectedCollectionId != null) {
|
||||
if (vaultFilter.selectedCollection) {
|
||||
return "searchCollection";
|
||||
}
|
||||
if (vaultFilter.selectedOrganizationId != null) {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<div class="tw-m-2.5 tw-text-center tw-h-16">
|
||||
<button
|
||||
(click)="selectStep()"
|
||||
[disabled]="disabled"
|
||||
class="tw-w-full tw-flex tw-border-none tw-bg-transparent tw-items-center"
|
||||
[ngClass]="{
|
||||
'hover:tw-bg-secondary-100': !disabled && step.editable
|
||||
}"
|
||||
[attr.aria-expanded]="selected"
|
||||
>
|
||||
<span
|
||||
class="tw-rounded-full tw-font-bold tw-leading-9 tw-mr-3.5 tw-w-9"
|
||||
*ngIf="!step.completed"
|
||||
[ngClass]="{
|
||||
'tw-text-contrast tw-bg-primary-500': selected,
|
||||
'tw-text-main tw-bg-secondary-300': !selected && !disabled && step.editable,
|
||||
'tw-text-muted tw-bg-transparent': disabled
|
||||
}"
|
||||
>
|
||||
{{ stepNumber }}
|
||||
</span>
|
||||
<span
|
||||
class="tw-text-contrast tw-bg-primary-500 tw-rounded-full tw-font-bold tw-leading-9 tw-mr-3.5 tw-w-9"
|
||||
*ngIf="step.completed"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-check tw-p-1" aria-hidden="true"></i>
|
||||
</span>
|
||||
<div
|
||||
class="tw-text-left tw-txt-main tw-leading-snug tw-h-12 tw-mt-3.5"
|
||||
[ngClass]="{
|
||||
'tw-font-bold': selected
|
||||
}"
|
||||
>
|
||||
<p
|
||||
class="main-label text tw-text-main tw-mb-1"
|
||||
[ngClass]="{
|
||||
'tw-mt-1': !step.subLabel
|
||||
}"
|
||||
>
|
||||
{{ step.label }}
|
||||
</p>
|
||||
<p class="sub-label small tw-text-muted">{{ step.subLabel }}</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
|
||||
import { VerticalStep } from "./vertical-step.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-vertical-step-content",
|
||||
templateUrl: "vertical-step-content.component.html",
|
||||
})
|
||||
export class VerticalStepContentComponent {
|
||||
@Output() onSelectStep = new EventEmitter<void>();
|
||||
|
||||
@Input() disabled = false;
|
||||
@Input() selected = false;
|
||||
@Input() step: VerticalStep;
|
||||
@Input() stepNumber: number;
|
||||
|
||||
selectStep() {
|
||||
this.onSelectStep.emit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<ng-template>
|
||||
<div
|
||||
class="tw-pl-7 tw-inline-block tw-w-11/12"
|
||||
[ngClass]="{ 'tw-border-0 tw-border-l tw-border-solid tw-border-secondary-300': applyBorder }"
|
||||
>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { CdkStep } from "@angular/cdk/stepper";
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-vertical-step",
|
||||
templateUrl: "vertical-step.component.html",
|
||||
providers: [{ provide: CdkStep, useExisting: VerticalStep }],
|
||||
})
|
||||
export class VerticalStep extends CdkStep {
|
||||
@Input() subLabel = "";
|
||||
@Input() applyBorder = true;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<div>
|
||||
<ul class="tw-flex tw-list-none tw-flex-col tw-flex-wrap tw-p-5">
|
||||
<li *ngFor="let step of steps; let i = index; let isLast = last">
|
||||
<app-vertical-step-content
|
||||
[disabled]="isStepDisabled(i)"
|
||||
[selected]="selectedIndex === i"
|
||||
[step]="step"
|
||||
[stepNumber]="i + 1"
|
||||
(onSelectStep)="selectStepByIndex(i)"
|
||||
></app-vertical-step-content>
|
||||
<div
|
||||
class="tw-pl-7 tw-inline-block"
|
||||
*ngIf="selectedIndex === i"
|
||||
[ngTemplateOutlet]="selected ? selected.content : null"
|
||||
></div>
|
||||
<div
|
||||
class="tw-h-6 tw-ml-8 tw-border-0 tw-border-l tw-border-solid tw-border-secondary-300"
|
||||
*ngIf="!isLast && !(selectedIndex === i)"
|
||||
></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -0,0 +1,29 @@
|
||||
import { CdkStepper } from "@angular/cdk/stepper";
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-vertical-stepper",
|
||||
templateUrl: "vertical-stepper.component.html",
|
||||
providers: [{ provide: CdkStepper, useExisting: VerticalStepperComponent }],
|
||||
})
|
||||
export class VerticalStepperComponent extends CdkStepper {
|
||||
@Input()
|
||||
activeClass = "active";
|
||||
|
||||
isNextButtonHidden() {
|
||||
return !(this.steps.length === this.selectedIndex + 1);
|
||||
}
|
||||
|
||||
isStepDisabled(index: number) {
|
||||
if (this.selectedIndex !== index) {
|
||||
return this.selectedIndex === index - 1
|
||||
? !this.steps.find((_, i) => i == index - 1)?.completed
|
||||
: true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
selectStepByIndex(index: number): void {
|
||||
this.selectedIndex = index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { SharedModule } from "../shared.module";
|
||||
|
||||
import { VerticalStepContentComponent } from "./vertical-step-content.component";
|
||||
import { VerticalStep } from "./vertical-step.component";
|
||||
import { VerticalStepperComponent } from "./vertical-stepper.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule],
|
||||
declarations: [VerticalStepperComponent, VerticalStep, VerticalStepContentComponent],
|
||||
exports: [VerticalStepperComponent, VerticalStep],
|
||||
})
|
||||
export class VerticalStepperModule {}
|
||||
@@ -86,6 +86,9 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
case this.organization.canManageSso:
|
||||
route = "manage/sso";
|
||||
break;
|
||||
case this.organization.canManageScim:
|
||||
route = "manage/scim";
|
||||
break;
|
||||
case this.organization.canAccessEventLogs:
|
||||
route = "manage/events";
|
||||
break;
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
{{ error }}
|
||||
</app-callout>
|
||||
<ng-container *ngIf="!done">
|
||||
<app-callout type="warning" *ngIf="users.length > 0 && !error">
|
||||
{{ usersWarning }}
|
||||
<app-callout type="warning" *ngIf="users.length > 0 && !error && isDeactivating">
|
||||
{{ "revokeUsersWarning" | i18n }}
|
||||
</app-callout>
|
||||
<table class="table table-hover table-list">
|
||||
<thead>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
||||
import { ModalConfig } from "@bitwarden/angular/services/modal.service";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
@@ -26,7 +25,6 @@ export class BulkDeactivateComponent {
|
||||
constructor(
|
||||
protected apiService: ApiService,
|
||||
protected i18nService: I18nService,
|
||||
private modalRef: ModalRef,
|
||||
config: ModalConfig
|
||||
) {
|
||||
this.isDeactivating = config.data.isDeactivating;
|
||||
@@ -35,21 +33,16 @@ export class BulkDeactivateComponent {
|
||||
}
|
||||
|
||||
get bulkTitle() {
|
||||
const titleKey = this.isDeactivating ? "deactivateUsers" : "activateUsers";
|
||||
const titleKey = this.isDeactivating ? "revokeUsers" : "restoreUsers";
|
||||
return this.i18nService.t(titleKey);
|
||||
}
|
||||
|
||||
get usersWarning() {
|
||||
const warningKey = this.isDeactivating ? "deactivateUsersWarning" : "activateUsersWarning";
|
||||
return this.i18nService.t(warningKey);
|
||||
}
|
||||
|
||||
async submit() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await this.performBulkUserAction();
|
||||
|
||||
const bulkMessage = this.isDeactivating ? "bulkDeactivatedMessage" : "bulkActivatedMessage";
|
||||
const bulkMessage = this.isDeactivating ? "bulkRevokedMessage" : "bulkRestoredMessage";
|
||||
response.data.forEach((entry) => {
|
||||
const error = entry.error !== "" ? entry.error : this.i18nService.t(bulkMessage);
|
||||
this.statuses.set(entry.id, error);
|
||||
@@ -60,7 +53,6 @@ export class BulkDeactivateComponent {
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
this.modalRef.close();
|
||||
}
|
||||
|
||||
protected async performBulkUserAction() {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</app-callout>
|
||||
<ng-container *ngIf="!done">
|
||||
<app-callout type="warning" *ngIf="users.length > 0 && !error">
|
||||
{{ "removeUsersWarning" | i18n }}
|
||||
{{ removeUsersWarning }}
|
||||
</app-callout>
|
||||
<table class="table table-hover table-list">
|
||||
<thead>
|
||||
|
||||
@@ -43,4 +43,8 @@ export class BulkRemoveComponent {
|
||||
const request = new OrganizationUserBulkRequest(this.users.map((user) => user.id));
|
||||
return await this.apiService.deleteManyOrganizationUsers(this.organizationId, request);
|
||||
}
|
||||
|
||||
protected get removeUsersWarning() {
|
||||
return this.i18nService.t("removeOrgUsersConfirmation");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,14 @@
|
||||
>
|
||||
{{ "singleSignOn" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="scim"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="organization.canManageScim && accessScim"
|
||||
>
|
||||
{{ "scim" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
routerLink="events"
|
||||
class="list-group-item"
|
||||
|
||||
@@ -4,6 +4,8 @@ import { ActivatedRoute } from "@angular/router";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
|
||||
import { flagEnabled } from "../../../utils/flags";
|
||||
|
||||
@Component({
|
||||
selector: "app-org-manage",
|
||||
templateUrl: "manage.component.html",
|
||||
@@ -14,6 +16,7 @@ export class ManageComponent implements OnInit {
|
||||
accessGroups = false;
|
||||
accessEvents = false;
|
||||
accessSso = false;
|
||||
accessScim = false;
|
||||
|
||||
constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {}
|
||||
|
||||
@@ -24,6 +27,12 @@ export class ManageComponent implements OnInit {
|
||||
this.accessSso = this.organization.useSso;
|
||||
this.accessEvents = this.organization.useEvents;
|
||||
this.accessGroups = this.organization.useGroups;
|
||||
|
||||
if (flagEnabled("scim")) {
|
||||
this.accessScim = this.organization.useScim;
|
||||
} else {
|
||||
this.accessScim = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
(click)="filter(null)"
|
||||
>
|
||||
{{ "all" | i18n }}
|
||||
<span class="badge badge-pill badge-info" *ngIf="allCount">{{ allCount }}</span>
|
||||
<span bitBadge badgeType="info" *ngIf="allCount">{{ allCount }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -18,7 +18,7 @@
|
||||
(click)="filter(userStatusType.Invited)"
|
||||
>
|
||||
{{ "invited" | i18n }}
|
||||
<span class="badge badge-pill badge-info" *ngIf="invitedCount">{{ invitedCount }}</span>
|
||||
<span bitBadge badgeType="info" *ngIf="invitedCount">{{ invitedCount }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -27,9 +27,7 @@
|
||||
(click)="filter(userStatusType.Accepted)"
|
||||
>
|
||||
{{ "accepted" | i18n }}
|
||||
<span class="badge badge-pill badge-warning" *ngIf="acceptedCount">{{
|
||||
acceptedCount
|
||||
}}</span>
|
||||
<span bitBadge badgeType="warning" *ngIf="acceptedCount">{{ acceptedCount }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -37,10 +35,8 @@
|
||||
[ngClass]="{ active: status == userStatusType.Deactivated }"
|
||||
(click)="filter(userStatusType.Deactivated)"
|
||||
>
|
||||
{{ "deactivated" | i18n }}
|
||||
<span class="badge badge-pill badge-info" *ngIf="deactivatedCount">{{
|
||||
deactivatedCount
|
||||
}}</span>
|
||||
{{ "revoked" | i18n }}
|
||||
<span bitBadge badgeType="info" *ngIf="deactivatedCount">{{ deactivatedCount }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
@@ -81,11 +77,11 @@
|
||||
</button>
|
||||
<button class="dropdown-item" appStopClick (click)="bulkActivate()">
|
||||
<i class="bwi bwi-fw bwi-plus-circle" aria-hidden="true"></i>
|
||||
{{ "activate" | i18n }}
|
||||
{{ "restoreAccess" | i18n }}
|
||||
</button>
|
||||
<button class="dropdown-item" appStopClick (click)="bulkDeactivate()">
|
||||
<i class="bwi bwi-fw bwi-minus-circle" aria-hidden="true"></i>
|
||||
{{ "deactivate" | i18n }}
|
||||
{{ "revokeAccess" | i18n }}
|
||||
</button>
|
||||
<button class="dropdown-item text-danger" appStopClick (click)="bulkRemove()">
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
@@ -156,14 +152,14 @@
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" appStopClick (click)="edit(u)">{{ u.email }}</a>
|
||||
<span class="badge badge-secondary" *ngIf="u.status === userStatusType.Invited">{{
|
||||
<span bitBadge badgeType="secondary" *ngIf="u.status === userStatusType.Invited">{{
|
||||
"invited" | i18n
|
||||
}}</span>
|
||||
<span class="badge badge-warning" *ngIf="u.status === userStatusType.Accepted">{{
|
||||
<span bitBadge badgeType="warning" *ngIf="u.status === userStatusType.Accepted">{{
|
||||
"accepted" | i18n
|
||||
}}</span>
|
||||
<span class="badge badge-secondary" *ngIf="u.status === userStatusType.Deactivated">{{
|
||||
"deactivated" | i18n
|
||||
<span bitBadge badgeType="secondary" *ngIf="u.status === userStatusType.Deactivated">{{
|
||||
"revoked" | i18n
|
||||
}}</span>
|
||||
<small class="text-muted d-block" *ngIf="u.name">{{ u.name }}</small>
|
||||
</td>
|
||||
@@ -263,7 +259,7 @@
|
||||
*ngIf="u.status === userStatusType.Deactivated"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-plus-circle" aria-hidden="true"></i>
|
||||
{{ "activate" | i18n }}
|
||||
{{ "restoreAccess" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
@@ -273,7 +269,7 @@
|
||||
*ngIf="u.status !== userStatusType.Deactivated"
|
||||
>
|
||||
<i class="bwi bwi-fw bwi-minus-circle" aria-hidden="true"></i>
|
||||
{{ "deactivate" | i18n }}
|
||||
{{ "revokeAccess" | i18n }}
|
||||
</a>
|
||||
<a class="dropdown-item text-danger" href="#" appStopClick (click)="remove(u)">
|
||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||
|
||||
@@ -397,12 +397,18 @@ export class PeopleComponent
|
||||
);
|
||||
}
|
||||
|
||||
protected deleteWarningMessage(user: OrganizationUserUserDetailsResponse): string {
|
||||
if (user.usesKeyConnector) {
|
||||
return this.i18nService.t("removeUserConfirmationKeyConnector");
|
||||
}
|
||||
protected async removeUserConfirmationDialog(user: OrganizationUserUserDetailsResponse) {
|
||||
const warningMessage = user.usesKeyConnector
|
||||
? this.i18nService.t("removeUserConfirmationKeyConnector")
|
||||
: this.i18nService.t("removeOrgUserConfirmation");
|
||||
|
||||
return super.deleteWarningMessage(user);
|
||||
return this.platformUtilsService.showDialog(
|
||||
warningMessage,
|
||||
this.i18nService.t("removeUserIdAccess", this.userNamePipe.transform(user)),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
}
|
||||
|
||||
private async showBulkStatus(
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
<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 bitBadge badgeType="success" *ngIf="policiesEnabledMap.get(p.type)">{{
|
||||
"on" | i18n
|
||||
}}</span>
|
||||
<small class="text-muted d-block">{{ p.description | i18n }}</small>
|
||||
</td>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<h2 class="modal-title" id="userAddEditTitle">
|
||||
{{ title }}
|
||||
<small class="text-muted" *ngIf="name">{{ name }}</small>
|
||||
<span class="badge badge-dark" *ngIf="isDeactivated">{{ "deactivated" | i18n }}</span>
|
||||
<span bitBadge badgeType="secondary" *ngIf="isDeactivated">{{ "revoked" | i18n }}</span>
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
@@ -383,41 +383,31 @@
|
||||
type="button"
|
||||
(click)="activate()"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'activate' | i18n }}"
|
||||
*ngIf="editMode && isDeactivated"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-plus-circle bwi-lg bwi-fw"
|
||||
[hidden]="form.loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
|
||||
[hidden]="!form.loading"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span>{{ "restoreAccess" | i18n }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
(click)="deactivate()"
|
||||
class="btn btn-outline-secondary"
|
||||
appA11yTitle="{{ 'deactivate' | i18n }}"
|
||||
*ngIf="editMode && !isDeactivated"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-minus-circle bwi-lg bwi-fw"
|
||||
[hidden]="form.loading"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
|
||||
[hidden]="!form.loading"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span>{{ "revokeAccess" | i18n }}</span>
|
||||
</button>
|
||||
<button
|
||||
#deleteBtn
|
||||
|
||||
@@ -187,7 +187,7 @@ export class UserAddEditComponent implements OnInit {
|
||||
);
|
||||
} else {
|
||||
const request = new OrganizationUserInviteRequest();
|
||||
request.emails = this.emails.trim().split(/\s*,\s*/);
|
||||
request.emails = [...new Set(this.emails.trim().split(/\s*,\s*/))];
|
||||
request.accessAll = this.access === "all";
|
||||
request.type = this.type;
|
||||
request.permissions = this.setRequestPermissions(
|
||||
@@ -216,10 +216,10 @@ export class UserAddEditComponent implements OnInit {
|
||||
|
||||
const message = this.usesKeyConnector
|
||||
? "removeUserConfirmationKeyConnector"
|
||||
: "removeUserConfirmation";
|
||||
: "removeOrgUserConfirmation";
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t(message),
|
||||
this.name,
|
||||
this.i18nService.t("removeUserIdAccess", this.name),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
@@ -251,9 +251,9 @@ export class UserAddEditComponent implements OnInit {
|
||||
}
|
||||
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("deactivateUserConfirmation"),
|
||||
this.i18nService.t("deactivateUserId", this.name),
|
||||
this.i18nService.t("deactivate"),
|
||||
this.i18nService.t("revokeUserConfirmation"),
|
||||
this.i18nService.t("revokeUserId", this.name),
|
||||
this.i18nService.t("revokeAccess"),
|
||||
this.i18nService.t("cancel"),
|
||||
"warning"
|
||||
);
|
||||
@@ -270,7 +270,7 @@ export class UserAddEditComponent implements OnInit {
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("deactivatedUserId", this.name)
|
||||
this.i18nService.t("revokedUserId", this.name)
|
||||
);
|
||||
this.isDeactivated = true;
|
||||
this.onDeactivatedUser.emit();
|
||||
@@ -284,17 +284,6 @@ export class UserAddEditComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("activateUserConfirmation"),
|
||||
this.i18nService.t("activateUserId", this.name),
|
||||
this.i18nService.t("activate"),
|
||||
this.i18nService.t("cancel"),
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.formPromise = this.apiService.activateOrganizationUser(
|
||||
this.organizationId,
|
||||
@@ -304,7 +293,7 @@ export class UserAddEditComponent implements OnInit {
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("activatedUserId", this.name)
|
||||
this.i18nService.t("restoredUserId", this.name)
|
||||
);
|
||||
this.isDeactivated = false;
|
||||
this.onActivatedUser.emit();
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
[formControl]="enabled"
|
||||
name="Enabled"
|
||||
/>
|
||||
<label class="form-check-label" for="enabled">{{ "enabled" | i18n }}</label>
|
||||
<label class="form-check-label" for="enabled">{{ "turnOn" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
[formControl]="enabled"
|
||||
name="Enabled"
|
||||
/>
|
||||
<label class="form-check-label" for="enabled">{{ "enabled" | i18n }}</label>
|
||||
<label class="form-check-label" for="enabled">{{ "turnOn" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
|
||||
export class MasterPasswordPolicy extends BasePolicy {
|
||||
name = "masterPass";
|
||||
name = "masterPassPolicyTitle";
|
||||
description = "masterPassPolicyDesc";
|
||||
type = PolicyType.MasterPassword;
|
||||
component = MasterPasswordPolicyComponent;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
[formControl]="enabled"
|
||||
name="Enabled"
|
||||
/>
|
||||
<label class="form-check-label" for="enabled">{{ "enabled" | i18n }}</label>
|
||||
<label class="form-check-label" for="enabled">{{ "turnOn" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
[formControl]="enabled"
|
||||
name="Enabled"
|
||||
/>
|
||||
<label class="form-check-label" for="enabled">{{
|
||||
"personalOwnershipCheckboxDesc" | i18n
|
||||
}}</label>
|
||||
<label class="form-check-label" for="enabled">{{ "turnOn" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
[formControl]="enabled"
|
||||
name="Enabled"
|
||||
/>
|
||||
<label class="form-check-label" for="enabled">{{ "enabled" | i18n }}</label>
|
||||
<label class="form-check-label" for="enabled">{{ "turnOn" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
[formControl]="enabled"
|
||||
name="Enabled"
|
||||
/>
|
||||
<label class="form-check-label" for="enabled">{{ "enabled" | i18n }}</label>
|
||||
<label class="form-check-label" for="enabled">{{ "turnOn" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
[formControl]="enabled"
|
||||
name="Enabled"
|
||||
/>
|
||||
<label class="form-check-label" for="enabled">{{ "enabled" | i18n }}</label>
|
||||
<label class="form-check-label" for="enabled">{{ "turnOn" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
[formControl]="enabled"
|
||||
name="Enabled"
|
||||
/>
|
||||
<label class="form-check-label" for="enabled">{{ "enabled" | i18n }}</label>
|
||||
<label class="form-check-label" for="enabled">{{ "turnOn" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
[formControl]="enabled"
|
||||
name="Enabled"
|
||||
/>
|
||||
<label class="form-check-label" for="enabled">{{ "enabled" | i18n }}</label>
|
||||
<label class="form-check-label" for="enabled">{{ "turnOn" | i18n }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||
import { BasePolicy, BasePolicyComponent } from "./base-policy.component";
|
||||
|
||||
export class TwoFactorAuthenticationPolicy extends BasePolicy {
|
||||
name = "twoStepLogin";
|
||||
name = "twoStepLoginPolicyTitle";
|
||||
description = "twoStepLoginPolicyDesc";
|
||||
type = PolicyType.TwoFactorAuthentication;
|
||||
component = TwoFactorAuthenticationPolicyComponent;
|
||||
|
||||
@@ -13,6 +13,7 @@ const permissions = {
|
||||
Permissions.ManageUsers,
|
||||
Permissions.ManagePolicies,
|
||||
Permissions.ManageSso,
|
||||
Permissions.ManageScim,
|
||||
],
|
||||
tools: [Permissions.AccessImportExport, Permissions.AccessReports],
|
||||
settings: [Permissions.ManageOrganization],
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
<span class="text-capitalize">{{
|
||||
isSponsoredSubscription ? "sponsored" : subscription.status || "-"
|
||||
}}</span>
|
||||
<span class="badge badge-warning" *ngIf="subscriptionMarkedForCancel">{{
|
||||
<span bitBadge badgeType="warning" *ngIf="subscriptionMarkedForCancel">{{
|
||||
"pendingCancellation" | i18n
|
||||
}}</span>
|
||||
</dd>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { ImportService as ImportServiceAbstraction } from "@bitwarden/common/abstractions/import.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<a
|
||||
href="#"
|
||||
appStopClick
|
||||
class="badge badge-primary"
|
||||
bitBadge
|
||||
*ngIf="!accessReports"
|
||||
(click)="upgradeOrganization()"
|
||||
>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||
import { EventService } from "@bitwarden/common/abstractions/event.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
import { Route, RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { AuthGuard } from "@bitwarden/angular/guards/auth.guard";
|
||||
import { LockGuard } from "@bitwarden/angular/guards/lock.guard";
|
||||
import { UnauthGuard } from "@bitwarden/angular/guards/unauth.guard";
|
||||
|
||||
import { flagEnabled, FlagName } from "../utils/flags";
|
||||
|
||||
import { AcceptEmergencyComponent } from "./accounts/accept-emergency.component";
|
||||
import { AcceptOrganizationComponent } from "./accounts/accept-organization.component";
|
||||
import { HintComponent } from "./accounts/hint.component";
|
||||
@@ -24,6 +26,7 @@ import { VerifyRecoverDeleteComponent } from "./accounts/verify-recover-delete.c
|
||||
import { HomeGuard } from "./guards/home.guard";
|
||||
import { FrontendLayoutComponent } from "./layouts/frontend-layout.component";
|
||||
import { UserLayoutComponent } from "./layouts/user-layout.component";
|
||||
import { TrialInitiationComponent } from "./modules/trial-initiation/trial-initiation.component";
|
||||
import { IndividualVaultModule } from "./modules/vault/modules/individual-vault/individual-vault.module";
|
||||
import { OrganizationsRoutingModule } from "./organizations/organization-routing.module";
|
||||
import { AcceptFamilySponsorshipComponent } from "./organizations/sponsorships/accept-family-sponsorship.component";
|
||||
@@ -60,7 +63,7 @@ const routes: Routes = [
|
||||
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuard] },
|
||||
{
|
||||
path: "register",
|
||||
component: RegisterComponent,
|
||||
component: flagEnabled("showTrial") ? TrialInitiationComponent : RegisterComponent,
|
||||
canActivate: [UnauthGuard],
|
||||
data: { titleId: "createAccount" },
|
||||
},
|
||||
@@ -251,3 +254,12 @@ const routes: Routes = [
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class OssRoutingModule {}
|
||||
|
||||
export function buildFlaggedRoute(flagName: FlagName, route: Route): Route {
|
||||
return flagEnabled(flagName)
|
||||
? route
|
||||
: {
|
||||
path: route.path,
|
||||
redirectTo: "/",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { LooseComponentsModule } from "./modules/loose-components.module";
|
||||
import { OrganizationCreateModule } from "./modules/organizations/create/organization-create.module";
|
||||
import { OrganizationManageModule } from "./modules/organizations/manage/organization-manage.module";
|
||||
import { OrganizationUserModule } from "./modules/organizations/users/organization-user.module";
|
||||
import { PipesModule } from "./modules/pipes/pipes.module";
|
||||
import { SharedModule } from "./modules/shared.module";
|
||||
import { TrialInitiationModule } from "./modules/trial-initiation/trial-initiation.module";
|
||||
import { VaultFilterModule } from "./modules/vault-filter/vault-filter.module";
|
||||
import { OrganizationBadgeModule } from "./modules/vault/modules/organization-badge/organization-badge.module";
|
||||
|
||||
@@ -12,13 +14,22 @@ import { OrganizationBadgeModule } from "./modules/vault/modules/organization-ba
|
||||
imports: [
|
||||
SharedModule,
|
||||
LooseComponentsModule,
|
||||
TrialInitiationModule,
|
||||
VaultFilterModule,
|
||||
OrganizationBadgeModule,
|
||||
PipesModule,
|
||||
OrganizationManageModule,
|
||||
OrganizationUserModule,
|
||||
OrganizationCreateModule,
|
||||
],
|
||||
exports: [
|
||||
SharedModule,
|
||||
LooseComponentsModule,
|
||||
TrialInitiationModule,
|
||||
VaultFilterModule,
|
||||
OrganizationBadgeModule,
|
||||
PipesModule,
|
||||
],
|
||||
exports: [LooseComponentsModule, VaultFilterModule, OrganizationBadgeModule, PipesModule],
|
||||
bootstrap: [],
|
||||
})
|
||||
export class OssModule {}
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<small>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span class="badge badge-warning">
|
||||
<span bitBadge badgeType="warning">
|
||||
{{ "exposedXTimes" | i18n: (exposedPasswordMap.get(c.id) | number) }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a
|
||||
class="badge badge-primary"
|
||||
bitBadge
|
||||
href="{{ cipherDocs.get(c.id) }}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<small>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span class="badge badge-warning">
|
||||
<span bitBadge badgeType="warning">
|
||||
{{ "reusedXTimes" | i18n: passwordUseMap.get(c.login.password) }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<small>{{ c.subTitle }}</small>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span class="badge badge-{{ passwordStrengthMap.get(c.id)[1] }}">
|
||||
<span bitBadge [badgeType]="passwordStrengthMap.get(c.id)[1]">
|
||||
{{ passwordStrengthMap.get(c.id)[0] | i18n }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
@@ -280,6 +280,20 @@ export class EventService {
|
||||
this.getShortId(ev.organizationUserId)
|
||||
);
|
||||
break;
|
||||
case EventType.OrganizationUser_Deactivated:
|
||||
msg = this.i18nService.t("revokedUserId", this.formatOrgUserId(ev));
|
||||
humanReadableMsg = this.i18nService.t(
|
||||
"revokedUserId",
|
||||
this.getShortId(ev.organizationUserId)
|
||||
);
|
||||
break;
|
||||
case EventType.OrganizationUser_Activated:
|
||||
msg = this.i18nService.t("restoredUserId", this.formatOrgUserId(ev));
|
||||
humanReadableMsg = this.i18nService.t(
|
||||
"restoredUserId",
|
||||
this.getShortId(ev.organizationUserId)
|
||||
);
|
||||
break;
|
||||
// Org
|
||||
case EventType.Organization_Updated:
|
||||
msg = humanReadableMsg = this.i18nService.t("editedOrgSettings");
|
||||
|
||||
@@ -64,6 +64,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="masterPasswordHint">{{ "masterPassHintLabel" | i18n }}</label>
|
||||
<input
|
||||
id="masterPasswordHint"
|
||||
class="form-control"
|
||||
maxlength="50"
|
||||
type="text"
|
||||
name="MasterPasswordHint"
|
||||
[(ngModel)]="masterPasswordHint"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitwarden/angular/components/change-password.component";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
@@ -35,6 +36,7 @@ import { UpdateKeyRequest } from "@bitwarden/common/models/request/updateKeyRequ
|
||||
export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
||||
rotateEncKey = false;
|
||||
currentMasterPassword: string;
|
||||
masterPasswordHint: string;
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
@@ -68,6 +70,8 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
||||
if (await this.keyConnectorService.getUsesKeyConnector()) {
|
||||
this.router.navigate(["/settings/security/two-factor"]);
|
||||
}
|
||||
|
||||
this.masterPasswordHint = (await this.apiService.getProfile()).masterPasswordHint;
|
||||
await super.ngOnInit();
|
||||
}
|
||||
|
||||
@@ -155,6 +159,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
||||
this.currentMasterPassword,
|
||||
null
|
||||
);
|
||||
request.masterPasswordHint = this.masterPasswordHint;
|
||||
request.newMasterPasswordHash = newMasterPasswordHash;
|
||||
request.key = newEncKey[1].encryptedString;
|
||||
|
||||
@@ -192,7 +197,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
||||
request.key = encKey[1].encryptedString;
|
||||
request.masterPasswordHash = masterPasswordHash;
|
||||
|
||||
const folders = await this.folderService.getAllDecrypted();
|
||||
const folders = await firstValueFrom(this.folderService.folderViews$);
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
if (folders[i].id == null) {
|
||||
continue;
|
||||
@@ -224,7 +229,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
||||
|
||||
await this.updateEmergencyAccesses(encKey[0]);
|
||||
|
||||
await this.updateAllResetPasswordKeys(encKey[0]);
|
||||
await this.updateAllResetPasswordKeys(encKey[0], masterPasswordHash);
|
||||
}
|
||||
|
||||
private async updateEmergencyAccesses(encKey: SymmetricCryptoKey) {
|
||||
@@ -252,7 +257,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
||||
}
|
||||
}
|
||||
|
||||
private async updateAllResetPasswordKeys(encKey: SymmetricCryptoKey) {
|
||||
private async updateAllResetPasswordKeys(encKey: SymmetricCryptoKey, masterPasswordHash: string) {
|
||||
const orgs = await this.organizationService.getAll();
|
||||
|
||||
for (const org of orgs) {
|
||||
@@ -270,6 +275,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
||||
|
||||
// Create/Execute request
|
||||
const request = new OrganizationUserResetPasswordEnrollmentRequest();
|
||||
request.masterPasswordHash = masterPasswordHash;
|
||||
request.resetPasswordKey = encryptedKey.encryptedString;
|
||||
|
||||
await this.apiService.putOrganizationUserResetPasswordEnrollment(org.id, org.userId, request);
|
||||
|
||||
@@ -46,28 +46,29 @@
|
||||
<td>
|
||||
<a href="#" appStopClick (click)="edit(c)">{{ c.email }}</a>
|
||||
<span
|
||||
class="badge badge-secondary"
|
||||
bitBadge
|
||||
badgeType="secondary"
|
||||
*ngIf="c.status === emergencyAccessStatusType.Invited"
|
||||
>{{ "invited" | i18n }}</span
|
||||
>
|
||||
<span class="badge badge-warning" *ngIf="c.status === emergencyAccessStatusType.Accepted">{{
|
||||
"accepted" | i18n
|
||||
}}</span>
|
||||
<span
|
||||
class="badge badge-warning"
|
||||
bitBadge
|
||||
badgeType="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.Accepted"
|
||||
>{{ "accepted" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
bitBadge
|
||||
badgeType="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
||||
>{{ "emergencyAccessRecoveryInitiated" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
class="badge badge-success"
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryApproved"
|
||||
>{{ "emergencyAccessRecoveryApproved" | i18n }}</span
|
||||
>
|
||||
|
||||
<span class="badge badge-primary" *ngIf="c.type === emergencyAccessType.View">{{
|
||||
"view" | i18n
|
||||
<span bitBadge *ngIf="c.status === emergencyAccessStatusType.RecoveryApproved">{{
|
||||
"emergencyAccessRecoveryApproved" | i18n
|
||||
}}</span>
|
||||
<span class="badge badge-primary" *ngIf="c.type === emergencyAccessType.Takeover">{{
|
||||
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.View">{{ "view" | i18n }}</span>
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.Takeover">{{
|
||||
"takeover" | i18n
|
||||
}}</span>
|
||||
|
||||
@@ -159,29 +160,30 @@
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ c.email }}</span>
|
||||
<span
|
||||
class="badge badge-secondary"
|
||||
*ngIf="c.status === emergencyAccessStatusType.Invited"
|
||||
>{{ "invited" | i18n }}</span
|
||||
>
|
||||
<span class="badge badge-warning" *ngIf="c.status === emergencyAccessStatusType.Accepted">{{
|
||||
"accepted" | i18n
|
||||
<span bitBadge *ngIf="c.status === emergencyAccessStatusType.Invited">{{
|
||||
"invited" | i18n
|
||||
}}</span>
|
||||
<span
|
||||
class="badge badge-warning"
|
||||
bitBadge
|
||||
badgeType="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.Accepted"
|
||||
>{{ "accepted" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
bitBadge
|
||||
badgeType="warning"
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
||||
>{{ "emergencyAccessRecoveryInitiated" | i18n }}</span
|
||||
>
|
||||
<span
|
||||
class="badge badge-success"
|
||||
bitBadge
|
||||
badgeType="success"
|
||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryApproved"
|
||||
>{{ "emergencyAccessRecoveryApproved" | i18n }}</span
|
||||
>
|
||||
|
||||
<span class="badge badge-primary" *ngIf="c.type === emergencyAccessType.View">{{
|
||||
"view" | i18n
|
||||
}}</span>
|
||||
<span class="badge badge-primary" *ngIf="c.type === emergencyAccessType.Takeover">{{
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.View">{{ "view" | i18n }}</span>
|
||||
<span bitBadge *ngIf="c.type === emergencyAccessType.Takeover">{{
|
||||
"takeover" | i18n
|
||||
}}</span>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||
import { EventService } from "@bitwarden/common/abstractions/event.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
|
||||
@@ -24,68 +24,20 @@
|
||||
</ng-container>
|
||||
<form
|
||||
#form
|
||||
[formGroup]="formGroup"
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
*ngIf="!loading && !selfHosted && this.plans"
|
||||
class="tw-pt-6"
|
||||
>
|
||||
<h2 class="mt-5">{{ "generalInformation" | i18n }}</h2>
|
||||
<div class="row" *ngIf="createOrganization">
|
||||
<div class="form-group col-6">
|
||||
<label for="name">{{ "organizationName" | i18n }}</label>
|
||||
<input id="name" class="form-control" type="text" name="Name" [(ngModel)]="name" required />
|
||||
</div>
|
||||
<div class="form-group col-6">
|
||||
<label for="billingEmail">{{ "billingEmail" | i18n }}</label>
|
||||
<input
|
||||
id="billingEmail"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="BillingEmail"
|
||||
[(ngModel)]="billingEmail"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group col-6" *ngIf="!!providerId">
|
||||
<label for="email">{{ "clientOwnerEmail" | i18n }}</label>
|
||||
<input
|
||||
id="email"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="Email"
|
||||
[(ngModel)]="clientOwnerEmail"
|
||||
required
|
||||
/>
|
||||
<small class="text-muted">{{ "clientOwnerDesc" | i18n: "20" }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!providerId && !acceptingSponsorship">
|
||||
<div class="form-group form-check">
|
||||
<input
|
||||
id="ownedBusiness"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
name="OwnedBusiness"
|
||||
[(ngModel)]="ownedBusiness"
|
||||
(change)="changedOwnedBusiness()"
|
||||
/>
|
||||
<label for="ownedBusiness" class="form-check-label">{{
|
||||
"accountOwnedBusiness" | i18n
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="row" *ngIf="ownedBusiness">
|
||||
<div class="form-group col-6">
|
||||
<label for="businessName">{{ "businessName" | i18n }}</label>
|
||||
<input
|
||||
id="businessName"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="BusinessName"
|
||||
[(ngModel)]="businessName"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<app-org-info
|
||||
(changedBusinessOwned)="changedOwnedBusiness()"
|
||||
[formGroup]="formGroup"
|
||||
[createOrganization]="createOrganization"
|
||||
[isProvider]="!!providerId"
|
||||
[acceptingSponsorship]="acceptingSponsorship"
|
||||
></app-org-info>
|
||||
<h2 class="mt-5">{{ "chooseYourPlan" | i18n }}</h2>
|
||||
<div *ngFor="let selectableProduct of selectableProducts" class="form-check form-check-block">
|
||||
<input
|
||||
@@ -94,7 +46,7 @@
|
||||
name="product"
|
||||
id="product{{ selectableProduct.product }}"
|
||||
[value]="selectableProduct.product"
|
||||
[(ngModel)]="product"
|
||||
formControlName="product"
|
||||
(change)="changedProduct()"
|
||||
/>
|
||||
<label class="form-check-label" for="product{{ selectableProduct.product }}">
|
||||
@@ -167,7 +119,7 @@
|
||||
<span *ngIf="selectableProduct.product == productTypes.Free">{{ "freeForever" | i18n }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div *ngIf="product !== productTypes.Free">
|
||||
<div *ngIf="formGroup.controls['product'].value !== productTypes.Free">
|
||||
<ng-container *ngIf="selectedPlan.hasAdditionalSeatsOption && !selectedPlan.baseSeats">
|
||||
<h2 class="mt-5">{{ "users" | i18n }}</h2>
|
||||
<div class="row">
|
||||
@@ -177,10 +129,8 @@
|
||||
id="additionalSeats"
|
||||
class="form-control"
|
||||
type="number"
|
||||
name="AdditionalSeats"
|
||||
[(ngModel)]="additionalSeats"
|
||||
min="1"
|
||||
max="100000"
|
||||
name="additionalSeats"
|
||||
formControlName="additionalSeats"
|
||||
placeholder="{{ 'userSeatsDesc' | i18n }}"
|
||||
required
|
||||
/>
|
||||
@@ -196,10 +146,8 @@
|
||||
id="additionalSeats"
|
||||
class="form-control"
|
||||
type="number"
|
||||
name="AdditionalSeats"
|
||||
[(ngModel)]="additionalSeats"
|
||||
min="0"
|
||||
max="100000"
|
||||
name="additionalSeats"
|
||||
formControlName="additionalSeats"
|
||||
placeholder="{{ 'userSeatsDesc' | i18n }}"
|
||||
/>
|
||||
<small class="text-muted form-text">{{
|
||||
@@ -215,10 +163,8 @@
|
||||
id="additionalStorage"
|
||||
class="form-control"
|
||||
type="number"
|
||||
name="AdditionalStorageGb"
|
||||
[(ngModel)]="additionalStorage"
|
||||
min="0"
|
||||
max="99"
|
||||
name="additionalStorageGb"
|
||||
formControlName="additionalStorage"
|
||||
step="1"
|
||||
placeholder="{{ 'additionalStorageGbDesc' | i18n }}"
|
||||
/>
|
||||
@@ -238,8 +184,8 @@
|
||||
id="premiumAccess"
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
name="PremiumAccessAddon"
|
||||
[(ngModel)]="premiumAccessAddon"
|
||||
name="premiumAccessAddon"
|
||||
formControlName="premiumAccessAddon"
|
||||
/>
|
||||
<label for="premiumAccess" class="form-check-label bold">{{
|
||||
"premiumAccess" | i18n
|
||||
@@ -255,10 +201,10 @@
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="BillingInterval"
|
||||
name="plan"
|
||||
id="interval{{ selectablePlan.type }}"
|
||||
[value]="selectablePlan.type"
|
||||
[(ngModel)]="plan"
|
||||
formControlName="plan"
|
||||
/>
|
||||
<label class="form-check-label" for="interval{{ selectablePlan.type }}">
|
||||
<ng-container *ngIf="selectablePlan.isAnnual">
|
||||
@@ -281,14 +227,15 @@
|
||||
<small *ngIf="selectablePlan.hasAdditionalSeatsOption">
|
||||
<span *ngIf="selectablePlan.baseSeats">{{ "additionalUsers" | i18n }}:</span>
|
||||
<span *ngIf="!selectablePlan.baseSeats">{{ "users" | i18n }}:</span>
|
||||
{{ additionalSeats || 0 }} ×
|
||||
{{ formGroup.controls["additionalSeats"].value || 0 }} ×
|
||||
{{ selectablePlan.seatPrice / 12 | currency: "$" }} × 12
|
||||
{{ "monthAbbr" | i18n }} = {{ seatTotal(selectablePlan) | currency: "$" }} /{{
|
||||
"year" | i18n
|
||||
}}
|
||||
</small>
|
||||
<small *ngIf="selectablePlan.hasAdditionalStorageOption">
|
||||
{{ "additionalStorageGb" | i18n }}: {{ additionalStorage || 0 }} ×
|
||||
{{ "additionalStorageGb" | i18n }}:
|
||||
{{ formGroup.controls["additionalStorage"].value || 0 }} ×
|
||||
{{ selectablePlan.additionalStoragePricePerGb / 12 | currency: "$" }} × 12
|
||||
{{ "monthAbbr" | i18n }} =
|
||||
{{ additionalStorageTotal(selectablePlan) | currency: "$" }} /{{ "year" | i18n }}
|
||||
@@ -314,18 +261,23 @@
|
||||
<small *ngIf="selectablePlan.hasAdditionalSeatsOption">
|
||||
<span *ngIf="selectablePlan.baseSeats">{{ "additionalUsers" | i18n }}:</span>
|
||||
<span *ngIf="!selectablePlan.baseSeats">{{ "users" | i18n }}:</span>
|
||||
{{ additionalSeats || 0 }} × {{ selectablePlan.seatPrice | currency: "$" }}
|
||||
{{ "monthAbbr" | i18n }} = {{ seatTotal(selectablePlan) | currency: "$" }} /{{
|
||||
"month" | i18n
|
||||
}}
|
||||
{{ formGroup.controls["additionalSeats"].value || 0 }} ×
|
||||
{{ selectablePlan.seatPrice | currency: "$" }} {{ "monthAbbr" | i18n }} =
|
||||
{{ seatTotal(selectablePlan) | currency: "$" }} /{{ "month" | i18n }}
|
||||
</small>
|
||||
<small *ngIf="selectablePlan.hasAdditionalStorageOption">
|
||||
{{ "additionalStorageGb" | i18n }}: {{ additionalStorage || 0 }} ×
|
||||
{{ "additionalStorageGb" | i18n }}:
|
||||
{{ formGroup.controls["additionalStorage"].value || 0 }} ×
|
||||
{{ selectablePlan.additionalStoragePricePerGb | currency: "$" }}
|
||||
{{ "monthAbbr" | i18n }} =
|
||||
{{ additionalStorageTotal(selectablePlan) | currency: "$" }} /{{ "month" | i18n }}
|
||||
</small>
|
||||
<small *ngIf="selectablePlan.hasPremiumAccessOption && premiumAccessAddon">
|
||||
<small
|
||||
*ngIf="
|
||||
selectablePlan.hasPremiumAccessOption &&
|
||||
formGroup.controls['premiumAccessAddon'].value
|
||||
"
|
||||
>
|
||||
{{ "premiumAccess" | i18n }}:
|
||||
{{ selectablePlan.premiumAccessOptionCost | currency: "$" }} {{ "monthAbbr" | i18n }} =
|
||||
{{ 40 | currency: "$" }}
|
||||
@@ -366,10 +318,9 @@
|
||||
<app-callout [type]="'error'">{{ "singleOrgBlockCreateMessage" | i18n }}</app-callout>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||
<span>{{ "submit" | i18n }}</span>
|
||||
</button>
|
||||
<bit-submit-button [loading]="form.loading" [disabled]="!formGroup.valid">{{
|
||||
"submit" | i18n
|
||||
}}</bit-submit-button>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" *ngIf="showCancel">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
@@ -42,22 +43,29 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
@Input() providerId: string;
|
||||
@Output() onSuccess = new EventEmitter();
|
||||
@Output() onCanceled = new EventEmitter();
|
||||
@Output() onTrialBillingSuccess = new EventEmitter();
|
||||
|
||||
loading = true;
|
||||
selfHosted = false;
|
||||
ownedBusiness = false;
|
||||
premiumAccessAddon = false;
|
||||
additionalStorage = 0;
|
||||
additionalSeats = 0;
|
||||
name: string;
|
||||
billingEmail: string;
|
||||
clientOwnerEmail: string;
|
||||
businessName: string;
|
||||
productTypes = ProductType;
|
||||
formPromise: Promise<any>;
|
||||
singleOrgPolicyBlock = false;
|
||||
isInTrialFlow = false;
|
||||
discount = 0;
|
||||
|
||||
formGroup = this.formBuilder.group({
|
||||
name: [""],
|
||||
billingEmail: ["", [Validators.email]],
|
||||
businessOwned: [false],
|
||||
premiumAccessAddon: [false],
|
||||
additionalStorage: [0, [Validators.min(0), Validators.max(99)]],
|
||||
additionalSeats: [0, [Validators.min(0), Validators.max(100000)]],
|
||||
clientOwnerEmail: ["", [Validators.email]],
|
||||
businessName: [""],
|
||||
plan: [this.plan],
|
||||
product: [this.product],
|
||||
});
|
||||
|
||||
plans: PlanResponse[];
|
||||
|
||||
constructor(
|
||||
@@ -70,7 +78,8 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
private policyService: PolicyService,
|
||||
private organizationService: OrganizationService,
|
||||
private logService: LogService,
|
||||
private messagingService: MessagingService
|
||||
private messagingService: MessagingService,
|
||||
private formBuilder: FormBuilder
|
||||
) {
|
||||
this.selfHosted = platformUtilsService.isSelfHost();
|
||||
}
|
||||
@@ -80,15 +89,25 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
const plans = await this.apiService.getPlans();
|
||||
this.plans = plans.data;
|
||||
if (this.product === ProductType.Enterprise || this.product === ProductType.Teams) {
|
||||
this.ownedBusiness = true;
|
||||
this.formGroup.controls.businessOwned.setValue(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.providerId) {
|
||||
this.ownedBusiness = true;
|
||||
this.formGroup.controls.businessOwned.setValue(true);
|
||||
this.changedOwnedBusiness();
|
||||
}
|
||||
|
||||
if (!this.createOrganization || this.acceptingSponsorship) {
|
||||
this.formGroup.controls.product.setValue(ProductType.Families);
|
||||
this.changedProduct();
|
||||
}
|
||||
|
||||
if (this.createOrganization) {
|
||||
this.formGroup.controls.name.addValidators(Validators.required);
|
||||
this.formGroup.controls.billingEmail.addValidators(Validators.required);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
@@ -97,7 +116,7 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
}
|
||||
|
||||
get selectedPlan() {
|
||||
return this.plans.find((plan) => plan.type === this.plan);
|
||||
return this.plans.find((plan) => plan.type === this.formGroup.controls.plan.value);
|
||||
}
|
||||
|
||||
get selectedPlanInterval() {
|
||||
@@ -107,7 +126,7 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
get selectableProducts() {
|
||||
let validPlans = this.plans.filter((plan) => plan.type !== PlanType.Custom);
|
||||
|
||||
if (this.ownedBusiness) {
|
||||
if (this.formGroup.controls.businessOwned.value) {
|
||||
validPlans = validPlans.filter((plan) => plan.canBeUsedByBusiness);
|
||||
}
|
||||
|
||||
@@ -132,8 +151,9 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
}
|
||||
|
||||
get selectablePlans() {
|
||||
return this.plans.filter(
|
||||
(plan) => !plan.legacyYear && !plan.disabled && plan.product === this.product
|
||||
return this.plans?.filter(
|
||||
(plan) =>
|
||||
!plan.legacyYear && !plan.disabled && plan.product === this.formGroup.controls.product.value
|
||||
);
|
||||
}
|
||||
|
||||
@@ -156,7 +176,10 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return plan.additionalStoragePricePerGb * Math.abs(this.additionalStorage || 0);
|
||||
return (
|
||||
plan.additionalStoragePricePerGb *
|
||||
Math.abs(this.formGroup.controls.additionalStorage.value || 0)
|
||||
);
|
||||
}
|
||||
|
||||
seatTotal(plan: PlanResponse): number {
|
||||
@@ -164,18 +187,27 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return plan.seatPrice * Math.abs(this.additionalSeats || 0);
|
||||
return plan.seatPrice * Math.abs(this.formGroup.controls.additionalSeats.value || 0);
|
||||
}
|
||||
|
||||
get subtotal() {
|
||||
let subTotal = this.selectedPlan.basePrice;
|
||||
if (this.selectedPlan.hasAdditionalSeatsOption && this.additionalSeats) {
|
||||
if (
|
||||
this.selectedPlan.hasAdditionalSeatsOption &&
|
||||
this.formGroup.controls.additionalSeats.value
|
||||
) {
|
||||
subTotal += this.seatTotal(this.selectedPlan);
|
||||
}
|
||||
if (this.selectedPlan.hasAdditionalStorageOption && this.additionalStorage) {
|
||||
if (
|
||||
this.selectedPlan.hasAdditionalStorageOption &&
|
||||
this.formGroup.controls.additionalStorage.value
|
||||
) {
|
||||
subTotal += this.additionalStorageTotal(this.selectedPlan);
|
||||
}
|
||||
if (this.selectedPlan.hasPremiumAccessOption && this.premiumAccessAddon) {
|
||||
if (
|
||||
this.selectedPlan.hasPremiumAccessOption &&
|
||||
this.formGroup.controls.premiumAccessAddon.value
|
||||
) {
|
||||
subTotal += this.selectedPlan.premiumAccessOptionPrice;
|
||||
}
|
||||
return subTotal - this.discount;
|
||||
@@ -206,30 +238,31 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
}
|
||||
|
||||
changedProduct() {
|
||||
this.plan = this.selectablePlans[0].type;
|
||||
this.formGroup.controls.plan.setValue(this.selectablePlans[0].type);
|
||||
if (!this.selectedPlan.hasPremiumAccessOption) {
|
||||
this.premiumAccessAddon = false;
|
||||
this.formGroup.controls.premiumAccessAddon.setValue(false);
|
||||
}
|
||||
if (!this.selectedPlan.hasAdditionalStorageOption) {
|
||||
this.additionalStorage = 0;
|
||||
this.formGroup.controls.additionalStorage.setValue(0);
|
||||
}
|
||||
if (!this.selectedPlan.hasAdditionalSeatsOption) {
|
||||
this.additionalSeats = 0;
|
||||
this.formGroup.controls.additionalSeats.setValue(0);
|
||||
} else if (
|
||||
!this.additionalSeats &&
|
||||
!this.formGroup.controls.additionalSeats.value &&
|
||||
!this.selectedPlan.baseSeats &&
|
||||
this.selectedPlan.hasAdditionalSeatsOption
|
||||
) {
|
||||
this.additionalSeats = 1;
|
||||
this.formGroup.controls.additionalSeats.setValue(1);
|
||||
}
|
||||
}
|
||||
|
||||
changedOwnedBusiness() {
|
||||
if (!this.ownedBusiness || this.selectedPlan.canBeUsedByBusiness) {
|
||||
if (!this.formGroup.controls.businessOwned.value || this.selectedPlan.canBeUsedByBusiness) {
|
||||
return;
|
||||
}
|
||||
this.product = ProductType.Teams;
|
||||
this.plan = PlanType.TeamsAnnually;
|
||||
this.formGroup.controls.product.setValue(ProductType.Teams);
|
||||
this.formGroup.controls.plan.setValue(PlanType.TeamsAnnually);
|
||||
this.changedProduct();
|
||||
}
|
||||
|
||||
changedCountry() {
|
||||
@@ -290,10 +323,18 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
|
||||
await this.apiService.refreshIdentityToken();
|
||||
await this.syncService.fullSync(true);
|
||||
if (!this.acceptingSponsorship) {
|
||||
|
||||
if (!this.acceptingSponsorship && !this.isInTrialFlow) {
|
||||
this.router.navigate(["/organizations/" + orgId]);
|
||||
}
|
||||
|
||||
if (this.isInTrialFlow) {
|
||||
this.onTrialBillingSuccess.emit({
|
||||
orgId: orgId,
|
||||
subLabelText: this.billingSubLabelText(),
|
||||
});
|
||||
}
|
||||
|
||||
return orgId;
|
||||
};
|
||||
|
||||
@@ -312,11 +353,13 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
|
||||
private async updateOrganization(orgId: string) {
|
||||
const request = new OrganizationUpgradeRequest();
|
||||
request.businessName = this.ownedBusiness ? this.businessName : null;
|
||||
request.additionalSeats = this.additionalSeats;
|
||||
request.additionalStorageGb = this.additionalStorage;
|
||||
request.businessName = this.formGroup.controls.businessOwned.value
|
||||
? this.formGroup.controls.businessName.value
|
||||
: null;
|
||||
request.additionalSeats = this.formGroup.controls.additionalSeats.value;
|
||||
request.additionalStorageGb = this.formGroup.controls.additionalStorage.value;
|
||||
request.premiumAccessAddon =
|
||||
this.selectedPlan.hasPremiumAccessOption && this.premiumAccessAddon;
|
||||
this.selectedPlan.hasPremiumAccessOption && this.formGroup.controls.premiumAccessAddon.value;
|
||||
request.planType = this.selectedPlan.type;
|
||||
request.billingAddressCountry = this.taxComponent.taxInfo.country;
|
||||
request.billingAddressPostalCode = this.taxComponent.taxInfo.postalCode;
|
||||
@@ -345,8 +388,8 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
const request = new OrganizationCreateRequest();
|
||||
request.key = key;
|
||||
request.collectionName = collectionCt;
|
||||
request.name = this.name;
|
||||
request.billingEmail = this.billingEmail;
|
||||
request.name = this.formGroup.controls.name.value;
|
||||
request.billingEmail = this.formGroup.controls.billingEmail.value;
|
||||
request.keys = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString);
|
||||
|
||||
if (this.selectedPlan.type === PlanType.Free) {
|
||||
@@ -356,11 +399,14 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
|
||||
request.paymentToken = tokenResult[0];
|
||||
request.paymentMethodType = tokenResult[1];
|
||||
request.businessName = this.ownedBusiness ? this.businessName : null;
|
||||
request.additionalSeats = this.additionalSeats;
|
||||
request.additionalStorageGb = this.additionalStorage;
|
||||
request.businessName = this.formGroup.controls.businessOwned.value
|
||||
? this.formGroup.controls.businessName.value
|
||||
: null;
|
||||
request.additionalSeats = this.formGroup.controls.additionalSeats.value;
|
||||
request.additionalStorageGb = this.formGroup.controls.additionalStorage.value;
|
||||
request.premiumAccessAddon =
|
||||
this.selectedPlan.hasPremiumAccessOption && this.premiumAccessAddon;
|
||||
this.selectedPlan.hasPremiumAccessOption &&
|
||||
this.formGroup.controls.premiumAccessAddon.value;
|
||||
request.planType = this.selectedPlan.type;
|
||||
request.billingAddressPostalCode = this.taxComponent.taxInfo.postalCode;
|
||||
request.billingAddressCountry = this.taxComponent.taxInfo.country;
|
||||
@@ -374,7 +420,10 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
}
|
||||
|
||||
if (this.providerId) {
|
||||
const providerRequest = new ProviderOrganizationCreateRequest(this.clientOwnerEmail, request);
|
||||
const providerRequest = new ProviderOrganizationCreateRequest(
|
||||
this.formGroup.controls.clientOwnerEmail.value,
|
||||
request
|
||||
);
|
||||
const providerKey = await this.cryptoService.getProviderKey(this.providerId);
|
||||
providerRequest.organizationCreateRequest.key = (
|
||||
await this.cryptoService.encrypt(orgKey.key, providerKey)
|
||||
@@ -409,4 +458,18 @@ export class OrganizationPlansComponent implements OnInit {
|
||||
|
||||
return orgId;
|
||||
}
|
||||
|
||||
private billingSubLabelText(): string {
|
||||
const selectedPlan = this.selectedPlan;
|
||||
const price = selectedPlan.basePrice === 0 ? selectedPlan.seatPrice : selectedPlan.basePrice;
|
||||
let text = "";
|
||||
|
||||
if (selectedPlan.isAnnual) {
|
||||
text += `${this.i18nService.t("annual")} ($${price}/${this.i18nService.t("yr")})`;
|
||||
} else {
|
||||
text += `${this.i18nService.t("monthly")} ($${price}/${this.i18nService.t("monthAbbr")})`;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,11 +58,11 @@
|
||||
</div>
|
||||
<ng-container *ngIf="showMethods && method === paymentMethodType.Card">
|
||||
<div class="row">
|
||||
<div class="form-group col-4">
|
||||
<div [ngClass]="trialFlow ? 'col-5' : 'col-4'" class="form-group">
|
||||
<label for="stripe-card-number-element">{{ "number" | i18n }}</label>
|
||||
<div id="stripe-card-number-element" class="form-control stripe-form-control"></div>
|
||||
</div>
|
||||
<div class="form-group col-8 d-flex align-items-end">
|
||||
<div *ngIf="!trialFlow" class="form-group col-8 d-flex align-items-end">
|
||||
<img
|
||||
src="../../images/cards.png"
|
||||
alt="Visa, MasterCard, Discover, AmEx, JCB, Diners Club, UnionPay"
|
||||
@@ -70,7 +70,7 @@
|
||||
height="32"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group col-4">
|
||||
<div [ngClass]="trialFlow ? 'col-3' : 'col-4'" class="form-group">
|
||||
<label for="stripe-card-expiry-element">{{ "expiration" | i18n }}</label>
|
||||
<div id="stripe-card-expiry-element" class="form-control stripe-form-control"></div>
|
||||
</div>
|
||||
|
||||
@@ -25,6 +25,7 @@ export class PaymentComponent implements OnInit, OnDestroy {
|
||||
@Input() hideBank = false;
|
||||
@Input() hidePaypal = false;
|
||||
@Input() hideCredit = false;
|
||||
@Input() trialFlow = false;
|
||||
|
||||
private destroy$: Subject<void> = new Subject<void>();
|
||||
|
||||
|
||||
@@ -30,16 +30,6 @@
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group" *ngIf="!hidePasswordHint">
|
||||
<label for="masterPasswordHint">{{ "masterPassHintLabel" | i18n }}</label>
|
||||
<input
|
||||
id="masterPasswordHint"
|
||||
class="form-control"
|
||||
type="text"
|
||||
name="MasterPasswordHint"
|
||||
[(ngModel)]="profile.masterPasswordHint"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="mb-3">
|
||||
|
||||
@@ -18,7 +18,6 @@ export class ProfileComponent implements OnInit {
|
||||
loading = true;
|
||||
profile: ProfileResponse;
|
||||
fingerprint: string;
|
||||
hidePasswordHint = false;
|
||||
|
||||
formPromise: Promise<any>;
|
||||
|
||||
@@ -41,7 +40,6 @@ export class ProfileComponent implements OnInit {
|
||||
if (fingerprint != null) {
|
||||
this.fingerprint = fingerprint.join("-");
|
||||
}
|
||||
this.hidePasswordHint = await this.keyConnectorService.getUsesKeyConnector();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { formatDate } from "@angular/common";
|
||||
import { Component, EventEmitter, Input, Output, OnInit } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
@@ -23,6 +24,8 @@ export class SponsoringOrgRowComponent implements OnInit {
|
||||
revokeSponsorshipPromise: Promise<any>;
|
||||
resendEmailPromise: Promise<any>;
|
||||
|
||||
private locale = "";
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
@@ -30,7 +33,9 @@ export class SponsoringOrgRowComponent implements OnInit {
|
||||
private platformUtilsService: PlatformUtilsService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
async ngOnInit() {
|
||||
this.locale = await firstValueFrom(this.i18nService.locale$);
|
||||
|
||||
this.setStatus(
|
||||
this.isSelfHosted,
|
||||
this.sponsoringOrg.familySponsorshipToDelete,
|
||||
@@ -98,7 +103,7 @@ export class SponsoringOrgRowComponent implements OnInit {
|
||||
// They want to delete but there is a valid until date which means there is an active sponsorship
|
||||
this.statusMessage = this.i18nService.t(
|
||||
"revokeWhenExpired",
|
||||
formatDate(validUntil, "MM/dd/yyyy", this.i18nService.locale)
|
||||
formatDate(validUntil, "MM/dd/yyyy", this.locale)
|
||||
);
|
||||
this.statusClass = "text-danger";
|
||||
} else if (toDelete) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div [ngClass]="trialFlow ? 'col-7' : 'col-6'">
|
||||
<div class="form-group">
|
||||
<label for="addressCountry">{{ "country" | i18n }}</label>
|
||||
<select
|
||||
@@ -265,7 +265,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<div [ngClass]="trialFlow ? 'col-5' : 'col-3'">
|
||||
<div class="form-group">
|
||||
<label for="addressPostalCode">{{ "zipPostalCode" | i18n }}</label>
|
||||
<input
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, EventEmitter, Output } from "@angular/core";
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
@@ -12,6 +12,7 @@ import { TaxRateResponse } from "@bitwarden/common/models/response/taxRateRespon
|
||||
templateUrl: "tax-info.component.html",
|
||||
})
|
||||
export class TaxInfoComponent {
|
||||
@Input() trialFlow = false;
|
||||
@Output() onCountryChanged = new EventEmitter();
|
||||
|
||||
loading = true;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
@@ -81,7 +82,7 @@ export class UpdateKeyComponent {
|
||||
|
||||
await this.syncService.fullSync(true);
|
||||
|
||||
const folders = await this.folderService.getAllDecrypted();
|
||||
const folders = await firstValueFrom(this.folderService.folderViews$);
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
if (folders[i].id == null) {
|
||||
continue;
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { ImportService as ImportServiceAbstraction } from "@bitwarden/common/abstractions/import.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
class="form-control"
|
||||
[disabled]="cipher.isDeleted || viewOnly"
|
||||
>
|
||||
<option *ngFor="let f of folders" [ngValue]="f.id">{{ f.name }}</option>
|
||||
<option *ngFor="let f of folders$ | async" [ngValue]="f.id">{{ f.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -194,7 +194,9 @@
|
||||
<a
|
||||
href="#"
|
||||
appStopClick
|
||||
class="badge badge-primary ml-3"
|
||||
bitBadge
|
||||
badgeType="primary"
|
||||
class="tw-ml-4"
|
||||
(click)="upgradeOrganization()"
|
||||
*ngIf="
|
||||
(organization && !organization.useTotp) ||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
|
||||
import { EventService } from "@bitwarden/common/abstractions/event.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder.service";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user