Skip to content

Commit 629f8ac

Browse files
committed
passkey config admin changes
1 parent 4fe5577 commit 629f8ac

File tree

9 files changed

+499
-2
lines changed

9 files changed

+499
-2
lines changed

etc/firebase-admin.auth.api.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export interface AllowlistOnlyWrap {
5151
export class Auth extends BaseAuth {
5252
// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts
5353
get app(): App;
54+
passkeyConfigManager(): PasskeyConfigManager;
5455
projectConfigManager(): ProjectConfigManager;
5556
tenantManager(): TenantManager;
5657
}
@@ -344,6 +345,41 @@ export interface OIDCUpdateAuthProviderRequest {
344345
responseType?: OAuthResponseType;
345346
}
346347

348+
// @public (undocumented)
349+
export class PasskeyConfig {
350+
// Warning: (ae-forgotten-export) The symbol "PasskeyConfigServerResponse" needs to be exported by the entry point index.d.ts
351+
constructor(response: PasskeyConfigServerResponse);
352+
// Warning: (ae-forgotten-export) The symbol "PasskeyConfigClientRequest" needs to be exported by the entry point index.d.ts
353+
//
354+
// (undocumented)
355+
static buildServerRequest(isCreateRequest: boolean, passkeyConfigRequest?: PasskeyConfigRequest, rpId?: string): PasskeyConfigClientRequest;
356+
// (undocumented)
357+
readonly expectedOrigins?: string[];
358+
// (undocumented)
359+
readonly name?: string;
360+
// (undocumented)
361+
readonly rpId?: string;
362+
// (undocumented)
363+
toJSON(): object;
364+
}
365+
366+
// @public (undocumented)
367+
export class PasskeyConfigManager {
368+
constructor(app: App);
369+
// (undocumented)
370+
createPasskeyConfig(rpId: string, passkeyConfigRequest: PasskeyConfigRequest, tenantId?: string): Promise<PasskeyConfigRequest>;
371+
// (undocumented)
372+
getPasskeyConfig(tenantId?: string): Promise<PasskeyConfig>;
373+
// (undocumented)
374+
updatePasskeyConfig(passkeyConfigRequest: PasskeyConfigRequest, tenantId?: string): Promise<PasskeyConfigRequest>;
375+
}
376+
377+
// @public (undocumented)
378+
export interface PasskeyConfigRequest {
379+
// (undocumented)
380+
expectedOrigins?: string[];
381+
}
382+
347383
// @public
348384
export interface PasswordPolicyConfig {
349385
constraints?: CustomStrengthOptionsConfig;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "firebase-admin",
3-
"version": "11.10.1",
3+
"version": "11.11.0",
44
"description": "Firebase admin SDK for Node.js",
55
"author": "Firebase <[email protected]> (https://firebase.google.com/)",
66
"license": "Apache-2.0",

src/auth/auth-api-request.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {
4343
SAMLUpdateAuthProviderRequest
4444
} from './auth-config';
4545
import { ProjectConfig, ProjectConfigServerResponse, UpdateProjectConfigRequest } from './project-config';
46+
import {PasskeyConfig, PasskeyConfigServerResponse, PasskeyConfigRequest} from './passkey-config';
4647

4748
/** Firebase Auth request header. */
4849
const FIREBASE_AUTH_HEADER = {
@@ -2070,6 +2071,54 @@ const CREATE_TENANT = new ApiSettings('/tenants', 'POST')
20702071
}
20712072
});
20722073

2074+
/** Instantiates the getPasskeyConfig endpoint settings. */
2075+
const GET_PASSKEY_CONFIG = new ApiSettings('/passkeyConfig', 'GET')
2076+
.setResponseValidator((response: any) => {
2077+
// Response should always contain at least the config name.
2078+
if (!validator.isNonEmptyString(response.name)) {
2079+
throw new FirebaseAuthError(
2080+
AuthClientErrorCode.INTERNAL_ERROR,
2081+
'INTERNAL ASSERT FAILED: Unable to get project config',
2082+
);
2083+
}
2084+
});
2085+
2086+
/** Instantiates the getPasskeyConfig endpoint settings. */
2087+
const GET_TENANT_PASSKEY_CONFIG = new ApiSettings('/tenants/{tenantId}/passkeyConfig', 'GET')
2088+
.setResponseValidator((response: any) => {
2089+
// Response should always contain at least the config name.
2090+
if (!validator.isNonEmptyString(response.name)) {
2091+
throw new FirebaseAuthError(
2092+
AuthClientErrorCode.INTERNAL_ERROR,
2093+
'INTERNAL ASSERT FAILED: Unable to get project config',
2094+
);
2095+
}
2096+
});
2097+
2098+
/** Instantiates the getPasskeyConfig endpoint settings. */
2099+
const UPDATE_PASSKEY_CONFIG = new ApiSettings('/passkeyConfig?updateMask={updateMask}', 'PATCH')
2100+
.setResponseValidator((response: any) => {
2101+
// Response should always contain at least the config name.
2102+
if (!validator.isNonEmptyString(response.name)) {
2103+
throw new FirebaseAuthError(
2104+
AuthClientErrorCode.INTERNAL_ERROR,
2105+
'INTERNAL ASSERT FAILED: Unable to get project config',
2106+
);
2107+
}
2108+
});
2109+
2110+
/** Instantiates the getPasskeyConfig endpoint settings. */
2111+
const UPDATE_TENANT_PASSKEY_CONFIG = new ApiSettings('/tenant/{tenantId}/passkeyConfig?updateMask={updateMask}', 'PATCH')
2112+
.setResponseValidator((response: any) => {
2113+
// Response should always contain at least the config name.
2114+
if (!validator.isNonEmptyString(response.name)) {
2115+
throw new FirebaseAuthError(
2116+
AuthClientErrorCode.INTERNAL_ERROR,
2117+
'INTERNAL ASSERT FAILED: Unable to get project config',
2118+
);
2119+
}
2120+
});
2121+
20732122

20742123
/**
20752124
* Utility for sending requests to Auth server that are Auth instance related. This includes user, tenant,
@@ -2245,6 +2294,27 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler {
22452294
return Promise.reject(e);
22462295
}
22472296
}
2297+
2298+
public getPasskeyConfig(tenantId?: string): Promise<PasskeyConfigServerResponse> {
2299+
return this.invokeRequestHandler(this.authResourceUrlBuilder, tenantId? GET_TENANT_PASSKEY_CONFIG: GET_PASSKEY_CONFIG, {}, {})
2300+
.then((response: any) => {
2301+
return response as PasskeyConfigServerResponse;
2302+
});
2303+
}
2304+
2305+
public updatePasskeyConfig(isCreateRequest: boolean, tenantId?: string, options?: PasskeyConfigRequest, rpId?: string): Promise<PasskeyConfigServerResponse> {
2306+
try {
2307+
const request = PasskeyConfig.buildServerRequest(isCreateRequest, options, rpId);
2308+
const updateMask = utils.generateUpdateMask(request);
2309+
return this.invokeRequestHandler(
2310+
this.authResourceUrlBuilder, tenantId? UPDATE_TENANT_PASSKEY_CONFIG: UPDATE_PASSKEY_CONFIG, request, { updateMask: updateMask.join(',') })
2311+
.then((response: any) => {
2312+
return response as PasskeyConfigServerResponse;
2313+
});
2314+
} catch (e) {
2315+
return Promise.reject(e);
2316+
}
2317+
}
22482318
}
22492319

22502320
/**

src/auth/auth.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { AuthRequestHandler } from './auth-api-request';
2020
import { TenantManager } from './tenant-manager';
2121
import { BaseAuth } from './base-auth';
2222
import { ProjectConfigManager } from './project-config-manager';
23+
import { PasskeyConfigManager } from './passkey-config-manager';
2324

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

3031
private readonly tenantManager_: TenantManager;
3132
private readonly projectConfigManager_: ProjectConfigManager;
33+
private readonly passkeyConfigManager_: PasskeyConfigManager;
3234
private readonly app_: App;
3335

3436
/**
@@ -41,6 +43,7 @@ export class Auth extends BaseAuth {
4143
this.app_ = app;
4244
this.tenantManager_ = new TenantManager(app);
4345
this.projectConfigManager_ = new ProjectConfigManager(app);
46+
this.passkeyConfigManager_ = new PasskeyConfigManager(app);
4447
}
4548

4649
/**
@@ -69,4 +72,13 @@ export class Auth extends BaseAuth {
6972
public projectConfigManager(): ProjectConfigManager {
7073
return this.projectConfigManager_;
7174
}
75+
76+
/**
77+
* Returns the passkey config manager instance.
78+
*
79+
* @returns The passkey config manager instance .
80+
*/
81+
public passkeyConfigManager(): PasskeyConfigManager {
82+
return this.passkeyConfigManager_;
83+
}
7284
}

src/auth/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,15 @@ export {
142142
ProjectConfigManager,
143143
} from './project-config-manager';
144144

145+
export {
146+
PasskeyConfigRequest,
147+
PasskeyConfig,
148+
} from './passkey-config';
149+
150+
export {
151+
PasskeyConfigManager,
152+
} from './passkey-config-manager';
153+
145154
export {
146155
DecodedIdToken,
147156
DecodedAuthBlockingToken

src/auth/passkey-config-manager.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*!
2+
* Copyright 2023 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { App } from '../app';
17+
import {
18+
AuthRequestHandler,
19+
} from './auth-api-request';
20+
import {PasskeyConfig, PasskeyConfigClientRequest, PasskeyConfigRequest, PasskeyConfigServerResponse} from './passkey-config';
21+
22+
23+
export class PasskeyConfigManager {
24+
private readonly authRequestHandler: AuthRequestHandler;
25+
26+
constructor(app: App) {
27+
this.authRequestHandler = new AuthRequestHandler(app);
28+
}
29+
30+
public getPasskeyConfig(tenantId?: string): Promise<PasskeyConfig> {
31+
return this.authRequestHandler.getPasskeyConfig()
32+
.then((response: PasskeyConfigServerResponse) => {
33+
return new PasskeyConfig(response);
34+
});
35+
}
36+
37+
public createPasskeyConfig(rpId: string, passkeyConfigRequest: PasskeyConfigRequest, tenantId?: string): Promise<PasskeyConfig> {
38+
return this.authRequestHandler.updatePasskeyConfig(true, tenantId, passkeyConfigRequest, rpId)
39+
.then((response: PasskeyConfigClientRequest) => {
40+
return new PasskeyConfig(response);
41+
})
42+
}
43+
44+
public updatePasskeyConfig(passkeyConfigRequest: PasskeyConfigRequest, tenantId?: string): Promise<PasskeyConfig> {
45+
return this.authRequestHandler.updatePasskeyConfig(false, tenantId, passkeyConfigRequest)
46+
.then((response: PasskeyConfigClientRequest) => {
47+
return new PasskeyConfig(response);
48+
})
49+
}
50+
}

src/auth/passkey-config.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*!
2+
* Copyright 2023 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import * as validator from '../utils/validator';
17+
import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error';
18+
import {deepCopy} from '../utils/deep-copy';
19+
20+
export interface PasskeyConfigRequest {
21+
expectedOrigins?: string[];
22+
}
23+
24+
export interface PasskeyConfigServerResponse {
25+
name?: string;
26+
rpId?: string;
27+
expectedOrigins?: string[];
28+
}
29+
30+
export interface PasskeyConfigClientRequest {
31+
rpId?: string;
32+
expectedOrigins?: string[];
33+
}
34+
35+
36+
export class PasskeyConfig {
37+
public readonly name?: string;
38+
public readonly rpId?: string;
39+
public readonly expectedOrigins?: string[];
40+
41+
private static validate(isCreateRequest: boolean, passkeyConfigRequest?: PasskeyConfigRequest, rpId?: string) {
42+
if(isCreateRequest && !validator.isNonEmptyString(rpId)) {
43+
throw new FirebaseAuthError(
44+
AuthClientErrorCode.INVALID_ARGUMENT,
45+
`'rpId' must be a valid non-empty string'`,
46+
);
47+
}
48+
if(!isCreateRequest && typeof rpId !== 'undefined') {
49+
throw new FirebaseAuthError(
50+
AuthClientErrorCode.INVALID_ARGUMENT,
51+
`'rpId' cannot be changed once created.'`,
52+
);
53+
}
54+
if(!validator.isNonNullObject(passkeyConfigRequest)) {
55+
throw new FirebaseAuthError(
56+
AuthClientErrorCode.INVALID_ARGUMENT,
57+
`'passkeyConfigRequest' must be a valid non-empty object.'`,
58+
);
59+
}
60+
const validKeys = {
61+
expectedOrigins: true,
62+
};
63+
// Check for unsupported top level attributes.
64+
for (const key in passkeyConfigRequest) {
65+
if (!(key in validKeys)) {
66+
throw new FirebaseAuthError(
67+
AuthClientErrorCode.INVALID_ARGUMENT,
68+
`'${key}' is not a valid PasskeyConfigRequest parameter.`,
69+
);
70+
}
71+
}
72+
if(!validator.isNonEmptyArray(passkeyConfigRequest.expectedOrigins) || !validator.isNonNullObject(passkeyConfigRequest.expectedOrigins)) {
73+
throw new FirebaseAuthError(
74+
AuthClientErrorCode.INVALID_ARGUMENT,
75+
`'passkeyConfigRequest.expectedOrigins' must be a valid non-empty array of strings.'`,
76+
);
77+
}
78+
for(const origin in passkeyConfigRequest.expectedOrigins) {
79+
if(!validator.isString(origin)) {
80+
throw new FirebaseAuthError(
81+
AuthClientErrorCode.INVALID_ARGUMENT,
82+
`'passkeyConfigRequest.expectedOrigins' must be a valid non-empty array of strings.'`,
83+
);
84+
}
85+
}
86+
};
87+
88+
public static buildServerRequest(isCreateRequest: boolean, passkeyConfigRequest?: PasskeyConfigRequest, rpId?: string): PasskeyConfigClientRequest {
89+
PasskeyConfig.validate(isCreateRequest, passkeyConfigRequest, rpId);
90+
let request: PasskeyConfigClientRequest = {};
91+
if(isCreateRequest && typeof rpId !== 'undefined') {
92+
request.rpId = rpId;
93+
}
94+
if(typeof request.expectedOrigins !== 'undefined') {
95+
request.expectedOrigins = passkeyConfigRequest?.expectedOrigins;
96+
}
97+
return request;
98+
};
99+
100+
constructor(response: PasskeyConfigServerResponse) {
101+
if(typeof response.name !== 'undefined') {
102+
this.name = response.name;
103+
}
104+
if(typeof response.rpId !== 'undefined') {
105+
this.rpId = response.rpId;
106+
};
107+
if(typeof response.expectedOrigins !== 'undefined') {
108+
this.expectedOrigins = response.expectedOrigins;
109+
}
110+
};
111+
112+
public toJSON(): object {
113+
const json = {
114+
name: deepCopy(this.name),
115+
rpId: deepCopy(this.rpId),
116+
expectedOrigins: deepCopy(this.expectedOrigins),
117+
};
118+
if(typeof json.name === 'undefined') {
119+
delete json.name;
120+
}
121+
if(typeof json.rpId === 'undefined') {
122+
delete json.rpId;
123+
}
124+
if(typeof json.expectedOrigins === 'undefined') {
125+
delete json.expectedOrigins;
126+
}
127+
return json;
128+
}
129+
130+
};
131+

0 commit comments

Comments
 (0)