diff --git a/jslib b/jslib
index 76ece834d1..b2c700ad28 160000
--- a/jslib
+++ b/jslib
@@ -1 +1 @@
-Subproject commit 76ece834d1d18e9cca71bb3c182d2284dae80958
+Subproject commit b2c700ad285d9336426284f766b434be8c509f0a
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index c0d3a9a61d..7679bbce34 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -25,6 +25,9 @@ import { GroupsComponent as OrgGroupsComponent } from './organizations/manage/gr
import { ManageComponent as OrgManageComponent } from './organizations/manage/manage.component';
import { PeopleComponent as OrgPeopleComponent } from './organizations/manage/people.component';
+import { AccountComponent as OrgAccountComponent } from './organizations/settings/account.component';
+import { SettingsComponent as OrgSettingsComponent } from './organizations/settings/settings.component';
+
import { ExportComponent as OrgExportComponent } from './organizations/tools/export.component';
import { ImportComponent as OrgImportComponent } from './organizations/tools/import.component';
import { ToolsComponent as OrgToolsComponent } from './organizations/tools/tools.component';
@@ -176,6 +179,16 @@ const routes: Routes = [
{ path: 'people', component: OrgPeopleComponent, data: { titleId: 'people' } },
],
},
+ {
+ path: 'settings',
+ component: OrgSettingsComponent,
+ canActivate: [OrganizationTypeGuardService],
+ data: { allowedTypes: [OrganizationUserType.Owner, OrganizationUserType.Admin] },
+ children: [
+ { path: '', pathMatch: 'full', redirectTo: 'account' },
+ { path: 'account', component: OrgAccountComponent, data: { titleId: 'myOrganization' } },
+ ],
+ },
],
},
{ path: '**', redirectTo: '' },
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 04462b2749..f440021c9f 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -51,6 +51,10 @@ import { PeopleComponent as OrgPeopleComponent } from './organizations/manage/pe
import { UserAddEditComponent as OrgUserAddEditComponent } from './organizations/manage/user-add-edit.component';
import { UserGroupsComponent as OrgUserGroupsComponent } from './organizations/manage/user-groups.component';
+import { AccountComponent as OrgAccountComponent } from './organizations/settings/account.component';
+import { DeleteOrganizationComponent } from './organizations/settings/delete-organization.component';
+import { SettingsComponent as OrgSettingComponent } from './organizations/settings/settings.component';
+
import { ExportComponent as OrgExportComponent } from './organizations/tools/export.component';
import { ImportComponent as OrgImportComponent } from './organizations/tools/import.component';
import { ToolsComponent as OrgToolsComponent } from './organizations/tools/tools.component';
@@ -164,6 +168,7 @@ import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
CreateOrganizationComponent,
DeauthorizeSessionsComponent,
DeleteAccountComponent,
+ DeleteOrganizationComponent,
DomainRulesComponent,
ExportComponent,
FallbackSrcDirective,
@@ -181,6 +186,7 @@ import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
ModalComponent,
NavbarComponent,
OptionsComponent,
+ OrgAccountComponent,
OrgAddEditComponent,
OrgAttachmentsComponent,
OrgCiphersComponent,
@@ -197,6 +203,7 @@ import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
OrgManageCollectionsComponent,
OrgManageComponent,
OrgPeopleComponent,
+ OrgSettingComponent,
OrgToolsComponent,
OrgUserAddEditComponent,
OrgUserGroupsComponent,
@@ -247,6 +254,7 @@ import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
CollectionsComponent,
DeauthorizeSessionsComponent,
DeleteAccountComponent,
+ DeleteOrganizationComponent,
FolderAddEditComponent,
ModalComponent,
OrgAddEditComponent,
diff --git a/src/app/layouts/organization-layout.component.html b/src/app/layouts/organization-layout.component.html
index 70246ad104..38222c574f 100644
--- a/src/app/layouts/organization-layout.component.html
+++ b/src/app/layouts/organization-layout.component.html
@@ -8,20 +8,20 @@
{{'organization' | i18n}}
-
+
-
{{'vault' | i18n}}
- -
+
-
{{'manage' | i18n}}
- -
+
-
{{'tools' | i18n}}
diff --git a/src/app/organizations/settings/account.component.html b/src/app/organizations/settings/account.component.html
new file mode 100644
index 0000000000..500ff87005
--- /dev/null
+++ b/src/app/organizations/settings/account.component.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
{{org.businessAddress1}}
+
{{org.businessAddress2}}
+
{{org.businessAddress3}}
+
{{org.businessCountry}}
+
{{org.businessTaxNumber}}
+
+{{'taxInformationDesc' | i18n}}
+
+ {{'contactSupport' | i18n}}
+
+
+
+
+
{{'dangerZoneDesc' | i18n}}
+
+
+
+
diff --git a/src/app/organizations/settings/account.component.ts b/src/app/organizations/settings/account.component.ts
new file mode 100644
index 0000000000..5c500998bb
--- /dev/null
+++ b/src/app/organizations/settings/account.component.ts
@@ -0,0 +1,81 @@
+import {
+ Component,
+ ComponentFactoryResolver,
+ ViewChild,
+ ViewContainerRef,
+} from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+
+import { ToasterService } from 'angular2-toaster';
+import { Angulartics2 } from 'angulartics2';
+
+import { ApiService } from 'jslib/abstractions/api.service';
+import { I18nService } from 'jslib/abstractions/i18n.service';
+import { SyncService } from 'jslib/abstractions/sync.service';
+
+import { OrganizationUpdateRequest } from 'jslib/models/request/organizationUpdateRequest';
+import { OrganizationResponse } from 'jslib/models/response/organizationResponse';
+
+import { ModalComponent } from '../../modal.component';
+import { DeleteOrganizationComponent } from './delete-organization.component';
+
+@Component({
+ selector: 'app-org-account',
+ templateUrl: 'account.component.html',
+})
+export class AccountComponent {
+ @ViewChild('deleteOrganizationTemplate', { read: ViewContainerRef }) deleteModalRef: ViewContainerRef;
+
+ loading = true;
+ org: OrganizationResponse;
+ formPromise: Promise;
+
+ private organizationId: string;
+ private modal: ModalComponent = null;
+
+ constructor(private componentFactoryResolver: ComponentFactoryResolver,
+ private apiService: ApiService, private i18nService: I18nService,
+ private analytics: Angulartics2, private toasterService: ToasterService,
+ private route: ActivatedRoute, private syncService: SyncService) { }
+
+ async ngOnInit() {
+ this.route.parent.parent.params.subscribe(async (params) => {
+ this.organizationId = params.organizationId;
+ try {
+ this.org = await this.apiService.getOrganization(this.organizationId);
+ } catch { }
+ });
+ this.loading = false;
+ }
+
+ async submit() {
+ try {
+ const request = new OrganizationUpdateRequest();
+ request.name = this.org.name;
+ request.businessName = this.org.businessName;
+ request.billingEmail = this.org.billingEmail;
+ this.formPromise = this.apiService.putOrganization(this.organizationId, request).then(() => {
+ return this.syncService.fullSync(true);
+ });
+ await this.formPromise;
+ this.analytics.eventTrack.next({ action: 'Updated Organization Settings' });
+ this.toasterService.popAsync('success', null, this.i18nService.t('organizationUpdated'));
+ } catch { }
+ }
+
+ deleteOrganization() {
+ if (this.modal != null) {
+ this.modal.close();
+ }
+
+ const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);
+ this.modal = this.deleteModalRef.createComponent(factory).instance;
+ const childComponent = this.modal.show(
+ DeleteOrganizationComponent, this.deleteModalRef);
+ childComponent.organizationId = this.organizationId;
+
+ this.modal.onClosed.subscribe(async () => {
+ this.modal = null;
+ });
+ }
+}
diff --git a/src/app/organizations/settings/delete-organization.component.html b/src/app/organizations/settings/delete-organization.component.html
new file mode 100644
index 0000000000..da16e474f8
--- /dev/null
+++ b/src/app/organizations/settings/delete-organization.component.html
@@ -0,0 +1,26 @@
+
diff --git a/src/app/organizations/settings/delete-organization.component.ts b/src/app/organizations/settings/delete-organization.component.ts
new file mode 100644
index 0000000000..148999cd4d
--- /dev/null
+++ b/src/app/organizations/settings/delete-organization.component.ts
@@ -0,0 +1,45 @@
+import { Component } from '@angular/core';
+import { Router } from '@angular/router';
+
+import { ToasterService } from 'angular2-toaster';
+import { Angulartics2 } from 'angulartics2';
+
+import { ApiService } from 'jslib/abstractions/api.service';
+import { CryptoService } from 'jslib/abstractions/crypto.service';
+import { I18nService } from 'jslib/abstractions/i18n.service';
+
+import { PasswordVerificationRequest } from 'jslib/models/request/passwordVerificationRequest';
+
+@Component({
+ selector: 'app-delete-organization',
+ templateUrl: 'delete-organization.component.html',
+})
+export class DeleteOrganizationComponent {
+ organizationId: string;
+
+ masterPassword: string;
+ formPromise: Promise;
+
+ constructor(private apiService: ApiService, private i18nService: I18nService,
+ private analytics: Angulartics2, private toasterService: ToasterService,
+ private cryptoService: CryptoService, private router: Router) { }
+
+ async submit() {
+ if (this.masterPassword == null || this.masterPassword === '') {
+ this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
+ this.i18nService.t('masterPassRequired'));
+ return;
+ }
+
+ const request = new PasswordVerificationRequest();
+ request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, null);
+ try {
+ this.formPromise = this.apiService.postDeleteOrganization(this.organizationId, request);
+ await this.formPromise;
+ this.analytics.eventTrack.next({ action: 'Deleted Organization' });
+ this.toasterService.popAsync('success', this.i18nService.t('organizationDeleted'),
+ this.i18nService.t('organizationDeletedDesc'));
+ this.router.navigate(['/']);
+ } catch { }
+ }
+}
diff --git a/src/app/organizations/settings/settings.component.html b/src/app/organizations/settings/settings.component.html
new file mode 100644
index 0000000000..9f413ab2a7
--- /dev/null
+++ b/src/app/organizations/settings/settings.component.html
@@ -0,0 +1,20 @@
+
diff --git a/src/app/organizations/settings/settings.component.ts b/src/app/organizations/settings/settings.component.ts
new file mode 100644
index 0000000000..ccaf24b3bc
--- /dev/null
+++ b/src/app/organizations/settings/settings.component.ts
@@ -0,0 +1,7 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-org-settings',
+ templateUrl: 'settings.component.html',
+})
+export class SettingsComponent { }
diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html
index 03c1943d84..3b4e9dd3b6 100644
--- a/src/app/settings/settings.component.html
+++ b/src/app/settings/settings.component.html
@@ -11,7 +11,7 @@
{{'options' | i18n}}
- Organizations
+ {{'organizations' | i18n}}
{{'billingAndLicensing' | i18n}}
@@ -23,7 +23,7 @@
{{'twoStepLogin' | i18n}}
- Domain Rules
+ {{'domainRules' | i18n}}
diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json
index 47416835b4..25b9a3cc4e 100644
--- a/src/locales/en/messages.json
+++ b/src/locales/en/messages.json
@@ -2143,5 +2143,32 @@
},
"deleteRecoverConfirmDesc": {
"message": "You have requested to delete your Bitwarden account. Click the button below to confirm."
+ },
+ "myOrganization": {
+ "message": "My Organization"
+ },
+ "deleteOrganization": {
+ "message": "Delete Organization"
+ },
+ "deleteOrganizationDesc": {
+ "message": "Proceed below to delete this organization and all associated data. Individual user accounts will remain, though they will not be associated to this organization anymore. "
+ },
+ "deleteOrganizationWarning": {
+ "message": "Deleting the organization is permanent. It cannot be undone."
+ },
+ "organizationDeleted": {
+ "message": "Organization Deleted"
+ },
+ "organizationDeletedDesc": {
+ "message": "The organization and all associated data has been deleted."
+ },
+ "organizationUpdated": {
+ "message": "Organization updated"
+ },
+ "taxInformation": {
+ "message": "Tax Information"
+ },
+ "taxInformationDesc": {
+ "message": "Please contact support to provide (or update) tax information for your invoices."
}
}