mirror of
https://github.com/bitwarden/browser
synced 2026-02-12 14:34:02 +00:00
Merge branch 'PS55-6-22' of https://github.com/bitwarden/clients into PS55-6-22
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<div class="form-group">
|
||||
<label for="folder">{{ "folder" | i18n }}</label>
|
||||
<select id="folder" name="FolderId" [(ngModel)]="folderId" class="form-control">
|
||||
<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>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
import { firstValueFrom, Observable } from "rxjs";
|
||||
|
||||
import { CipherService } from "@bitwarden/common/abstractions/cipher.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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { FolderView } from "@bitwarden/common/models/view/folderView";
|
||||
@@ -15,7 +16,7 @@ export class BulkMoveComponent implements OnInit {
|
||||
@Output() onMoved = new EventEmitter();
|
||||
|
||||
folderId: string = null;
|
||||
folders: FolderView[] = [];
|
||||
folders$: Observable<FolderView[]>;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(
|
||||
@@ -26,8 +27,8 @@ export class BulkMoveComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.folders = await this.folderService.getAllDecrypted();
|
||||
this.folderId = this.folders[0].id;
|
||||
this.folders$ = this.folderService.folderViews$;
|
||||
this.folderId = (await firstValueFrom(this.folders$))[0].id;
|
||||
}
|
||||
|
||||
async submit() {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { FolderAddEditComponent as BaseFolderAddEditComponent } from "@bitwarden/angular/components/folder-add-edit.component";
|
||||
import { FolderService } from "@bitwarden/common/abstractions/folder.service";
|
||||
import { FolderApiServiceAbstraction } from "@bitwarden/common/abstractions/folder/folder-api.service.abstraction";
|
||||
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 { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
@@ -13,10 +14,11 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
|
||||
export class FolderAddEditComponent extends BaseFolderAddEditComponent {
|
||||
constructor(
|
||||
folderService: FolderService,
|
||||
folderApiService: FolderApiServiceAbstraction,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
logService: LogService
|
||||
) {
|
||||
super(folderService, i18nService, platformUtilsService, logService);
|
||||
super(folderService, folderApiService, i18nService, platformUtilsService, logService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,6 +572,9 @@
|
||||
"createAccount": {
|
||||
"message": "Skep rekening"
|
||||
},
|
||||
"startTrial": {
|
||||
"message": "Start Trial"
|
||||
},
|
||||
"logIn": {
|
||||
"message": "Teken aan"
|
||||
},
|
||||
@@ -593,6 +596,9 @@
|
||||
"masterPassDesc": {
|
||||
"message": "Die hoofwagwoord is die wagwoord wat u gaan gebruik vir toegang tot u kluis. Dit is baie belangrik dat u u hoofwagwoord onthou. Daar is geen manier om dit terug te kry ingeval u dit vergeet het nie."
|
||||
},
|
||||
"masterPassImportant": {
|
||||
"message": "Master passwords cannot be recovered if you forget it!"
|
||||
},
|
||||
"masterPassHintDesc": {
|
||||
"message": "’n Hoofwagwoordwenk kan u help om u wagwoord te onthou, sou u dit vergeet."
|
||||
},
|
||||
@@ -623,11 +629,14 @@
|
||||
"invalidEmail": {
|
||||
"message": "Ongeldige e-posadres."
|
||||
},
|
||||
"masterPassRequired": {
|
||||
"message": "Hoofwagwoord word benodig."
|
||||
"masterPasswordRequired": {
|
||||
"message": "Master password is required."
|
||||
},
|
||||
"masterPassLength": {
|
||||
"message": "Hoofwagwoord moet ten minste 8 karakters lank wees."
|
||||
"confirmMasterPasswordRequired": {
|
||||
"message": "Master password retype is required."
|
||||
},
|
||||
"masterPasswordMinLength": {
|
||||
"message": "Master password must be at least 8 characters long."
|
||||
},
|
||||
"masterPassDoesntMatch": {
|
||||
"message": "Hoofwagwoordbevestiging stem nie ooreen nie."
|
||||
@@ -3159,8 +3168,8 @@
|
||||
"acceptPolicies": {
|
||||
"message": "Deur hierdie kassie af te merk stem u in tot die volgende:"
|
||||
},
|
||||
"acceptPoliciesError": {
|
||||
"message": "Gebruiksvoorwaardes en privaatheidsbeleid is nie erken nie."
|
||||
"acceptPoliciesRequired": {
|
||||
"message": "Terms of Service and Privacy Policy have not been acknowledged."
|
||||
},
|
||||
"termsOfService": {
|
||||
"message": "Gebruiksvoorwaardes"
|
||||
@@ -3209,6 +3218,9 @@
|
||||
"organizationIsDisabled": {
|
||||
"message": "Organisasie is gedeaktiveer."
|
||||
},
|
||||
"disabledOrganizationFilterError": {
|
||||
"message": "Items in disabled Organizations cannot be accessed. Contact your Organization owner for assistance."
|
||||
},
|
||||
"licenseIsExpired": {
|
||||
"message": "Lisensie het verstryk."
|
||||
},
|
||||
@@ -3333,14 +3345,20 @@
|
||||
"clone": {
|
||||
"message": "Kloon"
|
||||
},
|
||||
"masterPassPolicyTitle": {
|
||||
"message": "Master password requirements"
|
||||
},
|
||||
"masterPassPolicyDesc": {
|
||||
"message": "Stel minimum vereistes vir hoofwagwoordsterkte."
|
||||
},
|
||||
"twoStepLoginPolicyTitle": {
|
||||
"message": "Require two-step login"
|
||||
},
|
||||
"twoStepLoginPolicyDesc": {
|
||||
"message": "Vereis tweestapaantekening op gebruikers se persoonlike rekeninge."
|
||||
},
|
||||
"twoStepLoginPolicyWarning": {
|
||||
"message": "Organization members who are not Owners or Administrators and do not have two-step login enabled for their personal account will be removed from the organization and will receive an email notifying them about the change."
|
||||
"message": "Organization members who are not Owners or Administrators and do not have two-step login turned on for their account will be removed from the organization and will receive an email notifying them about the change."
|
||||
},
|
||||
"twoStepLoginPolicyUserWarning": {
|
||||
"message": "You are a member of an organization that requires two-step login to be enabled on your user account. If you disable all two-step login providers you will be automatically removed from these organizations."
|
||||
@@ -3567,7 +3585,7 @@
|
||||
"message": "Enkele organisasie"
|
||||
},
|
||||
"singleOrgDesc": {
|
||||
"message": "Restrict users from being able to join any other organizations."
|
||||
"message": "Restrict members from joining other organizations."
|
||||
},
|
||||
"singleOrgBlockCreateMessage": {
|
||||
"message": "U huidige organisasie het ’n beleid wat u nie toelaat om deel te neem aan meer as een organisasie nie. Kontak u organisasie se beheerders of teken aan met’n ander Bitwarden-rekening."
|
||||
@@ -3579,13 +3597,13 @@
|
||||
"message": "Enkelaantekenwaarmerking"
|
||||
},
|
||||
"requireSsoPolicyDesc": {
|
||||
"message": "Require users to log in with the Enterprise Single Sign-On method."
|
||||
"message": "Require members to log in with the Enterprise Single Sign-On method."
|
||||
},
|
||||
"prerequisite": {
|
||||
"message": "Voorvereiste"
|
||||
},
|
||||
"requireSsoPolicyReq": {
|
||||
"message": "The Single Organization enterprise policy must be enabled before activating this policy."
|
||||
"message": "The Single Organization enterprise policy must be turned on before activating this policy."
|
||||
},
|
||||
"requireSsoPolicyReqError": {
|
||||
"message": "Single Organization policy not enabled."
|
||||
@@ -3883,7 +3901,7 @@
|
||||
"message": "Persoonlike eienaarskap"
|
||||
},
|
||||
"personalOwnershipPolicyDesc": {
|
||||
"message": "Require users to save vault items to an organization by removing the personal ownership option."
|
||||
"message": "Require members to save items to an organization by removing the individual vault option."
|
||||
},
|
||||
"personalOwnershipExemption": {
|
||||
"message": "Organisasie-eienaars en -administrateurs is vrygestel van die afdwing van hierdie beleid."
|
||||
@@ -3895,7 +3913,7 @@
|
||||
"message": "Deaktiveer Send"
|
||||
},
|
||||
"disableSendPolicyDesc": {
|
||||
"message": "Do not allow users to create or edit a Bitwarden Send. Deleting an existing Send is still allowed.",
|
||||
"message": "Do not allow members to create or edit sends.",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"disableSendExemption": {
|
||||
@@ -3921,7 +3939,7 @@
|
||||
"message": "Organization users that can manage the organization's policies are exempt from this policy's enforcement."
|
||||
},
|
||||
"disableHideEmail": {
|
||||
"message": "Do not allow users to hide their email address from recipients when creating or editing a Send.",
|
||||
"message": "Always show member’s email address with recipients when creating or editing a send.",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"sendOptionsPolicyInEffect": {
|
||||
@@ -4099,7 +4117,7 @@
|
||||
}
|
||||
},
|
||||
"viewSendHiddenEmailWarning": {
|
||||
"message": "The Bitwarden user who created this Send has chosen to hide their email address. You should ensure you trust the source of this link before using or downloading its content.",
|
||||
"message": "Die Bitwarden-gebruiker wat hierdie Send geskep het, het gekies om hul e-posadres te verberg. U moet verseker dat u die bron van hierdie skakel vertrou voordat u die inhoud gebruik of aflaai.",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"expirationDateIsInvalid": {
|
||||
@@ -4220,7 +4238,7 @@
|
||||
"message": "Bestuur wagwoordherstel"
|
||||
},
|
||||
"resetPasswordPolicyDescription": {
|
||||
"message": "Allow administrators in the organization to reset organization users' master password."
|
||||
"message": "Allow admins to reset master passwords for members."
|
||||
},
|
||||
"resetPasswordPolicyWarning": {
|
||||
"message": "Gebruikers in die organisasie sal self moet inskryf of moet outomaties ingeskryf word voor beheerders hul hoofwagwoord sal kan terugstel."
|
||||
@@ -4467,7 +4485,7 @@
|
||||
"message": "Kluis-uittel"
|
||||
},
|
||||
"maximumVaultTimeoutDesc": {
|
||||
"message": "Configure a maximum vault timeout for all users."
|
||||
"message": "Set a maximum vault timeout for members."
|
||||
},
|
||||
"maximumVaultTimeoutLabel": {
|
||||
"message": "Maximum Vault Timeout"
|
||||
@@ -4507,10 +4525,10 @@
|
||||
"message": "Vault Timeout is not within allowed range."
|
||||
},
|
||||
"disablePersonalVaultExport": {
|
||||
"message": "Disable Personal Vault Export"
|
||||
"message": "Deaktiveer uitstuur van persoonlike kluis"
|
||||
},
|
||||
"disablePersonalVaultExportDesc": {
|
||||
"message": "Prohibits users from exporting their private vault data."
|
||||
"message": "Verbied gebruikers om hul privaatkluisdata uit te stuur."
|
||||
},
|
||||
"vaultExportDisabled": {
|
||||
"message": "Kluisuitstuur gedeaktiveer"
|
||||
@@ -4802,15 +4820,15 @@
|
||||
},
|
||||
"ssoPolicyHelpStart": {
|
||||
"message": "Aktiveer die",
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enable the SSO Authentication policy to require all members to log in with SSO.'"
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'"
|
||||
},
|
||||
"ssoPolicyHelpLink": {
|
||||
"message": "SSO-waarmerkbeleid",
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enable the SSO Authentication policy to require all members to log in with SSO.'"
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'"
|
||||
},
|
||||
"ssoPolicyHelpEnd": {
|
||||
"message": "om aantekening met SSO vir alle lede te verplig.",
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enable the SSO Authentication policy to require all members to log in with SSO.'"
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'"
|
||||
},
|
||||
"ssoPolicyHelpKeyConnector": {
|
||||
"message": "SSO-waarmerking en Enkelorganisasiebeleide word vereis om Key Connector-dekripsie op te stel."
|
||||
@@ -5165,5 +5183,35 @@
|
||||
"example": "My Email"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputRequired": {
|
||||
"message": "Input is required."
|
||||
},
|
||||
"inputEmail": {
|
||||
"message": "Input is not an email-address."
|
||||
},
|
||||
"inputMinLength": {
|
||||
"message": "Input must be at least $COUNT$ characters long.",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "8"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fieldsNeedAttention": {
|
||||
"message": "$COUNT$ veld(e) hierbo benodig u aandag.",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "4"
|
||||
}
|
||||
}
|
||||
},
|
||||
"turnOn": {
|
||||
"message": "Turn on"
|
||||
},
|
||||
"on": {
|
||||
"message": "On"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,6 +572,9 @@
|
||||
"createAccount": {
|
||||
"message": "إنشاء حساب"
|
||||
},
|
||||
"startTrial": {
|
||||
"message": "Start Trial"
|
||||
},
|
||||
"logIn": {
|
||||
"message": "تسجيل الدخول"
|
||||
},
|
||||
@@ -593,6 +596,9 @@
|
||||
"masterPassDesc": {
|
||||
"message": "The master password is the password you use to access your vault. It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it."
|
||||
},
|
||||
"masterPassImportant": {
|
||||
"message": "Master passwords cannot be recovered if you forget it!"
|
||||
},
|
||||
"masterPassHintDesc": {
|
||||
"message": "يمكن أن يساعدك تلميح كلمة المرور الرئيسية في تذكر كلمة المرور الخاصة بك في حال نسيتها."
|
||||
},
|
||||
@@ -623,11 +629,14 @@
|
||||
"invalidEmail": {
|
||||
"message": "عنوان البريد الإلكتروني غير صالح."
|
||||
},
|
||||
"masterPassRequired": {
|
||||
"message": "كلمة المرور الرئيسية مطلوبة."
|
||||
"masterPasswordRequired": {
|
||||
"message": "Master password is required."
|
||||
},
|
||||
"masterPassLength": {
|
||||
"message": "يجب أن يكون طول كلمة المرور الرئيسية 8 أحرف على الأقل."
|
||||
"confirmMasterPasswordRequired": {
|
||||
"message": "Master password retype is required."
|
||||
},
|
||||
"masterPasswordMinLength": {
|
||||
"message": "Master password must be at least 8 characters long."
|
||||
},
|
||||
"masterPassDoesntMatch": {
|
||||
"message": "لا يتطابق تأكيد كلمة المرور مع كلمة المرور."
|
||||
@@ -1067,7 +1076,7 @@
|
||||
"message": "حذف الحساب"
|
||||
},
|
||||
"deleteAccountDesc": {
|
||||
"message": "Proceed below to delete your account and all associated data."
|
||||
"message": "Proceed below to delete your account and all vault data."
|
||||
},
|
||||
"deleteAccountWarning": {
|
||||
"message": "Deleting your account is permanent. It cannot be undone."
|
||||
@@ -1076,7 +1085,7 @@
|
||||
"message": "Account Deleted"
|
||||
},
|
||||
"accountDeletedDesc": {
|
||||
"message": "Your account has been closed and all associated data has been deleted."
|
||||
"message": "Your Bitwarden account and vault data were permanently deleted."
|
||||
},
|
||||
"myAccount": {
|
||||
"message": "حسابي"
|
||||
@@ -1214,7 +1223,7 @@
|
||||
"message": "Domains updated"
|
||||
},
|
||||
"twoStepLogin": {
|
||||
"message": "Two-step Login"
|
||||
"message": "Two-step login"
|
||||
},
|
||||
"twoStepLoginDesc": {
|
||||
"message": "Secure your account by requiring an additional step when logging in."
|
||||
@@ -3159,7 +3168,7 @@
|
||||
"acceptPolicies": {
|
||||
"message": "By checking this box you agree to the following:"
|
||||
},
|
||||
"acceptPoliciesError": {
|
||||
"acceptPoliciesRequired": {
|
||||
"message": "Terms of Service and Privacy Policy have not been acknowledged."
|
||||
},
|
||||
"termsOfService": {
|
||||
@@ -3209,6 +3218,9 @@
|
||||
"organizationIsDisabled": {
|
||||
"message": "Organization is disabled."
|
||||
},
|
||||
"disabledOrganizationFilterError": {
|
||||
"message": "Items in disabled Organizations cannot be accessed. Contact your Organization owner for assistance."
|
||||
},
|
||||
"licenseIsExpired": {
|
||||
"message": "License is expired."
|
||||
},
|
||||
@@ -3333,20 +3345,26 @@
|
||||
"clone": {
|
||||
"message": "استنساخ"
|
||||
},
|
||||
"masterPassPolicyTitle": {
|
||||
"message": "Master password requirements"
|
||||
},
|
||||
"masterPassPolicyDesc": {
|
||||
"message": "Set minimum requirements for master password strength."
|
||||
"message": "Set requirements for master password strength."
|
||||
},
|
||||
"twoStepLoginPolicyTitle": {
|
||||
"message": "Require two-step login"
|
||||
},
|
||||
"twoStepLoginPolicyDesc": {
|
||||
"message": "Require users to set up two-step login on their personal accounts."
|
||||
"message": "Require members to set up two-step login."
|
||||
},
|
||||
"twoStepLoginPolicyWarning": {
|
||||
"message": "Organization members who are not Owners or Administrators and do not have two-step login enabled for their personal account will be removed from the organization and will receive an email notifying them about the change."
|
||||
"message": "Organization members who are not Owners or Administrators and do not have two-step login turned on for their account will be removed from the organization and will receive an email notifying them about the change."
|
||||
},
|
||||
"twoStepLoginPolicyUserWarning": {
|
||||
"message": "You are a member of an organization that requires two-step login to be enabled on your user account. If you disable all two-step login providers you will be automatically removed from these organizations."
|
||||
},
|
||||
"passwordGeneratorPolicyDesc": {
|
||||
"message": "Set minimum requirements for password generator configuration."
|
||||
"message": "Set requirements for password generator."
|
||||
},
|
||||
"passwordGeneratorPolicyInEffect": {
|
||||
"message": "One or more organization policies are affecting your generator settings."
|
||||
@@ -3564,10 +3582,10 @@
|
||||
"message": "Link SSO"
|
||||
},
|
||||
"singleOrg": {
|
||||
"message": "Single Organization"
|
||||
"message": "Single organization"
|
||||
},
|
||||
"singleOrgDesc": {
|
||||
"message": "Restrict users from being able to join any other organizations."
|
||||
"message": "Restrict members from joining other organizations."
|
||||
},
|
||||
"singleOrgBlockCreateMessage": {
|
||||
"message": "Your current organization has a policy that does not allow you to join more than one organization. Please contact your organization admins or sign up from a different Bitwarden account."
|
||||
@@ -3576,16 +3594,16 @@
|
||||
"message": "Organization members who are not Owners or Administrators and are already a member of another organization will be removed from your organization."
|
||||
},
|
||||
"requireSso": {
|
||||
"message": "Single Sign-On Authentication"
|
||||
"message": "Require single sign-on authentication"
|
||||
},
|
||||
"requireSsoPolicyDesc": {
|
||||
"message": "Require users to log in with the Enterprise Single Sign-On method."
|
||||
"message": "Require members to log in with the Enterprise Single Sign-On method."
|
||||
},
|
||||
"prerequisite": {
|
||||
"message": "Prerequisite"
|
||||
},
|
||||
"requireSsoPolicyReq": {
|
||||
"message": "The Single Organization enterprise policy must be enabled before activating this policy."
|
||||
"message": "The Single Organization enterprise policy must be turned on before activating this policy."
|
||||
},
|
||||
"requireSsoPolicyReqError": {
|
||||
"message": "Single Organization policy not enabled."
|
||||
@@ -3880,10 +3898,10 @@
|
||||
}
|
||||
},
|
||||
"personalOwnership": {
|
||||
"message": "Personal Ownership"
|
||||
"message": "Remove individual vault"
|
||||
},
|
||||
"personalOwnershipPolicyDesc": {
|
||||
"message": "Require users to save vault items to an organization by removing the personal ownership option."
|
||||
"message": "Require members to save items to an organization by removing the individual vault option."
|
||||
},
|
||||
"personalOwnershipExemption": {
|
||||
"message": "Organization Owners and Administrators are exempt from this policy's enforcement."
|
||||
@@ -3892,10 +3910,10 @@
|
||||
"message": "Due to an enterprise policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections."
|
||||
},
|
||||
"disableSend": {
|
||||
"message": "Disable Send"
|
||||
"message": "Remove Send"
|
||||
},
|
||||
"disableSendPolicyDesc": {
|
||||
"message": "Do not allow users to create or edit a Bitwarden Send. Deleting an existing Send is still allowed.",
|
||||
"message": "Do not allow members to create or edit sends.",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"disableSendExemption": {
|
||||
@@ -3910,7 +3928,7 @@
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"sendOptions": {
|
||||
"message": "Send Options",
|
||||
"message": "Send options",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"sendOptionsPolicyDesc": {
|
||||
@@ -3921,7 +3939,7 @@
|
||||
"message": "Organization users that can manage the organization's policies are exempt from this policy's enforcement."
|
||||
},
|
||||
"disableHideEmail": {
|
||||
"message": "Do not allow users to hide their email address from recipients when creating or editing a Send.",
|
||||
"message": "Always show member’s email address with recipients when creating or editing a send.",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"sendOptionsPolicyInEffect": {
|
||||
@@ -4217,10 +4235,10 @@
|
||||
"message": "Enrollment will allow organization administrators to change your master password"
|
||||
},
|
||||
"resetPasswordPolicy": {
|
||||
"message": "Master Password Reset"
|
||||
"message": "Master password reset"
|
||||
},
|
||||
"resetPasswordPolicyDescription": {
|
||||
"message": "Allow administrators in the organization to reset organization users' master password."
|
||||
"message": "Allow admins to reset master passwords for members."
|
||||
},
|
||||
"resetPasswordPolicyWarning": {
|
||||
"message": "Users in the organization will need to self-enroll or be auto-enrolled before administrators can reset their master password."
|
||||
@@ -4464,10 +4482,10 @@
|
||||
"message": "Your Master Password does not meet the policy requirements of this organization. In order to join the organization, you must update your Master Password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour."
|
||||
},
|
||||
"maximumVaultTimeout": {
|
||||
"message": "Vault Timeout"
|
||||
"message": "Vault timeout"
|
||||
},
|
||||
"maximumVaultTimeoutDesc": {
|
||||
"message": "Configure a maximum vault timeout for all users."
|
||||
"message": "Set a maximum vault timeout for members."
|
||||
},
|
||||
"maximumVaultTimeoutLabel": {
|
||||
"message": "Maximum Vault Timeout"
|
||||
@@ -4507,10 +4525,10 @@
|
||||
"message": "Vault Timeout is not within allowed range."
|
||||
},
|
||||
"disablePersonalVaultExport": {
|
||||
"message": "Disable Personal Vault Export"
|
||||
"message": "Remove individual vault export"
|
||||
},
|
||||
"disablePersonalVaultExportDesc": {
|
||||
"message": "Prohibits users from exporting their private vault data."
|
||||
"message": "Do not allow members to export their individual vault data."
|
||||
},
|
||||
"vaultExportDisabled": {
|
||||
"message": "Vault Export Disabled"
|
||||
@@ -4801,19 +4819,19 @@
|
||||
"message": "Once set up, your configuration will be saved and members will be able to authenticate using their Identity Provider credentials."
|
||||
},
|
||||
"ssoPolicyHelpStart": {
|
||||
"message": "Enable the",
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enable the SSO Authentication policy to require all members to log in with SSO.'"
|
||||
"message": "Use the",
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'"
|
||||
},
|
||||
"ssoPolicyHelpLink": {
|
||||
"message": "SSO Authentication policy",
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enable the SSO Authentication policy to require all members to log in with SSO.'"
|
||||
"message": "require single-sign-on authentication policy",
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'"
|
||||
},
|
||||
"ssoPolicyHelpEnd": {
|
||||
"message": "to require all members to log in with SSO.",
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enable the SSO Authentication policy to require all members to log in with SSO.'"
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'"
|
||||
},
|
||||
"ssoPolicyHelpKeyConnector": {
|
||||
"message": "SSO Authentication and Single Organization policies are required to set up Key Connector decryption."
|
||||
"message": "The require SSO authentication and single organization policies are required to set up Key Connector decryption."
|
||||
},
|
||||
"memberDecryptionOption": {
|
||||
"message": "Member Decryption Options"
|
||||
@@ -5165,5 +5183,35 @@
|
||||
"example": "My Email"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputRequired": {
|
||||
"message": "Input is required."
|
||||
},
|
||||
"inputEmail": {
|
||||
"message": "Input is not an email-address."
|
||||
},
|
||||
"inputMinLength": {
|
||||
"message": "Input must be at least $COUNT$ characters long.",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "8"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fieldsNeedAttention": {
|
||||
"message": "$COUNT$ field(s) above need your attention.",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "4"
|
||||
}
|
||||
}
|
||||
},
|
||||
"turnOn": {
|
||||
"message": "Turn on"
|
||||
},
|
||||
"on": {
|
||||
"message": "On"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,6 +572,9 @@
|
||||
"createAccount": {
|
||||
"message": "Hesab yarat"
|
||||
},
|
||||
"startTrial": {
|
||||
"message": "Sınağa Başla"
|
||||
},
|
||||
"logIn": {
|
||||
"message": "Giriş et"
|
||||
},
|
||||
@@ -593,6 +596,9 @@
|
||||
"masterPassDesc": {
|
||||
"message": "Ana parol, anbarınıza müraciət etmək üçün istifadə edəcəyiniz şifrədir. Ana parolu yadda saxlamaq çox vacibdir. Unutsanız, parolu bərpa etməyin heç bir yolu yoxdur."
|
||||
},
|
||||
"masterPassImportant": {
|
||||
"message": "Unutsanız, Ana parollar bərpa edilə bilməz!"
|
||||
},
|
||||
"masterPassHintDesc": {
|
||||
"message": "Ana parol məsləhəti, unutduğunuz parolu xatırlamağınıza kömək edir."
|
||||
},
|
||||
@@ -623,10 +629,13 @@
|
||||
"invalidEmail": {
|
||||
"message": "Etibarsız e-poçt ünvanı."
|
||||
},
|
||||
"masterPassRequired": {
|
||||
"masterPasswordRequired": {
|
||||
"message": "Ana parol lazımdır."
|
||||
},
|
||||
"masterPassLength": {
|
||||
"confirmMasterPasswordRequired": {
|
||||
"message": "Ana parolun yenidən yazılması lazımdır."
|
||||
},
|
||||
"masterPasswordMinLength": {
|
||||
"message": "Ana parol ən azı 8 simvol uzunluğunda olmalıdır."
|
||||
},
|
||||
"masterPassDoesntMatch": {
|
||||
@@ -3159,7 +3168,7 @@
|
||||
"acceptPolicies": {
|
||||
"message": "Bu qutunu işarələyərək aşağıdakılarla razılaşırsınız:"
|
||||
},
|
||||
"acceptPoliciesError": {
|
||||
"acceptPoliciesRequired": {
|
||||
"message": "Xidmət Şərtləri və Gizlilik Siyasəti qəbul edilməyib."
|
||||
},
|
||||
"termsOfService": {
|
||||
@@ -3209,6 +3218,9 @@
|
||||
"organizationIsDisabled": {
|
||||
"message": "Təşkilat sıradan çıxarıldı."
|
||||
},
|
||||
"disabledOrganizationFilterError": {
|
||||
"message": "Sıradan çıxarılmış Təşkilatlardakı elementlərə müraciət edilə bilmir. Kömək üçün Təşkilatınızın sahibi ilə əlaqə saxlayın."
|
||||
},
|
||||
"licenseIsExpired": {
|
||||
"message": "Lisenziyanın vaxtı bitib."
|
||||
},
|
||||
@@ -3333,9 +3345,15 @@
|
||||
"clone": {
|
||||
"message": "Klonla"
|
||||
},
|
||||
"masterPassPolicyTitle": {
|
||||
"message": "Ana parol tələbləri"
|
||||
},
|
||||
"masterPassPolicyDesc": {
|
||||
"message": "Ana parol gücü üçün minimum tələbləri tənzimlə."
|
||||
},
|
||||
"twoStepLoginPolicyTitle": {
|
||||
"message": "İki mərhələli girişi tələb et"
|
||||
},
|
||||
"twoStepLoginPolicyDesc": {
|
||||
"message": "İstifadəçilərin fərdi hesablarında \"iki mərhələli giriş\"i istifadə etmələrini məcburi et."
|
||||
},
|
||||
@@ -4802,15 +4820,15 @@
|
||||
},
|
||||
"ssoPolicyHelpStart": {
|
||||
"message": "Bütün üzvlərin SSO ilə",
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enable the SSO Authentication policy to require all members to log in with SSO.'"
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'"
|
||||
},
|
||||
"ssoPolicyHelpLink": {
|
||||
"message": "giriş etməsini məcburi etmək üçün",
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enable the SSO Authentication policy to require all members to log in with SSO.'"
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'"
|
||||
},
|
||||
"ssoPolicyHelpEnd": {
|
||||
"message": "SSO Siyasətini fəallaşdırın.",
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enable the SSO Authentication policy to require all members to log in with SSO.'"
|
||||
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'"
|
||||
},
|
||||
"ssoPolicyHelpKeyConnector": {
|
||||
"message": "Açar Bağlayıcı şifrə açmanı quraşdırmaq üçün SSO Kimlik Təsdiqləmə və Tək Təşkilat siyasətləri tələb olunur."
|
||||
@@ -5165,5 +5183,35 @@
|
||||
"example": "My Email"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputRequired": {
|
||||
"message": "Giriş lazımdır."
|
||||
},
|
||||
"inputEmail": {
|
||||
"message": "Giriş, bir e-poçt ünvanı deyil."
|
||||
},
|
||||
"inputMinLength": {
|
||||
"message": "Giriş, ən azı $COUNT$ simvol uzunluğunda olmalıdır.",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "8"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fieldsNeedAttention": {
|
||||
"message": "Yuxarıdakı $COUNT$ sahənin diqqətinizə ehtiyacı var.",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "4"
|
||||
}
|
||||
}
|
||||
},
|
||||
"turnOn": {
|
||||
"message": "İşə sal"
|
||||
},
|
||||
"on": {
|
||||
"message": "Açıqdır"
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user