mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-27 12:36:14 +01:00
fingerprint phrase confirmation
This commit is contained in:
parent
9b43ccbbc0
commit
05cfa99ea0
2
jslib
2
jslib
@ -1 +1 @@
|
||||
Subproject commit f485fbb6870203b60ac27bcbc2e12bb45f24b538
|
||||
Subproject commit f514e2bb676cb0ecca81c678cb054ce596999971
|
@ -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,
|
||||
|
@ -88,3 +88,4 @@
|
||||
<ng-template #addEdit></ng-template>
|
||||
<ng-template #groupsTemplate></ng-template>
|
||||
<ng-template #eventsTemplate></ng-template>
|
||||
<ng-template #confirmTemplate></ng-template>
|
||||
|
@ -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<boolean>(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>(
|
||||
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;
|
||||
|
35
src/app/organizations/manage/user-confirm.component.html
Normal file
35
src/app/organizations/manage/user-confirm.component.html
Normal file
@ -0,0 +1,35 @@
|
||||
<div class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">
|
||||
{{'confirmUser' | i18n}}
|
||||
<small class="text-muted" *ngIf="name">{{name}}</small>
|
||||
</h2>
|
||||
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
{{'fingerprintEnsureIntegrity' | i18n}}
|
||||
<a href="#" target="_blank" rel="noopener">{{'learnMore' | i18n}}</a>
|
||||
</p>
|
||||
<p><code>{{fingerprint}}</code></p>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="dontAskAgain" name="DontAskAgain" [(ngModel)]="dontAskAgain">
|
||||
<label class="form-check-label" for="dontAskAgain">
|
||||
{{'dontAskFingerprintAgain' | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}"></i>
|
||||
<span>{{'confirm' | i18n}}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'cancel' | i18n}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
84
src/app/organizations/manage/user-confirm.component.ts
Normal file
84
src/app/organizations/manage/user-confirm.component.ts
Normal file
@ -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<any>;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -18,7 +18,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<app-avatar data="{{profile.name || profile.email}}" [email]="profile.email" dynamic="true" size="75" fontSize="35"></app-avatar>
|
||||
<div class="mb-3">
|
||||
<app-avatar data="{{profile.name || profile.email}}" [email]="profile.email" dynamic="true" size="75"
|
||||
fontSize="35"></app-avatar>
|
||||
</div>
|
||||
<hr>
|
||||
<p *ngIf="fingerprint">
|
||||
{{'yourAccountsFingerprint' | i18n}}:
|
||||
<a href="#" target="_blank" rel="noopener" title="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o"></i></a><br>
|
||||
<code>{{fingerprint}}</code>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
|
@ -7,7 +7,9 @@ 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 { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { UpdateProfileRequest } from 'jslib/models/request/updateProfileRequest';
|
||||
|
||||
@ -20,15 +22,21 @@ import { ProfileResponse } from 'jslib/models/response/profileResponse';
|
||||
export class ProfileComponent implements OnInit {
|
||||
loading = true;
|
||||
profile: ProfileResponse;
|
||||
fingerprint: string;
|
||||
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private userService: UserService, private cryptoService: CryptoService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.profile = await this.apiService.getProfile();
|
||||
this.loading = false;
|
||||
const fingerprint = await this.cryptoService.getFingerprint(await this.userService.getUserId());
|
||||
if (fingerprint != null) {
|
||||
this.fingerprint = fingerprint.join('-');
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
|
@ -2188,6 +2188,9 @@
|
||||
"confirm": {
|
||||
"message": "Confirm"
|
||||
},
|
||||
"confirmUser": {
|
||||
"message": "Confirm User"
|
||||
},
|
||||
"hasBeenConfirmed": {
|
||||
"message": "$USER$ has been confirmed.",
|
||||
"placeholders": {
|
||||
@ -2547,9 +2550,22 @@
|
||||
"message": "This is an old file attachment the needs to be fixed. Click to learn more."
|
||||
},
|
||||
"fix": {
|
||||
"message": "Fix"
|
||||
"message": "Fix",
|
||||
"description": "This is a verb. ex. 'Fix The Car'"
|
||||
},
|
||||
"oldAttachmentsNeedFixDesc": {
|
||||
"message": "There are old file attachments in your vault that need to be fixed before you can rotate your account's encryption key."
|
||||
},
|
||||
"yourAccountsFingerprint": {
|
||||
"message": "Your account's fingerprint phrase",
|
||||
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing."
|
||||
},
|
||||
"fingerprintEnsureIntegrity": {
|
||||
"message": "The ensure the integrity of your encryption keys, please verify the user's fingerprint phrase before continuing.",
|
||||
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing."
|
||||
},
|
||||
"dontAskFingerprintAgain": {
|
||||
"message": "Don't ask to verify fingerprint phrase again",
|
||||
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing."
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { ConstantsService } from 'jslib/services';
|
||||
export class HtmlStorageService implements StorageService {
|
||||
private localStorageKeys = new Set(['appId', 'anonymousAppId', 'rememberedEmail', 'passwordGenerationOptions',
|
||||
ConstantsService.disableFaviconKey, ConstantsService.lockOptionKey, 'rememberEmail', 'enableGravatars',
|
||||
ConstantsService.localeKey, ConstantsService.lockOptionKey]);
|
||||
ConstantsService.localeKey, ConstantsService.lockOptionKey, ConstantsService.autoConfirmFingerprints]);
|
||||
private localStorageStartsWithKeys = ['twoFactorToken_', ConstantsService.collapsedGroupingsKey + '_'];
|
||||
|
||||
constructor(private platformUtilsService: PlatformUtilsService) { }
|
||||
|
Loading…
Reference in New Issue
Block a user