diff --git a/jslib b/jslib index f485fbb687..f514e2bb67 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit f485fbb6870203b60ac27bcbc2e12bb45f24b538 +Subproject commit f514e2bb676cb0ecca81c678cb054ce596999971 diff --git a/src/app/app.module.ts b/src/app/app.module.ts index f3ea7f12fd..b6811c400b 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -50,6 +50,7 @@ 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 { UserAddEditComponent as OrgUserAddEditComponent } from './organizations/manage/user-add-edit.component'; +import { UserConfirmComponent as OrgUserConfirmComponent } from './organizations/manage/user-confirm.component'; import { UserGroupsComponent as OrgUserGroupsComponent } from './organizations/manage/user-groups.component'; import { AccountComponent as OrgAccountComponent } from './organizations/settings/account.component'; @@ -253,6 +254,7 @@ registerLocaleData(localeZhCn, 'zh-CN'); OrgToolsComponent, OrgTwoFactorSetupComponent, OrgUserAddEditComponent, + OrgUserConfirmComponent, OrgUserGroupsComponent, OrganizationsComponent, OrganizationLayoutComponent, @@ -314,6 +316,7 @@ registerLocaleData(localeZhCn, 'zh-CN'); OrgEntityUsersComponent, OrgGroupAddEditComponent, OrgUserAddEditComponent, + OrgUserConfirmComponent, OrgUserGroupsComponent, PasswordGeneratorHistoryComponent, PurgeVaultComponent, diff --git a/src/app/organizations/manage/people.component.html b/src/app/organizations/manage/people.component.html index 7ca94fa5e2..f4127ee6ef 100644 --- a/src/app/organizations/manage/people.component.html +++ b/src/app/organizations/manage/people.component.html @@ -88,3 +88,4 @@ + diff --git a/src/app/organizations/manage/people.component.ts b/src/app/organizations/manage/people.component.ts index e3de41c362..e0b9d57825 100644 --- a/src/app/organizations/manage/people.component.ts +++ b/src/app/organizations/manage/people.component.ts @@ -13,10 +13,13 @@ import { import { ToasterService } from 'angular2-toaster'; import { Angulartics2 } from 'angulartics2'; +import { ConstantsService } from 'jslib/services/constants.service'; + import { ApiService } from 'jslib/abstractions/api.service'; import { CryptoService } from 'jslib/abstractions/crypto.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; +import { StorageService } from 'jslib/abstractions/storage.service'; import { UserService } from 'jslib/abstractions/user.service'; import { OrganizationUserConfirmRequest } from 'jslib/models/request/organizationUserConfirmRequest'; @@ -31,6 +34,7 @@ import { Utils } from 'jslib/misc/utils'; import { ModalComponent } from '../../modal.component'; import { EntityEventsComponent } from './entity-events.component'; import { UserAddEditComponent } from './user-add-edit.component'; +import { UserConfirmComponent } from './user-confirm.component'; import { UserGroupsComponent } from './user-groups.component'; @Component({ @@ -41,6 +45,7 @@ export class PeopleComponent implements OnInit { @ViewChild('addEdit', { read: ViewContainerRef }) addEditModalRef: ViewContainerRef; @ViewChild('groupsTemplate', { read: ViewContainerRef }) groupsModalRef: ViewContainerRef; @ViewChild('eventsTemplate', { read: ViewContainerRef }) eventsModalRef: ViewContainerRef; + @ViewChild('confirmTemplate', { read: ViewContainerRef }) confirmModalRef: ViewContainerRef; loading = true; organizationId: string; @@ -61,7 +66,8 @@ export class PeopleComponent implements OnInit { private i18nService: I18nService, private componentFactoryResolver: ComponentFactoryResolver, private platformUtilsService: PlatformUtilsService, private analytics: Angulartics2, private toasterService: ToasterService, private cryptoService: CryptoService, - private userService: UserService, private router: Router) { } + private userService: UserService, private router: Router, + private storageService: StorageService) { } async ngOnInit() { this.route.parent.parent.params.subscribe(async (params) => { @@ -213,17 +219,48 @@ export class PeopleComponent implements OnInit { } async confirm(user: OrganizationUserUserDetailsResponse) { + function updateUser(self: PeopleComponent) { + user.status = OrganizationUserStatusType.Confirmed; + const mapIndex = self.statusMap.get(OrganizationUserStatusType.Accepted).indexOf(user); + if (mapIndex > -1) { + self.statusMap.get(OrganizationUserStatusType.Accepted).splice(mapIndex, 1); + self.statusMap.get(OrganizationUserStatusType.Confirmed).push(user); + } + } + if (this.actionPromise != null) { return; } + + const autoConfirm = await this.storageService.get(ConstantsService.autoConfirmFingerprints); + if (autoConfirm == null || !autoConfirm) { + if (this.modal != null) { + this.modal.close(); + } + + const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent); + this.modal = this.groupsModalRef.createComponent(factory).instance; + const childComponent = this.modal.show( + UserConfirmComponent, this.confirmModalRef); + + childComponent.name = user != null ? user.name || user.email : null; + childComponent.organizationId = this.organizationId; + childComponent.organizationUserId = user != null ? user.id : null; + childComponent.userId = user != null ? user.userId : null; + childComponent.onConfirmedUser.subscribe(() => { + this.modal.close(); + updateUser(this); + }); + + this.modal.onClosed.subscribe(() => { + this.modal = null; + }); + return; + } + this.actionPromise = this.doConfirmation(user); await this.actionPromise; - user.status = OrganizationUserStatusType.Confirmed; - const mapIndex = this.statusMap.get(OrganizationUserStatusType.Accepted).indexOf(user); - if (mapIndex > -1) { - this.statusMap.get(OrganizationUserStatusType.Accepted).splice(mapIndex, 1); - this.statusMap.get(OrganizationUserStatusType.Confirmed).push(user); - } + updateUser(this); this.analytics.eventTrack.next({ action: 'Confirmed User' }); this.toasterService.popAsync('success', null, this.i18nService.t('hasBeenConfirmed', user.name || user.email)); this.actionPromise = null; diff --git a/src/app/organizations/manage/user-confirm.component.html b/src/app/organizations/manage/user-confirm.component.html new file mode 100644 index 0000000000..03530a6688 --- /dev/null +++ b/src/app/organizations/manage/user-confirm.component.html @@ -0,0 +1,35 @@ + diff --git a/src/app/organizations/manage/user-confirm.component.ts b/src/app/organizations/manage/user-confirm.component.ts new file mode 100644 index 0000000000..ff0d0662b4 --- /dev/null +++ b/src/app/organizations/manage/user-confirm.component.ts @@ -0,0 +1,84 @@ +import { + Component, + EventEmitter, + Input, + OnInit, + Output, +} from '@angular/core'; + +import { ToasterService } from 'angular2-toaster'; +import { Angulartics2 } from 'angulartics2'; + +import { ConstantsService } from 'jslib/services/constants.service'; + +import { ApiService } from 'jslib/abstractions/api.service'; +import { CryptoService } from 'jslib/abstractions/crypto.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; +import { StorageService } from 'jslib/abstractions/storage.service'; + +import { OrganizationUserConfirmRequest } from 'jslib/models/request/organizationUserConfirmRequest'; + +import { Utils } from 'jslib/misc/utils'; + +@Component({ + selector: 'app-user-confirm', + templateUrl: 'user-confirm.component.html', +}) +export class UserConfirmComponent implements OnInit { + @Input() name: string; + @Input() userId: string; + @Input() organizationUserId: string; + @Input() organizationId: string; + @Output() onConfirmedUser = new EventEmitter(); + + dontAskAgain = false; + loading = true; + fingerprint: string; + formPromise: Promise; + + private publicKey: Uint8Array = null; + + constructor(private apiService: ApiService, private i18nService: I18nService, + private analytics: Angulartics2, private toasterService: ToasterService, + private cryptoService: CryptoService, private storageService: StorageService) { } + + async ngOnInit() { + try { + const publicKeyResponse = await this.apiService.getUserPublicKey(this.userId); + if (publicKeyResponse != null) { + this.publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey); + const fingerprint = await this.cryptoService.getFingerprint(this.userId, this.publicKey.buffer); + if (fingerprint != null) { + this.fingerprint = fingerprint.join('-'); + } + } + } catch { } + this.loading = false; + } + + async submit() { + if (this.loading) { + return; + } + + if (this.dontAskAgain) { + await this.storageService.save(ConstantsService.autoConfirmFingerprints, true); + } + + try { + this.formPromise = this.doConfirmation(); + await this.formPromise; + this.analytics.eventTrack.next({ action: 'Confirmed User' }); + this.toasterService.popAsync('success', null, this.i18nService.t('hasBeenConfirmed', this.name)); + this.onConfirmedUser.emit(); + } catch { } + } + + private async doConfirmation() { + const orgKey = await this.cryptoService.getOrgKey(this.organizationId); + const key = await this.cryptoService.rsaEncrypt(orgKey.key, this.publicKey.buffer); + const request = new OrganizationUserConfirmRequest(); + request.key = key.encryptedString; + await this.apiService.postOrganizationUserConfirm(this.organizationId, this.organizationUserId, request); + } +} diff --git a/src/app/settings/profile.component.html b/src/app/settings/profile.component.html index 9093fd18b0..cc4bec43a8 100644 --- a/src/app/settings/profile.component.html +++ b/src/app/settings/profile.component.html @@ -18,7 +18,17 @@
- +
+ +
+
+

+ {{'yourAccountsFingerprint' | i18n}}: + +
+ {{fingerprint}} +