Skip to content

Commit

Permalink
passkey config admin changes
Browse files Browse the repository at this point in the history
  • Loading branch information
pragatimodi committed Oct 18, 2023
1 parent 4fe5577 commit 629f8ac
Show file tree
Hide file tree
Showing 9 changed files with 499 additions and 2 deletions.
36 changes: 36 additions & 0 deletions etc/firebase-admin.auth.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export interface AllowlistOnlyWrap {
export class Auth extends BaseAuth {
// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts
get app(): App;
passkeyConfigManager(): PasskeyConfigManager;
projectConfigManager(): ProjectConfigManager;
tenantManager(): TenantManager;
}
Expand Down Expand Up @@ -344,6 +345,41 @@ export interface OIDCUpdateAuthProviderRequest {
responseType?: OAuthResponseType;
}

// @public (undocumented)
export class PasskeyConfig {
// Warning: (ae-forgotten-export) The symbol "PasskeyConfigServerResponse" needs to be exported by the entry point index.d.ts
constructor(response: PasskeyConfigServerResponse);
// Warning: (ae-forgotten-export) The symbol "PasskeyConfigClientRequest" needs to be exported by the entry point index.d.ts
//
// (undocumented)
static buildServerRequest(isCreateRequest: boolean, passkeyConfigRequest?: PasskeyConfigRequest, rpId?: string): PasskeyConfigClientRequest;
// (undocumented)
readonly expectedOrigins?: string[];
// (undocumented)
readonly name?: string;
// (undocumented)
readonly rpId?: string;
// (undocumented)
toJSON(): object;
}

// @public (undocumented)
export class PasskeyConfigManager {
constructor(app: App);
// (undocumented)
createPasskeyConfig(rpId: string, passkeyConfigRequest: PasskeyConfigRequest, tenantId?: string): Promise<PasskeyConfigRequest>;
// (undocumented)
getPasskeyConfig(tenantId?: string): Promise<PasskeyConfig>;
// (undocumented)
updatePasskeyConfig(passkeyConfigRequest: PasskeyConfigRequest, tenantId?: string): Promise<PasskeyConfigRequest>;
}

// @public (undocumented)
export interface PasskeyConfigRequest {
// (undocumented)
expectedOrigins?: string[];
}

// @public
export interface PasswordPolicyConfig {
constraints?: CustomStrengthOptionsConfig;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "firebase-admin",
"version": "11.10.1",
"version": "11.11.0",
"description": "Firebase admin SDK for Node.js",
"author": "Firebase <firebase-support@google.com> (https://firebase.google.com/)",
"license": "Apache-2.0",
Expand Down
70 changes: 70 additions & 0 deletions src/auth/auth-api-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
SAMLUpdateAuthProviderRequest
} from './auth-config';
import { ProjectConfig, ProjectConfigServerResponse, UpdateProjectConfigRequest } from './project-config';
import {PasskeyConfig, PasskeyConfigServerResponse, PasskeyConfigRequest} from './passkey-config';

/** Firebase Auth request header. */
const FIREBASE_AUTH_HEADER = {
Expand Down Expand Up @@ -2070,6 +2071,54 @@ const CREATE_TENANT = new ApiSettings('/tenants', 'POST')
}
});

/** Instantiates the getPasskeyConfig endpoint settings. */
const GET_PASSKEY_CONFIG = new ApiSettings('/passkeyConfig', 'GET')
.setResponseValidator((response: any) => {
// Response should always contain at least the config name.
if (!validator.isNonEmptyString(response.name)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INTERNAL_ERROR,
'INTERNAL ASSERT FAILED: Unable to get project config',
);
}
});

/** Instantiates the getPasskeyConfig endpoint settings. */
const GET_TENANT_PASSKEY_CONFIG = new ApiSettings('/tenants/{tenantId}/passkeyConfig', 'GET')
.setResponseValidator((response: any) => {
// Response should always contain at least the config name.
if (!validator.isNonEmptyString(response.name)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INTERNAL_ERROR,
'INTERNAL ASSERT FAILED: Unable to get project config',
);
}
});

/** Instantiates the getPasskeyConfig endpoint settings. */
const UPDATE_PASSKEY_CONFIG = new ApiSettings('/passkeyConfig?updateMask={updateMask}', 'PATCH')
.setResponseValidator((response: any) => {
// Response should always contain at least the config name.
if (!validator.isNonEmptyString(response.name)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INTERNAL_ERROR,
'INTERNAL ASSERT FAILED: Unable to get project config',
);
}
});

/** Instantiates the getPasskeyConfig endpoint settings. */
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)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INTERNAL_ERROR,
'INTERNAL ASSERT FAILED: Unable to get project config',
);
}
});


/**
* Utility for sending requests to Auth server that are Auth instance related. This includes user, tenant,
Expand Down Expand Up @@ -2245,6 +2294,27 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler {
return Promise.reject(e);
}
}

public getPasskeyConfig(tenantId?: string): Promise<PasskeyConfigServerResponse> {
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> {
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(',') })
.then((response: any) => {
return response as PasskeyConfigServerResponse;
});
} catch (e) {
return Promise.reject(e);
}
}
}

/**
Expand Down
12 changes: 12 additions & 0 deletions src/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { AuthRequestHandler } from './auth-api-request';
import { TenantManager } from './tenant-manager';
import { BaseAuth } from './base-auth';
import { ProjectConfigManager } from './project-config-manager';
import { PasskeyConfigManager } from './passkey-config-manager';

/**
* Auth service bound to the provided app.
Expand All @@ -29,6 +30,7 @@ export class Auth extends BaseAuth {

private readonly tenantManager_: TenantManager;
private readonly projectConfigManager_: ProjectConfigManager;
private readonly passkeyConfigManager_: PasskeyConfigManager;
private readonly app_: App;

/**
Expand All @@ -41,6 +43,7 @@ export class Auth extends BaseAuth {
this.app_ = app;
this.tenantManager_ = new TenantManager(app);
this.projectConfigManager_ = new ProjectConfigManager(app);
this.passkeyConfigManager_ = new PasskeyConfigManager(app);
}

/**
Expand Down Expand Up @@ -69,4 +72,13 @@ export class Auth extends BaseAuth {
public projectConfigManager(): ProjectConfigManager {
return this.projectConfigManager_;
}

/**
* Returns the passkey config manager instance.
*
* @returns The passkey config manager instance .
*/
public passkeyConfigManager(): PasskeyConfigManager {
return this.passkeyConfigManager_;
}
}
9 changes: 9 additions & 0 deletions src/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@ export {
ProjectConfigManager,
} from './project-config-manager';

export {
PasskeyConfigRequest,
PasskeyConfig,
} from './passkey-config';

export {
PasskeyConfigManager,
} from './passkey-config-manager';

export {
DecodedIdToken,
DecodedAuthBlockingToken
Expand Down
50 changes: 50 additions & 0 deletions src/auth/passkey-config-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*!
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { App } from '../app';
import {
AuthRequestHandler,
} from './auth-api-request';
import {PasskeyConfig, PasskeyConfigClientRequest, PasskeyConfigRequest, PasskeyConfigServerResponse} from './passkey-config';


export class PasskeyConfigManager {
private readonly authRequestHandler: AuthRequestHandler;

constructor(app: App) {
this.authRequestHandler = new AuthRequestHandler(app);
}

public getPasskeyConfig(tenantId?: string): Promise<PasskeyConfig> {
return this.authRequestHandler.getPasskeyConfig()
.then((response: PasskeyConfigServerResponse) => {
return new PasskeyConfig(response);
});
}

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);
})
}

public updatePasskeyConfig(passkeyConfigRequest: PasskeyConfigRequest, tenantId?: string): Promise<PasskeyConfig> {
return this.authRequestHandler.updatePasskeyConfig(false, tenantId, passkeyConfigRequest)
.then((response: PasskeyConfigClientRequest) => {
return new PasskeyConfig(response);
})
}
}
131 changes: 131 additions & 0 deletions src/auth/passkey-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*!
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as validator from '../utils/validator';
import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error';
import {deepCopy} from '../utils/deep-copy';

export interface PasskeyConfigRequest {
expectedOrigins?: string[];
}

export interface PasskeyConfigServerResponse {
name?: string;
rpId?: string;
expectedOrigins?: string[];
}

export interface PasskeyConfigClientRequest {
rpId?: string;
expectedOrigins?: string[];
}


export class PasskeyConfig {
public readonly name?: string;
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'`,
);
}
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.'`,
);
}
const validKeys = {
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) || !validator.isNonNullObject(passkeyConfigRequest.expectedOrigins)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_ARGUMENT,
`'passkeyConfigRequest.expectedOrigins' must be a valid non-empty array of strings.'`,
);
}
for(const origin in passkeyConfigRequest.expectedOrigins) {
if(!validator.isString(origin)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_ARGUMENT,
`'passkeyConfigRequest.expectedOrigins' must be a valid non-empty array of strings.'`,
);
}
}
};

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;
}
if(typeof request.expectedOrigins !== 'undefined') {
request.expectedOrigins = passkeyConfigRequest?.expectedOrigins;
}
return request;
};

constructor(response: PasskeyConfigServerResponse) {
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;
}
};

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

};

Loading

0 comments on commit 629f8ac

Please sign in to comment.