Skip to content

Cleanup demo code & get it working with MFA flows #3380

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
594 changes: 294 additions & 300 deletions packages-exp/auth-exp/demo/src/index.js

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions packages-exp/auth-exp/demo/src/logging.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/**
* @license
* Copyright 2020 Google LLC
Expand All @@ -18,8 +19,8 @@
// Fix for IE8 when developer's console is not opened.
if (!window.console) {
window.console = {
log: function() {},
error: function() {}
log() {},
error() {}
};
}

Expand Down Expand Up @@ -77,7 +78,7 @@ function alertMessage_(message, cssClass) {
if (visibleModal.size() > 0) {
// Check first if the model has an overlaying-alert. If not, append the
// overlaying-alert container.
if (visibleModal.find('.overlaying-alert').size() == 0) {
if (visibleModal.find('.overlaying-alert').size() === 0) {
const $overlayingAlert = $(
'<div class="container-fluid overlaying-alert"></div>'
);
Expand All @@ -88,9 +89,9 @@ function alertMessage_(message, cssClass) {
$('#alert-messages').prepend(alertBox);
}
alertBox.fadeIn({
complete: function() {
setTimeout(function() {
alertBox.slideUp(400, function() {
complete() {
setTimeout(() => {
alertBox.slideUp(400, () => {
// On completion, remove the alert element from the DOM.
alertBox.remove();
});
Expand Down
32 changes: 29 additions & 3 deletions packages-exp/auth-exp/src/api/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export enum ServerError {
CREDENTIAL_MISMATCH = 'CREDENTIAL_MISMATCH',
CREDENTIAL_TOO_OLD_LOGIN_AGAIN = 'CREDENTIAL_TOO_OLD_LOGIN_AGAIN',
DYNAMIC_LINK_NOT_ACTIVATED = 'DYNAMIC_LINK_NOT_ACTIVATED',
EMAIL_CHANGE_NEEDS_VERIFICATION = 'EMAIL_CHANGE_NEEDS_VERIFICATION',
EMAIL_EXISTS = 'EMAIL_EXISTS',
EMAIL_NOT_FOUND = 'EMAIL_NOT_FOUND',
EXPIRED_OOB_CODE = 'EXPIRED_OOB_CODE',
Expand All @@ -43,6 +44,7 @@ export enum ServerError {
INVALID_IDP_RESPONSE = 'INVALID_IDP_RESPONSE',
INVALID_IDENTIFIER = 'INVALID_IDENTIFIER',
INVALID_MESSAGE_PAYLOAD = 'INVALID_MESSAGE_PAYLOAD',
INVALID_MFA_PENDING_CREDENTIAL = 'INVALID_MFA_PENDING_CREDENTIAL',
INVALID_OAUTH_CLIENT_ID = 'INVALID_OAUTH_CLIENT_ID',
INVALID_OOB_CODE = 'INVALID_OOB_CODE',
INVALID_PASSWORD = 'INVALID_PASSWORD',
Expand All @@ -54,12 +56,15 @@ export enum ServerError {
INVALID_SESSION_INFO = 'INVALID_SESSION_INFO',
INVALID_TEMPORARY_PROOF = 'INVALID_TEMPORARY_PROOF',
INVALID_TENANT_ID = 'INVALID_TENANT_ID',
MFA_ENROLLMENT_NOT_FOUND = 'MFA_ENROLLMENT_NOT_FOUND',
MISSING_ANDROID_PACKAGE_NAME = 'MISSING_ANDROID_PACKAGE_NAME',
MISSING_APP_CREDENTIAL = 'MISSING_APP_CREDENTIAL',
MISSING_CODE = 'MISSING_CODE',
MISSING_CONTINUE_URI = 'MISSING_CONTINUE_URI',
MISSING_CUSTOM_TOKEN = 'MISSING_CUSTOM_TOKEN',
MISSING_IOS_BUNDLE_ID = 'MISSING_IOS_BUNDLE_ID',
MISSING_MFA_ENROLLMENT_ID = 'MISSING_MFA_ENROLLMENT_ID',
MISSING_MFA_PENDING_CREDENTIAL = 'MISSING_MFA_PENDING_CREDENTIAL',
MISSING_OOB_CODE = 'MISSING_OOB_CODE',
MISSING_OR_INVALID_NONCE = 'MISSING_OR_INVALID_NONCE',
MISSING_PASSWORD = 'MISSING_PASSWORD',
Expand All @@ -71,12 +76,16 @@ export enum ServerError {
QUOTA_EXCEEDED = 'QUOTA_EXCEEDED',
RESET_PASSWORD_EXCEED_LIMIT = 'RESET_PASSWORD_EXCEED_LIMIT',
REJECTED_CREDENTIAL = 'REJECTED_CREDENTIAL',
SECOND_FACTOR_EXISTS = 'SECOND_FACTOR_EXISTS',
SECOND_FACTOR_LIMIT_EXCEEDED = 'SECOND_FACTOR_LIMIT_EXCEEDED',
SESSION_EXPIRED = 'SESSION_EXPIRED',
TENANT_ID_MISMATCH = 'TENANT_ID_MISMATCH',
TOKEN_EXPIRED = 'TOKEN_EXPIRED',
TOO_MANY_ATTEMPTS_TRY_LATER = 'TOO_MANY_ATTEMPTS_TRY_LATER',
UNSUPPORTED_FIRST_FACTOR = 'UNSUPPORTED_FIRST_FACTOR',
UNSUPPORTED_TENANT_OPERATION = 'UNSUPPORTED_TENANT_OPERATION',
UNAUTHORIZED_DOMAIN = 'UNAUTHORIZED_DOMAIN',
UNVERIFIED_EMAIL = 'UNVERIFIED_EMAIL',
USER_CANCELLED = 'USER_CANCELLED',
USER_DISABLED = 'USER_DISABLED',
USER_NOT_FOUND = 'USER_NOT_FOUND',
Expand All @@ -89,8 +98,8 @@ export enum ServerError {
export interface JsonError {
error: {
code: number;
message: ServerError;
errors: [
message: string;
errors?: [
{
message: ServerError;
domain: string;
Expand Down Expand Up @@ -224,5 +233,22 @@ export const SERVER_ERROR_MAP: ServerErrorMap<ServerError> = {
[ServerError.TENANT_ID_MISMATCH]: AuthErrorCode.TENANT_ID_MISMATCH,

// User actions (sign-up or deletion) disabled errors.
[ServerError.ADMIN_ONLY_OPERATION]: AuthErrorCode.ADMIN_ONLY_OPERATION
[ServerError.ADMIN_ONLY_OPERATION]: AuthErrorCode.ADMIN_ONLY_OPERATION,

// Multi factor related errors.
[ServerError.EMAIL_CHANGE_NEEDS_VERIFICATION]:
AuthErrorCode.EMAIL_CHANGE_NEEDS_VERIFICATION,
[ServerError.INVALID_MFA_PENDING_CREDENTIAL]:
AuthErrorCode.INVALID_MFA_SESSION,
[ServerError.MFA_ENROLLMENT_NOT_FOUND]: AuthErrorCode.MFA_INFO_NOT_FOUND,
[ServerError.MISSING_MFA_ENROLLMENT_ID]: AuthErrorCode.MISSING_MFA_INFO,
[ServerError.MISSING_MFA_PENDING_CREDENTIAL]:
AuthErrorCode.MISSING_MFA_SESSION,
[ServerError.SECOND_FACTOR_EXISTS]:
AuthErrorCode.SECOND_FACTOR_ALREADY_ENROLLED,
[ServerError.SECOND_FACTOR_LIMIT_EXCEEDED]:
AuthErrorCode.SECOND_FACTOR_LIMIT_EXCEEDED,
[ServerError.UNSUPPORTED_FIRST_FACTOR]:
AuthErrorCode.UNSUPPORTED_FIRST_FACTOR,
[ServerError.UNVERIFIED_EMAIL]: AuthErrorCode.UNVERIFIED_EMAIL
};
42 changes: 34 additions & 8 deletions packages-exp/auth-exp/src/api/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,36 @@ describe('api/_performApiRequest', () => {
);
await expect(promise).to.be.rejectedWith(
FirebaseError,
'Firebase: The email address is already in use by another account. (auth/email-already-in-use).'
'auth/email-already-in-use'
);
expect(mock.calls[0].request).to.eql(request);
});

it('should translate complex server errors to auth errors', async () => {
const mock = mockEndpoint(
Endpoint.SIGN_UP,
{
error: {
code: 400,
message: `${ServerError.INVALID_PHONE_NUMBER} : TOO_SHORT`,
errors: [
{
message: ServerError.EMAIL_EXISTS
}
]
}
},
400
);
const promise = _performApiRequest<typeof request, typeof serverResponse>(
auth,
HttpMethod.POST,
Endpoint.SIGN_UP,
request
);
await expect(promise).to.be.rejectedWith(
FirebaseError,
'auth/invalid-phone-number'
);
expect(mock.calls[0].request).to.eql(request);
});
Expand Down Expand Up @@ -140,7 +169,7 @@ describe('api/_performApiRequest', () => {
);
await expect(promise).to.be.rejectedWith(
FirebaseError,
'Firebase: An internal AuthError has occurred. (auth/internal-error).'
'auth/internal-error'
);
expect(mock.calls[0].request).to.eql(request);
});
Expand Down Expand Up @@ -172,7 +201,7 @@ describe('api/_performApiRequest', () => {
);
await expect(promise).to.be.rejectedWith(
FirebaseError,
'Firebase: Error (auth/argument-error).'
'auth/argument-error'
);
expect(mock.calls[0].request).to.eql(request);
});
Expand Down Expand Up @@ -201,10 +230,7 @@ describe('api/_performApiRequest', () => {
request
);
clock.tick(DEFAULT_API_TIMEOUT_MS.get() + 1);
await expect(promise).to.be.rejectedWith(
FirebaseError,
'Firebase: The operation has timed out. (auth/timeout).'
);
await expect(promise).to.be.rejectedWith(FirebaseError, 'auth/timeout');
clock.restore();
});

Expand All @@ -222,7 +248,7 @@ describe('api/_performApiRequest', () => {
);
await expect(promise).to.be.rejectedWith(
FirebaseError,
'Firebase: A network AuthError (such as timeout, interrupted connection or unreachable host) has occurred. (auth/network-request-failed).'
'auth/network-request-failed'
);
});
});
Expand Down
3 changes: 2 additions & 1 deletion packages-exp/auth-exp/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ export async function _performFetchWithErrorHandling<V>(
return response.json();
} else {
const json: JsonError = await response.json();
const authError = errorMap[json.error.message];
const serverErrorCode = json.error.message.split(' : ')[0] as ServerError;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider using a regex to make this potentially-less-brittle
/\s*:/ should do it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like the existing SDK does it a bit different... https://github.com/firebase/firebase-js-sdk/blob/master/packages/auth/src/rpchandler.js#L2916 they use everything to the right of the colon as the message, overriding whatever is in the error map

const authError = errorMap[serverErrorCode];
if (authError) {
fail(auth.name, authError);
} else {
Expand Down
26 changes: 21 additions & 5 deletions packages-exp/auth-exp/src/core/credentials/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { Auth } from '../../model/auth';
import { IdTokenResponse } from '../../model/id_token';
import { AuthErrorCode } from '../errors';
import { EmailAuthProvider } from '../providers/email';
import { debugFail, fail } from '../util/assert';
import { fail } from '../util/assert';
import { AuthCredential } from './';

export class EmailAuthCredential implements AuthCredential {
Expand Down Expand Up @@ -61,12 +61,28 @@ export class EmailAuthCredential implements AuthCredential {
);
}

toJSON(): never {
debugFail('Method not implemented.');
toJSON(): object {
return {
email: this.email,
password: this.password,
signInMethod: this.signInMethod
};
}

static fromJSON(_json: object | string): EmailAuthCredential | null {
debugFail('Method not implemented');
static fromJSON(json: object | string): EmailAuthCredential | null {
const obj = typeof json === 'string' ? JSON.parse(json) : json;
if (obj?.email && obj?.password) {
if (
obj.signInMethod === EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD
) {
return this._fromEmailAndPassword(obj.email, obj.password);
} else if (
obj.signInMethod === EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD
) {
return this._fromEmailAndCode(obj.email, obj.password);
}
}
return null;
}

async _getIdTokenResponse(auth: Auth): Promise<IdTokenResponse> {
Expand Down
27 changes: 26 additions & 1 deletion packages-exp/auth-exp/src/core/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const enum AuthErrorCode {
CREDENTIAL_MISMATCH = 'custom-token-mismatch',
CREDENTIAL_TOO_OLD_LOGIN_AGAIN = 'requires-recent-login',
DYNAMIC_LINK_NOT_ACTIVATED = 'dynamic-link-not-activated',
EMAIL_CHANGE_NEEDS_VERIFICATION = 'email-change-needs-verification',
EMAIL_EXISTS = 'email-already-in-use',
EXPIRED_OOB_CODE = 'expired-action-code',
EXPIRED_POPUP_REQUEST = 'cancelled-popup-request',
Expand All @@ -54,6 +55,7 @@ export const enum AuthErrorCode {
INVALID_EMAIL = 'invalid-email',
INVALID_IDP_RESPONSE = 'invalid-credential',
INVALID_MESSAGE_PAYLOAD = 'invalid-message-payload',
INVALID_MFA_SESSION = 'invalid-multi-factor-session',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

INVALID_OAUTH_CLIENT_ID = 'invalid-oauth-client-id',
INVALID_OAUTH_PROVIDER = 'invalid-oauth-provider',
INVALID_OOB_CODE = 'invalid-action-code',
Expand All @@ -66,6 +68,8 @@ export const enum AuthErrorCode {
INVALID_SENDER = 'invalid-sender',
INVALID_SESSION_INFO = 'invalid-verification-id',
INVALID_TENANT_ID = 'invalid-tenant-id',
MFA_INFO_NOT_FOUND = 'multi-factor-info-not-found',
MFA_REQUIRED = 'multi-factor-auth-required',
MISSING_ANDROID_PACKAGE_NAME = 'missing-android-pkg-name',
MISSING_APP_CREDENTIAL = 'missing-app-credential',
MISSING_AUTH_DOMAIN = 'auth-domain-config-required',
Expand All @@ -74,10 +78,11 @@ export const enum AuthErrorCode {
MISSING_IFRAME_START = 'missing-iframe-start',
MISSING_IOS_BUNDLE_ID = 'missing-ios-bundle-id',
MISSING_OR_INVALID_NONCE = 'missing-or-invalid-nonce',
MISSING_MFA_INFO = 'missing-multi-factor-info',
MISSING_MFA_SESSION = 'missing-multi-factor-session',
MISSING_PHONE_NUMBER = 'missing-phone-number',
MISSING_SESSION_INFO = 'missing-verification-id',
MODULE_DESTROYED = 'app-deleted',
MFA_REQUIRED = 'multi-factor-auth-required',
NEED_CONFIRMATION = 'account-exists-with-different-credential',
NETWORK_REQUEST_FAILED = 'network-request-failed',
NULL_USER = 'null-user',
Expand All @@ -92,13 +97,17 @@ export const enum AuthErrorCode {
REDIRECT_CANCELLED_BY_USER = 'redirect-cancelled-by-user',
REDIRECT_OPERATION_PENDING = 'redirect-operation-pending',
REJECTED_CREDENTIAL = 'rejected-credential',
SECOND_FACTOR_ALREADY_ENROLLED = 'second-factor-already-in-use',
SECOND_FACTOR_LIMIT_EXCEEDED = 'maximum-second-factor-count-exceeded',
TENANT_ID_MISMATCH = 'tenant-id-mismatch',
TIMEOUT = 'timeout',
TOKEN_EXPIRED = 'user-token-expired',
TOO_MANY_ATTEMPTS_TRY_LATER = 'too-many-requests',
UNAUTHORIZED_DOMAIN = 'unauthorized-continue-uri',
UNSUPPORTED_FIRST_FACTOR = 'unsupported-first-factor',
UNSUPPORTED_PERSISTENCE = 'unsupported-persistence-type',
UNSUPPORTED_TENANT_OPERATION = 'unsupported-tenant-operation',
UNVERIFIED_EMAIL = 'unverified-email',
USER_CANCELLED = 'user-cancelled',
USER_DELETED = 'user-not-found',
USER_DISABLED = 'user-disabled',
Expand Down Expand Up @@ -139,6 +148,8 @@ const ERRORS: ErrorMap<AuthErrorCode> = {
[AuthErrorCode.DYNAMIC_LINK_NOT_ACTIVATED]:
'Please activate Dynamic Links in the Firebase Console and agree to the terms and ' +
'conditions.',
[AuthErrorCode.EMAIL_CHANGE_NEEDS_VERIFICATION]:
'Multi-factor users must always have a verified email.',
[AuthErrorCode.EMAIL_EXISTS]:
'The email address is already in use by another account.',
[AuthErrorCode.EXPIRED_OOB_CODE]: 'The action code has expired.',
Expand Down Expand Up @@ -180,6 +191,8 @@ const ERRORS: ErrorMap<AuthErrorCode> = {
[AuthErrorCode.INVALID_MESSAGE_PAYLOAD]:
'The email template corresponding to this action contains invalid characters in its message. ' +
'Please fix by going to the Auth email templates section in the Firebase Console.',
[AuthErrorCode.INVALID_MFA_SESSION]:
'The request does not contain a valid proof of first factor successful sign-in.',
[AuthErrorCode.INVALID_OAUTH_PROVIDER]:
'EmailAuthProvider is not supported for this operation. This operation ' +
'only supports OAuth providers.',
Expand Down Expand Up @@ -231,12 +244,17 @@ const ERRORS: ErrorMap<AuthErrorCode> = {
'The request does not contain a valid nonce. This can occur if the ' +
'SHA-256 hash of the provided raw nonce does not match the hashed nonce ' +
'in the ID token payload.',
[AuthErrorCode.MISSING_MFA_INFO]: 'No second factor identifier is provided.',
[AuthErrorCode.MISSING_MFA_SESSION]:
'The request is missing proof of first factor successful sign-in.',
[AuthErrorCode.MISSING_PHONE_NUMBER]:
'To send verification codes, provide a phone number for the recipient.',
[AuthErrorCode.MISSING_SESSION_INFO]:
'The phone auth credential was created with an empty verification ID.',
[AuthErrorCode.MODULE_DESTROYED]:
'This instance of FirebaseApp has been deleted.',
[AuthErrorCode.MFA_INFO_NOT_FOUND]:
'The user does not have a second factor matching the identifier provided.',
[AuthErrorCode.MFA_REQUIRED]:
'Proof of ownership of a second factor is required to complete sign-in.',
[AuthErrorCode.NEED_CONFIRMATION]:
Expand Down Expand Up @@ -273,6 +291,10 @@ const ERRORS: ErrorMap<AuthErrorCode> = {
'A redirect sign-in operation is already pending.',
[AuthErrorCode.REJECTED_CREDENTIAL]:
'The request contains malformed or mismatching credentials.',
[AuthErrorCode.SECOND_FACTOR_ALREADY_ENROLLED]:
'The second factor is already enrolled on this account.',
[AuthErrorCode.SECOND_FACTOR_LIMIT_EXCEEDED]:
'The maximum allowed number of second factors on a user has been exceeded.',
[AuthErrorCode.TENANT_ID_MISMATCH]:
"The provided tenant ID does not match the Auth instance's tenant ID",
[AuthErrorCode.TIMEOUT]: 'The operation has timed out.',
Expand All @@ -284,10 +306,13 @@ const ERRORS: ErrorMap<AuthErrorCode> = {
[AuthErrorCode.UNAUTHORIZED_DOMAIN]:
'The domain of the continue URL is not whitelisted. Please whitelist ' +
'the domain in the Firebase console.',
[AuthErrorCode.UNSUPPORTED_FIRST_FACTOR]:
'Enrolling a second factor or signing in with a multi-factor account requires sign-in with a supported first factor.',
[AuthErrorCode.UNSUPPORTED_PERSISTENCE]:
'The current environment does not support the specified persistence type.',
[AuthErrorCode.UNSUPPORTED_TENANT_OPERATION]:
'This operation is not supported in a multi-tenant context.',
[AuthErrorCode.UNVERIFIED_EMAIL]: 'The operation requires a verified email.',
[AuthErrorCode.USER_CANCELLED]:
'The user did not grant your application the permissions it requested.',
[AuthErrorCode.USER_DELETED]:
Expand Down
Loading