mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 07:13:32 +00:00
SG-680 - Domain Add Edit Dialog - (1) Random generation of DNS TXT Record now working (2) DNS TXT Record Copyable (3) Additional translations added (4) Info callout added
This commit is contained in:
@@ -5537,5 +5537,24 @@
|
|||||||
},
|
},
|
||||||
"verifyDomain": {
|
"verifyDomain": {
|
||||||
"message": "Verify domain"
|
"message": "Verify domain"
|
||||||
|
},
|
||||||
|
"copyDnsTxtRecord": {
|
||||||
|
"message": "Copy DNS TXT record"
|
||||||
|
},
|
||||||
|
"dnsTxtRecord": {
|
||||||
|
"message": "DNS TXT record"
|
||||||
|
},
|
||||||
|
"dnsTxtRecordInputHint": {
|
||||||
|
"message": "Copy and paste the TXT record into your DNS Provider."
|
||||||
|
},
|
||||||
|
"domainNameInputHint": {
|
||||||
|
"message": "Example: mydomain.com. Subdomains require separate entries to be verified."
|
||||||
|
},
|
||||||
|
"automaticDomainVerification": {
|
||||||
|
"message": "Automatic Domain Verification"
|
||||||
|
},
|
||||||
|
"automaticDomainVerificationProcess": {
|
||||||
|
"message": "Bitwarden will attempt to verify the domain 3 times during the first 72 hours. If the domain can’t be verified, check the DNS record in your host and manually verify."
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,19 +3,30 @@
|
|||||||
<div bitDialogContent>
|
<div bitDialogContent>
|
||||||
<form [formGroup]="domainForm">
|
<form [formGroup]="domainForm">
|
||||||
<bit-form-field>
|
<bit-form-field>
|
||||||
<bit-label>Domain name</bit-label>
|
<bit-label>{{ "domainName" | i18n }}</bit-label>
|
||||||
<input bitInput formControlName="domainName" />
|
<input bitInput formControlName="domainName" />
|
||||||
<bit-hint
|
<bit-hint>{{ "domainNameInputHint" | i18n }}</bit-hint>
|
||||||
>Example: mydomain.com. Subdomains require separate entries to be verified.</bit-hint
|
|
||||||
>
|
|
||||||
</bit-form-field>
|
</bit-form-field>
|
||||||
|
|
||||||
<bit-form-field>
|
<bit-form-field>
|
||||||
<bit-label>DNS TXT record</bit-label>
|
<bit-label>{{ "dnsTxtRecord" | i18n }}</bit-label>
|
||||||
<input bitInput formControlName="txt" />
|
<input bitInput formControlName="txt" />
|
||||||
<bit-hint>Copy and paste the TXT record into your DNS Provider.</bit-hint>
|
<bit-hint>{{ "dnsTxtRecordInputHint" | i18n }}</bit-hint>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
bitSuffix
|
||||||
|
bitButton
|
||||||
|
appA11yTitle="{{ 'copyDnsTxtRecord' | i18n }}"
|
||||||
|
(click)="copyDnsTxt()"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
</bit-form-field>
|
</bit-form-field>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<bit-callout type="info" title="{{ 'automaticDomainVerification' | i18n }}">
|
||||||
|
{{ "automaticDomainVerificationProcess" | i18n }}
|
||||||
|
</bit-callout>
|
||||||
</div>
|
</div>
|
||||||
<div bitDialogFooter class="tw-flex tw-flex-row tw-items-center tw-gap-2">
|
<div bitDialogFooter class="tw-flex tw-flex-row tw-items-center tw-gap-2">
|
||||||
<button bitButton buttonType="primary">{{ "verifyDomain" | i18n }}</button>
|
<button bitButton buttonType="primary">{{ "verifyDomain" | i18n }}</button>
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||||
import { Component, Inject, OnInit } from "@angular/core";
|
import { Component, Inject, OnInit } from "@angular/core";
|
||||||
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
|
import { FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms";
|
||||||
|
|
||||||
|
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||||
|
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||||
import { OrganizationDomainResponse } from "@bitwarden/common/abstractions/organization-domain/responses/organization-domain.response";
|
import { OrganizationDomainResponse } from "@bitwarden/common/abstractions/organization-domain/responses/organization-domain.response";
|
||||||
|
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||||
|
import { Utils } from "@bitwarden/common/misc/utils";
|
||||||
export interface DomainAddEditDialogData {
|
export interface DomainAddEditDialogData {
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
orgDomain: OrganizationDomainResponse;
|
orgDomain: OrganizationDomainResponse;
|
||||||
@@ -17,18 +20,57 @@ export class DomainAddEditDialogComponent implements OnInit {
|
|||||||
dialogSize: "small" | "default" | "large" = "default";
|
dialogSize: "small" | "default" | "large" = "default";
|
||||||
disablePadding = false;
|
disablePadding = false;
|
||||||
|
|
||||||
|
// TODO: custom validator for preventing https:// or www. on domainName
|
||||||
domainForm: FormGroup = this.formBuilder.group({
|
domainForm: FormGroup = this.formBuilder.group({
|
||||||
domainName: ["", [Validators.required]],
|
domainName: ["", [Validators.required]],
|
||||||
txt: [""],
|
txt: [{ value: null, disabled: true }],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
get domainNameCtrl(): FormControl {
|
||||||
|
return this.domainForm.controls.domainName as FormControl;
|
||||||
|
}
|
||||||
|
get txtCtrl(): FormControl {
|
||||||
|
return this.domainForm.controls.txt as FormControl;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public dialogRef: DialogRef,
|
public dialogRef: DialogRef,
|
||||||
@Inject(DIALOG_DATA) public data: DomainAddEditDialogData,
|
@Inject(DIALOG_DATA) public data: DomainAddEditDialogData,
|
||||||
private formBuilder: FormBuilder
|
private formBuilder: FormBuilder,
|
||||||
|
private cryptoFunctionService: CryptoFunctionServiceAbstraction,
|
||||||
|
private platformUtilsService: PlatformUtilsService,
|
||||||
|
private i18nService: I18nService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
async ngOnInit(): Promise<void> {
|
||||||
// If we have data.orgDomain, then editing, otherwise creating new domain
|
// If we have data.orgDomain, then editing, otherwise creating new domain
|
||||||
|
await this.populateForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
async populateForm(): Promise<void> {
|
||||||
|
if (this.data.orgDomain) {
|
||||||
|
// Edit
|
||||||
|
this.domainForm.patchValue(this.data.orgDomain);
|
||||||
|
} else {
|
||||||
|
// Add
|
||||||
|
|
||||||
|
// Figuring out the proper length of our DNS TXT Record value was fun.
|
||||||
|
// DNS-Based Service Discovery RFC: https://www.ietf.org/rfc/rfc6763.txt; see section 6.1
|
||||||
|
// Google uses 43 chars for their TXT record value: https://support.google.com/a/answer/2716802
|
||||||
|
// So, chose a magic # of 33 bytes to achieve at least that once converted to base 64 (47 char length).
|
||||||
|
const generatedTxt = `bw=${Utils.fromBufferToB64(
|
||||||
|
await this.cryptoFunctionService.randomBytes(33)
|
||||||
|
)}`;
|
||||||
|
this.txtCtrl.setValue(generatedTxt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copyDnsTxt() {
|
||||||
|
this.platformUtilsService.copyToClipboard(this.txtCtrl.value);
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"info",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("valueCopied", this.i18nService.t("dnsTxtRecord"))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user