mirror of
https://github.com/bitwarden/browser
synced 2026-01-08 03:23:50 +00:00
[PM-11901] Refactoring self-hosting license file uploader (#11083)
This commit is contained in:
@@ -13,6 +13,8 @@ import { OffboardingSurveyComponent } from "./offboarding-survey.component";
|
||||
import { PaymentV2Component } from "./payment/payment-v2.component";
|
||||
import { PaymentComponent } from "./payment/payment.component";
|
||||
import { PaymentMethodComponent } from "./payment-method.component";
|
||||
import { IndividualSelfHostingLicenseUploaderComponent } from "./self-hosting-license-uploader/individual-self-hosting-license-uploader.component";
|
||||
import { OrganizationSelfHostingLicenseUploaderComponent } from "./self-hosting-license-uploader/organization-self-hosting-license-uploader.component";
|
||||
import { SecretsManagerSubscribeComponent } from "./sm-subscribe.component";
|
||||
import { TaxInfoComponent } from "./tax-info.component";
|
||||
import { UpdateLicenseDialogComponent } from "./update-license-dialog.component";
|
||||
@@ -40,6 +42,8 @@ import { VerifyBankAccountComponent } from "./verify-bank-account/verify-bank-ac
|
||||
OffboardingSurveyComponent,
|
||||
AdjustPaymentDialogV2Component,
|
||||
AdjustStorageDialogV2Component,
|
||||
IndividualSelfHostingLicenseUploaderComponent,
|
||||
OrganizationSelfHostingLicenseUploaderComponent,
|
||||
],
|
||||
exports: [
|
||||
SharedModule,
|
||||
@@ -53,6 +57,8 @@ import { VerifyBankAccountComponent } from "./verify-bank-account/verify-bank-ac
|
||||
OffboardingSurveyComponent,
|
||||
VerifyBankAccountComponent,
|
||||
PaymentV2Component,
|
||||
IndividualSelfHostingLicenseUploaderComponent,
|
||||
OrganizationSelfHostingLicenseUploaderComponent,
|
||||
],
|
||||
})
|
||||
export class BillingSharedModule {}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
import { LicenseUploaderFormModel } from "./license-uploader-form-model";
|
||||
|
||||
/**
|
||||
* Shared implementation for processing license file uploads.
|
||||
* @remarks Requires self-hosting.
|
||||
*/
|
||||
export abstract class AbstractSelfHostingLicenseUploaderComponent {
|
||||
protected form: FormGroup;
|
||||
|
||||
protected constructor(
|
||||
protected readonly formBuilder: FormBuilder,
|
||||
protected readonly i18nService: I18nService,
|
||||
protected readonly platformUtilsService: PlatformUtilsService,
|
||||
protected readonly toastService: ToastService,
|
||||
protected readonly tokenService: TokenService,
|
||||
) {
|
||||
const isSelfHosted = this.platformUtilsService.isSelfHost();
|
||||
|
||||
if (!isSelfHosted) {
|
||||
throw new Error("This component should only be used in self-hosted environments");
|
||||
}
|
||||
|
||||
this.form = this.formBuilder.group({
|
||||
file: [null, [Validators.required]],
|
||||
});
|
||||
this.submit = this.submit.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the submitted license upload form model.
|
||||
* @protected
|
||||
*/
|
||||
protected get formValue(): LicenseUploaderFormModel {
|
||||
return this.form.value as LicenseUploaderFormModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when a different license file is selected.
|
||||
* @param event
|
||||
*/
|
||||
onLicenseFileSelectedChanged(event: Event): void {
|
||||
const element = event.target as HTMLInputElement;
|
||||
this.form.value.file = element.files.length > 0 ? element.files[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the license upload form.
|
||||
* @protected
|
||||
*/
|
||||
protected async submit(): Promise<void> {
|
||||
this.form.markAllAsTouched();
|
||||
|
||||
if (this.form.invalid) {
|
||||
return this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
message: this.i18nService.t("selectFile"),
|
||||
});
|
||||
}
|
||||
|
||||
const emailVerified = await this.tokenService.getEmailVerified();
|
||||
if (!emailVerified) {
|
||||
return this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
message: this.i18nService.t("verifyEmailFirst"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
abstract get description(): string;
|
||||
|
||||
abstract get hintFileName(): string;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Component, EventEmitter, Output } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
import { AbstractSelfHostingLicenseUploaderComponent } from "../../shared/self-hosting-license-uploader/abstract-self-hosting-license-uploader.component";
|
||||
|
||||
/**
|
||||
* Processes license file uploads for individual plans.
|
||||
* @remarks Requires self-hosting.
|
||||
*/
|
||||
@Component({
|
||||
selector: "individual-self-hosting-license-uploader",
|
||||
templateUrl: "./self-hosting-license-uploader.component.html",
|
||||
})
|
||||
export class IndividualSelfHostingLicenseUploaderComponent extends AbstractSelfHostingLicenseUploaderComponent {
|
||||
/**
|
||||
* Emitted when a license file has been successfully uploaded & processed.
|
||||
*/
|
||||
@Output() onLicenseFileUploaded: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
constructor(
|
||||
protected readonly apiService: ApiService,
|
||||
protected readonly formBuilder: FormBuilder,
|
||||
protected readonly i18nService: I18nService,
|
||||
protected readonly platformUtilsService: PlatformUtilsService,
|
||||
protected readonly syncService: SyncService,
|
||||
protected readonly toastService: ToastService,
|
||||
protected readonly tokenService: TokenService,
|
||||
) {
|
||||
super(formBuilder, i18nService, platformUtilsService, toastService, tokenService);
|
||||
}
|
||||
|
||||
protected async submit(): Promise<void> {
|
||||
await super.submit();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("license", this.formValue.file);
|
||||
|
||||
await this.apiService.postAccountLicense(formData);
|
||||
|
||||
await this.apiService.refreshIdentityToken();
|
||||
await this.syncService.fullSync(true);
|
||||
|
||||
this.onLicenseFileUploaded.emit();
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return "uploadLicenseFilePremium";
|
||||
}
|
||||
|
||||
get hintFileName(): string {
|
||||
return "bitwarden_premium_license.json";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface LicenseUploaderFormModel {
|
||||
file: File;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Component, EventEmitter, Output } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { OrgKey } from "@bitwarden/common/types/key";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
import { AbstractSelfHostingLicenseUploaderComponent } from "../../shared/self-hosting-license-uploader/abstract-self-hosting-license-uploader.component";
|
||||
|
||||
/**
|
||||
* Processes license file uploads for organizations.
|
||||
* @remarks Requires self-hosting.
|
||||
*/
|
||||
@Component({
|
||||
selector: "organization-self-hosting-license-uploader",
|
||||
templateUrl: "./self-hosting-license-uploader.component.html",
|
||||
})
|
||||
export class OrganizationSelfHostingLicenseUploaderComponent extends AbstractSelfHostingLicenseUploaderComponent {
|
||||
/**
|
||||
* Notifies the parent component of the `organizationId` the license was created for.
|
||||
*/
|
||||
@Output() onLicenseFileUploaded: EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
constructor(
|
||||
protected readonly formBuilder: FormBuilder,
|
||||
protected readonly i18nService: I18nService,
|
||||
protected readonly platformUtilsService: PlatformUtilsService,
|
||||
protected readonly toastService: ToastService,
|
||||
protected readonly tokenService: TokenService,
|
||||
private readonly apiService: ApiService,
|
||||
private readonly encryptService: EncryptService,
|
||||
private readonly cryptoService: CryptoService,
|
||||
private readonly organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private readonly syncService: SyncService,
|
||||
) {
|
||||
super(formBuilder, i18nService, platformUtilsService, toastService, tokenService);
|
||||
}
|
||||
|
||||
protected async submit(): Promise<void> {
|
||||
await super.submit();
|
||||
|
||||
const orgKey = await this.cryptoService.makeOrgKey<OrgKey>();
|
||||
const key = orgKey[0].encryptedString;
|
||||
const collection = await this.encryptService.encrypt(
|
||||
this.i18nService.t("defaultCollection"),
|
||||
orgKey[1],
|
||||
);
|
||||
const collectionCt = collection.encryptedString;
|
||||
const orgKeys = await this.cryptoService.makeKeyPair(orgKey[1]);
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append("license", this.formValue.file);
|
||||
fd.append("key", key);
|
||||
fd.append("collectionName", collectionCt);
|
||||
const response = await this.organizationApiService.createLicense(fd);
|
||||
const orgId = response.id;
|
||||
|
||||
await this.apiService.refreshIdentityToken();
|
||||
|
||||
// Org Keys live outside of the OrganizationLicense - add the keys to the org here
|
||||
const request = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString);
|
||||
await this.organizationApiService.updateKeys(orgId, request);
|
||||
|
||||
await this.apiService.refreshIdentityToken();
|
||||
await this.syncService.fullSync(true);
|
||||
|
||||
this.onLicenseFileUploaded.emit(orgId);
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return "uploadLicenseFileOrg";
|
||||
}
|
||||
|
||||
get hintFileName(): string {
|
||||
return "bitwarden_organization_license.json";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<p bitTypography="body1">{{ "uploadLicenseFileOrg" | i18n }}</p>
|
||||
<form [formGroup]="form" [bitSubmit]="submit">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ description | i18n }}</bit-label>
|
||||
<div>
|
||||
<button bitButton type="button" buttonType="secondary" (click)="fileSelector.click()">
|
||||
{{ "chooseFile" | i18n }}
|
||||
</button>
|
||||
{{ form.value.file ? form.value.file.name : ("noFileChosen" | i18n) }}
|
||||
</div>
|
||||
<input
|
||||
#fileSelector
|
||||
bitInput
|
||||
type="file"
|
||||
formControlName="file"
|
||||
(change)="onLicenseFileSelectedChanged($event)"
|
||||
accept="application/JSON"
|
||||
hidden
|
||||
class="tw-hidden"
|
||||
/>
|
||||
<bit-hint>{{ "licenseFileDesc" | i18n: hintFileName }}</bit-hint>
|
||||
</bit-form-field>
|
||||
<button type="submit" bitButton bitFormButton buttonType="primary">
|
||||
{{ "submit" | i18n }}
|
||||
</button>
|
||||
</form>
|
||||
Reference in New Issue
Block a user