diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index c61ecacd290..d6a871895f4 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -5557,7 +5557,25 @@ "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." }, "invalidDomainNameMessage": { - "message": "'https://', 'http://', or 'www.' domain prefixes not allowed." + "message": "Input is not a valid format. Example: mydomain.com. Subdomains require separate entries to be verified." + }, + "domainRemoved": { + "message": "Domain removed" + }, + "domainSaved": { + "message": "Domain saved" + }, + "domainVerified": { + "message": "Domain verified" + }, + "domainNotVerified": { + "message": "$DOMAIN$ not verified. Check your DNS record.", + "placeholders": { + "DOMAIN": { + "content": "$1", + "example": "bitwarden.com" + } + } } } diff --git a/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html b/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html index 2f95a634240..8f70f9752f5 100644 --- a/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html +++ b/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html @@ -1,7 +1,20 @@ - {{ "newDomain" | i18n }} + + {{ "newDomain" | i18n }} + {{ "verifyDomain" | i18n }} + + {{ + data.orgDomain.domainName + }} + + +
-
+ + + + + {{ "domainName" | i18n }} @@ -29,10 +42,17 @@
- -
diff --git a/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts b/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts index 4032714ed9d..bce006ae006 100644 --- a/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts @@ -4,14 +4,17 @@ 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 { OrgDomainApiServiceAbstraction } from "@bitwarden/common/abstractions/organization-domain/org-domain-api.service.abstraction"; 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"; +import { OrganizationDomainRequest } from "@bitwarden/common/services/organization-domain/requests/organization-domain.request"; import { domainNameValidator } from "./domain-name.validator"; export interface DomainAddEditDialogData { organizationId: string; orgDomain: OrganizationDomainResponse; + existingDomainNames: Array; } @Component({ @@ -22,6 +25,8 @@ export class DomainAddEditDialogComponent implements OnInit { dialogSize: "small" | "default" | "large" = "default"; disablePadding = false; + // TODO: should invalidDomainNameMessage have something like: "'https://', 'http://', or 'www.' domain prefixes not allowed." + // TODO: write separate uniqueIn validator w/ translated msg: "You can’t claim the same domain twice." domainForm: FormGroup = this.formBuilder.group({ domainName: [ "", @@ -37,13 +42,17 @@ export class DomainAddEditDialogComponent implements OnInit { return this.domainForm.controls.txt as FormControl; } + submitting = false; + deleting = false; + constructor( public dialogRef: DialogRef, @Inject(DIALOG_DATA) public data: DomainAddEditDialogData, private formBuilder: FormBuilder, private cryptoFunctionService: CryptoFunctionServiceAbstraction, private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService + private i18nService: I18nService, + private orgDomainApiService: OrgDomainApiServiceAbstraction ) {} async ngOnInit(): Promise { @@ -55,6 +64,7 @@ export class DomainAddEditDialogComponent implements OnInit { if (this.data.orgDomain) { // Edit this.domainForm.patchValue(this.data.orgDomain); + this.domainForm.disable(); } else { // Add @@ -69,12 +79,78 @@ export class DomainAddEditDialogComponent implements OnInit { } } - copyDnsTxt() { + copyDnsTxt(): void { this.platformUtilsService.copyToClipboard(this.txtCtrl.value); this.platformUtilsService.showToast( - "info", + "success", null, this.i18nService.t("valueCopied", this.i18nService.t("dnsTxtRecord")) ); } + + // TODO: error handling? + + // TODO: probably will be a need to split into different actions: save == save + verify + // and if edit true, then verify is verify. + + // Need to display verified status somewhere + // If verified, no action can be taken but delete + // If saved & unverified, can prompt verification + + async saveDomain(): Promise { + this.submitting = true; + this.domainForm.disable(); + + const request: OrganizationDomainRequest = new OrganizationDomainRequest( + this.txtCtrl.value, + this.domainNameCtrl.value + ); + + await this.orgDomainApiService.post(this.data.organizationId, request); + + //TODO: figure out how to handle DomainVerifiedException + + this.platformUtilsService.showToast("success", null, this.i18nService.t("domainSaved")); + this.submitting = false; + + // TODO: verify before closing modal; close if successful + this.dialogRef.close(); + } + + async verifyDomain(): Promise { + this.submitting = true; + this.domainForm.disable(); + + const success: boolean = await this.orgDomainApiService.verify( + this.data.organizationId, + this.data.orgDomain.id + ); + + this.submitting = false; + + if (success) { + this.platformUtilsService.showToast("success", null, this.i18nService.t("domainVerified")); + this.dialogRef.close(); + } else { + this.platformUtilsService.showToast( + "error", + null, + this.i18nService.t("domainNotVerified", this.domainNameCtrl.value) + ); + + // TODO: discuss with Danielle / Gbubemi: + // Someone else is using [domain]. Use a different domain to continue. + // I only have a bool to indicate success or failure.. not why it failed. + } + } + + async deleteDomain(): Promise { + // TODO: Do I need an are you sure prompt? + + this.deleting = true; + await this.orgDomainApiService.delete(this.data.organizationId, this.data.orgDomain.id); + this.deleting = false; + this.platformUtilsService.showToast("success", null, this.i18nService.t("domainRemoved")); + this.dialogRef.close(); + } } diff --git a/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-verification.component.html b/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-verification.component.html index 415d220d9bd..825f58e27ff 100644 --- a/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-verification.component.html +++ b/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-verification.component.html @@ -15,52 +15,85 @@ {{ "loading" | i18n }} - - -
- - - - Name - Status - Last Checked - Options - - - - - - {{ orgDomain.domainName }} - - {{ orgDomain.domainName }} - - test - test - - - -
+ +
+ + + + Name + Status + Last Checked + Options + + + + + + + {{ + orgDomain.domainName + }} + + + Unverified + Verified + + + + {{ orgDomain.nextRunDate | date: "medium" }} + -
- domain - -
- {{ "noDomains" | i18n }} + + + + + + + + + + +
-
- - {{ "noDomainsSubText" | i18n }} - -
+
+ domain - -
- ¸ +
+ {{ "noDomains" | i18n }} +
+ +
+ + {{ "noDomainsSubText" | i18n }} + +
+ + +
+
diff --git a/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-verification.component.ts b/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-verification.component.ts index d0de2f4db89..607da81becc 100644 --- a/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-verification.component.ts +++ b/bitwarden_license/bit-web/src/app/organizations/manage/domain-verification/domain-verification.component.ts @@ -68,6 +68,19 @@ export class DomainVerificationComponent implements OnInit, OnDestroy { const domainAddEditDialogData: DomainAddEditDialogData = { organizationId: this.organizationId, orgDomain: null, + existingDomainNames: [], + }; + + this.dialogService.open(DomainAddEditDialogComponent, { + data: domainAddEditDialogData, + }); + } + + editDomain(orgDomain: OrganizationDomainResponse) { + const domainAddEditDialogData: DomainAddEditDialogData = { + organizationId: this.organizationId, + orgDomain: orgDomain, + existingDomainNames: [], }; this.dialogService.open(DomainAddEditDialogComponent, { diff --git a/libs/common/src/abstractions/organization-domain/org-domain-api.service.abstraction.ts b/libs/common/src/abstractions/organization-domain/org-domain-api.service.abstraction.ts index 35d9e9d4eae..e0418037848 100644 --- a/libs/common/src/abstractions/organization-domain/org-domain-api.service.abstraction.ts +++ b/libs/common/src/abstractions/organization-domain/org-domain-api.service.abstraction.ts @@ -1,3 +1,5 @@ +import { OrganizationDomainRequest } from "../../services/organization-domain/requests/organization-domain.request"; + import { OrganizationDomainResponse } from "./responses/organization-domain.response"; export abstract class OrgDomainApiServiceAbstraction { @@ -6,7 +8,10 @@ export abstract class OrgDomainApiServiceAbstraction { orgId: string, orgDomainId: string ) => Promise; - post: (orgId: string, orgDomain: OrganizationDomainResponse) => Promise; + post: ( + orgId: string, + orgDomain: OrganizationDomainRequest + ) => Promise; verify: (orgId: string, orgDomainId: string) => Promise; delete: (orgId: string, orgDomainId: string) => Promise; } diff --git a/libs/common/src/services/organization-domain/org-domain-api.service.ts b/libs/common/src/services/organization-domain/org-domain-api.service.ts index 6e95c2dcaf3..f24d7e93e4a 100644 --- a/libs/common/src/services/organization-domain/org-domain-api.service.ts +++ b/libs/common/src/services/organization-domain/org-domain-api.service.ts @@ -45,13 +45,14 @@ export class OrgDomainApiService implements OrgDomainApiServiceAbstraction { return response; } - async post(orgId: string, orgDomain: OrganizationDomainResponse): Promise { - const request = new OrganizationDomainRequest(orgDomain); - + async post( + orgId: string, + orgDomainReq: OrganizationDomainRequest + ): Promise { const result = await this.apiService.send( "POST", - `/organizations/${orgId}`, - request, + `/organizations/${orgId}/domain`, + orgDomainReq, true, true ); @@ -66,7 +67,7 @@ export class OrgDomainApiService implements OrgDomainApiServiceAbstraction { async verify(orgId: string, orgDomainId: string): Promise { const result: boolean = await this.apiService.send( "POST", - `/organizations/${orgId}/${orgDomainId}/verify`, + `/organizations/${orgId}/domain/${orgDomainId}/verify`, null, true, true @@ -76,7 +77,13 @@ export class OrgDomainApiService implements OrgDomainApiServiceAbstraction { } async delete(orgId: string, orgDomainId: string): Promise { - this.apiService.send("DELETE", `/organizations/${orgId}/${orgDomainId}`, null, true, false); + this.apiService.send( + "DELETE", + `/organizations/${orgId}/domain/${orgDomainId}`, + null, + true, + false + ); this.orgDomainService.delete([orgDomainId]); } diff --git a/libs/common/src/services/organization-domain/requests/organization-domain.request.ts b/libs/common/src/services/organization-domain/requests/organization-domain.request.ts index 54e9fd96f51..fb515e3cbc9 100644 --- a/libs/common/src/services/organization-domain/requests/organization-domain.request.ts +++ b/libs/common/src/services/organization-domain/requests/organization-domain.request.ts @@ -1,11 +1,9 @@ -import { OrganizationDomainResponse } from "../../../abstractions/organization-domain/responses/organization-domain.response"; - export class OrganizationDomainRequest { txt: string; domainName: string; - constructor(orgDomainResponse: OrganizationDomainResponse) { - this.txt = orgDomainResponse.txt; - this.domainName = orgDomainResponse.domainName; + constructor(txt: string, domainName: string) { + this.txt = txt; + this.domainName = domainName; } }