Skip to content

Commit 2c36f93

Browse files
committed
Cleanup demo code & get it working with MFA flows
- lint the demo - fix some error handling code - fix verifyPhoneNumber when called with MFA context - add to/fromJSON to EmailAuthCredential
1 parent 49a7240 commit 2c36f93

File tree

10 files changed

+578
-348
lines changed

10 files changed

+578
-348
lines changed

packages-exp/auth-exp/demo/src/index.js

Lines changed: 294 additions & 300 deletions
Large diffs are not rendered by default.

packages-exp/auth-exp/demo/src/logging.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
12
/**
23
* @license
34
* Copyright 2020 Google LLC
@@ -18,8 +19,8 @@
1819
// Fix for IE8 when developer's console is not opened.
1920
if (!window.console) {
2021
window.console = {
21-
log: function() {},
22-
error: function() {}
22+
log() {},
23+
error() {}
2324
};
2425
}
2526

@@ -77,7 +78,7 @@ function alertMessage_(message, cssClass) {
7778
if (visibleModal.size() > 0) {
7879
// Check first if the model has an overlaying-alert. If not, append the
7980
// overlaying-alert container.
80-
if (visibleModal.find('.overlaying-alert').size() == 0) {
81+
if (visibleModal.find('.overlaying-alert').size() === 0) {
8182
const $overlayingAlert = $(
8283
'<div class="container-fluid overlaying-alert"></div>'
8384
);
@@ -88,9 +89,9 @@ function alertMessage_(message, cssClass) {
8889
$('#alert-messages').prepend(alertBox);
8990
}
9091
alertBox.fadeIn({
91-
complete: function() {
92-
setTimeout(function() {
93-
alertBox.slideUp(400, function() {
92+
complete() {
93+
setTimeout(() => {
94+
alertBox.slideUp(400, () => {
9495
// On completion, remove the alert element from the DOM.
9596
alertBox.remove();
9697
});

packages-exp/auth-exp/src/api/errors.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export enum ServerError {
2727
CREDENTIAL_MISMATCH = 'CREDENTIAL_MISMATCH',
2828
CREDENTIAL_TOO_OLD_LOGIN_AGAIN = 'CREDENTIAL_TOO_OLD_LOGIN_AGAIN',
2929
DYNAMIC_LINK_NOT_ACTIVATED = 'DYNAMIC_LINK_NOT_ACTIVATED',
30+
EMAIL_CHANGE_NEEDS_VERIFICATION = 'EMAIL_CHANGE_NEEDS_VERIFICATION',
3031
EMAIL_EXISTS = 'EMAIL_EXISTS',
3132
EMAIL_NOT_FOUND = 'EMAIL_NOT_FOUND',
3233
EXPIRED_OOB_CODE = 'EXPIRED_OOB_CODE',
@@ -43,6 +44,7 @@ export enum ServerError {
4344
INVALID_IDP_RESPONSE = 'INVALID_IDP_RESPONSE',
4445
INVALID_IDENTIFIER = 'INVALID_IDENTIFIER',
4546
INVALID_MESSAGE_PAYLOAD = 'INVALID_MESSAGE_PAYLOAD',
47+
INVALID_MFA_PENDING_CREDENTIAL = 'INVALID_MFA_PENDING_CREDENTIAL',
4648
INVALID_OAUTH_CLIENT_ID = 'INVALID_OAUTH_CLIENT_ID',
4749
INVALID_OOB_CODE = 'INVALID_OOB_CODE',
4850
INVALID_PASSWORD = 'INVALID_PASSWORD',
@@ -54,12 +56,15 @@ export enum ServerError {
5456
INVALID_SESSION_INFO = 'INVALID_SESSION_INFO',
5557
INVALID_TEMPORARY_PROOF = 'INVALID_TEMPORARY_PROOF',
5658
INVALID_TENANT_ID = 'INVALID_TENANT_ID',
59+
MFA_ENROLLMENT_NOT_FOUND = 'MFA_ENROLLMENT_NOT_FOUND',
5760
MISSING_ANDROID_PACKAGE_NAME = 'MISSING_ANDROID_PACKAGE_NAME',
5861
MISSING_APP_CREDENTIAL = 'MISSING_APP_CREDENTIAL',
5962
MISSING_CODE = 'MISSING_CODE',
6063
MISSING_CONTINUE_URI = 'MISSING_CONTINUE_URI',
6164
MISSING_CUSTOM_TOKEN = 'MISSING_CUSTOM_TOKEN',
6265
MISSING_IOS_BUNDLE_ID = 'MISSING_IOS_BUNDLE_ID',
66+
MISSING_MFA_ENROLLMENT_ID = 'MISSING_MFA_ENROLLMENT_ID',
67+
MISSING_MFA_PENDING_CREDENTIAL = 'MISSING_MFA_PENDING_CREDENTIAL',
6368
MISSING_OOB_CODE = 'MISSING_OOB_CODE',
6469
MISSING_OR_INVALID_NONCE = 'MISSING_OR_INVALID_NONCE',
6570
MISSING_PASSWORD = 'MISSING_PASSWORD',
@@ -71,12 +76,16 @@ export enum ServerError {
7176
QUOTA_EXCEEDED = 'QUOTA_EXCEEDED',
7277
RESET_PASSWORD_EXCEED_LIMIT = 'RESET_PASSWORD_EXCEED_LIMIT',
7378
REJECTED_CREDENTIAL = 'REJECTED_CREDENTIAL',
79+
SECOND_FACTOR_EXISTS = 'SECOND_FACTOR_EXISTS',
80+
SECOND_FACTOR_LIMIT_EXCEEDED = 'SECOND_FACTOR_LIMIT_EXCEEDED',
7481
SESSION_EXPIRED = 'SESSION_EXPIRED',
7582
TENANT_ID_MISMATCH = 'TENANT_ID_MISMATCH',
7683
TOKEN_EXPIRED = 'TOKEN_EXPIRED',
7784
TOO_MANY_ATTEMPTS_TRY_LATER = 'TOO_MANY_ATTEMPTS_TRY_LATER',
85+
UNSUPPORTED_FIRST_FACTOR = 'UNSUPPORTED_FIRST_FACTOR',
7886
UNSUPPORTED_TENANT_OPERATION = 'UNSUPPORTED_TENANT_OPERATION',
7987
UNAUTHORIZED_DOMAIN = 'UNAUTHORIZED_DOMAIN',
88+
UNVERIFIED_EMAIL = 'UNVERIFIED_EMAIL',
8089
USER_CANCELLED = 'USER_CANCELLED',
8190
USER_DISABLED = 'USER_DISABLED',
8291
USER_NOT_FOUND = 'USER_NOT_FOUND',
@@ -89,8 +98,8 @@ export enum ServerError {
8998
export interface JsonError {
9099
error: {
91100
code: number;
92-
message: ServerError;
93-
errors: [
101+
message: string;
102+
errors?: [
94103
{
95104
message: ServerError;
96105
domain: string;
@@ -224,5 +233,22 @@ export const SERVER_ERROR_MAP: ServerErrorMap<ServerError> = {
224233
[ServerError.TENANT_ID_MISMATCH]: AuthErrorCode.TENANT_ID_MISMATCH,
225234

226235
// User actions (sign-up or deletion) disabled errors.
227-
[ServerError.ADMIN_ONLY_OPERATION]: AuthErrorCode.ADMIN_ONLY_OPERATION
236+
[ServerError.ADMIN_ONLY_OPERATION]: AuthErrorCode.ADMIN_ONLY_OPERATION,
237+
238+
// Multi factor related errors.
239+
[ServerError.EMAIL_CHANGE_NEEDS_VERIFICATION]:
240+
AuthErrorCode.EMAIL_CHANGE_NEEDS_VERIFICATION,
241+
[ServerError.INVALID_MFA_PENDING_CREDENTIAL]:
242+
AuthErrorCode.INVALID_MFA_SESSION,
243+
[ServerError.MFA_ENROLLMENT_NOT_FOUND]: AuthErrorCode.MFA_INFO_NOT_FOUND,
244+
[ServerError.MISSING_MFA_ENROLLMENT_ID]: AuthErrorCode.MISSING_MFA_INFO,
245+
[ServerError.MISSING_MFA_PENDING_CREDENTIAL]:
246+
AuthErrorCode.MISSING_MFA_SESSION,
247+
[ServerError.SECOND_FACTOR_EXISTS]:
248+
AuthErrorCode.SECOND_FACTOR_ALREADY_ENROLLED,
249+
[ServerError.SECOND_FACTOR_LIMIT_EXCEEDED]:
250+
AuthErrorCode.SECOND_FACTOR_LIMIT_EXCEEDED,
251+
[ServerError.UNSUPPORTED_FIRST_FACTOR]:
252+
AuthErrorCode.UNSUPPORTED_FIRST_FACTOR,
253+
[ServerError.UNVERIFIED_EMAIL]: AuthErrorCode.UNVERIFIED_EMAIL
228254
};

packages-exp/auth-exp/src/api/index.test.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,36 @@ describe('api/_performApiRequest', () => {
111111
);
112112
await expect(promise).to.be.rejectedWith(
113113
FirebaseError,
114-
'Firebase: The email address is already in use by another account. (auth/email-already-in-use).'
114+
'auth/email-already-in-use'
115+
);
116+
expect(mock.calls[0].request).to.eql(request);
117+
});
118+
119+
it('should translate complex server errors to auth errors', async () => {
120+
const mock = mockEndpoint(
121+
Endpoint.SIGN_UP,
122+
{
123+
error: {
124+
code: 400,
125+
message: `${ServerError.INVALID_PHONE_NUMBER} : TOO_SHORT`,
126+
errors: [
127+
{
128+
message: ServerError.EMAIL_EXISTS
129+
}
130+
]
131+
}
132+
},
133+
400
134+
);
135+
const promise = _performApiRequest<typeof request, typeof serverResponse>(
136+
auth,
137+
HttpMethod.POST,
138+
Endpoint.SIGN_UP,
139+
request
140+
);
141+
await expect(promise).to.be.rejectedWith(
142+
FirebaseError,
143+
'auth/invalid-phone-number'
115144
);
116145
expect(mock.calls[0].request).to.eql(request);
117146
});
@@ -140,7 +169,7 @@ describe('api/_performApiRequest', () => {
140169
);
141170
await expect(promise).to.be.rejectedWith(
142171
FirebaseError,
143-
'Firebase: An internal AuthError has occurred. (auth/internal-error).'
172+
'auth/internal-error'
144173
);
145174
expect(mock.calls[0].request).to.eql(request);
146175
});
@@ -172,7 +201,7 @@ describe('api/_performApiRequest', () => {
172201
);
173202
await expect(promise).to.be.rejectedWith(
174203
FirebaseError,
175-
'Firebase: Error (auth/argument-error).'
204+
'auth/argument-error'
176205
);
177206
expect(mock.calls[0].request).to.eql(request);
178207
});
@@ -201,10 +230,7 @@ describe('api/_performApiRequest', () => {
201230
request
202231
);
203232
clock.tick(DEFAULT_API_TIMEOUT_MS.get() + 1);
204-
await expect(promise).to.be.rejectedWith(
205-
FirebaseError,
206-
'Firebase: The operation has timed out. (auth/timeout).'
207-
);
233+
await expect(promise).to.be.rejectedWith(FirebaseError, 'auth/timeout');
208234
clock.restore();
209235
});
210236

@@ -222,7 +248,7 @@ describe('api/_performApiRequest', () => {
222248
);
223249
await expect(promise).to.be.rejectedWith(
224250
FirebaseError,
225-
'Firebase: A network AuthError (such as timeout, interrupted connection or unreachable host) has occurred. (auth/network-request-failed).'
251+
'auth/network-request-failed'
226252
);
227253
});
228254
});

packages-exp/auth-exp/src/api/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ export async function _performFetchWithErrorHandling<V>(
125125
return response.json();
126126
} else {
127127
const json: JsonError = await response.json();
128-
const authError = errorMap[json.error.message];
128+
const serverErrorCode = json.error.message.split(' : ')[0] as ServerError;
129+
const authError = errorMap[serverErrorCode];
129130
if (authError) {
130131
fail(auth.name, authError);
131132
} else {

packages-exp/auth-exp/src/core/credentials/email.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { Auth } from '../../model/auth';
2727
import { IdTokenResponse } from '../../model/id_token';
2828
import { AuthErrorCode } from '../errors';
2929
import { EmailAuthProvider } from '../providers/email';
30-
import { debugFail, fail } from '../util/assert';
30+
import { fail } from '../util/assert';
3131
import { AuthCredential } from './';
3232

3333
export class EmailAuthCredential implements AuthCredential {
@@ -61,12 +61,28 @@ export class EmailAuthCredential implements AuthCredential {
6161
);
6262
}
6363

64-
toJSON(): never {
65-
debugFail('Method not implemented.');
64+
toJSON(): object {
65+
return {
66+
email: this.email,
67+
password: this.password,
68+
signInMethod: this.signInMethod
69+
};
6670
}
6771

68-
static fromJSON(_json: object | string): EmailAuthCredential | null {
69-
debugFail('Method not implemented');
72+
static fromJSON(json: object | string): EmailAuthCredential | null {
73+
const obj = typeof json === 'string' ? JSON.parse(json) : json;
74+
if (obj?.email && obj?.password) {
75+
if (
76+
obj.signInMethod === EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD
77+
) {
78+
return this._fromEmailAndPassword(obj.email, obj.password);
79+
} else if (
80+
obj.signInMethod === EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD
81+
) {
82+
return this._fromEmailAndCode(obj.email, obj.password);
83+
}
84+
}
85+
return null;
7086
}
7187

7288
async _getIdTokenResponse(auth: Auth): Promise<IdTokenResponse> {

packages-exp/auth-exp/src/core/errors.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const enum AuthErrorCode {
3636
CREDENTIAL_MISMATCH = 'custom-token-mismatch',
3737
CREDENTIAL_TOO_OLD_LOGIN_AGAIN = 'requires-recent-login',
3838
DYNAMIC_LINK_NOT_ACTIVATED = 'dynamic-link-not-activated',
39+
EMAIL_CHANGE_NEEDS_VERIFICATION = 'email-change-needs-verification',
3940
EMAIL_EXISTS = 'email-already-in-use',
4041
EXPIRED_OOB_CODE = 'expired-action-code',
4142
EXPIRED_POPUP_REQUEST = 'cancelled-popup-request',
@@ -54,6 +55,7 @@ export const enum AuthErrorCode {
5455
INVALID_EMAIL = 'invalid-email',
5556
INVALID_IDP_RESPONSE = 'invalid-credential',
5657
INVALID_MESSAGE_PAYLOAD = 'invalid-message-payload',
58+
INVALID_MFA_SESSION = 'invalid-multi-factor-session',
5759
INVALID_OAUTH_CLIENT_ID = 'invalid-oauth-client-id',
5860
INVALID_OAUTH_PROVIDER = 'invalid-oauth-provider',
5961
INVALID_OOB_CODE = 'invalid-action-code',
@@ -66,6 +68,8 @@ export const enum AuthErrorCode {
6668
INVALID_SENDER = 'invalid-sender',
6769
INVALID_SESSION_INFO = 'invalid-verification-id',
6870
INVALID_TENANT_ID = 'invalid-tenant-id',
71+
MFA_INFO_NOT_FOUND = 'multi-factor-info-not-found',
72+
MFA_REQUIRED = 'multi-factor-auth-required',
6973
MISSING_ANDROID_PACKAGE_NAME = 'missing-android-pkg-name',
7074
MISSING_APP_CREDENTIAL = 'missing-app-credential',
7175
MISSING_AUTH_DOMAIN = 'auth-domain-config-required',
@@ -74,10 +78,11 @@ export const enum AuthErrorCode {
7478
MISSING_IFRAME_START = 'missing-iframe-start',
7579
MISSING_IOS_BUNDLE_ID = 'missing-ios-bundle-id',
7680
MISSING_OR_INVALID_NONCE = 'missing-or-invalid-nonce',
81+
MISSING_MFA_INFO = 'missing-multi-factor-info',
82+
MISSING_MFA_SESSION = 'missing-multi-factor-session',
7783
MISSING_PHONE_NUMBER = 'missing-phone-number',
7884
MISSING_SESSION_INFO = 'missing-verification-id',
7985
MODULE_DESTROYED = 'app-deleted',
80-
MFA_REQUIRED = 'multi-factor-auth-required',
8186
NEED_CONFIRMATION = 'account-exists-with-different-credential',
8287
NETWORK_REQUEST_FAILED = 'network-request-failed',
8388
NULL_USER = 'null-user',
@@ -92,13 +97,17 @@ export const enum AuthErrorCode {
9297
REDIRECT_CANCELLED_BY_USER = 'redirect-cancelled-by-user',
9398
REDIRECT_OPERATION_PENDING = 'redirect-operation-pending',
9499
REJECTED_CREDENTIAL = 'rejected-credential',
100+
SECOND_FACTOR_ALREADY_ENROLLED = 'second-factor-already-in-use',
101+
SECOND_FACTOR_LIMIT_EXCEEDED = 'maximum-second-factor-count-exceeded',
95102
TENANT_ID_MISMATCH = 'tenant-id-mismatch',
96103
TIMEOUT = 'timeout',
97104
TOKEN_EXPIRED = 'user-token-expired',
98105
TOO_MANY_ATTEMPTS_TRY_LATER = 'too-many-requests',
99106
UNAUTHORIZED_DOMAIN = 'unauthorized-continue-uri',
107+
UNSUPPORTED_FIRST_FACTOR = 'unsupported-first-factor',
100108
UNSUPPORTED_PERSISTENCE = 'unsupported-persistence-type',
101109
UNSUPPORTED_TENANT_OPERATION = 'unsupported-tenant-operation',
110+
UNVERIFIED_EMAIL = 'unverified-email',
102111
USER_CANCELLED = 'user-cancelled',
103112
USER_DELETED = 'user-not-found',
104113
USER_DISABLED = 'user-disabled',
@@ -139,6 +148,8 @@ const ERRORS: ErrorMap<AuthErrorCode> = {
139148
[AuthErrorCode.DYNAMIC_LINK_NOT_ACTIVATED]:
140149
'Please activate Dynamic Links in the Firebase Console and agree to the terms and ' +
141150
'conditions.',
151+
[AuthErrorCode.EMAIL_CHANGE_NEEDS_VERIFICATION]:
152+
'Multi-factor users must always have a verified email.',
142153
[AuthErrorCode.EMAIL_EXISTS]:
143154
'The email address is already in use by another account.',
144155
[AuthErrorCode.EXPIRED_OOB_CODE]: 'The action code has expired.',
@@ -180,6 +191,8 @@ const ERRORS: ErrorMap<AuthErrorCode> = {
180191
[AuthErrorCode.INVALID_MESSAGE_PAYLOAD]:
181192
'The email template corresponding to this action contains invalid characters in its message. ' +
182193
'Please fix by going to the Auth email templates section in the Firebase Console.',
194+
[AuthErrorCode.INVALID_MFA_SESSION]:
195+
'The request does not contain a valid proof of first factor successful sign-in.',
183196
[AuthErrorCode.INVALID_OAUTH_PROVIDER]:
184197
'EmailAuthProvider is not supported for this operation. This operation ' +
185198
'only supports OAuth providers.',
@@ -231,12 +244,17 @@ const ERRORS: ErrorMap<AuthErrorCode> = {
231244
'The request does not contain a valid nonce. This can occur if the ' +
232245
'SHA-256 hash of the provided raw nonce does not match the hashed nonce ' +
233246
'in the ID token payload.',
247+
[AuthErrorCode.MISSING_MFA_INFO]: 'No second factor identifier is provided.',
248+
[AuthErrorCode.MISSING_MFA_SESSION]:
249+
'The request is missing proof of first factor successful sign-in.',
234250
[AuthErrorCode.MISSING_PHONE_NUMBER]:
235251
'To send verification codes, provide a phone number for the recipient.',
236252
[AuthErrorCode.MISSING_SESSION_INFO]:
237253
'The phone auth credential was created with an empty verification ID.',
238254
[AuthErrorCode.MODULE_DESTROYED]:
239255
'This instance of FirebaseApp has been deleted.',
256+
[AuthErrorCode.MFA_INFO_NOT_FOUND]:
257+
'The user does not have a second factor matching the identifier provided.',
240258
[AuthErrorCode.MFA_REQUIRED]:
241259
'Proof of ownership of a second factor is required to complete sign-in.',
242260
[AuthErrorCode.NEED_CONFIRMATION]:
@@ -273,6 +291,10 @@ const ERRORS: ErrorMap<AuthErrorCode> = {
273291
'A redirect sign-in operation is already pending.',
274292
[AuthErrorCode.REJECTED_CREDENTIAL]:
275293
'The request contains malformed or mismatching credentials.',
294+
[AuthErrorCode.SECOND_FACTOR_ALREADY_ENROLLED]:
295+
'The second factor is already enrolled on this account.',
296+
[AuthErrorCode.SECOND_FACTOR_LIMIT_EXCEEDED]:
297+
'The maximum allowed number of second factors on a user has been exceeded.',
276298
[AuthErrorCode.TENANT_ID_MISMATCH]:
277299
"The provided tenant ID does not match the Auth instance's tenant ID",
278300
[AuthErrorCode.TIMEOUT]: 'The operation has timed out.',
@@ -284,10 +306,13 @@ const ERRORS: ErrorMap<AuthErrorCode> = {
284306
[AuthErrorCode.UNAUTHORIZED_DOMAIN]:
285307
'The domain of the continue URL is not whitelisted. Please whitelist ' +
286308
'the domain in the Firebase console.',
309+
[AuthErrorCode.UNSUPPORTED_FIRST_FACTOR]:
310+
'Enrolling a second factor or signing in with a multi-factor account requires sign-in with a supported first factor.',
287311
[AuthErrorCode.UNSUPPORTED_PERSISTENCE]:
288312
'The current environment does not support the specified persistence type.',
289313
[AuthErrorCode.UNSUPPORTED_TENANT_OPERATION]:
290314
'This operation is not supported in a multi-tenant context.',
315+
[AuthErrorCode.UNVERIFIED_EMAIL]: 'The operation requires a verified email.',
291316
[AuthErrorCode.USER_CANCELLED]:
292317
'The user did not grant your application the permissions it requested.',
293318
[AuthErrorCode.USER_DELETED]:

0 commit comments

Comments
 (0)