Skip to content

Commit 69ff8eb

Browse files
sam-gcPabloLIONkevinthecheung
authored
[Auth] Fix error typing and bring MFA error into alignment with the current API standard (#5616)
* fix interface AuthError (#5609) * fix interface AuthError change interface AuthError to correct structure * remove `declare` * Refactor MFA error to properly follow the new format * Add changeset * Add doc comment to MultiFactorError.customData * Apply suggestions from code review Co-authored-by: Kevin Cheung <[email protected]> * Update changeset to patch Co-authored-by: Pablion <[email protected]> Co-authored-by: Kevin Cheung <[email protected]>
1 parent 4d36404 commit 69ff8eb

File tree

10 files changed

+70
-56
lines changed

10 files changed

+70
-56
lines changed

.changeset/fresh-ways-think.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@firebase/auth": patch
3+
---
4+
5+
Fix the public `AuthError` typing, and update the `MultiFactorError` implementation to follow the new standard (all fields listed under `customData`)

common/api-review/auth.api.md

+9-5
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,12 @@ export class AuthCredential {
120120

121121
// @public
122122
export interface AuthError extends FirebaseError {
123-
readonly appName: string;
124-
readonly email?: string;
125-
readonly phoneNumber?: string;
126-
readonly tenantid?: string;
123+
readonly customData: {
124+
readonly appName: string;
125+
readonly email?: string;
126+
readonly phoneNumber?: string;
127+
readonly tenantId?: string;
128+
};
127129
}
128130

129131
// @public
@@ -443,7 +445,9 @@ export interface MultiFactorAssertion {
443445

444446
// @public
445447
export interface MultiFactorError extends AuthError {
446-
readonly operationType: typeof OperationType[keyof typeof OperationType];
448+
readonly customData: AuthError['customData'] & {
449+
readonly operationType: typeof OperationType[keyof typeof OperationType];
450+
};
447451
}
448452

449453
// @public

packages/auth/src/api/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ export async function _performSignInRequest<T, V extends IdTokenResponse>(
197197
)) as V;
198198
if ('mfaPendingCredential' in serverResponse) {
199199
_fail(auth, AuthErrorCode.MFA_REQUIRED, {
200-
serverResponse
200+
_serverResponse: serverResponse
201201
});
202202
}
203203

packages/auth/src/core/errors.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ export interface NamedErrorParams {
398398
phoneNumber?: string;
399399
tenantId?: string;
400400
user?: User;
401-
serverResponse?: object;
401+
_serverResponse?: object;
402402
}
403403

404404
/**
@@ -431,7 +431,7 @@ export interface AuthErrorParams extends GenericAuthErrorParams {
431431
[AuthErrorCode.NO_AUTH_EVENT]: { appName?: AppName };
432432
[AuthErrorCode.MFA_REQUIRED]: {
433433
appName: AppName;
434-
serverResponse: IdTokenMfaResponse;
434+
_serverResponse: IdTokenMfaResponse;
435435
};
436436
[AuthErrorCode.INVALID_CORDOVA_CONFIGURATION]: {
437437
appName: AppName;

packages/auth/src/core/strategies/credential.test.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,15 @@ describe('core/strategies/credential', () => {
139139
stub(authCredential, '_getIdTokenResponse').returns(
140140
Promise.reject(
141141
_createError(auth, AuthErrorCode.MFA_REQUIRED, {
142-
serverResponse
142+
_serverResponse: serverResponse
143143
})
144144
)
145145
);
146146
const error = await expect(
147147
signInWithCredential(auth, authCredential)
148148
).to.be.rejectedWith(MultiFactorError);
149-
expect(error.operationType).to.eq(OperationType.SIGN_IN);
150-
expect(error.serverResponse).to.eql(serverResponse);
151-
expect(error.user).to.be.undefined;
149+
expect(error.customData.operationType).to.eq(OperationType.SIGN_IN);
150+
expect(error.customData._serverResponse).to.eql(serverResponse);
152151
});
153152
});
154153

packages/auth/src/core/user/reauthenticate.test.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -148,16 +148,15 @@ describe('core/user/reauthenticate', () => {
148148
stub(credential, '_getReauthenticationResolver').returns(
149149
Promise.reject(
150150
_createError(user.auth, AuthErrorCode.MFA_REQUIRED, {
151-
serverResponse
151+
_serverResponse: serverResponse
152152
})
153153
)
154154
);
155155
const error = await expect(
156156
_reauthenticate(user, credential)
157157
).to.be.rejectedWith(MultiFactorError);
158-
expect(error.operationType).to.eq(OperationType.REAUTHENTICATE);
159-
expect(error.serverResponse).to.eql(serverResponse);
160-
expect(error.user).to.eq(user);
158+
expect(error.customData.operationType).to.eq(OperationType.REAUTHENTICATE);
159+
expect(error.customData._serverResponse).to.eql(serverResponse);
161160
});
162161

163162
it('should return a valid user credential', async () => {

packages/auth/src/mfa/mfa_error.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,15 @@ import { AuthCredential } from '../core/credentials';
2525
import { IdTokenMfaResponse } from '../api/authentication/mfa';
2626
import { OperationType } from '../model/enums';
2727

28+
export type MultiFactorErrorData = MultiFactorErrorPublic['customData'] & {
29+
_serverResponse: IdTokenMfaResponse;
30+
};
31+
2832
export class MultiFactorError
2933
extends FirebaseError
3034
implements MultiFactorErrorPublic
3135
{
32-
readonly name = 'FirebaseError';
33-
readonly code: string;
34-
readonly appName: string;
35-
readonly serverResponse: IdTokenMfaResponse;
36-
37-
readonly tenantId?: string;
36+
readonly customData: MultiFactorErrorData;
3837

3938
private constructor(
4039
auth: AuthInternal,
@@ -45,11 +44,12 @@ export class MultiFactorError
4544
super(error.code, error.message);
4645
// https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
4746
Object.setPrototypeOf(this, MultiFactorError.prototype);
48-
this.appName = auth.name;
49-
this.code = error.code;
50-
this.tenantId = auth.tenantId ?? undefined;
51-
this.serverResponse = error.customData!
52-
.serverResponse as IdTokenMfaResponse;
47+
this.customData = {
48+
appName: auth.name,
49+
tenantId: auth.tenantId ?? undefined,
50+
_serverResponse: error.customData!._serverResponse as IdTokenMfaResponse,
51+
operationType,
52+
};
5353
}
5454

5555
static _fromErrorAndOperation(

packages/auth/src/mfa/mfa_resolver.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ describe('core/mfa/mfa_resolver/MultiFactorResolver', () => {
5454
auth = await testAuth();
5555
auth.tenantId = 'tenant-id';
5656
underlyingError = _createError(auth, AuthErrorCode.MFA_REQUIRED, {
57-
serverResponse: {
57+
_serverResponse: {
5858
localId: 'local-id',
5959
mfaPendingCredential: 'mfa-pending-credential',
6060
mfaInfo: [

packages/auth/src/mfa/mfa_resolver.ts

+9-8
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,18 @@ export class MultiFactorResolverImpl implements MultiFactorResolver {
4949
error: MultiFactorErrorInternal
5050
): MultiFactorResolverImpl {
5151
const auth = _castAuth(authExtern);
52-
const hints = (error.serverResponse.mfaInfo || []).map(enrollment =>
52+
const serverResponse = error.customData._serverResponse;
53+
const hints = (serverResponse.mfaInfo || []).map(enrollment =>
5354
MultiFactorInfoImpl._fromServerResponse(auth, enrollment)
5455
);
5556

5657
_assert(
57-
error.serverResponse.mfaPendingCredential,
58+
serverResponse.mfaPendingCredential,
5859
auth,
5960
AuthErrorCode.INTERNAL_ERROR
6061
);
6162
const session = MultiFactorSessionImpl._fromMfaPendingCredential(
62-
error.serverResponse.mfaPendingCredential
63+
serverResponse.mfaPendingCredential
6364
);
6465

6566
return new MultiFactorResolverImpl(
@@ -70,12 +71,12 @@ export class MultiFactorResolverImpl implements MultiFactorResolver {
7071
): Promise<UserCredentialInternal> => {
7172
const mfaResponse = await assertion._process(auth, session);
7273
// Clear out the unneeded fields from the old login response
73-
delete error.serverResponse.mfaInfo;
74-
delete error.serverResponse.mfaPendingCredential;
74+
delete serverResponse.mfaInfo;
75+
delete serverResponse.mfaPendingCredential;
7576

7677
// Use in the new token & refresh token in the old response
7778
const idTokenResponse = {
78-
...error.serverResponse,
79+
...serverResponse,
7980
idToken: mfaResponse.idToken,
8081
refreshToken: mfaResponse.refreshToken
8182
};
@@ -129,9 +130,9 @@ export function getMultiFactorResolver(
129130
): MultiFactorResolver {
130131
const authModular = getModularInstance(auth);
131132
const errorInternal = error as MultiFactorErrorInternal;
132-
_assert(error.operationType, authModular, AuthErrorCode.ARGUMENT_ERROR);
133+
_assert(error.customData.operationType, authModular, AuthErrorCode.ARGUMENT_ERROR);
133134
_assert(
134-
errorInternal.serverResponse?.mfaPendingCredential,
135+
errorInternal.customData._serverResponse?.mfaPendingCredential,
135136
authModular,
136137
AuthErrorCode.ARGUMENT_ERROR
137138
);

packages/auth/src/model/public_types.ts

+26-20
Original file line numberDiff line numberDiff line change
@@ -121,21 +121,24 @@ export type NextOrObserver<T> = NextFn<T | null> | Observer<T | null>;
121121
* @public
122122
*/
123123
export interface AuthError extends FirebaseError {
124-
/** The name of the Firebase App which triggered this error. */
125-
readonly appName: string;
126-
/** The email of the user's account, used for sign-in/linking. */
127-
readonly email?: string;
128-
/** The phone number of the user's account, used for sign-in/linking. */
129-
readonly phoneNumber?: string;
130-
/**
131-
* The tenant ID being used for sign-in/linking.
132-
*
133-
* @remarks
134-
* If you use {@link signInWithRedirect} to sign in,
135-
* you have to set the tenant ID on {@link Auth} instance again as the tenant ID is not persisted
136-
* after redirection.
137-
*/
138-
readonly tenantid?: string;
124+
/** Details about the Firebase Auth error. */
125+
readonly customData: {
126+
/** The name of the Firebase App which triggered this error. */
127+
readonly appName: string;
128+
/** The email address of the user's account, used for sign-in and linking. */
129+
readonly email?: string;
130+
/** The phone number of the user's account, used for sign-in and linking. */
131+
readonly phoneNumber?: string;
132+
/**
133+
* The tenant ID being used for sign-in and linking.
134+
*
135+
* @remarks
136+
* If you use {@link signInWithRedirect} to sign in,
137+
* you have to set the tenant ID on the {@link Auth} instance again as the tenant ID is not persisted
138+
* after redirection.
139+
*/
140+
readonly tenantId?: string;
141+
};
139142
}
140143

141144
/**
@@ -599,11 +602,14 @@ export interface MultiFactorAssertion {
599602
*
600603
* @public
601604
*/
602-
export interface MultiFactorError extends AuthError {
603-
/**
604-
* The type of operation (e.g., sign-in, link, or reauthenticate) during which the error was raised.
605-
*/
606-
readonly operationType: typeof OperationTypeMap[keyof typeof OperationTypeMap];
605+
export interface MultiFactorError extends AuthError {
606+
/** Details about the MultiFactorError. */
607+
readonly customData: AuthError['customData'] & {
608+
/**
609+
* The type of operation (sign-in, linking, or re-authentication) that raised the error.
610+
*/
611+
readonly operationType: typeof OperationTypeMap[keyof typeof OperationTypeMap];
612+
}
607613
}
608614

609615
/**

0 commit comments

Comments
 (0)