Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
629f8ac
passkey config admin changes
pragatimodi Oct 4, 2023
a2c2431
Bug Fix for issue #2320 (#2321)
rishabhAjay Oct 3, 2023
62b0003
fixed unit test errors
dependabot[bot] Oct 4, 2023
f0b0ea6
lint fixes + integration
dependabot[bot] Oct 12, 2023
ac9a82f
remove integration tests
pragatimodi Oct 18, 2023
b40b1ab
adding comments
pragatimodi Oct 18, 2023
b35cb33
undo package json changes
pragatimodi Oct 18, 2023
8bd4d39
Merge branch 'master' into passkeys
pragatimodi Oct 18, 2023
5c16959
passkey config admin changes
pragatimodi Oct 4, 2023
218e7d3
fixed unit test errors
dependabot[bot] Oct 4, 2023
0977e7a
lint fixes + integration
dependabot[bot] Oct 12, 2023
dfe7f4a
remove integration tests
pragatimodi Oct 18, 2023
1bb1526
undo package json changes
pragatimodi Oct 18, 2023
abfb295
adding comments
pragatimodi Oct 18, 2023
045d959
Merge branch 'passkeys' of https://github.com/firebase/firebase-admin…
pragatimodi Oct 18, 2023
fff81ce
undo package json changes
pragatimodi Oct 18, 2023
9d69dd8
undo package json changes
pragatimodi Oct 18, 2023
7127be9
Merge branch 'passkeys' of https://github.com/firebase/firebase-admin…
pragatimodi Oct 18, 2023
1ab9273
undo duplicate npm changes
pragatimodi Oct 18, 2023
c294ff4
Apply suggestions from code review
pragatimodi Oct 19, 2023
6492438
update toJSON for string values
pragatimodi Apr 2, 2024
f9d35c3
Merge branch 'master' into passkeys
pragatimodi Apr 3, 2024
041bf17
user record changes for getAccountInfo() (#2341)
pragatimodi Apr 3, 2024
0389dc1
correct endpoint
pragatimodi Apr 3, 2024
5364938
fix tenant endpoint handling
pragatimodi Apr 3, 2024
8d3384b
fix lint
pragatimodi Apr 3, 2024
cec0cbe
add api:extractor changes
pragatimodi Apr 3, 2024
2c70e61
remove uncommented code
pragatimodi Jun 26, 2024
64203c3
Merge branch 'master' into passkeys
pragatimodi Jun 27, 2024
bf58ff0
Merge branch 'master' into passkeys
pragatimodi Jul 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
lint fixes + integration
  • Loading branch information
dependabot[bot] authored and pragatimodi committed Oct 18, 2023
commit 0977e7a822264bfbcdd18da0d06b732d242234f0
14 changes: 9 additions & 5 deletions src/auth/auth-api-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import {
SAMLUpdateAuthProviderRequest
} from './auth-config';
import { ProjectConfig, ProjectConfigServerResponse, UpdateProjectConfigRequest } from './project-config';
import {PasskeyConfig, PasskeyConfigServerResponse, PasskeyConfigRequest} from './passkey-config';
import { PasskeyConfig, PasskeyConfigServerResponse, PasskeyConfigRequest } from './passkey-config';

/** Firebase Auth request header. */
const FIREBASE_AUTH_HEADER = {
Expand Down Expand Up @@ -2108,7 +2108,8 @@ const UPDATE_PASSKEY_CONFIG = new ApiSettings('/passkeyConfig?updateMask={update
});

/** Instantiates the getPasskeyConfig endpoint settings. */
const UPDATE_TENANT_PASSKEY_CONFIG = new ApiSettings('/tenant/{tenantId}/passkeyConfig?updateMask={updateMask}', 'PATCH')
const UPDATE_TENANT_PASSKEY_CONFIG = new ApiSettings(
'/tenant/{tenantId}/passkeyConfig?updateMask={updateMask}', 'PATCH')
.setResponseValidator((response: any) => {
// Response should always contain at least the config name.
if (!validator.isNonEmptyString(response.name)) {
Expand Down Expand Up @@ -2296,18 +2297,21 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler {
}

public getPasskeyConfig(tenantId?: string): Promise<PasskeyConfigServerResponse> {
return this.invokeRequestHandler(this.authResourceUrlBuilder, tenantId? GET_TENANT_PASSKEY_CONFIG: GET_PASSKEY_CONFIG, {}, {})
return this.invokeRequestHandler(this.authResourceUrlBuilder,
tenantId? GET_TENANT_PASSKEY_CONFIG: GET_PASSKEY_CONFIG, {}, {})
.then((response: any) => {
return response as PasskeyConfigServerResponse;
});
}

public updatePasskeyConfig(isCreateRequest: boolean, tenantId?: string, options?: PasskeyConfigRequest, rpId?: string): Promise<PasskeyConfigServerResponse> {
public updatePasskeyConfig(isCreateRequest: boolean, tenantId?: string,
options?: PasskeyConfigRequest, rpId?: string): Promise<PasskeyConfigServerResponse> {
try {
const request = PasskeyConfig.buildServerRequest(isCreateRequest, options, rpId);
const updateMask = utils.generateUpdateMask(request);
return this.invokeRequestHandler(
this.authResourceUrlBuilder, tenantId? UPDATE_TENANT_PASSKEY_CONFIG: UPDATE_PASSKEY_CONFIG, request, { updateMask: updateMask.join(',') })
this.authResourceUrlBuilder, tenantId? UPDATE_TENANT_PASSKEY_CONFIG: UPDATE_PASSKEY_CONFIG,
request, { updateMask: updateMask.join(',') })
.then((response: any) => {
return response as PasskeyConfigServerResponse;
});
Expand Down
10 changes: 8 additions & 2 deletions src/auth/passkey-config-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ import { App } from '../app';
import {
AuthRequestHandler,
} from './auth-api-request';
import { PasskeyConfig, PasskeyConfigClientRequest, PasskeyConfigRequest, PasskeyConfigServerResponse } from './passkey-config';
import {
PasskeyConfig,
PasskeyConfigClientRequest,
PasskeyConfigRequest,
PasskeyConfigServerResponse
} from './passkey-config';


export class PasskeyConfigManager {
Expand All @@ -34,7 +39,8 @@ export class PasskeyConfigManager {
});
}

public createPasskeyConfig(rpId: string, passkeyConfigRequest: PasskeyConfigRequest, tenantId?: string): Promise<PasskeyConfig> {
public createPasskeyConfig(rpId: string, passkeyConfigRequest: PasskeyConfigRequest,
tenantId?: string): Promise<PasskeyConfig> {
return this.authRequestHandler.updatePasskeyConfig(true, tenantId, passkeyConfigRequest, rpId)
.then((response: PasskeyConfigClientRequest) => {
return new PasskeyConfig(response);
Expand Down
101 changes: 51 additions & 50 deletions src/auth/passkey-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/
import * as validator from '../utils/validator';
import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error';
import {deepCopy} from '../utils/deep-copy';
import { deepCopy } from '../utils/deep-copy';

export interface PasskeyConfigRequest {
expectedOrigins?: string[];
Expand All @@ -38,94 +38,95 @@ export class PasskeyConfig {
public readonly rpId?: string;
public readonly expectedOrigins?: string[];

private static validate(isCreateRequest: boolean, passkeyConfigRequest?: PasskeyConfigRequest, rpId?: string) {
if(isCreateRequest && !validator.isNonEmptyString(rpId)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_ARGUMENT,
`'rpId' must be a valid non-empty string'`,
);
private static validate(isCreateRequest: boolean, passkeyConfigRequest?: PasskeyConfigRequest, rpId?: string): void {
if (isCreateRequest && !validator.isNonEmptyString(rpId)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_ARGUMENT,
'\'rpId\' must be a valid non-empty string\'',
);
}
if(!isCreateRequest && typeof rpId !== 'undefined') {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_ARGUMENT,
`'rpId' cannot be changed once created.'`,
);
if (!isCreateRequest && typeof rpId !== 'undefined') {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_ARGUMENT,
'\'rpId\' cannot be changed once created.\'',
);
}
if(!validator.isNonNullObject(passkeyConfigRequest)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_ARGUMENT,
`'passkeyConfigRequest' must be a valid non-empty object.'`,
);
if (!validator.isNonNullObject(passkeyConfigRequest)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_ARGUMENT,
'\'passkeyConfigRequest\' must be a valid non-empty object.\'',
);
}
const validKeys = {
expectedOrigins: true,
expectedOrigins: true,
};
// Check for unsupported top level attributes.
for (const key in passkeyConfigRequest) {
if (!(key in validKeys)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_ARGUMENT,
`'${key}' is not a valid PasskeyConfigRequest parameter.`,
);
}
}
if(!validator.isNonEmptyArray(passkeyConfigRequest.expectedOrigins)) {
if (!(key in validKeys)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_ARGUMENT,
`'passkeyConfigRequest.expectedOrigins' must be a valid non-empty array of strings.'`,
);
AuthClientErrorCode.INVALID_ARGUMENT,
`'${key}' is not a valid PasskeyConfigRequest parameter.`,
);
}
}
if (!validator.isNonEmptyArray(passkeyConfigRequest.expectedOrigins)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_ARGUMENT,
'\'passkeyConfigRequest.expectedOrigins\' must be a valid non-empty array of strings.\'',
);
}
for (const origin of passkeyConfigRequest.expectedOrigins) {
if (!validator.isNonEmptyString(origin)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_ARGUMENT,
`'passkeyConfigRequest.expectedOrigins' must be a valid non-empty array of strings.'`,
'\'passkeyConfigRequest.expectedOrigins\' must be a valid non-empty array of strings.\'',
);
}
}
};
}

public static buildServerRequest(isCreateRequest: boolean, passkeyConfigRequest?: PasskeyConfigRequest, rpId?: string): PasskeyConfigClientRequest {
public static buildServerRequest(isCreateRequest: boolean, passkeyConfigRequest?: PasskeyConfigRequest,
rpId?: string): PasskeyConfigClientRequest {
PasskeyConfig.validate(isCreateRequest, passkeyConfigRequest, rpId);
let request: PasskeyConfigClientRequest = {};
if(isCreateRequest && typeof rpId !== 'undefined') {
request.rpId = rpId;
const request: PasskeyConfigClientRequest = {};
if (isCreateRequest && typeof rpId !== 'undefined') {
request.rpId = rpId;
}
if(typeof passkeyConfigRequest?.expectedOrigins !== 'undefined') {
request.expectedOrigins = passkeyConfigRequest.expectedOrigins;
if (typeof passkeyConfigRequest?.expectedOrigins !== 'undefined') {
request.expectedOrigins = passkeyConfigRequest.expectedOrigins;
}
return request;
};
}

constructor(response: PasskeyConfigServerResponse) {
if(typeof response.name !== 'undefined') {
this.name = response.name;
if (typeof response.name !== 'undefined') {
this.name = response.name;
}
if(typeof response.rpId !== 'undefined') {
this.rpId = response.rpId;
};
if(typeof response.expectedOrigins !== 'undefined') {
this.expectedOrigins = response.expectedOrigins;
if (typeof response.rpId !== 'undefined') {
this.rpId = response.rpId;
}
};
if (typeof response.expectedOrigins !== 'undefined') {
this.expectedOrigins = response.expectedOrigins;
}
}

public toJSON(): object {
const json = {
name: deepCopy(this.name),
rpId: deepCopy(this.rpId),
expectedOrigins: deepCopy(this.expectedOrigins),
};
if(typeof json.name === 'undefined') {
if (typeof json.name === 'undefined') {
delete json.name;
}
if(typeof json.rpId === 'undefined') {
if (typeof json.rpId === 'undefined') {
delete json.rpId;
}
if(typeof json.expectedOrigins === 'undefined') {
if (typeof json.expectedOrigins === 'undefined') {
delete json.expectedOrigins;
}
return json;
}

};
}

45 changes: 22 additions & 23 deletions test/integration/auth.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2198,44 +2198,43 @@ describe('admin.auth', () => {
});

describe('Passkey config management operations', () => {
// Define expected passkey configuration
const expectedPasskeyConfig = {
name: `projects/{$projectId}/passkeyConfig`,
rpId: `{$projectId}.firebaseapp.com`,
expectedOrigins: ['app1', 'example.com'],
};

// Before each test, reset the passkey config to the initial state
beforeEach(async () => {
const resetRequest = { expectedOrigins: expectedPasskeyConfig.expectedOrigins };
await getAuth().passkeyConfigManager().updatePasskeyConfig(resetRequest);
// const resetRequest = { expectedOrigins: expectedPasskeyConfig.expectedOrigins };
// await getAuth().passkeyConfigManager().updatePasskeyConfig(resetRequest);
});

it('createPasskeyConfig() should create passkey config with expected passkeyConfig', async () => {
const rpId = `{$projectId}.firebaseapp.com`;
const rpId = projectId + '.firebaseapp.com';
const createRequest = { expectedOrigins: ['app1', 'example.com'] };

const createdPasskeyConfig = await getAuth().passkeyConfigManager().createPasskeyConfig(rpId, createRequest);
const passkeyConfigObj = createdPasskeyConfig.toJSON();

expect(passkeyConfigObj).to.deep.equal(expectedPasskeyConfig);
expect(createdPasskeyConfig.name).to.deep.equal('projects/' + projectId + '/passkeyConfig');
expect(createdPasskeyConfig.rpId).to.deep.equal(projectId + '.firebaseapp.com');
expect(createdPasskeyConfig.expectedOrigins).to.deep.equal(['app1', 'example.com']);
});

// TODO: uncomment when the GET endpoint is released in prod
// it('getPasskeyConfig() should resolve with expected passkeyConfig', async () => {
// const actualPasskeyConfig = await getAuth().passkeyConfigManager().getPasskeyConfig();

it('getPasskeyConfig() should resolve with expected passkeyConfig', async () => {
const actualPasskeyConfig = await getAuth().passkeyConfigManager().getPasskeyConfig();
const actualPasskeyConfigObj = actualPasskeyConfig.toJSON();

expect(actualPasskeyConfigObj).to.deep.equal(expectedPasskeyConfig);
});
// expect(actualPasskeyConfig.name).to.deep.equal('projects/' + projectId + '/passkeyConfig');
// expect(actualPasskeyConfig.rpId).to.deep.equal(projectId + '.firebaseapp.com');
// expect(actualPasskeyConfig.expectedOrigins).to.deep.equal(['app1', 'example.com']);
// });

it('updatePasskeyConfig() should resolve with updated expectedOrigins', async () => {
const updateRequest = { expectedOrigins: ['app1', 'example.com', 'app2'] };
const expectedUpdatedPasskeyConfig = { ...expectedPasskeyConfig, expectedOrigins: updateRequest.expectedOrigins };
const updateRequest = {
expectedOrigins: ['app1', 'example.com', 'app2']
};

const updatedPasskeyConfig = await getAuth().passkeyConfigManager().updatePasskeyConfig(updateRequest);
const passkeyConfigObj = updatedPasskeyConfig.toJSON();

expect(passkeyConfigObj).to.deep.equal(expectedUpdatedPasskeyConfig);

expect(updatedPasskeyConfig.name).to.deep.equal('projects/' + projectId + '/passkeyConfig');
// TODO: backend validation needs to fixed in order for this statement to succeed.
// expect(updatedPasskeyConfig.rpId).to.deep.equal(projectId + '.firebaseapp.com');
expect(updatedPasskeyConfig.expectedOrigins).to.deep.equal(['app1', 'example.com', 'app2']);
});
});

Expand Down
41 changes: 20 additions & 21 deletions test/unit/auth/passkey-config-manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ describe('PasskeyConfigManager', () => {
let malformedAccessTokenPasskeyConfigManager: PasskeyConfigManager;
let rejectedPromiseAccessTokenPasskeyConfigManager: PasskeyConfigManager;
const GET_CONFIG_RESPONSE: PasskeyConfigServerResponse = {
name: `projects/project-id/passkeyConfig`,
rpId: `project-id.firebaseapp.com`,
name: 'projects/project-id/passkeyConfig',
rpId: 'project-id.firebaseapp.com',
expectedOrigins: ['app1', 'example.com'],
};
};

before(() => {
mockApp = mocks.app();
Expand Down Expand Up @@ -121,7 +121,7 @@ describe('PasskeyConfigManager', () => {
});

describe('createPasskeyConfig()', () => {
const rpId: string = 'project-id.firebaseapp.com';
const rpId = 'project-id.firebaseapp.com';
const expectedOrigins: string[] = ['app1', 'example.com']
const passkeyConfigRequest: PasskeyConfigRequest = {
expectedOrigins: expectedOrigins ,
Expand All @@ -131,31 +131,30 @@ describe('PasskeyConfigManager', () => {
AuthClientErrorCode.INTERNAL_ERROR,
'Unable to create the config provided.');
// Stubs used to simulate underlying API calls.
let stubs: sinon.SinonStub[] = [];
const stubs: sinon.SinonStub[] = [];
afterEach(() => {
sinon.restore();
});

it('should be rejected given no passkeyConfigOptions', () => {
return (passkeyConfigManager as any).createPasskeyConfig(null as unknown as PasskeyConfigRequest)
.should.eventually.be.rejected.and.have.property('code', 'auth/argument-error');
});
return (passkeyConfigManager as any).createPasskeyConfig(null as unknown as PasskeyConfigRequest)
.should.eventually.be.rejected.and.have.property('code', 'auth/argument-error');
});

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');
});
it('should be rejected given an app which returns null access tokens', () => {
return nullAccessTokenPasskeyConfigManager.createPasskeyConfig(rpId, passkeyConfigRequest)
.should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential');
});

it('should be rejected given an app which returns invalid access tokens', () => {
return malformedAccessTokenPasskeyConfigManager.createPasskeyConfig(rpId, passkeyConfigRequest)
.should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential');
});
it('should be rejected given an app which returns invalid access tokens', () => {
return malformedAccessTokenPasskeyConfigManager.createPasskeyConfig(rpId, passkeyConfigRequest)
.should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential');
});

it('should be rejected given an app which fails to generate access tokens', () => {
return rejectedPromiseAccessTokenPasskeyConfigManager.createPasskeyConfig(rpId, passkeyConfigRequest)
.should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential');
});
it('should be rejected given an app which fails to generate access tokens', () => {
return rejectedPromiseAccessTokenPasskeyConfigManager.createPasskeyConfig(rpId, passkeyConfigRequest)
.should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential');
});

it('should resolve with a PasskeyConfig on createPasskeyConfig request success', () => {
// Stub createPasskeyConfig to return expected result.
Expand Down
Loading