Skip to content

Commit

Permalink
user record passkeys info
Browse files Browse the repository at this point in the history
  • Loading branch information
pragatimodi committed Oct 18, 2023
1 parent 15865e5 commit 9fd534d
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 2 deletions.
72 changes: 72 additions & 0 deletions src/auth/user-record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -81,6 +88,7 @@ export interface GetAccountInfoUserResponse {
tenantId?: string;
providerUserInfo?: ProviderUserInfoResponse[];
mfaInfo?: MultiFactorInfoResponse[];
passkeyInfo?: PasskeyInfoResponse[];
createdAt?: string;
lastLoginAt?: string;
lastRefreshAt?: string;
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -637,6 +694,15 @@ export class UserRecord {
if (multiFactor.enrolledFactors.length > 0) {
utils.addReadonlyGetter(this, 'multiFactor', multiFactor);
}
if(response.passkeyInfo) {

Check failure on line 697 in src/auth/user-record.ts

View workflow job for this annotation

GitHub Actions / build (14.x)

Expected space(s) after "if"
let passkeys: PasskeyInfo[] = [];

Check failure on line 698 in src/auth/user-record.ts

View workflow job for this annotation

GitHub Actions / build (14.x)

'passkeys' is never reassigned. Use 'const' instead
response.passkeyInfo.forEach((passkey) => {
passkeys.push(new PasskeyInfo(passkey));
});
if(passkeys.length > 0) {

Check failure on line 702 in src/auth/user-record.ts

View workflow job for this annotation

GitHub Actions / build (14.x)

Expected space(s) after "if"
utils.addReadonlyGetter(this, 'passkeyInfo', passkeys);
}
}
}

/**
Expand Down Expand Up @@ -664,6 +730,12 @@ export class UserRecord {
if (this.multiFactor) {
json.multiFactor = this.multiFactor.toJSON();
}
if(this.passkeyInfo) {

Check failure on line 733 in src/auth/user-record.ts

View workflow job for this annotation

GitHub Actions / build (14.x)

Expected space(s) after "if"
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.
Expand Down
1 change: 0 additions & 1 deletion test/unit/auth/passkey-config-manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
Expand Down
86 changes: 85 additions & 1 deletion test/unit/auth/user-record.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -100,6 +100,18 @@ function getValidUserResponse(tenantId?: string): GetAccountInfoUserResponse {
phoneInfo: '+16505556789',
},
],
passkeyInfo: [
{
name: "[email protected]",
credentialId: "credentialId1",
displayName: "passkey1",
},
{
name: "[email protected]",
credentialId: "credentialId2",
displayName: "passkey2",
}
]
};
if (typeof tenantId !== 'undefined') {
response.tenantId = tenantId;
Expand Down Expand Up @@ -185,6 +197,18 @@ function getUserJSON(tenantId?: string): object {
},
],
},
passkeyInfo: [
{
name: "[email protected]",
credentialId: "credentialId1",
displayName: "passkey1",
},
{
name: "[email protected]",
credentialId: "credentialId2",
displayName: "passkey2",
}
]
};
}

Expand Down Expand Up @@ -663,6 +687,66 @@ describe('MultiFactorSettings', () => {
});
});

describe('PasskeyInfo', () => {
const passkeyInfoData = {
name: 'John Doe',
credentialId: 'credential123',
displayName: '[email protected]',
};
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', () => {
Expand Down

0 comments on commit 9fd534d

Please sign in to comment.