PM-4877: Only allow replacing passkeys for the same userhandle (#9804)

* Initial draft

* small cleanup

* show vaul items without passkeys

* Refactored a bit

* tests run for me?

* Fixed platform test

* null and undefined

* lint
This commit is contained in:
Anders Åberg 2024-07-16 19:39:41 +02:00 committed by GitHub
parent aa8c5b1516
commit dbc9b9c90b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 28 additions and 2 deletions

View File

@ -65,6 +65,7 @@ export type BrowserFido2Message = { sessionId: string } & (
type: "ConfirmNewCredentialRequest";
credentialName: string;
userName: string;
userHandle: string;
userVerification: boolean;
fallbackSupported: boolean;
rpId: string;
@ -242,6 +243,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
async confirmNewCredential({
credentialName,
userName,
userHandle,
userVerification,
rpId,
}: NewCredentialParams): Promise<{ cipherId: string; userVerified: boolean }> {
@ -250,6 +252,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi
sessionId: this.sessionId,
credentialName,
userName,
userHandle,
userVerification,
fallbackSupported: this.fallbackSupported,
rpId,

View File

@ -143,8 +143,10 @@ export class Fido2Component implements OnInit, OnDestroy {
this.ciphers = (await this.cipherService.getAllDecrypted()).filter(
(cipher) => cipher.type === CipherType.Login && !cipher.isDeleted,
);
this.displayedCiphers = this.ciphers.filter((cipher) =>
cipher.login.matchesUri(this.url, equivalentDomains),
this.displayedCiphers = this.ciphers.filter(
(cipher) =>
cipher.login.matchesUri(this.url, equivalentDomains) &&
this.hasNoOtherPasskeys(cipher, message.userHandle),
);
if (this.displayedCiphers.length > 0) {
@ -405,4 +407,18 @@ export class Fido2Component implements OnInit, OnDestroy {
...msg,
});
}
/**
* This methods returns true if a cipher either has no passkeys, or has a passkey matching with userHandle
* @param userHandle
*/
private hasNoOtherPasskeys(cipher: CipherView, userHandle: string): boolean {
if (cipher.login.fido2Credentials == null || cipher.login.fido2Credentials.length === 0) {
return true;
}
return cipher.login.fido2Credentials.some((passkey) => {
passkey.userHandle === userHandle;
});
}
}

View File

@ -12,6 +12,11 @@ export interface NewCredentialParams {
*/
userName: string;
/**
* The userhandle (userid) of the user.
*/
userHandle: string;
/**
* Whether or not the user must be verified before completing the operation.
*/

View File

@ -215,6 +215,7 @@ describe("FidoAuthenticatorService", () => {
expect(userInterfaceSession.confirmNewCredential).toHaveBeenCalledWith({
credentialName: params.rpEntity.name,
userName: params.userEntity.name,
userHandle: Fido2Utils.bufferToString(params.userEntity.id),
userVerification,
rpId: params.rpEntity.id,
} as NewCredentialParams);

View File

@ -112,6 +112,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
const response = await userInterfaceSession.confirmNewCredential({
credentialName: params.rpEntity.name,
userName: params.userEntity.name,
userHandle: Fido2Utils.bufferToString(params.userEntity.id),
userVerification: params.requireUserVerification,
rpId: params.rpEntity.id,
});