From 9fd534df811d04a5c794179b80bcaa266083e73a Mon Sep 17 00:00:00 2001 From: Pragati Date: Wed, 18 Oct 2023 10:29:29 -0700 Subject: [PATCH] user record passkeys info --- src/auth/user-record.ts | 72 ++++++++++++++++ test/unit/auth/passkey-config-manager.spec.ts | 1 - test/unit/auth/user-record.spec.ts | 86 ++++++++++++++++++- 3 files changed, 157 insertions(+), 2 deletions(-) diff --git a/src/auth/user-record.ts b/src/auth/user-record.ts index 5b00151401..8b6aed3061 100644 --- a/src/auth/user-record.ts +++ b/src/auth/user-record.ts @@ -56,6 +56,13 @@ export interface TotpInfoResponse { [key: string]: unknown; } +export interface PasskeyInfoResponse { + name?: string; + credentialId?: string; + displayName?: string; + [key: string]: unknown; +} + export interface ProviderUserInfoResponse { rawId: string; displayName?: string; @@ -81,6 +88,7 @@ export interface GetAccountInfoUserResponse { tenantId?: string; providerUserInfo?: ProviderUserInfoResponse[]; mfaInfo?: MultiFactorInfoResponse[]; + passkeyInfo?: PasskeyInfoResponse[]; createdAt?: string; lastLoginAt?: string; lastRefreshAt?: string; @@ -357,6 +365,50 @@ export class MultiFactorSettings { } } +/** + * Information about passkeys + */ +export class PasskeyInfo { + /** + * The name of the user. + */ + public readonly name?: string; + /** + * Identifier for the registered credential. + */ + public readonly credentialId: string; + /** + * The human-readable name of the user, intended for display. + */ + public readonly displayName?: string; + + constructor(response: PasskeyInfoResponse) { + if (!isNonNullObject(response)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INTERNAL_ERROR, + 'INTERNAL ASSERT FAILED: Invalid passkey info response'); + } + utils.addReadonlyGetter(this, 'name', response.name); + utils.addReadonlyGetter(this, 'credentialId', response.credentialId); + utils.addReadonlyGetter(this, 'displayName', response.displayName); + } + + /** + * Returns a JSON-serializable representation of this passkey info object. + * + * @returns A JSON-serializable representation of this passkey info object. + */ + public toJSON(): object { + const json: any = { + name: this.name, + credentialId: this.credentialId, + displayName: this.displayName, + } + return json; + } + +} + /** * Represents a user's metadata. */ @@ -582,6 +634,11 @@ export class UserRecord { */ public readonly multiFactor?: MultiFactorSettings; + /** + * Information about Passkeys + */ + public readonly passkeyInfo?: PasskeyInfo[]; + /** * @param response - The server side response returned from the getAccountInfo * endpoint. @@ -637,6 +694,15 @@ export class UserRecord { if (multiFactor.enrolledFactors.length > 0) { utils.addReadonlyGetter(this, 'multiFactor', multiFactor); } + if(response.passkeyInfo) { + let passkeys: PasskeyInfo[] = []; + response.passkeyInfo.forEach((passkey) => { + passkeys.push(new PasskeyInfo(passkey)); + }); + if(passkeys.length > 0) { + utils.addReadonlyGetter(this, 'passkeyInfo', passkeys); + } + } } /** @@ -664,6 +730,12 @@ export class UserRecord { if (this.multiFactor) { json.multiFactor = this.multiFactor.toJSON(); } + if(this.passkeyInfo) { + json.passkeyInfo = []; + this.passkeyInfo.forEach((passkey) => { + json.passkeyInfo.push(passkey.toJSON()); + }) + } json.providerData = []; for (const entry of this.providerData) { // Convert each provider data to json. diff --git a/test/unit/auth/passkey-config-manager.spec.ts b/test/unit/auth/passkey-config-manager.spec.ts index ae645f3eaa..5ce7c576ca 100644 --- a/test/unit/auth/passkey-config-manager.spec.ts +++ b/test/unit/auth/passkey-config-manager.spec.ts @@ -142,7 +142,6 @@ describe('PasskeyConfigManager', () => { }); it('should be rejected given an app which returns null access tokens', () => { - console.log('TEST===' + JSON.stringify(passkeyConfigRequest)); return nullAccessTokenPasskeyConfigManager.createPasskeyConfig(rpId, passkeyConfigRequest) .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); }); diff --git a/test/unit/auth/user-record.spec.ts b/test/unit/auth/user-record.spec.ts index dc332c13b9..d635617294 100644 --- a/test/unit/auth/user-record.spec.ts +++ b/test/unit/auth/user-record.spec.ts @@ -21,7 +21,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import { deepCopy } from '../../../src/utils/deep-copy'; import { - GetAccountInfoUserResponse, ProviderUserInfoResponse, MultiFactorInfoResponse, TotpMultiFactorInfo, + GetAccountInfoUserResponse, ProviderUserInfoResponse, MultiFactorInfoResponse, TotpMultiFactorInfo, PasskeyInfo, } from '../../../src/auth/user-record'; import { UserInfo, UserMetadata, UserRecord, MultiFactorSettings, MultiFactorInfo, PhoneMultiFactorInfo, @@ -100,6 +100,18 @@ function getValidUserResponse(tenantId?: string): GetAccountInfoUserResponse { phoneInfo: '+16505556789', }, ], + passkeyInfo: [ + { + name: "name1@google.com", + credentialId: "credentialId1", + displayName: "passkey1", + }, + { + name: "name2@google.com", + credentialId: "credentialId2", + displayName: "passkey2", + } + ] }; if (typeof tenantId !== 'undefined') { response.tenantId = tenantId; @@ -185,6 +197,18 @@ function getUserJSON(tenantId?: string): object { }, ], }, + passkeyInfo: [ + { + name: "name1@google.com", + credentialId: "credentialId1", + displayName: "passkey1", + }, + { + name: "name2@google.com", + credentialId: "credentialId2", + displayName: "passkey2", + } + ] }; } @@ -663,6 +687,66 @@ describe('MultiFactorSettings', () => { }); }); +describe('PasskeyInfo', () => { + const passkeyInfoData = { + name: 'John Doe', + credentialId: 'credential123', + displayName: 'john.doe@example.com', + }; + const passkeyInfo = new PasskeyInfo(passkeyInfoData); + + describe('constructor', () => { + it('should create a PasskeyInfo object with valid data', () => { + expect(passkeyInfo).to.be.an.instanceOf(PasskeyInfo); + }); + + it('should throw when missing required fields', () => { + expect(() => { + return new PasskeyInfo({}); + }).to.throw('INTERNAL ASSERT FAILED: Invalid passkey info response'); + }); + }); + + describe('getters', () => { + it('should return the expected name', () => { + expect(passkeyInfo.name).to.equal(passkeyInfoData.name); + }); + + it('should throw when modifying readonly name property', () => { + expect(() => { + (passkeyInfo as any).name = 'Modified Name'; + }).to.throw(Error); + }); + + it('should return the expected credentialId', () => { + expect(passkeyInfo.credentialId).to.equal(passkeyInfoData.credentialId); + }); + + it('should throw when modifying readonly credentialId property', () => { + expect(() => { + (passkeyInfo as any).credentialId = 'modifiedCredential'; + }).to.throw(Error); + }); + + it('should return the expected displayName', () => { + expect(passkeyInfo.displayName).to.equal(passkeyInfoData.displayName); + }); + + it('should throw when modifying readonly displayName property', () => { + expect(() => { + (passkeyInfo as any).displayName = 'modifiedDisplayName'; + }).to.throw(Error); + }); + }); + + describe('toJSON', () => { + it('should return the expected JSON object', () => { + expect(passkeyInfo.toJSON()).to.deep.equal(passkeyInfoData); + }); + }); +}); + + describe('UserInfo', () => { describe('constructor', () => { it('should throw when an empty object is provided', () => {