From 486ffbd7f4fdb8bfa9466633d3034585f69cef0e Mon Sep 17 00:00:00 2001 From: Rich Gowman Date: Fri, 18 Oct 2019 11:06:08 -0400 Subject: [PATCH 1/6] Enable 'structNullChecks' ts compiler option. In the majority of cases, I simply turned stuff like this: ``` function fname(param: T) { if (!param) throw Error("invalid param; must not be nullish"); ... } ``` into this: ``` function fname(param: T|null|undefined) { if (!param) throw Error("invalid param; must not be nullish"); ... } ``` A more sensible approach would be eliminate the possibility of null/undef and eliminate the checks too, but that's a bit more involved and this patch is already enormous. But I think these sorts of improvements could be taken advantage of opportunistically as the code is worked on in the future. --- src/auth/action-code-settings-builder.ts | 2 +- src/auth/auth-api-request.ts | 28 ++-- src/auth/auth-config.ts | 27 +++- src/auth/auth.ts | 4 +- src/auth/credential.ts | 43 +++-- src/auth/tenant.ts | 8 +- src/auth/token-generator.ts | 39 +++-- src/auth/token-verifier.ts | 10 +- src/auth/user-import-builder.ts | 24 +-- src/auth/user-record.ts | 6 +- src/database/database.ts | 5 +- src/firebase-app.ts | 4 +- src/firestore/firestore.ts | 7 +- src/index.d.ts | 2 +- src/instance-id/instance-id.ts | 2 +- src/messaging/batch-request.ts | 6 +- src/messaging/messaging-errors.ts | 19 +-- src/messaging/messaging-types.ts | 26 +-- src/messaging/messaging.ts | 6 +- .../project-management-api-request.ts | 13 +- src/project-management/project-management.ts | 5 +- .../security-rules-api-client.ts | 7 +- src/security-rules/security-rules.ts | 2 +- src/storage/storage.ts | 2 +- src/utils/api-request.ts | 89 +++++++---- src/utils/error.ts | 13 +- src/utils/index.ts | 4 +- src/utils/validator.ts | 8 +- test/integration/auth.spec.ts | 150 +++++++++++------- test/integration/database.spec.ts | 2 +- test/integration/firestore.spec.ts | 8 +- test/integration/project-management.spec.ts | 12 +- test/integration/security-rules.spec.ts | 20 +-- test/integration/setup.ts | 2 +- test/resources/mocks.ts | 6 +- test/unit/auth/auth-api-request.spec.ts | 5 +- test/unit/auth/auth.spec.ts | 15 +- test/unit/auth/credential.spec.ts | 5 +- test/unit/auth/tenant-manager.spec.ts | 4 +- test/unit/auth/tenant.spec.ts | 15 +- test/unit/auth/token-generator.spec.ts | 6 +- test/unit/auth/token-verifier.spec.ts | 14 +- test/unit/firebase-app.spec.ts | 11 +- test/unit/firebase-namespace.spec.ts | 2 +- test/unit/firebase.spec.ts | 2 +- test/unit/firestore/firestore.spec.ts | 12 +- test/unit/instance-id/instance-id.spec.ts | 4 +- test/unit/messaging/messaging.spec.ts | 38 ++--- .../project-management-api-request.spec.ts | 9 +- .../security-rules-api-client.spec.ts | 2 +- .../security-rules/security-rules.spec.ts | 10 +- test/unit/utils/api-request.spec.ts | 70 ++++---- test/unit/utils/index.spec.ts | 4 +- tsconfig.json | 2 +- 54 files changed, 504 insertions(+), 337 deletions(-) diff --git a/src/auth/action-code-settings-builder.ts b/src/auth/action-code-settings-builder.ts index 0961722850..3c2ca8c63e 100644 --- a/src/auth/action-code-settings-builder.ts +++ b/src/auth/action-code-settings-builder.ts @@ -63,7 +63,7 @@ export class ActionCodeSettingsBuilder { * object used to initiliaze this server request builder. * @constructor */ - constructor(actionCodeSettings: ActionCodeSettings) { + constructor(actionCodeSettings?: ActionCodeSettings) { if (!validator.isNonNullObject(actionCodeSettings)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, diff --git a/src/auth/auth-api-request.ts b/src/auth/auth-api-request.ts index 60ba987e4d..e5113e37ae 100755 --- a/src/auth/auth-api-request.ts +++ b/src/auth/auth-api-request.ts @@ -97,7 +97,7 @@ class AuthResourceUrlBuilder { * @param {string} version The endpoint API version. * @constructor */ - constructor(protected projectId: string, protected version: string = 'v1') { + constructor(protected projectId: string | null, protected version: string = 'v1') { this.urlFormat = FIREBASE_AUTH_BASE_URL_FORMAT; } @@ -132,7 +132,7 @@ class TenantAwareAuthResourceUrlBuilder extends AuthResourceUrlBuilder { * @param {string} tenantId The tenant ID. * @constructor */ - constructor(protected projectId: string, protected version: string, protected tenantId: string) { + constructor(protected projectId: string | null, protected version: string, protected tenantId: string) { super(projectId, version); this.urlFormat = FIREBASE_AUTH_TENANT_URL_FORMAT; } @@ -683,7 +683,7 @@ const LIST_INBOUND_SAML_CONFIGS = new ApiSettings('/inboundSamlConfigs', 'GET') * Class that provides the mechanism to send requests to the Firebase Auth backend endpoints. */ export abstract class AbstractAuthRequestHandler { - protected readonly projectId: string; + protected readonly projectId: string | null; protected readonly httpClient: AuthorizedHttpClient; private authUrlBuilder: AuthResourceUrlBuilder; private projectConfigUrlBuilder: AuthResourceUrlBuilder; @@ -693,7 +693,7 @@ export abstract class AbstractAuthRequestHandler { * @return {string|null} The error code if present; null otherwise. */ private static getErrorCode(response: any): string | null { - return (validator.isNonNullObject(response) && response.error && (response.error as any).message) || null; + return (validator.isNonNullObject(response) && (response as any).error && (response as any).error.message) || null; } /** @@ -844,7 +844,7 @@ export abstract class AbstractAuthRequestHandler { } // If no remaining user in request after client side processing, there is no need // to send the request to the server. - if (request.users.length === 0) { + if (!request.users || request.users.length === 0) { return Promise.resolve(userImportBuilder.buildResponse([])); } return this.invokeRequestHandler(this.getAuthUrlBuilder(), FIREBASE_AUTH_UPLOAD_ACCOUNT, request) @@ -881,7 +881,7 @@ export abstract class AbstractAuthRequestHandler { * @return {Promise} A promise that resolves when the operation completes * with the user id that was edited. */ - public setCustomUserClaims(uid: string, customUserClaims: object): Promise { + public setCustomUserClaims(uid: string, customUserClaims: object | null): Promise { // Validate user UID. if (!validator.isUid(uid)) { return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_UID)); @@ -1059,7 +1059,7 @@ export abstract class AbstractAuthRequestHandler { * @param {string} email The email of the user the link is being sent to. * @param {ActionCodeSettings=} actionCodeSettings The optional action code setings which defines whether * the link is to be handled by a mobile app and the additional state information to be passed in the - * deep link, etc. + * deep link, etc. Required when requestType == 'EMAIL_SIGNIN' * @return {Promise} A promise that resolves with the email action link. */ public getEmailActionLink( @@ -1156,7 +1156,7 @@ export abstract class AbstractAuthRequestHandler { // Construct backend request. let request; try { - request = OIDCConfig.buildServerRequest(options); + request = OIDCConfig.buildServerRequest(options) || {}; } catch (e) { return Promise.reject(e); } @@ -1278,7 +1278,7 @@ export abstract class AbstractAuthRequestHandler { // Construct backend request. let request; try { - request = SAMLConfig.buildServerRequest(options); + request = SAMLConfig.buildServerRequest(options) || {}; } catch (e) { return Promise.reject(e); } @@ -1311,7 +1311,7 @@ export abstract class AbstractAuthRequestHandler { // Construct backend request. let request: SAMLConfigServerRequest; try { - request = SAMLConfig.buildServerRequest(options, true); + request = SAMLConfig.buildServerRequest(options, true) || {}; } catch (e) { return Promise.reject(e); } @@ -1366,6 +1366,14 @@ export abstract class AbstractAuthRequestHandler { if (err instanceof HttpError) { const error = err.response.data; const errorCode = AbstractAuthRequestHandler.getErrorCode(error); + if (!errorCode) { + throw new FirebaseAuthError( + AuthClientErrorCode.INTERNAL_ERROR, + 'Error returned from server: ' + error + '. Additionally, an ' + + 'internal error occurred while attempting to extract the ' + + 'errorcode from the error.', + ); + } throw FirebaseAuthError.fromServerError(errorCode, /* message */ undefined, error); } throw err; diff --git a/src/auth/auth-config.ts b/src/auth/auth-config.ts index 96144dc244..0249769128 100755 --- a/src/auth/auth-config.ts +++ b/src/auth/auth-config.ts @@ -193,7 +193,7 @@ export class EmailSignInConfig implements EmailSignInProviderConfig { * * @param {any} options The options object to validate. */ - private static validate(options: {[key: string]: any}) { + private static validate(options: EmailSignInProviderConfig) { // TODO: Validate the request. const validKeys = { enabled: true, @@ -306,7 +306,7 @@ export class SAMLConfig implements SAMLAuthProviderConfig { }; if (options.x509Certificates) { for (const cert of (options.x509Certificates || [])) { - request.idpConfig.idpCertificates.push({x509Certificate: cert}); + request.idpConfig!.idpCertificates!.push({x509Certificate: cert}); } } } @@ -467,7 +467,10 @@ export class SAMLConfig implements SAMLAuthProviderConfig { constructor(response: SAMLConfigServerResponse) { if (!response || !response.idpConfig || + !response.idpConfig.idpEntityId || + !response.idpConfig.ssoUrl || !response.spConfig || + !response.spConfig.spEntityId || !response.name || !(validator.isString(response.name) && SAMLConfig.getProviderIdFromResourceName(response.name))) { @@ -475,7 +478,15 @@ export class SAMLConfig implements SAMLAuthProviderConfig { AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid SAML configuration response'); } - this.providerId = SAMLConfig.getProviderIdFromResourceName(response.name); + + const providerId = SAMLConfig.getProviderIdFromResourceName(response.name); + if (!providerId) { + throw new FirebaseAuthError( + AuthClientErrorCode.INTERNAL_ERROR, + 'INTERNAL ASSERT FAILED: Invalid SAML configuration response'); + } + this.providerId = providerId; + // RP config. this.rpEntityId = response.spConfig.spEntityId; this.callbackURL = response.spConfig.callbackUri; @@ -663,7 +674,15 @@ export class OIDCConfig implements OIDCAuthProviderConfig { AuthClientErrorCode.INTERNAL_ERROR, 'INTERNAL ASSERT FAILED: Invalid OIDC configuration response'); } - this.providerId = OIDCConfig.getProviderIdFromResourceName(response.name); + + const providerId = OIDCConfig.getProviderIdFromResourceName(response.name); + if (!providerId) { + throw new FirebaseAuthError( + AuthClientErrorCode.INTERNAL_ERROR, + 'INTERNAL ASSERT FAILED: Invalid SAML configuration response'); + } + this.providerId = providerId; + this.clientId = response.clientId; this.issuer = response.issuer; // When enabled is undefined, it takes its default value of false. diff --git a/src/auth/auth.ts b/src/auth/auth.ts index 30cd2fa74f..92bada0ac4 100755 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -104,7 +104,7 @@ export class BaseAuth { * minting. * @constructor */ - constructor(protected readonly projectId: string, + constructor(protected readonly projectId: string | null, protected readonly authRequestHandler: T, cryptoSigner: CryptoSigner) { this.tokenGenerator = new FirebaseTokenGenerator(cryptoSigner); @@ -731,7 +731,7 @@ export class Auth extends BaseAuth implements FirebaseServic * @param {FirebaseApp} app The project ID for an app. * @return {string} The FirebaseApp's project ID. */ - private static getProjectId(app: FirebaseApp): string { + private static getProjectId(app: FirebaseApp): string | null { if (typeof app !== 'object' || app === null || !('options' in app)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, diff --git a/src/auth/credential.ts b/src/auth/credential.ts index 6428b2651a..635ef2e8ee 100644 --- a/src/auth/credential.ts +++ b/src/auth/credential.ts @@ -19,7 +19,7 @@ import fs = require('fs'); import os = require('os'); import path = require('path'); -import {AppErrorCodes, FirebaseAppError} from '../utils/error'; +import {AppErrorCodes, FirebaseAppError, FirebaseAuthError, AuthClientErrorCode} from '../utils/error'; import {HttpClient, HttpRequestConfig, HttpError, HttpResponse} from '../utils/api-request'; import {Agent} from 'http'; @@ -69,7 +69,7 @@ export class RefreshToken { * Tries to load a RefreshToken from a path. If the path is not present, returns null. * Throws if data at the path is invalid. */ - public static fromPath(filePath: string): RefreshToken { + public static fromPath(filePath: string): RefreshToken | null { let jsonString: string; try { @@ -224,7 +224,7 @@ function getDetailFromResponse(response: HttpResponse): string { } return detail; } - return response.text; + return response.text || 'Missing Error Payload'; } /** @@ -234,7 +234,7 @@ export class CertCredential implements FirebaseCredential { private readonly certificate: Certificate; private readonly httpClient: HttpClient; - private readonly httpAgent: Agent; + private readonly httpAgent?: Agent; constructor(serviceAccountPathOrObject: string | object, httpAgent?: Agent) { this.certificate = (typeof serviceAccountPathOrObject === 'string') ? @@ -306,8 +306,8 @@ export interface FirebaseCredential extends Credential { * @param {Credential} credential A Credential instance. * @return {Certificate} A Certificate instance or null. */ -export function tryGetCertificate(credential: Credential): Certificate | null { - if (isFirebaseCredential(credential)) { +export function tryGetCertificate(credential?: Credential): Certificate | null { + if (credential && isFirebaseCredential(credential)) { return credential.getCertificate(); } @@ -325,11 +325,22 @@ export class RefreshTokenCredential implements Credential { private readonly refreshToken: RefreshToken; private readonly httpClient: HttpClient; - private readonly httpAgent: Agent; + private readonly httpAgent?: Agent; constructor(refreshTokenPathOrObject: string | object, httpAgent?: Agent) { - this.refreshToken = (typeof refreshTokenPathOrObject === 'string') ? - RefreshToken.fromPath(refreshTokenPathOrObject) : new RefreshToken(refreshTokenPathOrObject); + if (typeof refreshTokenPathOrObject === 'string') { + const refreshTokenOrNull = RefreshToken.fromPath(refreshTokenPathOrObject); + if (!refreshTokenOrNull) { + throw new FirebaseAuthError( + AuthClientErrorCode.NOT_FOUND, + 'The file refered to by the refreshTokenPathOrObject parameter (' + + refreshTokenPathOrObject + ') was not found.', + ); + } + this.refreshToken = refreshTokenOrNull; + } else { + this.refreshToken = new RefreshToken(refreshTokenPathOrObject); + } this.httpClient = new HttpClient(); this.httpAgent = httpAgent; } @@ -362,7 +373,7 @@ export class RefreshTokenCredential implements Credential { export class MetadataServiceCredential implements Credential { private readonly httpClient = new HttpClient(); - private readonly httpAgent: Agent; + private readonly httpAgent?: Agent; constructor(httpAgent?: Agent) { this.httpAgent = httpAgent; @@ -396,10 +407,12 @@ export class ApplicationDefaultCredential implements FirebaseCredential { } // It is OK to not have this file. If it is present, it must be valid. - const refreshToken = RefreshToken.fromPath(GCLOUD_CREDENTIAL_PATH); - if (refreshToken) { - this.credential_ = new RefreshTokenCredential(refreshToken, httpAgent); - return; + if (GCLOUD_CREDENTIAL_PATH) { + const refreshToken = RefreshToken.fromPath(GCLOUD_CREDENTIAL_PATH); + if (refreshToken) { + this.credential_ = new RefreshTokenCredential(refreshToken, httpAgent); + return; + } } this.credential_ = new MetadataServiceCredential(httpAgent); @@ -409,7 +422,7 @@ export class ApplicationDefaultCredential implements FirebaseCredential { return this.credential_.getAccessToken(); } - public getCertificate(): Certificate { + public getCertificate(): Certificate | null { return tryGetCertificate(this.credential_); } diff --git a/src/auth/tenant.ts b/src/auth/tenant.ts index 8fda7d3c7f..3875b253e2 100755 --- a/src/auth/tenant.ts +++ b/src/auth/tenant.ts @@ -117,17 +117,17 @@ export class Tenant { } } // Validate displayName type if provided. - if (typeof request.displayName !== 'undefined' && - !validator.isNonEmptyString(request.displayName)) { + if (typeof (request as any).displayName !== 'undefined' && + !validator.isNonEmptyString((request as any).displayName)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, `"${label}.displayName" must be a valid non-empty string.`, ); } // Validate emailSignInConfig type if provided. - if (typeof request.emailSignInConfig !== 'undefined') { + if (typeof (request as any).emailSignInConfig !== 'undefined') { // This will throw an error if invalid. - EmailSignInConfig.buildServerRequest(request.emailSignInConfig); + EmailSignInConfig.buildServerRequest((request as any).emailSignInConfig); } } diff --git a/src/auth/token-generator.ts b/src/auth/token-generator.ts index ea626e8c39..c8677b291e 100644 --- a/src/auth/token-generator.ts +++ b/src/auth/token-generator.ts @@ -133,7 +133,7 @@ export class ServiceAccountSigner implements CryptoSigner { */ export class IAMSigner implements CryptoSigner { private readonly httpClient: AuthorizedHttpClient; - private serviceAccountId: string; + private serviceAccountId?: string; constructor(httpClient: AuthorizedHttpClient, serviceAccountId?: string) { if (!httpClient) { @@ -170,14 +170,21 @@ export class IAMSigner implements CryptoSigner { if (err instanceof HttpError) { const error = err.response.data; let errorCode: string; - let errorMsg: string; - if (validator.isNonNullObject(error) && error.error) { - errorCode = error.error.status || null; + let errorMsg: string | undefined; + if (validator.isNonNullObject(error) && (error as any).error) { + errorCode = (error as any).error.status; const description = 'Please refer to https://firebase.google.com/docs/auth/admin/create-custom-tokens ' + 'for more details on how to use and troubleshoot this feature.'; - errorMsg = `${error.error.message}; ${description}` || null; + errorMsg = `${(error as any).error.message}; ${description}`; + + throw FirebaseAuthError.fromServerError(errorCode, errorMsg, error); } - throw FirebaseAuthError.fromServerError(errorCode, errorMsg, error); + throw new FirebaseAuthError( + AuthClientErrorCode.INTERNAL_ERROR, + 'Error returned from server: ' + error + '. Additionally, an ' + + 'internal error occurred while attempting to extract the ' + + 'errorcode from the error.', + ); } throw err; }); @@ -199,8 +206,14 @@ export class IAMSigner implements CryptoSigner { }; const client = new HttpClient(); return client.send(request).then((response) => { + if (!response.text) { + throw new FirebaseAuthError( + AuthClientErrorCode.INTERNAL_ERROR, + 'HTTP Response missing payload', + ); + } this.serviceAccountId = response.text; - return this.serviceAccountId; + return response.text; }).catch((err) => { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_CREDENTIAL, @@ -220,9 +233,11 @@ export class IAMSigner implements CryptoSigner { * @return {CryptoSigner} A CryptoSigner instance. */ export function cryptoSignerFromApp(app: FirebaseApp): CryptoSigner { - const cert = tryGetCertificate(app.options.credential); - if (cert != null && validator.isNonEmptyString(cert.privateKey) && validator.isNonEmptyString(cert.clientEmail)) { - return new ServiceAccountSigner(cert); + if (app.options.credential) { + const cert = tryGetCertificate(app.options.credential); + if (cert != null && validator.isNonEmptyString(cert.privateKey) && validator.isNonEmptyString(cert.clientEmail)) { + return new ServiceAccountSigner(cert); + } } return new IAMSigner(new AuthorizedHttpClient(app), app.options.serviceAccountId); } @@ -254,7 +269,7 @@ export class FirebaseTokenGenerator { * service account key and containing the provided payload. */ public createCustomToken(uid: string, developerClaims?: {[key: string]: any}): Promise { - let errorMessage: string; + let errorMessage: string | undefined; if (typeof uid !== 'string' || uid === '') { errorMessage = 'First argument to createCustomToken() must be a non-empty string uid.'; } else if (uid.length > 128) { @@ -263,7 +278,7 @@ export class FirebaseTokenGenerator { errorMessage = 'Second argument to createCustomToken() must be an object containing the developer claims.'; } - if (typeof errorMessage !== 'undefined') { + if (errorMessage) { throw new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, errorMessage); } diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index 7b89fcbca4..5bd34775c1 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -74,7 +74,7 @@ export class FirebaseTokenVerifier { private readonly shortNameArticle: string; constructor(private clientCertUrl: string, private algorithm: string, - private issuer: string, private projectId: string, + private issuer: string, private projectId: string | null, private tokenInfo: FirebaseTokenInfo) { if (!validator.isURL(clientCertUrl)) { throw new FirebaseAuthError( @@ -162,7 +162,7 @@ export class FirebaseTokenVerifier { const verifyJwtTokenDocsMessage = ` See ${this.tokenInfo.url} ` + `for details on how to retrieve ${this.shortNameArticle} ${this.tokenInfo.shortName}.`; - let errorMessage: string; + let errorMessage: string | undefined; if (!fullDecodedToken) { errorMessage = `Decoding ${this.tokenInfo.jwtName} failed. Make sure you passed the entire string JWT ` + `which represents ${this.shortNameArticle} ${this.tokenInfo.shortName}.` + verifyJwtTokenDocsMessage; @@ -200,7 +200,7 @@ export class FirebaseTokenVerifier { errorMessage = `${this.tokenInfo.jwtName} has "sub" (subject) claim longer than 128 characters.` + verifyJwtTokenDocsMessage; } - if (typeof errorMessage !== 'undefined') { + if (errorMessage) { return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, errorMessage)); } @@ -327,7 +327,7 @@ export class FirebaseTokenVerifier { * @param {string} projectId Project ID string. * @return {FirebaseTokenVerifier} */ -export function createIdTokenVerifier(projectId: string): FirebaseTokenVerifier { +export function createIdTokenVerifier(projectId: string | null): FirebaseTokenVerifier { return new FirebaseTokenVerifier( CLIENT_CERT_URL, ALGORITHM_RS256, @@ -343,7 +343,7 @@ export function createIdTokenVerifier(projectId: string): FirebaseTokenVerifier * @param {string} projectId Project ID string. * @return {FirebaseTokenVerifier} */ -export function createSessionCookieVerifier(projectId: string): FirebaseTokenVerifier { +export function createSessionCookieVerifier(projectId: string | null): FirebaseTokenVerifier { return new FirebaseTokenVerifier( SESSION_COOKIE_CERT_URL, ALGORITHM_RS256, diff --git a/src/auth/user-import-builder.ts b/src/auth/user-import-builder.ts index d35c90ecd7..396dfa49da 100644 --- a/src/auth/user-import-builder.ts +++ b/src/auth/user-import-builder.ts @@ -127,13 +127,13 @@ export type ValidatorFunction = (data: UploadAccountUser) => void; /** * @param {any} obj The object to check for number field within. * @param {string} key The entry key. - * @return {number|undefined} The corresponding number if available. + * @return {number} The corresponding number if available. Otherwise, NaN. */ -function getNumberField(obj: any, key: string): number | undefined { +function getNumberField(obj: any, key: string): number { if (typeof obj[key] !== 'undefined' && obj[key] !== null) { return parseInt(obj[key].toString(), 10); } - return undefined; + return NaN; } @@ -184,7 +184,7 @@ function populateUploadAccountUser( } if (validator.isArray(user.providerData)) { user.providerData.forEach((providerData) => { - result.providerUserInfo.push({ + result.providerUserInfo!.push({ providerId: providerData.providerId, rawId: providerData.uid, email: providerData.email, @@ -200,7 +200,7 @@ function populateUploadAccountUser( delete result[key]; } } - if (result.providerUserInfo.length === 0) { + if (result.providerUserInfo!.length === 0) { delete result.providerUserInfo; } // Validate the constructured user individual request. This will throw if an error @@ -231,16 +231,16 @@ export class UserImportBuilder { * @constructor */ constructor( - private users: UserImportRecord[], - private options?: UserImportOptions, - private userRequestValidator?: ValidatorFunction) { + users: UserImportRecord[], + options?: UserImportOptions, + userRequestValidator?: ValidatorFunction) { this.requiresHashOptions = false; this.validatedUsers = []; this.userImportResultErrors = []; this.indexMap = {}; - this.validatedUsers = this.populateUsers(this.users, this.userRequestValidator); - this.validatedOptions = this.populateOptions(this.options, this.requiresHashOptions); + this.validatedUsers = this.populateUsers(users, userRequestValidator); + this.validatedOptions = this.populateOptions(options, this.requiresHashOptions); } /** @@ -264,7 +264,7 @@ export class UserImportBuilder { failedUploads: Array<{index: number, message: string}>): UserImportResult { // Initialize user import result. const importResult: UserImportResult = { - successCount: this.users.length - this.userImportResultErrors.length, + successCount: this.validatedUsers.length, failureCount: this.userImportResultErrors.length, errors: deepCopy(this.userImportResultErrors), }; @@ -296,7 +296,7 @@ export class UserImportBuilder { * @return {UploadAccountOptions} The populated UploadAccount options. */ private populateOptions( - options: UserImportOptions, requiresHashOptions: boolean): UploadAccountOptions { + options: UserImportOptions | undefined, requiresHashOptions: boolean): UploadAccountOptions { let populatedOptions: UploadAccountOptions; if (!requiresHashOptions) { return {}; diff --git a/src/auth/user-record.ts b/src/auth/user-record.ts index 3110264b13..f8c3adafc7 100644 --- a/src/auth/user-record.ts +++ b/src/auth/user-record.ts @@ -27,9 +27,9 @@ const B64_REDACTED = Buffer.from('REDACTED').toString('base64'); * Parses a time stamp string or number and returns the corresponding date if valid. * * @param {any} time The unix timestamp string or number in milliseconds. - * @return {string} The corresponding date as a UTC string, if valid. + * @return {string} The corresponding date as a UTC string, if valid. Otherwise, null. */ -function parseDate(time: any): string { +function parseDate(time: any): string | null { try { const date = new Date(parseInt(time, 10)); if (!isNaN(date.getTime())) { @@ -196,7 +196,7 @@ export class UserRecord { // Ignore error. utils.addReadonlyGetter(this, 'customClaims', undefined); } - let validAfterTime: string = null; + let validAfterTime: string | null = null; // Convert validSince first to UTC milliseconds and then to UTC date string. if (typeof response.validSince !== 'undefined') { validAfterTime = parseDate(response.validSince * 1000); diff --git a/src/database/database.ts b/src/database/database.ts index 184c423c5c..666447f4ae 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -2,7 +2,7 @@ import {URL} from 'url'; import * as path from 'path'; import {FirebaseApp} from '../firebase-app'; -import {FirebaseDatabaseError, AppErrorCodes} from '../utils/error'; +import {FirebaseDatabaseError, AppErrorCodes, FirebaseAppError} from '../utils/error'; import {FirebaseServiceInterface, FirebaseServiceInternalsInterface} from '../firebase-service'; import {Database} from '@firebase/database'; @@ -140,6 +140,9 @@ class DatabaseRulesClient { }; return this.httpClient.send(req) .then((resp) => { + if (!resp.text) { + throw new FirebaseAppError(AppErrorCodes.INTERNAL_ERROR, 'HTTP response missing data.'); + } return resp.text; }) .catch((err) => { diff --git a/src/firebase-app.ts b/src/firebase-app.ts index 1b21aefa8b..8ac3a6ba88 100644 --- a/src/firebase-app.ts +++ b/src/firebase-app.ts @@ -67,7 +67,7 @@ export interface FirebaseAccessToken { export class FirebaseAppInternals { private isDeleted_ = false; private cachedToken_: FirebaseAccessToken; - private cachedTokenPromise_: Promise; + private cachedTokenPromise_: Promise | null; private tokenListeners_: Array<(token: string) => void>; private tokenRefreshTimeout_: NodeJS.Timer; @@ -282,7 +282,7 @@ export class FirebaseApp { (this as {[key: string]: any})[serviceName] = this.getService_.bind(this, serviceName); }); - this.INTERNAL = new FirebaseAppInternals(this.options_.credential); + this.INTERNAL = new FirebaseAppInternals(credential); } /** diff --git a/src/firestore/firestore.ts b/src/firestore/firestore.ts index c284ee67e7..b683781638 100644 --- a/src/firestore/firestore.ts +++ b/src/firestore/firestore.ts @@ -71,8 +71,11 @@ export function getFirestoreOptions(app: FirebaseApp): Settings { }); } - const projectId: string = utils.getProjectId(app); - const cert: Certificate = tryGetCertificate(app.options.credential); + const projectId: string | null = utils.getProjectId(app); + let cert: Certificate | null = null; + if (app.options.credential) { + cert = tryGetCertificate(app.options.credential); + } const { version: firebaseVersion } = require('../../package.json'); if (cert != null) { // cert is available when the SDK has been initialized with a service account JSON file, diff --git a/src/index.d.ts b/src/index.d.ts index 4dde842fb1..ca05462af1 100755 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -140,7 +140,7 @@ declare namespace admin { * [Authenticate with limited privileges](/docs/database/admin/start#authenticate-with-limited-privileges) * for detailed documentation and code samples. */ - databaseAuthVariableOverride?: Object; + databaseAuthVariableOverride?: Object | null; /** * The URL of the Realtime Database from which to read and write data. diff --git a/src/instance-id/instance-id.ts b/src/instance-id/instance-id.ts index ee544fa4e0..60bc52565b 100644 --- a/src/instance-id/instance-id.ts +++ b/src/instance-id/instance-id.ts @@ -55,7 +55,7 @@ export class InstanceId implements FirebaseServiceInterface { ); } - const projectId: string = utils.getProjectId(app); + const projectId: string | null = utils.getProjectId(app); if (!validator.isNonEmptyString(projectId)) { // Assert for an explicit projct ID (either via AppOptions or the cert itself). throw new FirebaseInstanceIdError( diff --git a/src/messaging/batch-request.ts b/src/messaging/batch-request.ts index 76cdb0f8b1..b0b2937a32 100644 --- a/src/messaging/batch-request.ts +++ b/src/messaging/batch-request.ts @@ -17,6 +17,7 @@ import { HttpClient, HttpRequestConfig, HttpResponse, parseHttpResponse, } from '../utils/api-request'; +import { FirebaseAppError, AppErrorCodes } from '../utils/error'; const PART_BOUNDARY: string = '__END_OF_PART__'; const TEN_SECONDS_IN_MILLIS = 10000; @@ -74,6 +75,9 @@ export class BatchRequestClient { timeout: TEN_SECONDS_IN_MILLIS, }; return this.httpClient.send(request).then((response) => { + if (!response.multipart) { + throw new FirebaseAppError(AppErrorCodes.INTERNAL_ERROR, 'Expected a multipart response.'); + } return response.multipart.map((buff) => { return parseHttpResponse(buff, request); }); @@ -128,7 +132,7 @@ function serializeSubRequest(request: SubRequest): string { messagePayload += 'Content-Type: application/json; charset=UTF-8\r\n'; if (request.headers) { Object.keys(request.headers).forEach((key) => { - messagePayload += `${key}: ${request.headers[key]}\r\n`; + messagePayload += `${key}: ${request.headers![key]}\r\n`; }); } messagePayload += '\r\n'; diff --git a/src/messaging/messaging-errors.ts b/src/messaging/messaging-errors.ts index 90bca0f2b1..05d257855f 100644 --- a/src/messaging/messaging-errors.ts +++ b/src/messaging/messaging-errors.ts @@ -67,21 +67,22 @@ export function createFirebaseError(err: HttpError): FirebaseMessagingError { */ export function getErrorCode(response: any): string | null { if (validator.isNonNullObject(response) && 'error' in response) { - if (validator.isString(response.error)) { - return response.error; + const error = (response as any).error; + if (validator.isString(error)) { + return error; } - if (validator.isArray(response.error.details)) { + if (validator.isArray(error.details)) { const fcmErrorType = 'type.googleapis.com/google.firebase.fcm.v1.FcmError'; - for (const element of response.error.details) { + for (const element of error.details) { if (element['@type'] === fcmErrorType) { return element.errorCode; } } } - if ('status' in response.error) { - return response.error.status; + if ('status' in error) { + return error.status; } else { - return response.error.message; + return error.message; } } @@ -97,8 +98,8 @@ export function getErrorCode(response: any): string | null { function getErrorMessage(response: any): string | null { if (validator.isNonNullObject(response) && 'error' in response && - validator.isNonEmptyString(response.error.message)) { - return response.error.message; + validator.isNonEmptyString((response as any).error.message)) { + return (response as any).error.message; } return null; } diff --git a/src/messaging/messaging-types.ts b/src/messaging/messaging-types.ts index aaea9e01c6..fb2ced9d0e 100644 --- a/src/messaging/messaging-types.ts +++ b/src/messaging/messaging-types.ts @@ -196,7 +196,7 @@ export interface NotificationMessagePayload { clickAction?: string; titleLocKey?: string; titleLocArgs?: string; - [other: string]: string; + [other: string]: string | undefined; } /* Composite messaging payload (data and notification payloads are both optional) */ @@ -319,7 +319,7 @@ export function validateMessage(message: Message) { * @param {object} map An object to be validated. * @param {string} label A label to be included in the errors thrown. */ -function validateStringMap(map: {[key: string]: any}, label: string) { +function validateStringMap(map: {[key: string]: any} | undefined, label: string) { if (typeof map === 'undefined') { return; } else if (!validator.isNonNullObject(map)) { @@ -339,7 +339,7 @@ function validateStringMap(map: {[key: string]: any}, label: string) { * * @param {WebpushConfig} config An object to be validated. */ -function validateWebpushConfig(config: WebpushConfig) { +function validateWebpushConfig(config: WebpushConfig | undefined) { if (typeof config === 'undefined') { return; } else if (!validator.isNonNullObject(config)) { @@ -356,7 +356,7 @@ function validateWebpushConfig(config: WebpushConfig) { * * @param {ApnsConfig} config An object to be validated. */ -function validateApnsConfig(config: ApnsConfig) { +function validateApnsConfig(config: ApnsConfig | undefined) { if (typeof config === 'undefined') { return; } else if (!validator.isNonNullObject(config)) { @@ -373,7 +373,7 @@ function validateApnsConfig(config: ApnsConfig) { * * @param {ApnsFcmOptions} fcmOptions An object to be validated. */ -function validateApnsFcmOptions(fcmOptions: ApnsFcmOptions) { +function validateApnsFcmOptions(fcmOptions: ApnsFcmOptions | undefined) { if (typeof fcmOptions === 'undefined') { return; } else if (!validator.isNonNullObject(fcmOptions)) { @@ -411,7 +411,7 @@ function validateApnsFcmOptions(fcmOptions: ApnsFcmOptions) { * * @param {FcmOptions} fcmOptions An object to be validated. */ -function validateFcmOptions(fcmOptions: FcmOptions) { +function validateFcmOptions(fcmOptions: FcmOptions | undefined) { if (typeof fcmOptions === 'undefined') { return; } else if (!validator.isNonNullObject(fcmOptions)) { @@ -430,7 +430,7 @@ function validateFcmOptions(fcmOptions: FcmOptions) { * * @param {Notification} notification An object to be validated. */ -function validateNotification(notification: Notification) { +function validateNotification(notification: Notification | undefined) { if (typeof notification === 'undefined') { return; } else if (!validator.isNonNullObject(notification)) { @@ -461,7 +461,7 @@ function validateNotification(notification: Notification) { * * @param {ApnsPayload} payload An object to be validated. */ -function validateApnsPayload(payload: ApnsPayload) { +function validateApnsPayload(payload: ApnsPayload | undefined) { if (typeof payload === 'undefined') { return; } else if (!validator.isNonNullObject(payload)) { @@ -519,7 +519,7 @@ function validateAps(aps: Aps) { } } -function validateApsSound(sound: string | CriticalSound) { +function validateApsSound(sound: string | CriticalSound | undefined) { if (typeof sound === 'undefined' || validator.isNonEmptyString(sound)) { return; } else if (!validator.isNonNullObject(sound)) { @@ -565,7 +565,7 @@ function validateApsSound(sound: string | CriticalSound) { * * @param {string | ApsAlert} alert An alert string or an object to be validated. */ -function validateApsAlert(alert: string | ApsAlert) { +function validateApsAlert(alert: string | ApsAlert | undefined) { if (typeof alert === 'undefined' || validator.isString(alert)) { return; } else if (!validator.isNonNullObject(alert)) { @@ -614,7 +614,7 @@ function validateApsAlert(alert: string | ApsAlert) { * * @param {AndroidConfig} config An object to be validated. */ -function validateAndroidConfig(config: AndroidConfig) { +function validateAndroidConfig(config: AndroidConfig | undefined) { if (typeof config === 'undefined') { return; } else if (!validator.isNonNullObject(config)) { @@ -660,7 +660,7 @@ function validateAndroidConfig(config: AndroidConfig) { * * @param {AndroidNotification} notification An object to be validated. */ -function validateAndroidNotification(notification: AndroidNotification) { +function validateAndroidNotification(notification: AndroidNotification | undefined) { if (typeof notification === 'undefined') { return; } else if (!validator.isNonNullObject(notification)) { @@ -708,7 +708,7 @@ function validateAndroidNotification(notification: AndroidNotification) { * * @param {AndroidFcmOptions} fcmOptions An object to be validated. */ -function validateAndroidFcmOptions(fcmOptions: AndroidFcmOptions) { +function validateAndroidFcmOptions(fcmOptions: AndroidFcmOptions | undefined) { if (typeof fcmOptions === 'undefined') { return; } else if (!validator.isNonNullObject(fcmOptions)) { diff --git a/src/messaging/messaging.ts b/src/messaging/messaging.ts index 81d50c0400..52b6517697 100644 --- a/src/messaging/messaging.ts +++ b/src/messaging/messaging.ts @@ -221,7 +221,7 @@ export class Messaging implements FirebaseServiceInterface { ); } - const projectId: string = utils.getProjectId(app); + const projectId: string | null = utils.getProjectId(app); if (!validator.isNonEmptyString(projectId)) { // Assert for an explicit project ID (either via AppOptions or the cert itself). throw new FirebaseMessagingError( @@ -796,7 +796,7 @@ export class Messaging implements FirebaseServiceInterface { // Validate the data payload object does not contain blacklisted properties if ('data' in payloadCopy) { BLACKLISTED_DATA_PAYLOAD_KEYS.forEach((blacklistedKey) => { - if (blacklistedKey in payloadCopy.data) { + if (blacklistedKey in payloadCopy.data!) { throw new FirebaseMessagingError( MessagingClientErrorCode.INVALID_PAYLOAD, `Messaging payload contains the blacklisted "data.${ blacklistedKey }" property.`, @@ -806,7 +806,7 @@ export class Messaging implements FirebaseServiceInterface { } // Convert whitelisted camelCase keys to underscore_case - if ('notification' in payloadCopy) { + if (payloadCopy.notification) { utils.renameProperties(payloadCopy.notification, CAMELCASED_NOTIFICATION_PAYLOAD_KEYS_MAP); } diff --git a/src/project-management/project-management-api-request.ts b/src/project-management/project-management-api-request.ts index abbdc9d656..14d533ddb0 100644 --- a/src/project-management/project-management-api-request.ts +++ b/src/project-management/project-management-api-request.ts @@ -64,7 +64,7 @@ export class ProjectManagementRequestHandler { `https://${PROJECT_MANAGEMENT_HOST_AND_PORT}${PROJECT_MANAGEMENT_BETA_PATH}`; private readonly httpClient: AuthorizedHttpClient; - private static wrapAndRethrowHttpError(errStatusCode: number, errText: string) { + private static wrapAndRethrowHttpError(errStatusCode: number, errText?: string) { let errorCode: ProjectManagementErrorCode; let errorMessage: string; @@ -102,6 +102,9 @@ export class ProjectManagementRequestHandler { errorMessage = 'An unknown server error was returned.'; } + if (!errText) { + errText = ''; + } throw new FirebaseProjectManagementError( errorCode, `${ errorMessage } Status code: ${ errStatusCode }. Raw server response: "${ errText }".`); @@ -216,7 +219,7 @@ export class ProjectManagementRequestHandler { return this .invokeRequestHandler( 'PATCH', `${resourceName}?update_mask=display_name`, requestData, 'v1beta1') - .then(() => null); + .then(() => undefined); } /** @@ -240,7 +243,7 @@ export class ProjectManagementRequestHandler { }; return this .invokeRequestHandler('POST', `${parentResourceName}/sha`, requestData, 'v1beta1') - .then(() => null); + .then(() => undefined); } /** @@ -267,7 +270,7 @@ export class ProjectManagementRequestHandler { public deleteResource(resourceName: string): Promise { return this .invokeRequestHandler('DELETE', resourceName, /* requestData */ null, 'v1beta1') - .then(() => null); + .then(() => undefined); } private pollRemoteOperationWithExponentialBackoff( @@ -301,7 +304,7 @@ export class ProjectManagementRequestHandler { private invokeRequestHandler( method: HttpMethod, path: string, - requestData: object, + requestData: object | null, apiVersion: ('v1' | 'v1beta1') = 'v1'): Promise { const baseUrlToUse = (apiVersion === 'v1') ? this.baseUrl : this.baseBetaUrl; const request: HttpRequestConfig = { diff --git a/src/project-management/project-management.ts b/src/project-management/project-management.ts index 434bfbc412..2c9c39794c 100644 --- a/src/project-management/project-management.ts +++ b/src/project-management/project-management.ts @@ -62,14 +62,15 @@ export class ProjectManagement implements FirebaseServiceInterface { } // Assert that a specific project ID was provided within the app. - this.projectId = utils.getProjectId(app); - if (!validator.isNonEmptyString(this.projectId)) { + const projectId = utils.getProjectId(app); + if (!validator.isNonEmptyString(projectId)) { throw new FirebaseProjectManagementError( 'invalid-project-id', 'Failed to determine project ID. Initialize the SDK with service account credentials, or ' + 'set project ID as an app option. Alternatively, set the GOOGLE_CLOUD_PROJECT ' + 'environment variable.'); } + this.projectId = projectId; this.resourceName = `projects/${this.projectId}`; this.requestHandler = new ProjectManagementRequestHandler(app); diff --git a/src/security-rules/security-rules-api-client.ts b/src/security-rules/security-rules-api-client.ts index 9d957f5e6e..4e8102ec42 100644 --- a/src/security-rules/security-rules-api-client.ts +++ b/src/security-rules/security-rules-api-client.ts @@ -57,7 +57,7 @@ export class SecurityRulesApiClient { private readonly projectIdPrefix: string; private readonly url: string; - constructor(private readonly httpClient: HttpClient, projectId: string) { + constructor(private readonly httpClient: HttpClient, projectId: string | null) { if (!validator.isNonNullObject(httpClient)) { throw new FirebaseSecurityRulesError( 'invalid-argument', 'HttpClient must be a non-null object.'); @@ -235,7 +235,10 @@ export class SecurityRulesApiClient { } const error: Error = (response.data as ErrorResponse).error || {}; - const code = ERROR_CODE_MAPPING[error.status] || 'unknown-error'; + let code: SecurityRulesErrorCode = 'unknown-error'; + if (error.status && error.status in ERROR_CODE_MAPPING) { + code = ERROR_CODE_MAPPING[error.status]; + } const message = error.message || `Unknown server error: ${response.text}`; return new FirebaseSecurityRulesError(code, message); } diff --git a/src/security-rules/security-rules.ts b/src/security-rules/security-rules.ts index 8e5258c03a..9205d3ace9 100644 --- a/src/security-rules/security-rules.ts +++ b/src/security-rules/security-rules.ts @@ -370,5 +370,5 @@ class SecurityRulesInternals implements FirebaseServiceInternalsInterface { } function stripProjectIdPrefix(name: string): string { - return name.split('/').pop(); + return name.split('/').pop()!; } diff --git a/src/storage/storage.ts b/src/storage/storage.ts index 5ec64fce20..aa4b3992f9 100644 --- a/src/storage/storage.ts +++ b/src/storage/storage.ts @@ -70,7 +70,7 @@ export class Storage implements FirebaseServiceInterface { }); } - const cert: Certificate = tryGetCertificate(app.options.credential); + const cert: Certificate | null = tryGetCertificate(app.options.credential); if (cert != null) { // cert is available when the SDK has been initialized with a service account JSON file, // or by setting the GOOGLE_APPLICATION_CREDENTIALS envrionment variable. diff --git a/src/utils/api-request.ts b/src/utils/api-request.ts index fa5b669e91..462443467e 100644 --- a/src/utils/api-request.ts +++ b/src/utils/api-request.ts @@ -38,7 +38,7 @@ export interface HttpRequestConfig { /** Target URL of the request. Should be a well-formed URL including protocol, hostname, port and path. */ url: string; headers?: {[key: string]: string}; - data?: string | object | Buffer; + data?: string | object | Buffer | null; /** Connect and read timeout (in milliseconds) for the outgoing request. */ timeout?: number; httpAgent?: http.Agent; @@ -51,9 +51,9 @@ export interface HttpResponse { readonly status: number; readonly headers: any; /** Response data as a raw string. */ - readonly text: string; + readonly text?: string; /** Response data as a parsed JSON object. */ - readonly data: any; + readonly data?: any; /** For multipart responses, the payloads of individual parts. */ readonly multipart?: Buffer[]; /** @@ -66,8 +66,8 @@ export interface HttpResponse { interface LowLevelResponse { status: number; headers: http.IncomingHttpHeaders; - request: http.ClientRequest; - data: string; + request: http.ClientRequest | null; + data?: string; multipart?: Buffer[]; config: HttpRequestConfig; } @@ -83,7 +83,7 @@ class DefaultHttpResponse implements HttpResponse { public readonly status: number; public readonly headers: any; - public readonly text: string; + public readonly text?: string; private readonly parsedData: any; private readonly parseError: Error; @@ -97,6 +97,9 @@ class DefaultHttpResponse implements HttpResponse { this.headers = resp.headers; this.text = resp.data; try { + if (!resp.data) { + throw new FirebaseAppError(AppErrorCodes.INTERNAL_ERROR, 'HTTP response missing data.'); + } this.parsedData = JSON.parse(resp.data); } catch (err) { this.parsedData = undefined; @@ -130,7 +133,7 @@ class MultipartHttpResponse implements HttpResponse { public readonly status: number; public readonly headers: any; - public readonly multipart: Buffer[]; + public readonly multipart?: Buffer[]; constructor(resp: LowLevelResponse) { this.status = resp.status; @@ -239,7 +242,7 @@ function validateRetryConfig(retry: RetryConfig) { export class HttpClient { - constructor(private readonly retry: RetryConfig = defaultRetryConfig()) { + constructor(private readonly retry: RetryConfig | null = defaultRetryConfig()) { if (this.retry) { validateRetryConfig(this.retry); } @@ -278,7 +281,7 @@ export class HttpClient { }) .catch((err: LowLevelError) => { const [delayMillis, canRetry] = this.getRetryDelayMillis(retryAttempts, err); - if (canRetry && delayMillis <= this.retry.maxDelayInMillis) { + if (canRetry && this.retry && delayMillis <= this.retry.maxDelayInMillis) { return this.waitForRetry(delayMillis).then(() => { return this.sendWithRetry(config, retryAttempts + 1); }); @@ -354,8 +357,12 @@ export class HttpClient { return statusCodes.indexOf(err.response.status) !== -1; } - const retryCodes = this.retry.ioErrorCodes || []; - return retryCodes.indexOf(err.code) !== -1; + if (err.code) { + const retryCodes = this.retry.ioErrorCodes || []; + return retryCodes.indexOf(err.code) !== -1; + } + + return false; } /** @@ -380,6 +387,10 @@ export class HttpClient { return 0; } + if (!this.retry) { + throw new FirebaseAppError(AppErrorCodes.INTERNAL_ERROR, 'Expected this.retry to exist.'); + } + const backOffFactor = this.retry.backOffFactor || 0; const delayInSeconds = (2 ** retryAttempts) * backOffFactor; return Math.min(delayInSeconds * 1000, this.retry.maxDelayInMillis); @@ -442,7 +453,7 @@ class AsyncHttpCall { private readonly config: HttpRequestConfigImpl; private readonly options: https.RequestOptions; - private readonly entity: Buffer; + private readonly entity: Buffer | undefined; private readonly promise: Promise; private resolve: (_: any) => void; @@ -459,6 +470,11 @@ class AsyncHttpCall { try { this.config = new HttpRequestConfigImpl(config); this.options = this.config.buildRequestOptions(); + if (!this.options.headers) { + throw new FirebaseAppError( + AppErrorCodes.INTERNAL_ERROR, + 'Expected headers to be present in built request options object'); + } this.entity = this.config.buildEntity(this.options.headers); this.promise = new Promise((resolve, reject) => { this.resolve = resolve; @@ -484,10 +500,10 @@ class AsyncHttpCall { this.enhanceAndReject(err, null, req); }); - const timeout: number = this.config.timeout; + const timeout: number | undefined = this.config.timeout; if (timeout) { // Listen to timeouts and throw an error. - req.setTimeout(this.config.timeout, () => { + req.setTimeout(timeout, () => { req.abort(); this.rejectWithError(`timeout of ${timeout}ms exceeded`, 'ETIMEDOUT', req); }); @@ -502,6 +518,12 @@ class AsyncHttpCall { return; } + if (!res.statusCode) { + throw new FirebaseAppError( + AppErrorCodes.INTERNAL_ERROR, + 'Expected a statusCode on the response from a ClientRequest'); + } + const response: LowLevelResponse = { status: res.statusCode, headers: res.headers, @@ -522,10 +544,13 @@ class AsyncHttpCall { /** * Extracts multipart boundary from the HTTP header. The content-type header of a multipart * response has the form 'multipart/subtype; boundary=string'. + * + * If the content-type header does not exist, or does not start with + * 'multipart/', then null will be returned. */ - private getMultipartBoundary(headers: http.IncomingHttpHeaders): string { + private getMultipartBoundary(headers: http.IncomingHttpHeaders): string | null { const contentType = headers['content-type']; - if (!contentType.startsWith('multipart/')) { + if (!contentType || !contentType.startsWith('multipart/')) { return null; } @@ -550,7 +575,7 @@ class AsyncHttpCall { // Uncompress the response body transparently if required. let respStream: Readable = res; const encodings = ['gzip', 'compress', 'deflate']; - if (encodings.indexOf(res.headers['content-encoding']) !== -1) { + if (res.headers['content-encoding'] && encodings.indexOf(res.headers['content-encoding']) !== -1) { // Add the unzipper to the body stream processing pipeline. const zlib: typeof zlibmod = require('zlib'); respStream = respStream.pipe(zlib.createUnzip()); @@ -579,7 +604,7 @@ class AsyncHttpCall { }); multipartParser.on('finish', () => { - response.data = null; + response.data = undefined; response.multipart = responseBuffer; this.finalizeResponse(response); }); @@ -594,8 +619,8 @@ class AsyncHttpCall { }); respStream.on('error', (err) => { - const req: http.ClientRequest = response.request; - if (req.aborted) { + const req: http.ClientRequest | null = response.request; + if (req && req.aborted) { return; } this.enhanceAndReject(err, null, req); @@ -630,8 +655,8 @@ class AsyncHttpCall { */ private rejectWithError( message: string, - code?: string, - request?: http.ClientRequest, + code?: string | null, + request?: http.ClientRequest | null, response?: LowLevelResponse) { const error = new Error(message); @@ -640,8 +665,8 @@ class AsyncHttpCall { private enhanceAndReject( error: any, - code: string, - request?: http.ClientRequest, + code?: string | null, + request?: http.ClientRequest | null, response?: LowLevelResponse) { this.reject(this.enhanceError(error, code, request, response)); @@ -653,8 +678,8 @@ class AsyncHttpCall { */ private enhanceError( error: any, - code: string, - request?: http.ClientRequest, + code?: string | null, + request?: http.ClientRequest | null, response?: LowLevelResponse): LowLevelError { error.config = this.config; @@ -688,7 +713,7 @@ class HttpRequestConfigImpl implements HttpRequestConfig { return this.config.headers; } - get data(): string | object | Buffer | undefined { + get data(): string | object | Buffer | undefined | null { return this.config.data; } @@ -703,7 +728,7 @@ class HttpRequestConfigImpl implements HttpRequestConfig { public buildRequestOptions(): https.RequestOptions { const parsed = this.buildUrl(); const protocol = parsed.protocol; - let port: string = parsed.port; + let port: string | undefined = parsed.port; if (!port) { const isHttps = protocol === 'https:'; port = isHttps ? '443' : '80'; @@ -720,8 +745,8 @@ class HttpRequestConfigImpl implements HttpRequestConfig { }; } - public buildEntity(headers: http.OutgoingHttpHeaders): Buffer { - let data: Buffer; + public buildEntity(headers: http.OutgoingHttpHeaders): Buffer | undefined { + let data: Buffer | undefined; if (!this.hasEntity() || !this.isEntityEnclosingRequest()) { return data; } @@ -834,7 +859,7 @@ export class ApiSettings { * @param {ApiCallbackFunction} requestValidator The request validator. * @return {ApiSettings} The current API settings instance. */ - public setRequestValidator(requestValidator: ApiCallbackFunction): ApiSettings { + public setRequestValidator(requestValidator: ApiCallbackFunction | null): ApiSettings { const nullFunction: (_: object) => void = (_: object) => undefined; this.requestValidator = requestValidator || nullFunction; return this; @@ -849,7 +874,7 @@ export class ApiSettings { * @param {ApiCallbackFunction} responseValidator The response validator. * @return {ApiSettings} The current API settings instance. */ - public setResponseValidator(responseValidator: ApiCallbackFunction): ApiSettings { + public setResponseValidator(responseValidator: ApiCallbackFunction | null): ApiSettings { const nullFunction: (_: object) => void = (_: object) => undefined; this.responseValidator = responseValidator || nullFunction; return this; diff --git a/src/utils/error.ts b/src/utils/error.ts index 0949dff13d..347d752aea 100755 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -249,12 +249,15 @@ export class FirebaseMessagingError extends PrefixedFirebaseError { * @return {FirebaseMessagingError} The corresponding developer-facing error. */ public static fromServerError( - serverErrorCode: string, - message?: string, + serverErrorCode: string | null, + message?: string | null, rawServerResponse?: object, ): FirebaseMessagingError { // If not found, default to unknown error. - const clientCodeKey = MESSAGING_SERVER_TO_CLIENT_CODE[serverErrorCode] || 'UNKNOWN_ERROR'; + let clientCodeKey = 'UNKNOWN_ERROR'; + if (serverErrorCode && serverErrorCode in MESSAGING_SERVER_TO_CLIENT_CODE) { + clientCodeKey = MESSAGING_SERVER_TO_CLIENT_CODE[serverErrorCode]; + } const error: ErrorInfo = deepCopy((MessagingClientErrorCode as any)[clientCodeKey]); error.message = message || error.message; @@ -642,6 +645,10 @@ export class AuthClientErrorCode { code: 'user-not-found', message: 'There is no user record corresponding to the provided identifier.', }; + public static NOT_FOUND = { + code: 'not-found', + message: 'The requested resource was not found.', + }; } /** diff --git a/src/utils/index.ts b/src/utils/index.ts index ea91418730..a9e45d7cf0 100755 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -63,13 +63,13 @@ export function addReadonlyGetter(obj: object, prop: string, value: any): void { * * @return {string} A project ID string or null. */ -export function getProjectId(app: FirebaseApp): string { +export function getProjectId(app: FirebaseApp): string | null { const options: FirebaseAppOptions = app.options; if (validator.isNonEmptyString(options.projectId)) { return options.projectId; } - const cert: Certificate = tryGetCertificate(options.credential); + const cert: Certificate | null = tryGetCertificate(options.credential); if (cert != null && validator.isNonEmptyString(cert.projectId)) { return cert.projectId; } diff --git a/src/utils/validator.ts b/src/utils/validator.ts index 771c2270f7..8edc54a97e 100644 --- a/src/utils/validator.ts +++ b/src/utils/validator.ts @@ -22,7 +22,7 @@ import url = require('url'); * @param {any} value The value to validate. * @return {boolean} Whether the value is byte buffer or not. */ -export function isBuffer(value: any): boolean { +export function isBuffer(value: any): value is Buffer { return value instanceof Buffer; } @@ -32,7 +32,7 @@ export function isBuffer(value: any): boolean { * @param {any} value The value to validate. * @return {boolean} Whether the value is an array or not. */ -export function isArray(value: any): boolean { +export function isArray(value: any): value is T[] { return Array.isArray(value); } @@ -122,7 +122,7 @@ export function isObject(value: any): boolean { * @param {any} value The value to validate. * @return {boolean} Whether the value is a non-null object or not. */ -export function isNonNullObject(value: any): boolean { +export function isNonNullObject(value: T | null | undefined): value is T { return isObject(value) && value !== null; } @@ -213,7 +213,7 @@ export function isURL(urlStr: any): boolean { } // Validate hostname: Can contain letters, numbers, underscore and dashes separated by a dot. // Each zone must not start with a hyphen or underscore. - if (!/^[a-zA-Z0-9]+[\w\-]*([\.]?[a-zA-Z0-9]+[\w\-]*)*$/.test(hostname)) { + if (!hostname || !/^[a-zA-Z0-9]+[\w\-]*([\.]?[a-zA-Z0-9]+[\w\-]*)*$/.test(hostname)) { return false; } // Allow for pathnames: (/chars+)*/? diff --git a/test/integration/auth.spec.ts b/test/integration/auth.spec.ts index 0f0157c131..1fc6b2ead4 100755 --- a/test/integration/auth.spec.ts +++ b/test/integration/auth.spec.ts @@ -30,6 +30,7 @@ import url = require('url'); import * as mocks from '../resources/mocks'; import { AuthProviderConfig } from '../../src/auth/auth-config'; import { deepExtend, deepCopy } from '../../src/utils/deep-copy'; +import { User } from '@firebase/auth-types'; /* tslint:disable:no-var-requires */ const chalk = require('chalk'); @@ -192,7 +193,7 @@ describe('admin.auth', () => { + 'add the "Firebase Authentication Admin" permission. See ' + 'instructions in CONTRIBUTING.md', ).to.be.ok; - expect(listUsersResult.users[0].passwordHash.length).greaterThan(0); + expect(listUsersResult.users[0].passwordHash!.length).greaterThan(0); expect( listUsersResult.users[0].passwordSalt, @@ -200,23 +201,24 @@ describe('admin.auth', () => { + 'add the "Firebase Authentication Admin" permission. See ' + 'instructions in CONTRIBUTING.md', ).to.be.ok; - expect(listUsersResult.users[0].passwordSalt.length).greaterThan(0); + expect(listUsersResult.users[0].passwordSalt!.length).greaterThan(0); expect(listUsersResult.users[1].uid).to.equal(uids[2]); - expect(listUsersResult.users[1].passwordHash.length).greaterThan(0); - expect(listUsersResult.users[1].passwordSalt.length).greaterThan(0); + expect(listUsersResult.users[1].passwordHash!.length).greaterThan(0); + expect(listUsersResult.users[1].passwordSalt!.length).greaterThan(0); }); }); it('revokeRefreshTokens() invalidates existing sessions and ID tokens', () => { - let currentIdToken: string = null; - let currentUser: any = null; + let currentIdToken: string; + let currentUser: User; // Sign in with an email and password account. - return firebase.auth().signInWithEmailAndPassword(mockUserData.email, mockUserData.password) + return firebase.auth!().signInWithEmailAndPassword(mockUserData.email, mockUserData.password) .then(({user}) => { - currentUser = user; + expect(user).to.exist; + currentUser = user!; // Get user's ID token. - return user.getIdToken(); + return user!.getIdToken(); }) .then((idToken) => { currentIdToken = idToken; @@ -246,12 +248,13 @@ describe('admin.auth', () => { }) .then(() => { // New sign-in should succeed. - return firebase.auth().signInWithEmailAndPassword( + return firebase.auth!().signInWithEmailAndPassword( mockUserData.email, mockUserData.password); }) .then(({user}) => { // Get new session's ID token. - return user.getIdToken(); + expect(user).to.exist; + return user!.getIdToken(); }) .then((idToken) => { // ID token for new session should be valid even with revocation check. @@ -269,12 +272,14 @@ describe('admin.auth', () => { .then((userRecord) => { // Confirm custom claims set on the UserRecord. expect(userRecord.customClaims).to.deep.equal(customClaims); - return firebase.auth().signInWithEmailAndPassword( - userRecord.email, mockUserData.password); + expect(userRecord.email).to.exist; + return firebase.auth!().signInWithEmailAndPassword( + userRecord.email!, mockUserData.password); }) .then(({user}) => { - // Get the user's ID token. - return user.getIdToken(); + // Get the user's ID token. + expect(user).to.exist; + return user!.getIdToken(); }) .then((idToken) => { // Verify ID token contents. @@ -297,7 +302,8 @@ describe('admin.auth', () => { // Custom claims should be cleared. expect(userRecord.customClaims).to.deep.equal({}); // Force token refresh. All claims should be cleared. - return firebase.auth().currentUser.getIdToken(true); + expect(firebase.auth!().currentUser).to.exist; + return firebase.auth!().currentUser!.getIdToken(true); }) .then((idToken) => { // Verify ID token contents. @@ -362,10 +368,11 @@ describe('admin.auth', () => { isAdmin: true, }) .then((customToken) => { - return firebase.auth().signInWithCustomToken(customToken); + return firebase.auth!().signInWithCustomToken(customToken); }) .then(({user}) => { - return user.getIdToken(); + expect(user).to.exist; + return user!.getIdToken(); }) .then((idToken) => { return admin.auth().verifyIdToken(idToken); @@ -381,10 +388,11 @@ describe('admin.auth', () => { isAdmin: true, }) .then((customToken) => { - return firebase.auth().signInWithCustomToken(customToken); + return firebase.auth!().signInWithCustomToken(customToken); }) .then(({user}) => { - return user.getIdToken(); + expect(user).to.exist; + return user!.getIdToken(); }) .then((idToken) => { return admin.auth(noServiceAccountApp).verifyIdToken(idToken); @@ -418,7 +426,7 @@ describe('admin.auth', () => { // Sign out after each test. afterEach(() => { - return firebase.auth().signOut(); + return firebase.auth!().signOut(); }); // Delete test user at the end of test suite. @@ -435,15 +443,16 @@ describe('admin.auth', () => { .then((link) => { const code = getActionCode(link); expect(getContinueUrl(link)).equal(actionCodeSettings.url); - return firebase.auth().confirmPasswordReset(code, newPassword); + return firebase.auth!().confirmPasswordReset(code, newPassword); }) .then(() => { - return firebase.auth().signInWithEmailAndPassword(email, newPassword); + return firebase.auth!().signInWithEmailAndPassword(email, newPassword); }) .then((result) => { - expect(result.user.email).to.equal(email); + expect(result.user).to.exist; + expect(result.user!.email).to.equal(email); // Password reset also verifies the user's email. - expect(result.user.emailVerified).to.be.true; + expect(result.user!.emailVerified).to.be.true; }); }); @@ -457,14 +466,15 @@ describe('admin.auth', () => { .then((link) => { const code = getActionCode(link); expect(getContinueUrl(link)).equal(actionCodeSettings.url); - return firebase.auth().applyActionCode(code); + return firebase.auth!().applyActionCode(code); }) .then(() => { - return firebase.auth().signInWithEmailAndPassword(email, userData.password); + return firebase.auth!().signInWithEmailAndPassword(email, userData.password); }) .then((result) => { - expect(result.user.email).to.equal(email); - expect(result.user.emailVerified).to.be.true; + expect(result.user).to.exist; + expect(result.user!.email).to.equal(email); + expect(result.user!.emailVerified).to.be.true; }); }); @@ -472,11 +482,12 @@ describe('admin.auth', () => { return admin.auth().generateSignInWithEmailLink(email, actionCodeSettings) .then((link) => { expect(getContinueUrl(link)).equal(actionCodeSettings.url); - return firebase.auth().signInWithEmailLink(email, link); + return firebase.auth!().signInWithEmailLink(email, link); }) .then((result) => { - expect(result.user.email).to.equal(email); - expect(result.user.emailVerified).to.be.true; + expect(result.user).to.exist; + expect(result.user!.email).to.equal(email); + expect(result.user!.emailVerified).to.be.true; }); }); }); @@ -663,7 +674,8 @@ describe('admin.auth', () => { return tenantAwareAuth.getUser(createdUserUid); }) .then((userRecord) => { - expect(new Date(userRecord.tokensValidAfterTime).getTime()) + expect(userRecord.tokensValidAfterTime).to.exist; + expect(new Date(userRecord.tokensValidAfterTime!).getTime()) .to.be.greaterThan(lastValidSinceTime); }); }); @@ -1191,8 +1203,11 @@ describe('admin.auth', () => { it('creates a valid Firebase session cookie', () => { return admin.auth().createCustomToken(uid, {admin: true, groupId: '1234'}) - .then((customToken) => firebase.auth().signInWithCustomToken(customToken)) - .then(({user}) => user.getIdToken()) + .then((customToken) => firebase.auth!().signInWithCustomToken(customToken)) + .then(({user}) => { + expect(user).to.exist; + return user!.getIdToken(); + }) .then((idToken) => { currentIdToken = idToken; return admin.auth().verifyIdToken(idToken); @@ -1224,8 +1239,11 @@ describe('admin.auth', () => { it('creates a revocable session cookie', () => { let currentSessionCookie: string; return admin.auth().createCustomToken(uid2) - .then((customToken) => firebase.auth().signInWithCustomToken(customToken)) - .then(({user}) => user.getIdToken()) + .then((customToken) => firebase.auth!().signInWithCustomToken(customToken)) + .then(({user}) => { + expect(user).to.exist; + return user!.getIdToken(); + }) .then((idToken) => { // One day long session cookie. return admin.auth().createSessionCookie(idToken, {expiresIn}); @@ -1248,8 +1266,11 @@ describe('admin.auth', () => { it('fails when called with a revoked ID token', () => { return admin.auth().createCustomToken(uid3, {admin: true, groupId: '1234'}) - .then((customToken) => firebase.auth().signInWithCustomToken(customToken)) - .then(({user}) => user.getIdToken()) + .then((customToken) => firebase.auth!().signInWithCustomToken(customToken)) + .then(({user}) => { + expect(user).to.exist; + return user!.getIdToken(); + }) .then((idToken) => { currentIdToken = idToken; return new Promise((resolve) => setTimeout(() => resolve( @@ -1273,8 +1294,11 @@ describe('admin.auth', () => { it('fails when called with a Firebase ID token', () => { return admin.auth().createCustomToken(uid) - .then((customToken) => firebase.auth().signInWithCustomToken(customToken)) - .then(({user}) => user.getIdToken()) + .then((customToken) => firebase.auth!().signInWithCustomToken(customToken)) + .then(({user}) => { + expect(user).to.exist; + return user!.getIdToken(); + }) .then((idToken) => { return admin.auth().verifySessionCookie(idToken) .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); @@ -1317,7 +1341,8 @@ describe('admin.auth', () => { }, } as any, computePasswordHash: (userImportTest: UserImportTest): Buffer => { - const currentHashKey = userImportTest.importOptions.hash.key.toString('utf8'); + expect(userImportTest.importOptions.hash.key).to.exist; + const currentHashKey = userImportTest.importOptions.hash.key!.toString('utf8'); const currentRawPassword = userImportTest.rawPassword; const currentRawSalt = userImportTest.rawSalt; return crypto.createHmac('sha256', currentHashKey) @@ -1384,11 +1409,16 @@ describe('admin.auth', () => { } as any, computePasswordHash: (userImportTest: UserImportTest): Buffer => { const currentRawPassword = userImportTest.rawPassword; - const currentRawSalt = userImportTest.rawSalt; - const N = userImportTest.importOptions.hash.memoryCost; - const r = userImportTest.importOptions.hash.blockSize; - const p = userImportTest.importOptions.hash.parallelization; - const dkLen = userImportTest.importOptions.hash.derivedKeyLength; + expect(userImportTest.rawSalt); + const currentRawSalt = userImportTest.rawSalt!; + expect(userImportTest.importOptions.hash.memoryCost).to.exist; + const N = userImportTest.importOptions.hash.memoryCost!; + expect(userImportTest.importOptions.hash.blockSize).to.exist; + const r = userImportTest.importOptions.hash.blockSize!; + expect(userImportTest.importOptions.hash.parallelization).to.exist; + const p = userImportTest.importOptions.hash.parallelization!; + expect(userImportTest.importOptions.hash.derivedKeyLength).to.exist; + const dkLen = userImportTest.importOptions.hash.derivedKeyLength!; return Buffer.from(scrypt.hashSync( currentRawPassword, {N, r, p}, dkLen, Buffer.from(currentRawSalt))); }, @@ -1405,8 +1435,10 @@ describe('admin.auth', () => { } as any, computePasswordHash: (userImportTest: UserImportTest): Buffer => { const currentRawPassword = userImportTest.rawPassword; - const currentRawSalt = userImportTest.rawSalt; - const currentRounds = userImportTest.importOptions.hash.rounds; + expect(userImportTest.rawSalt).to.exist; + const currentRawSalt = userImportTest.rawSalt!; + expect(userImportTest.importOptions.hash.rounds).to.exist; + const currentRounds = userImportTest.importOptions.hash.rounds!; return crypto.pbkdf2Sync( currentRawPassword, currentRawSalt, currentRounds, 64, 'sha256'); }, @@ -1542,12 +1574,14 @@ function testImportAndSignInUser( expect(result.successCount).to.equal(1); expect(result.errors.length).to.equal(0); // Sign in with an email and password to the imported account. - return firebase.auth().signInWithEmailAndPassword(users[0].email, rawPassword); + return firebase.auth!().signInWithEmailAndPassword(users[0].email, rawPassword); }) .then(({user}) => { // Confirm successful sign-in. - expect(user.email).to.equal(users[0].email); - expect(user.providerData[0].providerId).to.equal('password'); + expect(user).to.exist; + expect(user!.email).to.equal(users[0].email); + expect(user!.providerData[0]).to.exist; + expect(user!.providerData[0]!.providerId).to.equal('password'); }); } @@ -1603,7 +1637,9 @@ function cleanup() { */ function getActionCode(link: string): string { const parsedUrl = new url.URL(link); - return parsedUrl.searchParams.get('oobCode'); + const oobCode = parsedUrl.searchParams.get('oobCode'); + expect(oobCode).to.exist; + return oobCode!; } /** @@ -1614,7 +1650,9 @@ function getActionCode(link: string): string { */ function getContinueUrl(link: string): string { const parsedUrl = new url.URL(link); - return parsedUrl.searchParams.get('continueUrl'); + const continueUrl = parsedUrl.searchParams.get('continueUrl'); + expect(continueUrl).to.exist; + return continueUrl!; } /** @@ -1625,7 +1663,9 @@ function getContinueUrl(link: string): string { */ function getTenantId(link: string): string { const parsedUrl = new url.URL(link); - return parsedUrl.searchParams.get('tenantId'); + const tenantId = parsedUrl.searchParams.get('tenantId'); + expect(tenantId).to.exist; + return tenantId!; } /** diff --git a/test/integration/database.spec.ts b/test/integration/database.spec.ts index e98395a220..3f9818a405 100644 --- a/test/integration/database.spec.ts +++ b/test/integration/database.spec.ts @@ -178,7 +178,7 @@ describe('admin.database', () => { // @ts-ignore: purposely unused method. function addValueEventListener( db: admin.database.Database, - callback: (s: admin.database.DataSnapshot) => any) { + callback: (s: admin.database.DataSnapshot | null) => any) { const eventType: admin.database.EventType = 'value'; db.ref().on(eventType, callback); } diff --git a/test/integration/firestore.spec.ts b/test/integration/firestore.spec.ts index 62a62dd4cd..085212b9c2 100644 --- a/test/integration/firestore.spec.ts +++ b/test/integration/firestore.spec.ts @@ -75,8 +75,9 @@ describe('admin.firestore', () => { }) .then((snapshot) => { const data = snapshot.data(); - expect(data.timestamp).is.not.null; - expect(data.timestamp).to.be.instanceOf(admin.firestore.Timestamp); + expect(data).to.exist; + expect(data!.timestamp).is.not.null; + expect(data!.timestamp).to.be.instanceOf(admin.firestore.Timestamp); return reference.delete(); }) .should.eventually.be.fulfilled; @@ -124,7 +125,8 @@ describe('admin.firestore', () => { }) .then((snapshot) => { const data = snapshot.data(); - expect(data.sisterCity.path).to.deep.equal(source.path); + expect(data).to.exist; + expect(data!.sisterCity.path).to.deep.equal(source.path); const promises = []; promises.push(source.delete()); promises.push(target.delete()); diff --git a/test/integration/project-management.spec.ts b/test/integration/project-management.spec.ts index 12de46db80..4cf269c363 100644 --- a/test/integration/project-management.spec.ts +++ b/test/integration/project-management.spec.ts @@ -62,7 +62,7 @@ describe('admin.projectManagement', () => { const metadataOwnedByTest = metadatas.find((metadata) => isIntegrationTestApp(metadata.packageName)); expect(metadataOwnedByTest).to.exist; - expect(metadataOwnedByTest.appId).to.equal(androidApp.appId); + expect(metadataOwnedByTest!.appId).to.equal(androidApp.appId); }); }); }); @@ -76,7 +76,7 @@ describe('admin.projectManagement', () => { const metadataOwnedByTest = metadatas.find((metadata) => isIntegrationTestApp(metadata.bundleId)); expect(metadataOwnedByTest).to.exist; - expect(metadataOwnedByTest.appId).to.equal(iosApp.appId); + expect(metadataOwnedByTest!.appId).to.equal(iosApp.appId); }); }); }); @@ -258,7 +258,7 @@ function deleteAllShaCertificates(androidApp: admin.projectManagement.AndroidApp .then((shaCertificates: admin.projectManagement.ShaCertificate[]) => { return Promise.all(shaCertificates.map((cert) => androidApp.deleteShaCertificate(cert))); }) - .then(() => null); + .then(() => undefined); } /** @@ -286,14 +286,14 @@ function generateUniqueProjectDisplayName() { * @return {boolean} True if the specified appNamespace belongs to these integration tests. */ function isIntegrationTestApp(appNamespace: string): boolean { - return appNamespace && appNamespace.startsWith(APP_NAMESPACE_PREFIX); + return appNamespace ? appNamespace.startsWith(APP_NAMESPACE_PREFIX) : false; } /** * @return {boolean} True if the specified appDisplayName belongs to these integration tests. */ -function isIntegrationTestAppDisplayName(appDisplayName: string): boolean { - return appDisplayName && appDisplayName.startsWith(APP_DISPLAY_NAME_PREFIX); +function isIntegrationTestAppDisplayName(appDisplayName: string | undefined): boolean { + return appDisplayName ? appDisplayName.startsWith(APP_DISPLAY_NAME_PREFIX) : false; } /** diff --git a/test/integration/security-rules.spec.ts b/test/integration/security-rules.spec.ts index 881b1509fb..ce86ed932e 100644 --- a/test/integration/security-rules.spec.ts +++ b/test/integration/security-rules.spec.ts @@ -137,11 +137,11 @@ describe('admin.securityRules', () => { }); describe('Cloud Firestore', () => { - let oldRuleset: admin.securityRules.Ruleset = null; - let newRuleset: admin.securityRules.Ruleset = null; + let oldRuleset: admin.securityRules.Ruleset | null = null; + let newRuleset: admin.securityRules.Ruleset | null = null; function revertFirestoreRulesetIfModified(): Promise { - if (!newRuleset) { + if (!newRuleset || !oldRuleset) { return Promise.resolve(); } @@ -173,23 +173,23 @@ describe('admin.securityRules', () => { scheduleForDelete(ruleset); newRuleset = ruleset; - expect(ruleset.name).to.not.equal(oldRuleset.name); + expect(ruleset.name).to.not.equal(oldRuleset!.name); verifyFirestoreRuleset(ruleset); return admin.securityRules().getFirestoreRuleset(); }) .then((ruleset) => { - expect(ruleset.name).to.equal(newRuleset.name); + expect(ruleset.name).to.equal(newRuleset!.name); verifyFirestoreRuleset(ruleset); }); }); }); describe('Cloud Storage', () => { - let oldRuleset: admin.securityRules.Ruleset = null; - let newRuleset: admin.securityRules.Ruleset = null; + let oldRuleset: admin.securityRules.Ruleset | null = null; + let newRuleset: admin.securityRules.Ruleset | null = null; function revertStorageRulesetIfModified(): Promise { - if (!newRuleset) { + if (!newRuleset || !oldRuleset) { return Promise.resolve(); } @@ -221,14 +221,14 @@ describe('admin.securityRules', () => { scheduleForDelete(ruleset); newRuleset = ruleset; - expect(ruleset.name).to.not.equal(oldRuleset.name); + expect(ruleset.name).to.not.equal(oldRuleset!.name); expect(ruleset.name).to.match(RULESET_NAME_PATTERN); const createTime = new Date(ruleset.createTime); expect(ruleset.createTime).equals(createTime.toUTCString()); return admin.securityRules().getStorageRuleset(); }) .then((ruleset) => { - expect(ruleset.name).to.equal(newRuleset.name); + expect(ruleset.name).to.equal(newRuleset!.name); }); }); }); diff --git a/test/integration/setup.ts b/test/integration/setup.ts index 414a7366d4..332ad5d83a 100644 --- a/test/integration/setup.ts +++ b/test/integration/setup.ts @@ -118,7 +118,7 @@ class CertificatelessCredential implements Credential { return this.delegate.getAccessToken(); } - public getCertificate(): Certificate { + public getCertificate(): Certificate | null { return null; } } diff --git a/test/resources/mocks.ts b/test/resources/mocks.ts index 822cd68ff0..3928141a44 100644 --- a/test/resources/mocks.ts +++ b/test/resources/mocks.ts @@ -86,7 +86,7 @@ export class MockCredential implements Credential { }); } - public getCertificate(): Certificate { + public getCertificate(): Certificate | null { return null; } } @@ -111,7 +111,7 @@ export function appWithOptions(options: FirebaseAppOptions): FirebaseApp { } export function appReturningNullAccessToken(): FirebaseApp { - const nullFn: () => Promise = () => null; + const nullFn: () => Promise|null = () => null; return new FirebaseApp({ credential: { getAccessToken: nullFn, @@ -226,7 +226,7 @@ export function generateSessionCookie(overrides?: object, expiresIn?: number): s export function firebaseServiceFactory( firebaseApp: FirebaseApp, - extendApp: (props: object) => void, + extendApp?: (props: object) => void, ): FirebaseServiceInterface { const result = { app: firebaseApp, diff --git a/test/unit/auth/auth-api-request.spec.ts b/test/unit/auth/auth-api-request.spec.ts index ffcf7d556f..7d6911056a 100755 --- a/test/unit/auth/auth-api-request.spec.ts +++ b/test/unit/auth/auth-api-request.spec.ts @@ -44,6 +44,7 @@ import { SAMLUpdateAuthProviderRequest, SAMLConfigServerResponse, } from '../../../src/auth/auth-config'; import {TenantOptions} from '../../../src/auth/tenant'; +import { UpdateRequest } from '../../../src/auth/user-record'; chai.should(); chai.use(sinonChai); @@ -1597,7 +1598,7 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { const path = handler.path('v1', '/accounts:update', 'project_id'); const method = 'POST'; const uid = '12345678'; - const validData = { + const validData: UpdateRequest = { displayName: 'John Doe', email: 'user@example.com', emailVerified: true, @@ -1605,8 +1606,8 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { photoURL: 'http://localhost/1234/photo.png', password: 'password', phoneNumber: '+11234567890', - ignoredProperty: 'value', }; + (validData as any).ignoredProperty = 'value'; const expectedValidData = { localId: uid, displayName: 'John Doe', diff --git a/test/unit/auth/auth.spec.ts b/test/unit/auth/auth.spec.ts index 8793caf5bc..e44ab9229b 100755 --- a/test/unit/auth/auth.spec.ts +++ b/test/unit/auth/auth.spec.ts @@ -26,7 +26,7 @@ import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; import {Auth, TenantAwareAuth, BaseAuth, DecodedIdToken} from '../../../src/auth/auth'; -import {UserRecord} from '../../../src/auth/user-record'; +import {UserRecord, CreateRequest, UpdateRequest} from '../../../src/auth/user-record'; import {FirebaseApp} from '../../../src/firebase-app'; import {FirebaseTokenGenerator} from '../../../src/auth/token-generator'; import { @@ -411,6 +411,9 @@ AUTH_CONFIGS.forEach((testConfig) => { const tenantId = testConfig.supportsTenantManagement ? undefined : TENANT_ID; const expectedUserRecord = getValidUserRecord(getValidGetAccountInfoResponse(tenantId)); // Set auth_time of token to expected user's tokensValidAfterTime. + if (!expectedUserRecord.tokensValidAfterTime) { + throw new Error("getValidUserRecord didn't properly set tokensValidAfterTime."); + } const validSince = new Date(expectedUserRecord.tokensValidAfterTime); // Set expected uid to expected user's. const uid = expectedUserRecord.uid; @@ -652,6 +655,9 @@ AUTH_CONFIGS.forEach((testConfig) => { const tenantId = testConfig.supportsTenantManagement ? undefined : TENANT_ID; const expectedUserRecord = getValidUserRecord(getValidGetAccountInfoResponse(tenantId)); // Set auth_time of token to expected user's tokensValidAfterTime. + if (!expectedUserRecord.tokensValidAfterTime) { + throw new Error("getValidUserRecord didn't properly set tokensValidAfterTime."); + } const validSince = new Date(expectedUserRecord.tokensValidAfterTime); // Set expected uid to expected user's. const uid = expectedUserRecord.uid; @@ -1236,7 +1242,7 @@ AUTH_CONFIGS.forEach((testConfig) => { }); it('should be rejected given invalid properties', () => { - return auth.createUser(null) + return auth.createUser(null as unknown as CreateRequest) .then(() => { throw new Error('Unexpected success'); }) @@ -1390,7 +1396,7 @@ AUTH_CONFIGS.forEach((testConfig) => { }); it('should be rejected given invalid properties', () => { - return auth.updateUser(uid, null) + return auth.updateUser(uid, null as unknown as UpdateRequest) .then(() => { throw new Error('Unexpected success'); }) @@ -1927,6 +1933,9 @@ AUTH_CONFIGS.forEach((testConfig) => { const expectedError = new FirebaseAuthError(AuthClientErrorCode.INVALID_ID_TOKEN); const expectedUserRecord = getValidUserRecord(getValidGetAccountInfoResponse(tenantId)); // Set auth_time of token to expected user's tokensValidAfterTime. + if (!expectedUserRecord.tokensValidAfterTime) { + throw new Error("getValidUserRecord didn't properly set tokensValidAfterTime."); + } const validSince = new Date(expectedUserRecord.tokensValidAfterTime); // Set expected uid to expected user's. const uid = expectedUserRecord.uid; diff --git a/test/unit/auth/credential.spec.ts b/test/unit/auth/credential.spec.ts index 76c624fb2b..792db85c20 100644 --- a/test/unit/auth/credential.spec.ts +++ b/test/unit/auth/credential.spec.ts @@ -44,7 +44,10 @@ chai.use(chaiAsPromised); const expect = chai.expect; const GCLOUD_CREDENTIAL_SUFFIX = 'gcloud/application_default_credentials.json'; -const GCLOUD_CREDENTIAL_PATH = path.resolve(process.env.HOME, '.config', GCLOUD_CREDENTIAL_SUFFIX); +if (!process.env.HOME) { + throw new Error('$HOME environment variable must be set to run the tests.'); +} +const GCLOUD_CREDENTIAL_PATH = path.resolve(process.env.HOME!, '.config', GCLOUD_CREDENTIAL_SUFFIX); const MOCK_REFRESH_TOKEN_CONFIG = { client_id: 'test_client_id', client_secret: 'test_client_secret', diff --git a/test/unit/auth/tenant-manager.spec.ts b/test/unit/auth/tenant-manager.spec.ts index 0b86792144..3149e92cb5 100644 --- a/test/unit/auth/tenant-manager.spec.ts +++ b/test/unit/auth/tenant-manager.spec.ts @@ -401,7 +401,7 @@ describe('TenantManager', () => { }); it('should be rejected given invalid TenantOptions', () => { - return tenantManager.createTenant(null) + return tenantManager.createTenant(null as unknown as TenantOptions) .then(() => { throw new Error('Unexpected success'); }) @@ -509,7 +509,7 @@ describe('TenantManager', () => { }); it('should be rejected given invalid TenantOptions', () => { - return tenantManager.updateTenant(tenantId, null) + return tenantManager.updateTenant(tenantId, null as unknown as TenantOptions) .then(() => { throw new Error('Unexpected success'); }) diff --git a/test/unit/auth/tenant.spec.ts b/test/unit/auth/tenant.spec.ts index c4f63a6de0..1fb1e24dfa 100755 --- a/test/unit/auth/tenant.spec.ts +++ b/test/unit/auth/tenant.spec.ts @@ -20,7 +20,7 @@ import * as sinonChai from 'sinon-chai'; import * as chaiAsPromised from 'chai-as-promised'; import {deepCopy} from '../../../src/utils/deep-copy'; -import {EmailSignInConfig} from '../../../src/auth/auth-config'; +import {EmailSignInConfig, EmailSignInProviderConfig} from '../../../src/auth/auth-config'; import { Tenant, TenantOptions, TenantServerResponse, } from '../../../src/auth/tenant'; @@ -33,14 +33,14 @@ chai.use(chaiAsPromised); const expect = chai.expect; describe('Tenant', () => { - const serverRequest = { + const serverRequest: TenantServerResponse = { name: 'projects/project1/tenants/TENANT-ID', displayName: 'TENANT-DISPLAY-NAME', allowPasswordSignup: true, enableEmailLinkSignin: true, }; - const clientRequest = { + const clientRequest: TenantOptions = { displayName: 'TENANT-DISPLAY-NAME', emailSignInConfig: { enabled: true, @@ -62,7 +62,7 @@ describe('Tenant', () => { it('should throw on invalid EmailSignInConfig object', () => { const tenantOptionsClientRequest = deepCopy(clientRequest); - tenantOptionsClientRequest.emailSignInConfig = null; + tenantOptionsClientRequest.emailSignInConfig = null as unknown as EmailSignInProviderConfig; expect(() => Tenant.buildServerRequest(tenantOptionsClientRequest, !createRequest)) .to.throw('"EmailSignInConfig" must be a non-null object.'); }); @@ -123,7 +123,7 @@ describe('Tenant', () => { it('should throw on invalid EmailSignInConfig', () => { const tenantOptionsClientRequest: TenantOptions = deepCopy(clientRequest); - tenantOptionsClientRequest.emailSignInConfig = null; + tenantOptionsClientRequest.emailSignInConfig = null as unknown as EmailSignInProviderConfig; expect(() => Tenant.buildServerRequest(tenantOptionsClientRequest, createRequest)) .to.throw('"EmailSignInConfig" must be a non-null object.'); @@ -216,8 +216,9 @@ describe('Tenant', () => { expect(tenantWithoutAllowPasswordSignup.displayName).to.equal(serverResponse.displayName); expect(tenantWithoutAllowPasswordSignup.tenantId).to.equal('TENANT-ID'); - expect(tenantWithoutAllowPasswordSignup.emailSignInConfig.enabled).to.be.false; - expect(tenantWithoutAllowPasswordSignup.emailSignInConfig.passwordRequired).to.be.true; + expect(tenantWithoutAllowPasswordSignup.emailSignInConfig).to.exist; + expect(tenantWithoutAllowPasswordSignup.emailSignInConfig!.enabled).to.be.false; + expect(tenantWithoutAllowPasswordSignup.emailSignInConfig!.passwordRequired).to.be.true; }).not.to.throw(); }); }); diff --git a/test/unit/auth/token-generator.spec.ts b/test/unit/auth/token-generator.spec.ts index 072d6c2509..1a9e27186a 100644 --- a/test/unit/auth/token-generator.spec.ts +++ b/test/unit/auth/token-generator.spec.ts @@ -257,7 +257,7 @@ describe('CryptoSigner', () => { describe('FirebaseTokenGenerator', () => { let tokenGenerator: FirebaseTokenGenerator; - let clock: sinon.SinonFakeTimers; + let clock: sinon.SinonFakeTimers | undefined; beforeEach(() => { const cert = new Certificate(mocks.certificateObject); tokenGenerator = new FirebaseTokenGenerator(new ServiceAccountSigner(cert)); @@ -441,13 +441,13 @@ describe('FirebaseTokenGenerator', () => { .then((result) => { token = result; - clock.tick((ONE_HOUR_IN_SECONDS * 1000) - 1); + clock!.tick((ONE_HOUR_IN_SECONDS * 1000) - 1); // Token should still be valid return verifyToken(token, mocks.keyPairs[0].public); }) .then(() => { - clock.tick(1); + clock!.tick(1); // Token should now be invalid return verifyToken(token, mocks.keyPairs[0].public) diff --git a/test/unit/auth/token-verifier.spec.ts b/test/unit/auth/token-verifier.spec.ts index a7f09634f4..25fee52317 100644 --- a/test/unit/auth/token-verifier.spec.ts +++ b/test/unit/auth/token-verifier.spec.ts @@ -107,7 +107,7 @@ function mockFailedFetchPublicKeys(): nock.Scope { describe('FirebaseTokenVerifier', () => { let tokenVerifier: verifier.FirebaseTokenVerifier; let tokenGenerator: FirebaseTokenGenerator; - let clock: sinon.SinonFakeTimers; + let clock: sinon.SinonFakeTimers | undefined; let httpsSpy: sinon.SinonSpy; beforeEach(() => { // Needed to generate custom token for testing. @@ -408,7 +408,7 @@ describe('FirebaseTokenVerifier', () => { // Token should still be valid return tokenVerifier.verifyJWT(mockIdToken).then(() => { - clock.tick(1); + clock!.tick(1); // Token should now be invalid return tokenVerifier.verifyJWT(mockIdToken) @@ -436,7 +436,7 @@ describe('FirebaseTokenVerifier', () => { // Cookie should still be valid return tokenVerifierSessionCookie.verifyJWT(mockSessionCookie).then(() => { - clock.tick(1); + clock!.tick(1); // Cookie should now be invalid return tokenVerifierSessionCookie.verifyJWT(mockSessionCookie) @@ -567,20 +567,20 @@ describe('FirebaseTokenVerifier', () => { return tokenVerifier.verifyJWT(mockIdToken).then(() => { expect(https.request).to.have.been.calledOnce; - clock.tick(999); + clock!.tick(999); return tokenVerifier.verifyJWT(mockIdToken); }).then(() => { expect(https.request).to.have.been.calledOnce; - clock.tick(1); + clock!.tick(1); return tokenVerifier.verifyJWT(mockIdToken); }).then(() => { // One second has passed expect(https.request).to.have.been.calledTwice; - clock.tick(999); + clock!.tick(999); return tokenVerifier.verifyJWT(mockIdToken); }).then(() => { expect(https.request).to.have.been.calledTwice; - clock.tick(1); + clock!.tick(1); return tokenVerifier.verifyJWT(mockIdToken); }).then(() => { // Two seconds have passed diff --git a/test/unit/firebase-app.spec.ts b/test/unit/firebase-app.spec.ts index c2bb365997..75262b1456 100644 --- a/test/unit/firebase-app.spec.ts +++ b/test/unit/firebase-app.spec.ts @@ -66,7 +66,7 @@ describe('FirebaseApp', () => { let getTokenStub: sinon.SinonStub; let firebaseNamespace: FirebaseNamespace; let firebaseNamespaceInternals: FirebaseNamespaceInternals; - let firebaseConfigVar: string; + let firebaseConfigVar: string | undefined; beforeEach(() => { getTokenStub = sinon.stub(CertCredential.prototype, 'getAccessToken').resolves({ @@ -747,7 +747,8 @@ describe('FirebaseApp', () => { return mockApp.INTERNAL.getToken(true).then((token1) => { // Stub the getToken() method to return a rejected promise. getTokenStub.restore(); - getTokenStub = sinon.stub(mockApp.options.credential, 'getAccessToken') + expect(mockApp.options.credential).to.exist; + getTokenStub = sinon.stub(mockApp.options.credential!, 'getAccessToken') .rejects(new Error('Intentionally rejected')); // Forward the clock to exactly five minutes before expiry. @@ -789,7 +790,8 @@ describe('FirebaseApp', () => { // Stub the credential's getAccessToken() method to always return a rejected promise. getTokenStub.restore(); - getTokenStub = sinon.stub(mockApp.options.credential, 'getAccessToken') + expect(mockApp.options.credential).to.exist; + getTokenStub = sinon.stub(mockApp.options.credential!, 'getAccessToken') .rejects(new Error('Intentionally rejected')); // Expect the call count to initially be zero. @@ -910,7 +912,8 @@ describe('FirebaseApp', () => { it('proactively refreshes the token at the next full minute if it expires in five minutes or less', () => { // Turn off default mocking of one hour access tokens and replace it with a short-lived token. getTokenStub.restore(); - getTokenStub = sinon.stub(mockApp.options.credential, 'getAccessToken').resolves({ + expect(mockApp.options.credential).to.exist; + getTokenStub = sinon.stub(mockApp.options.credential!, 'getAccessToken').resolves({ access_token: utils.generateRandomAccessToken(), expires_in: 3 * 60 + 10, }); diff --git a/test/unit/firebase-namespace.spec.ts b/test/unit/firebase-namespace.spec.ts index 8680eac25a..f52b50bfc9 100644 --- a/test/unit/firebase-namespace.spec.ts +++ b/test/unit/firebase-namespace.spec.ts @@ -320,7 +320,7 @@ describe('FirebaseNamespace', () => { it('should throw given no service name', () => { expect(() => { - firebaseNamespace.INTERNAL.registerService(undefined, mocks.firebaseServiceFactory); + firebaseNamespace.INTERNAL.registerService(undefined as unknown as string, mocks.firebaseServiceFactory); }).to.throw(`No service name provided. Service name must be a non-empty string.`); }); diff --git a/test/unit/firebase.spec.ts b/test/unit/firebase.spec.ts index a38a2942ab..8d3f0cb1ec 100644 --- a/test/unit/firebase.spec.ts +++ b/test/unit/firebase.spec.ts @@ -127,7 +127,7 @@ describe('Firebase', () => { }); it('should initialize SDK given an application default credential', () => { - let credPath: string; + let credPath: string | undefined; credPath = process.env.GOOGLE_APPLICATION_CREDENTIALS; process.env.GOOGLE_APPLICATION_CREDENTIALS = path.resolve(__dirname, '../resources/mock.key.json'); firebaseAdmin.initializeApp({ diff --git a/test/unit/firestore/firestore.spec.ts b/test/unit/firestore/firestore.spec.ts index 09d2193800..9105dab4e7 100644 --- a/test/unit/firestore/firestore.spec.ts +++ b/test/unit/firestore/firestore.spec.ts @@ -32,9 +32,9 @@ describe('Firestore', () => { let projectIdApp: FirebaseApp; let firestore: any; - let appCredentials: string; - let googleCloudProject: string; - let gcloudProject: string; + let appCredentials: string | undefined; + let googleCloudProject: string | undefined; + let gcloudProject: string | undefined; const invalidCredError = 'Failed to initialize Google Cloud Firestore client with the available ' + 'credentials. Must initialize the SDK with a certificate credential or application default ' @@ -62,7 +62,11 @@ describe('Firestore', () => { }); afterEach(() => { - process.env.GOOGLE_APPLICATION_CREDENTIALS = appCredentials; + if (appCredentials) { + process.env.GOOGLE_APPLICATION_CREDENTIALS = appCredentials; + } else { + delete process.env.GOOGLE_APPLICATION_CREDENTIALS; + } if (googleCloudProject) { process.env.GOOGLE_CLOUD_PROJECT = googleCloudProject; } else { diff --git a/test/unit/instance-id/instance-id.spec.ts b/test/unit/instance-id/instance-id.spec.ts index d03bfea3ba..081e9ddb82 100644 --- a/test/unit/instance-id/instance-id.spec.ts +++ b/test/unit/instance-id/instance-id.spec.ts @@ -46,8 +46,8 @@ describe('InstanceId', () => { let malformedAccessTokenClient: InstanceId; let rejectedPromiseAccessTokenClient: InstanceId; - let googleCloudProject: string; - let gcloudProject: string; + let googleCloudProject: string | undefined; + let gcloudProject: string | undefined; const noProjectIdError = 'Failed to determine project ID for InstanceId. Initialize the SDK ' + 'with service account credentials or set project ID as an app option. Alternatively set the ' diff --git a/test/unit/messaging/messaging.spec.ts b/test/unit/messaging/messaging.spec.ts index 85cfa54f58..3b0c4a2ae6 100644 --- a/test/unit/messaging/messaging.spec.ts +++ b/test/unit/messaging/messaging.spec.ts @@ -398,10 +398,10 @@ describe('Messaging', () => { describe('send()', () => { it('should throw given no message', () => { expect(() => { - messaging.send(undefined as Message); + messaging.send(undefined as unknown as Message); }).to.throw('Message must be a non-null object'); expect(() => { - messaging.send(null); + messaging.send(null as unknown as Message); }).to.throw('Message must be a non-null object'); }); @@ -549,16 +549,16 @@ describe('Messaging', () => { expect(response.messageId).to.be.undefined; expect(response.error).to.have.property('code', code); if (msg) { - expect(response.error.toString()).to.contain(msg); + expect(response.error!.toString()).to.contain(msg); } } it('should throw given no messages', () => { expect(() => { - messaging.sendAll(undefined as Message[]); + messaging.sendAll(undefined as unknown as Message[]); }).to.throw('messages must be a non-empty array'); expect(() => { - messaging.sendAll(null); + messaging.sendAll(null as unknown as Message[]); }).to.throw('messages must be a non-empty array'); expect(() => { messaging.sendAll([]); @@ -804,7 +804,7 @@ describe('Messaging', () => { ], }; - let stub: sinon.SinonStub; + let stub: sinon.SinonStub | null; afterEach(() => { if (stub) { @@ -815,7 +815,7 @@ describe('Messaging', () => { it('should throw given no messages', () => { expect(() => { - messaging.sendMulticast(undefined as MulticastMessage); + messaging.sendMulticast(undefined as unknown as MulticastMessage); }).to.throw('MulticastMessage must be a non-null object'); expect(() => { messaging.sendMulticast({} as any); @@ -851,9 +851,9 @@ describe('Messaging', () => { .then((response: BatchResponse) => { expect(response).to.deep.equal(mockResponse); expect(stub).to.have.been.calledOnce; - const messages: Message[] = stub.args[0][0]; + const messages: Message[] = stub!.args[0][0]; expect(messages.length).to.equal(3); - expect(stub.args[0][1]).to.be.undefined; + expect(stub!.args[0][1]).to.be.undefined; messages.forEach((message, idx) => { expect((message as any).token).to.equal(tokens[idx]); expect(message.android).to.be.undefined; @@ -880,9 +880,9 @@ describe('Messaging', () => { .then((response: BatchResponse) => { expect(response).to.deep.equal(mockResponse); expect(stub).to.have.been.calledOnce; - const messages: Message[] = stub.args[0][0]; + const messages: Message[] = stub!.args[0][0]; expect(messages.length).to.equal(3); - expect(stub.args[0][1]).to.be.undefined; + expect(stub!.args[0][1]).to.be.undefined; messages.forEach((message, idx) => { expect((message as any).token).to.equal(tokens[idx]); expect(message.android).to.deep.equal(multicast.android); @@ -901,7 +901,7 @@ describe('Messaging', () => { .then((response: BatchResponse) => { expect(response).to.deep.equal(mockResponse); expect(stub).to.have.been.calledOnce; - expect(stub.args[0][1]).to.be.true; + expect(stub!.args[0][1]).to.be.true; }); }); @@ -1095,7 +1095,7 @@ describe('Messaging', () => { expect(response.messageId).to.be.undefined; expect(response.error).to.have.property('code', code); if (msg) { - expect(response.error.toString()).to.contain(msg); + expect(response.error!.toString()).to.contain(msg); } } }); @@ -1116,7 +1116,7 @@ describe('Messaging', () => { it('should throw given no registration token(s) argument', () => { expect(() => { - messaging.sendToDevice(undefined as string, mocks.messaging.payloadDataOnly); + messaging.sendToDevice(undefined as unknown as string, mocks.messaging.payloadDataOnly); }).to.throw(invalidArgumentError); }); @@ -1438,7 +1438,7 @@ describe('Messaging', () => { it('should throw given no notification key argument', () => { expect(() => { - messaging.sendToDeviceGroup(undefined as string, mocks.messaging.payloadDataOnly); + messaging.sendToDeviceGroup(undefined as unknown as string, mocks.messaging.payloadDataOnly); }).to.throw(invalidArgumentError); }); @@ -1686,7 +1686,7 @@ describe('Messaging', () => { it('should throw given no topic argument', () => { expect(() => { - messaging.sendToTopic(undefined as string, mocks.messaging.payload); + messaging.sendToTopic(undefined as unknown as string, mocks.messaging.payload); }).to.throw(invalidArgumentError); }); @@ -1913,7 +1913,7 @@ describe('Messaging', () => { it('should throw given no condition argument', () => { expect(() => { - messaging.sendToCondition(undefined as string, mocks.messaging.payloadDataOnly); + messaging.sendToCondition(undefined as unknown as string, mocks.messaging.payloadDataOnly); }).to.throw(invalidArgumentError); }); @@ -3424,7 +3424,7 @@ describe('Messaging', () => { it('should throw given no registration token(s) argument', () => { expect(() => { - messagingService[methodName](undefined as string, mocks.messaging.topic); + messagingService[methodName](undefined as unknown as string, mocks.messaging.topic); }).to.throw(invalidRegistrationTokensArgumentError); }); @@ -3479,7 +3479,7 @@ describe('Messaging', () => { it('should throw given no topic argument', () => { expect(() => { - messagingService[methodName](mocks.messaging.registrationToken, undefined as string); + messagingService[methodName](mocks.messaging.registrationToken, undefined as unknown as string); }).to.throw(invalidTopicArgumentError); }); diff --git a/test/unit/project-management/project-management-api-request.spec.ts b/test/unit/project-management/project-management-api-request.spec.ts index 9b9e6ec0c4..87a3e334af 100644 --- a/test/unit/project-management/project-management-api-request.spec.ts +++ b/test/unit/project-management/project-management-api-request.spec.ts @@ -435,8 +435,7 @@ describe('ProjectManagementRequestHandler', () => { displayName: newDisplayName, }; return requestHandler.setDisplayName(ANDROID_APP_RESOURCE_NAME, newDisplayName) - .then((result) => { - expect(result).to.deep.equal(null); + .then(() => { expect(stub).to.have.been.calledOnce.and.calledWith({ method: 'PATCH', url, @@ -489,8 +488,7 @@ describe('ProjectManagementRequestHandler', () => { certType: 'SHA_1', }; return requestHandler.addAndroidShaCertificate(ANDROID_APP_RESOURCE_NAME, certificateToAdd) - .then((result) => { - expect(result).to.deep.equal(null); + .then(() => { expect(stub).to.have.been.calledOnce.and.calledWith({ method: 'POST', url, @@ -567,8 +565,7 @@ describe('ProjectManagementRequestHandler', () => { const url = `https://${HOST}:${PORT}/v1beta1/${resourceName}`; return requestHandler.deleteResource(resourceName) - .then((result) => { - expect(result).to.deep.equal(null); + .then(() => { expect(stub).to.have.been.calledOnce.and.calledWith({ method: 'DELETE', url, diff --git a/test/unit/security-rules/security-rules-api-client.spec.ts b/test/unit/security-rules/security-rules-api-client.spec.ts index d2a0de58ee..b0f506c5d5 100644 --- a/test/unit/security-rules/security-rules-api-client.spec.ts +++ b/test/unit/security-rules/security-rules-api-client.spec.ts @@ -55,7 +55,7 @@ describe('SecurityRulesApiClient', () => { describe('Constructor', () => { it('should throw when the HttpClient is null', () => { - expect(() => new SecurityRulesApiClient(null, 'test')) + expect(() => new SecurityRulesApiClient(null as unknown as HttpClient, 'test')) .to.throw('HttpClient must be a non-null object.'); }); diff --git a/test/unit/security-rules/security-rules.spec.ts b/test/unit/security-rules/security-rules.spec.ts index 2407c54a91..44b4655a0d 100644 --- a/test/unit/security-rules/security-rules.spec.ts +++ b/test/unit/security-rules/security-rules.spec.ts @@ -31,7 +31,15 @@ const expect = chai.expect; describe('SecurityRules', () => { const EXPECTED_ERROR = new FirebaseSecurityRulesError('internal-error', 'message'); - const FIRESTORE_RULESET_RESPONSE = { + const FIRESTORE_RULESET_RESPONSE: { + // This type is effectively a RulesetResponse, but with non-readonly fields + // to allow easier use from within the tests. An improvement would be to + // alter this into a helper that creates customized RulesetResponses based + // on the needs of the test, as that would ensure type-safety. + name: string, + createTime: string, + source: object | null, + } = { name: 'projects/test-project/rulesets/foo', createTime: '2019-03-08T23:45:23.288047Z', source: { diff --git a/test/unit/utils/api-request.spec.ts b/test/unit/utils/api-request.spec.ts index 9480ee8d0a..440a9de044 100644 --- a/test/unit/utils/api-request.spec.ts +++ b/test/unit/utils/api-request.spec.ts @@ -106,9 +106,9 @@ function testRetryConfig(): RetryConfig { describe('HttpClient', () => { let mockedRequests: nock.Scope[] = []; - let transportSpy: sinon.SinonSpy = null; - let delayStub: sinon.SinonStub = null; - let clock: sinon.SinonFakeTimers = null; + let transportSpy: sinon.SinonSpy | null = null; + let delayStub: sinon.SinonStub | null = null; + let clock: sinon.SinonFakeTimers | null = null; const sampleMultipartData = '--boundary\r\n' + 'Content-type: application/json\r\n\r\n' @@ -239,7 +239,7 @@ describe('HttpClient', () => { expect(resp.status).to.equal(200); expect(resp.headers['content-type']).to.equal('multipart/mixed; boundary=boundary'); expect(resp.multipart).to.not.be.undefined; - expect(resp.multipart.length).to.equal(0); + expect(resp.multipart!.length).to.equal(0); expect(() => { resp.text; }).to.throw('Unable to parse multipart payload as text'); expect(() => { resp.data; }).to.throw('Unable to parse multipart payload as JSON'); expect(resp.isJson()).to.be.false; @@ -260,10 +260,8 @@ describe('HttpClient', () => { }).then((resp) => { expect(resp.status).to.equal(200); expect(resp.headers['content-type']).to.equal('multipart/mixed; boundary=boundary'); - expect(resp.multipart).to.not.be.undefined; - expect(resp.multipart.length).to.equal(2); - expect(resp.multipart[0].toString('utf-8')).to.equal('{"foo": 1}'); - expect(resp.multipart[1].toString('utf-8')).to.equal('foo bar'); + expect(resp.multipart).to.exist; + expect(resp.multipart!.map((buffer) => buffer.toString('utf-8'))).to.deep.equal(['{"foo": 1}', 'foo bar']); expect(() => { resp.text; }).to.throw('Unable to parse multipart payload as text'); expect(() => { resp.data; }).to.throw('Unable to parse multipart payload as JSON'); expect(resp.isJson()).to.be.false; @@ -284,10 +282,8 @@ describe('HttpClient', () => { }).then((resp) => { expect(resp.status).to.equal(200); expect(resp.headers['content-type']).to.equal('multipart/something; boundary=boundary'); - expect(resp.multipart).to.not.be.undefined; - expect(resp.multipart.length).to.equal(2); - expect(resp.multipart[0].toString('utf-8')).to.equal('{"foo": 1}'); - expect(resp.multipart[1].toString('utf-8')).to.equal('foo bar'); + expect(resp.multipart).to.exist; + expect(resp.multipart!.map((buffer) => buffer.toString('utf-8'))).to.deep.equal(['{"foo": 1}', 'foo bar']); expect(() => { resp.text; }).to.throw('Unable to parse multipart payload as text'); expect(() => { resp.data; }).to.throw('Unable to parse multipart payload as JSON'); expect(resp.isJson()).to.be.false; @@ -358,8 +354,8 @@ describe('HttpClient', () => { httpAgent, }).then((resp) => { expect(resp.status).to.equal(200); - expect(transportSpy.callCount).to.equal(1); - const options = transportSpy.args[0][0]; + expect(transportSpy!.callCount).to.equal(1); + const options = transportSpy!.args[0][0]; expect(options.agent).to.equal(httpAgent); }); }); @@ -645,10 +641,8 @@ describe('HttpClient', () => { const resp = err.response; expect(resp.status).to.equal(500); expect(resp.headers['content-type']).to.equal('multipart/mixed; boundary=boundary'); - expect(resp.multipart).to.not.be.undefined; - expect(resp.multipart.length).to.equal(2); - expect(resp.multipart[0].toString('utf-8')).to.equal('{"foo": 1}'); - expect(resp.multipart[1].toString('utf-8')).to.equal('foo bar'); + expect(resp.multipart).to.exist; + expect(resp.multipart!.map((buffer) => buffer.toString('utf-8'))).to.deep.equal(['{"foo": 1}', 'foo bar']); expect(() => { resp.text; }).to.throw('Unable to parse multipart payload as text'); expect(() => { resp.data; }).to.throw('Unable to parse multipart payload as JSON'); expect(resp.isJson()).to.be.false; @@ -925,8 +919,8 @@ describe('HttpClient', () => { expect(resp.headers['content-type']).to.equal('application/json'); expect(resp.data).to.deep.equal({}); expect(resp.isJson()).to.be.true; - expect(delayStub.callCount).to.equal(4); - const delays = delayStub.args.map((args) => args[0]); + expect(delayStub!.callCount).to.equal(4); + const delays = delayStub!.args.map((args) => args[0]); expect(delays).to.deep.equal([0, 1000, 2000, 4000]); }); }); @@ -956,8 +950,8 @@ describe('HttpClient', () => { expect(resp.headers['content-type']).to.equal('application/json'); expect(resp.data).to.deep.equal({}); expect(resp.isJson()).to.be.true; - expect(delayStub.callCount).to.equal(4); - const delays = delayStub.args.map((args) => args[0]); + expect(delayStub!.callCount).to.equal(4); + const delays = delayStub!.args.map((args) => args[0]); expect(delays).to.deep.equal([0, 2000, 4000, 4000]); }); }); @@ -986,8 +980,8 @@ describe('HttpClient', () => { expect(resp.headers['content-type']).to.equal('application/json'); expect(resp.data).to.deep.equal({}); expect(resp.isJson()).to.be.true; - expect(delayStub.callCount).to.equal(4); - const delays = delayStub.args.map((args) => args[0]); + expect(delayStub!.callCount).to.equal(4); + const delays = delayStub!.args.map((args) => args[0]); expect(delays).to.deep.equal([0, 0, 0, 0]); }); }); @@ -1019,8 +1013,8 @@ describe('HttpClient', () => { expect(resp.headers['content-type']).to.equal('application/json'); expect(resp.data).to.deep.equal(respData); expect(resp.isJson()).to.be.true; - expect(delayStub.callCount).to.equal(1); - expect(delayStub.args[0][0]).to.equal(30 * 1000); + expect(delayStub!.callCount).to.equal(1); + expect(delayStub!.args[0][0]).to.equal(30 * 1000); }); }); @@ -1055,8 +1049,8 @@ describe('HttpClient', () => { expect(resp.headers['content-type']).to.equal('application/json'); expect(resp.data).to.deep.equal(respData); expect(resp.isJson()).to.be.true; - expect(delayStub.callCount).to.equal(1); - expect(delayStub.args[0][0]).to.equal(30 * 1000); + expect(delayStub!.callCount).to.equal(1); + expect(delayStub!.args[0][0]).to.equal(30 * 1000); }); }); @@ -1089,8 +1083,8 @@ describe('HttpClient', () => { expect(resp.headers['content-type']).to.equal('application/json'); expect(resp.data).to.deep.equal(respData); expect(resp.isJson()).to.be.true; - expect(delayStub.callCount).to.equal(1); - expect(delayStub.args[0][0]).to.equal(0); + expect(delayStub!.callCount).to.equal(1); + expect(delayStub!.args[0][0]).to.equal(0); }); }); @@ -1121,8 +1115,8 @@ describe('HttpClient', () => { expect(resp.headers['content-type']).to.equal('application/json'); expect(resp.data).to.deep.equal(respData); expect(resp.isJson()).to.be.true; - expect(delayStub.callCount).to.equal(1); - expect(delayStub.args[0][0]).to.equal(0); + expect(delayStub!.callCount).to.equal(1); + expect(delayStub!.args[0][0]).to.equal(0); }); }); @@ -1223,7 +1217,7 @@ describe('AuthorizedHttpClient', () => { }); describe('HTTP Agent', () => { - let transportSpy: sinon.SinonSpy = null; + let transportSpy: sinon.SinonSpy | null = null; let mockAppWithAgent: FirebaseApp; let agentForApp: Agent; @@ -1237,7 +1231,7 @@ describe('AuthorizedHttpClient', () => { }); afterEach(() => { - transportSpy.restore(); + transportSpy!.restore(); transportSpy = null; return mockAppWithAgent.delete(); }); @@ -1258,8 +1252,8 @@ describe('AuthorizedHttpClient', () => { httpAgent, }).then((resp) => { expect(resp.status).to.equal(200); - expect(transportSpy.callCount).to.equal(1); - const options = transportSpy.args[0][0]; + expect(transportSpy!.callCount).to.equal(1); + const options = transportSpy!.args[0][0]; expect(options.agent).to.equal(httpAgent); }); }); @@ -1278,8 +1272,8 @@ describe('AuthorizedHttpClient', () => { url: mockUrl, }).then((resp) => { expect(resp.status).to.equal(200); - expect(transportSpy.callCount).to.equal(1); - const options = transportSpy.args[0][0]; + expect(transportSpy!.callCount).to.equal(1); + const options = transportSpy!.args[0][0]; expect(options.agent).to.equal(agentForApp); }); }); diff --git a/test/unit/utils/index.spec.ts b/test/unit/utils/index.spec.ts index 1bdfc27069..7eb333774d 100755 --- a/test/unit/utils/index.spec.ts +++ b/test/unit/utils/index.spec.ts @@ -67,8 +67,8 @@ describe('toWebSafeBase64()', () => { }); describe('getProjectId()', () => { - let googleCloudProject: string; - let gcloudProject: string; + let googleCloudProject: string | undefined; + let gcloudProject: string | undefined; before(() => { googleCloudProject = process.env.GOOGLE_CLOUD_PROJECT; diff --git a/tsconfig.json b/tsconfig.json index 090ece94fd..e13292b408 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,7 @@ "noImplicitThis": true, "alwaysStrict": true, "strictBindCallApply": true, - //"strictNullChecks": true, + "strictNullChecks": true, "strictFunctionTypes": true, //"strictPropertyInitialization": true, "lib": ["es2015"], From ad16d691644c5add5e4bf9975a3ae04e999bb49c Mon Sep 17 00:00:00 2001 From: Rich Gowman Date: Tue, 22 Oct 2019 10:27:39 -0400 Subject: [PATCH 2/6] Add @firebase/auth-types to dev dependencies And also update @firebase/auth to most recent to match. --- package-lock.json | 147 ++++++++++++++-------------------------------- package.json | 3 +- 2 files changed, 46 insertions(+), 104 deletions(-) diff --git a/package-lock.json b/package-lock.json index 87f74326c6..4ef8955fd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -187,18 +187,18 @@ "integrity": "sha512-8erNMHc0V26gA6Nj4W9laVrQrXHsj9K2TEM7eL2IQogGSHLL4vet3UNekYfcGQ2cjfvwUjMzd+BNS/8S7GnfiA==" }, "@firebase/auth": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.11.3.tgz", - "integrity": "sha512-MFjnQGzZM89pqQItHNf8QPbCj0PjaFomd3JGUpnyxVwMyuovsRxVmBofi8mq/eiwzy7qwvRHFB8ngevWkkdAMA==", + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.12.2.tgz", + "integrity": "sha512-oC7eSaEbpNnLuh3WvERANbD5V5L2OP/IE/8qfCb0niik4v4NzHcZckf8/U2KBD3/dUfWtBvw3mWNazmv8NZQQA==", "dev": true, "requires": { - "@firebase/auth-types": "0.7.0" + "@firebase/auth-types": "0.8.2" } }, "@firebase/auth-types": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.7.0.tgz", - "integrity": "sha512-QEG9azYwssGWcb4NaKFHe3Piez0SG46nRlu76HM4/ob0sjjNpNTY1Z5C3IoeJYknp2kMzuQi0TTW8tjEgkUAUA==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.8.2.tgz", + "integrity": "sha512-qcP7wZ76CIb7IN+K544GomA42cCS36KZmQ3n9Ou1JsYplEaMo52x4UuQTZFqlRoMaUWi61oQ9jiuE5tOAMJwDA==", "dev": true }, "@firebase/database": { @@ -333,8 +333,7 @@ "@google-cloud/promisify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.2.tgz", - "integrity": "sha512-7WfV4R/3YV5T30WRZW0lqmvZy9hE2/p9MvpI34WuKa2Wz62mLu5XplGTFEMK6uTbJCLWUxTcZ4J4IyClKucE5g==", - "optional": true + "integrity": "sha512-7WfV4R/3YV5T30WRZW0lqmvZy9hE2/p9MvpI34WuKa2Wz62mLu5XplGTFEMK6uTbJCLWUxTcZ4J4IyClKucE5g==" }, "@google-cloud/storage": { "version": "3.0.2", @@ -435,32 +434,27 @@ "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", - "optional": true + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" }, "@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "optional": true + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" }, "@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "optional": true + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" }, "@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", - "optional": true + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" }, "@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", - "optional": true, "requires": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" @@ -469,32 +463,27 @@ "@protobufjs/float": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", - "optional": true + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" }, "@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", - "optional": true + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" }, "@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", - "optional": true + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" }, "@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", - "optional": true + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" }, "@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", - "optional": true + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, "@sinonjs/commons": { "version": "1.4.0", @@ -604,8 +593,7 @@ "@types/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", - "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==", - "optional": true + "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==" }, "@types/minimatch": { "version": "3.0.3", @@ -706,7 +694,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "optional": true, "requires": { "event-target-shim": "^5.0.0" } @@ -737,7 +724,6 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", - "optional": true, "requires": { "es6-promisify": "^5.0.0" } @@ -1767,8 +1753,7 @@ "bignumber.js": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", - "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==", - "optional": true + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==" }, "binary-extensions": { "version": "1.13.1", @@ -2859,14 +2844,12 @@ "es6-promise": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz", - "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==", - "optional": true + "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==" }, "es6-promisify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "optional": true, "requires": { "es6-promise": "^4.0.3" } @@ -2948,8 +2931,7 @@ "event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "optional": true + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" }, "execa": { "version": "1.0.0", @@ -3148,8 +3130,7 @@ "fast-text-encoding": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz", - "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==", - "optional": true + "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==" }, "faye-websocket": { "version": "0.11.3", @@ -3476,8 +3457,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -3498,14 +3478,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3520,20 +3498,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -3650,8 +3625,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -3663,7 +3637,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3678,7 +3651,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3686,14 +3658,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3712,7 +3682,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3800,8 +3769,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3813,7 +3781,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3899,8 +3866,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -3936,7 +3902,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3956,7 +3921,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4000,14 +3964,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -4027,7 +3989,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.0.1.tgz", "integrity": "sha512-c1NXovTxkgRJTIgB2FrFmOFg4YIV6N/bAa4f/FZ4jIw13Ql9ya/82x69CswvotJhbV3DiGnlTZwoq2NVXk2Irg==", - "optional": true, "requires": { "abort-controller": "^3.0.0", "extend": "^3.0.2", @@ -4039,7 +4000,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-2.0.0.tgz", "integrity": "sha512-BN6KUUWo6WLkDRst+Y7bqpXq1PYMrKUecNLRdZESp7oYtMjWcZdAM0UYvcip8wb0GXNO/j8Z8HTccK4iYtMvyQ==", - "optional": true, "requires": { "gaxios": "^2.0.0", "json-bigint": "^0.3.0" @@ -4277,7 +4237,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-4.0.0.tgz", "integrity": "sha512-yyxl74G16GjKLevccXK3/DYEXphtI9Q2Qw3Eh7y8scjBKNL0IbAZF1mi999gC0tkfG6J23sCbd9tMEbNYeWfJQ==", - "optional": true, "requires": { "arrify": "^2.0.0", "base64-js": "^1.3.0", @@ -4293,14 +4252,12 @@ "arrify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "optional": true + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" }, "semver": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", - "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==", - "optional": true + "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==" } } }, @@ -4335,7 +4292,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.0.tgz", "integrity": "sha512-n8eGSKzWOb9/EmSBIh81sPvsQM939QlpHMXahTZDzuRIpCu09x3Oaqz+mXGjL4TeCvSbcnOC0YZRvjkJ9s9lnA==", - "optional": true, "requires": { "node-forge": "^0.8.0" }, @@ -4343,8 +4299,7 @@ "node-forge": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.3.tgz", - "integrity": "sha512-5lv9UKmvTBog+m4AWL8XpZnr3WbNKxYL2M77i903ylY/huJIooSTDHyUWQ/OppFuKQpAGMk6qNtDymSJNRIEIg==", - "optional": true + "integrity": "sha512-5lv9UKmvTBog+m4AWL8XpZnr3WbNKxYL2M77i903ylY/huJIooSTDHyUWQ/OppFuKQpAGMk6qNtDymSJNRIEIg==" } } }, @@ -4363,7 +4318,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-3.0.0.tgz", "integrity": "sha512-IY9HVi78D4ykVHn+ThI7rlcpdFtKyo9e9YLim9S9T3rp6fEnfeTexcrqzSpExVshPofsdauLKIa8dEnzX7ZLfQ==", - "optional": true, "requires": { "gaxios": "^2.0.0", "google-p12-pem": "^2.0.0", @@ -4375,8 +4329,7 @@ "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "optional": true + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" } } }, @@ -4941,7 +4894,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", - "optional": true, "requires": { "agent-base": "^4.1.0", "debug": "^3.1.0" @@ -5547,7 +5499,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=", - "optional": true, "requires": { "bignumber.js": "^7.0.0" } @@ -6007,14 +5958,12 @@ "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "optional": true + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "optional": true, "requires": { "yallist": "^3.0.2" } @@ -6180,8 +6129,7 @@ "mime": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.3.tgz", - "integrity": "sha512-QgrPRJfE+riq5TPZMcHZOtm8c6K/yYrMbKIoRfapfiGLxS8OTeIfRhUGW5LU7MlRa52KOAGCfUNruqLrIBvWZw==", - "optional": true + "integrity": "sha512-QgrPRJfE+riq5TPZMcHZOtm8c6K/yYrMbKIoRfapfiGLxS8OTeIfRhUGW5LU7MlRa52KOAGCfUNruqLrIBvWZw==" }, "mime-db": { "version": "1.37.0", @@ -6454,8 +6402,7 @@ "node-fetch": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", - "optional": true + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" }, "node-forge": { "version": "0.7.4", @@ -7155,7 +7102,6 @@ "version": "6.8.8", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", - "optional": true, "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -7175,8 +7121,7 @@ "@types/node": { "version": "10.14.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.7.tgz", - "integrity": "sha512-on4MmIDgHXiuJDELPk1NFaKVUxxCFr37tm8E9yN6rAiF5Pzp/9bBfBHkoexqRiY+hk/Z04EJU9kKEb59YqJ82A==", - "optional": true + "integrity": "sha512-on4MmIDgHXiuJDELPk1NFaKVUxxCFr37tm8E9yN6rAiF5Pzp/9bBfBHkoexqRiY+hk/Z04EJU9kKEb59YqJ82A==" } } }, @@ -8180,7 +8125,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", - "optional": true, "requires": { "stubs": "^3.0.0" } @@ -8261,8 +8205,7 @@ "stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", - "optional": true + "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=" }, "supports-color": { "version": "2.0.0", @@ -9336,8 +9279,7 @@ "xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "optional": true + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" }, "xml-name-validator": { "version": "3.0.0", @@ -9371,8 +9313,7 @@ "yallist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "optional": true + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" }, "yargs": { "version": "13.2.4", diff --git a/package.json b/package.json index 9a9b61107e..510932df04 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,8 @@ }, "devDependencies": { "@firebase/app": "^0.4.10", - "@firebase/auth": "^0.11.3", + "@firebase/auth": "^0.12.2", + "@firebase/auth-types": "^0.8.2", "@types/bcrypt": "^2.0.0", "@types/chai": "^3.4.34", "@types/chai-as-promised": "0.0.29", From 63a1eb6a6cff4448f90683a12c0850829155f815 Mon Sep 17 00:00:00 2001 From: Rich Gowman Date: Mon, 25 Nov 2019 12:26:43 -0500 Subject: [PATCH 3/6] review feedback --- src/auth/action-code-settings-builder.ts | 2 +- src/auth/auth-api-request.ts | 10 ++++++++-- src/auth/auth.ts | 1 - src/auth/credential.ts | 10 +++++----- src/auth/tenant.ts | 8 ++++---- src/auth/token-generator.ts | 8 +++----- src/firestore/firestore.ts | 5 +---- src/messaging/messaging-errors.ts | 2 +- test/integration/auth.spec.ts | 8 +++++++- test/unit/auth/auth-api-request.spec.ts | 16 ++++------------ test/unit/auth/auth.spec.ts | 14 ++++++++------ 11 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/auth/action-code-settings-builder.ts b/src/auth/action-code-settings-builder.ts index 3c2ca8c63e..0961722850 100644 --- a/src/auth/action-code-settings-builder.ts +++ b/src/auth/action-code-settings-builder.ts @@ -63,7 +63,7 @@ export class ActionCodeSettingsBuilder { * object used to initiliaze this server request builder. * @constructor */ - constructor(actionCodeSettings?: ActionCodeSettings) { + constructor(actionCodeSettings: ActionCodeSettings) { if (!validator.isNonNullObject(actionCodeSettings)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, diff --git a/src/auth/auth-api-request.ts b/src/auth/auth-api-request.ts index e5113e37ae..c40a408fc9 100755 --- a/src/auth/auth-api-request.ts +++ b/src/auth/auth-api-request.ts @@ -693,7 +693,7 @@ export abstract class AbstractAuthRequestHandler { * @return {string|null} The error code if present; null otherwise. */ private static getErrorCode(response: any): string | null { - return (validator.isNonNullObject(response) && (response as any).error && (response as any).error.message) || null; + return (validator.isNonNullObject(response) && response.error && response.error.message) || null; } /** @@ -1068,9 +1068,15 @@ export abstract class AbstractAuthRequestHandler { let request = {requestType, email, returnOobLink: true}; // ActionCodeSettings required for email link sign-in to determine the url where the sign-in will // be completed. + if (typeof actionCodeSettings === 'undefined' && requestType === 'EMAIL_SIGNIN') { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + "`actionCodeSettings` is required when `requestType` === 'EMAIL_SIGNIN'", + ); + } if (typeof actionCodeSettings !== 'undefined' || requestType === 'EMAIL_SIGNIN') { try { - const builder = new ActionCodeSettingsBuilder(actionCodeSettings); + const builder = new ActionCodeSettingsBuilder(actionCodeSettings!); request = deepExtend(request, builder.buildRequest()); } catch (e) { return Promise.reject(e); diff --git a/src/auth/auth.ts b/src/auth/auth.ts index 92bada0ac4..86177c2cdd 100755 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -569,7 +569,6 @@ export class BaseAuth { return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); } - /** * Verifies the decoded Firebase issued JWT is not revoked. Returns a promise that resolves * with the decoded claims on success. Rejects the promise with revocation error if revoked. diff --git a/src/auth/credential.ts b/src/auth/credential.ts index 635ef2e8ee..521f71bedd 100644 --- a/src/auth/credential.ts +++ b/src/auth/credential.ts @@ -224,7 +224,7 @@ function getDetailFromResponse(response: HttpResponse): string { } return detail; } - return response.text || 'Missing Error Payload'; + return response.text || 'Missing error payload'; } /** @@ -306,7 +306,7 @@ export interface FirebaseCredential extends Credential { * @param {Credential} credential A Credential instance. * @return {Certificate} A Certificate instance or null. */ -export function tryGetCertificate(credential?: Credential): Certificate | null { +export function tryGetCertificate(credential: Credential | null | undefined): Certificate | null { if (credential && isFirebaseCredential(credential)) { return credential.getCertificate(); } @@ -329,15 +329,15 @@ export class RefreshTokenCredential implements Credential { constructor(refreshTokenPathOrObject: string | object, httpAgent?: Agent) { if (typeof refreshTokenPathOrObject === 'string') { - const refreshTokenOrNull = RefreshToken.fromPath(refreshTokenPathOrObject); - if (!refreshTokenOrNull) { + const refreshToken = RefreshToken.fromPath(refreshTokenPathOrObject); + if (!refreshToken) { throw new FirebaseAuthError( AuthClientErrorCode.NOT_FOUND, 'The file refered to by the refreshTokenPathOrObject parameter (' + refreshTokenPathOrObject + ') was not found.', ); } - this.refreshToken = refreshTokenOrNull; + this.refreshToken = refreshToken; } else { this.refreshToken = new RefreshToken(refreshTokenPathOrObject); } diff --git a/src/auth/tenant.ts b/src/auth/tenant.ts index 3875b253e2..8fda7d3c7f 100755 --- a/src/auth/tenant.ts +++ b/src/auth/tenant.ts @@ -117,17 +117,17 @@ export class Tenant { } } // Validate displayName type if provided. - if (typeof (request as any).displayName !== 'undefined' && - !validator.isNonEmptyString((request as any).displayName)) { + if (typeof request.displayName !== 'undefined' && + !validator.isNonEmptyString(request.displayName)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, `"${label}.displayName" must be a valid non-empty string.`, ); } // Validate emailSignInConfig type if provided. - if (typeof (request as any).emailSignInConfig !== 'undefined') { + if (typeof request.emailSignInConfig !== 'undefined') { // This will throw an error if invalid. - EmailSignInConfig.buildServerRequest((request as any).emailSignInConfig); + EmailSignInConfig.buildServerRequest(request.emailSignInConfig); } } diff --git a/src/auth/token-generator.ts b/src/auth/token-generator.ts index c8677b291e..21f1047b2c 100644 --- a/src/auth/token-generator.ts +++ b/src/auth/token-generator.ts @@ -169,13 +169,11 @@ export class IAMSigner implements CryptoSigner { }).catch((err) => { if (err instanceof HttpError) { const error = err.response.data; - let errorCode: string; - let errorMsg: string | undefined; - if (validator.isNonNullObject(error) && (error as any).error) { - errorCode = (error as any).error.status; + if (validator.isNonNullObject(error) && error.error) { + const errorCode = error.error.status; const description = 'Please refer to https://firebase.google.com/docs/auth/admin/create-custom-tokens ' + 'for more details on how to use and troubleshoot this feature.'; - errorMsg = `${(error as any).error.message}; ${description}`; + const errorMsg = `${error.error.message}; ${description}`; throw FirebaseAuthError.fromServerError(errorCode, errorMsg, error); } diff --git a/src/firestore/firestore.ts b/src/firestore/firestore.ts index b683781638..c7afa87b7c 100644 --- a/src/firestore/firestore.ts +++ b/src/firestore/firestore.ts @@ -72,10 +72,7 @@ export function getFirestoreOptions(app: FirebaseApp): Settings { } const projectId: string | null = utils.getProjectId(app); - let cert: Certificate | null = null; - if (app.options.credential) { - cert = tryGetCertificate(app.options.credential); - } + const cert: Certificate | null = tryGetCertificate(app.options.credential); const { version: firebaseVersion } = require('../../package.json'); if (cert != null) { // cert is available when the SDK has been initialized with a service account JSON file, diff --git a/src/messaging/messaging-errors.ts b/src/messaging/messaging-errors.ts index 05d257855f..1fd1809aec 100644 --- a/src/messaging/messaging-errors.ts +++ b/src/messaging/messaging-errors.ts @@ -67,7 +67,7 @@ export function createFirebaseError(err: HttpError): FirebaseMessagingError { */ export function getErrorCode(response: any): string | null { if (validator.isNonNullObject(response) && 'error' in response) { - const error = (response as any).error; + const error = response.error; if (validator.isString(error)) { return error; } diff --git a/test/integration/auth.spec.ts b/test/integration/auth.spec.ts index 1fc6b2ead4..e9679e0eca 100755 --- a/test/integration/auth.spec.ts +++ b/test/integration/auth.spec.ts @@ -1409,16 +1409,22 @@ describe('admin.auth', () => { } as any, computePasswordHash: (userImportTest: UserImportTest): Buffer => { const currentRawPassword = userImportTest.rawPassword; - expect(userImportTest.rawSalt); + + expect(userImportTest.rawSalt).to.exist; const currentRawSalt = userImportTest.rawSalt!; + expect(userImportTest.importOptions.hash.memoryCost).to.exist; const N = userImportTest.importOptions.hash.memoryCost!; + expect(userImportTest.importOptions.hash.blockSize).to.exist; const r = userImportTest.importOptions.hash.blockSize!; + expect(userImportTest.importOptions.hash.parallelization).to.exist; const p = userImportTest.importOptions.hash.parallelization!; + expect(userImportTest.importOptions.hash.derivedKeyLength).to.exist; const dkLen = userImportTest.importOptions.hash.derivedKeyLength!; + return Buffer.from(scrypt.hashSync( currentRawPassword, {N, r, p}, dkLen, Buffer.from(currentRawSalt))); }, diff --git a/test/unit/auth/auth-api-request.spec.ts b/test/unit/auth/auth-api-request.spec.ts index 7d6911056a..cc9386065a 100755 --- a/test/unit/auth/auth-api-request.spec.ts +++ b/test/unit/auth/auth-api-request.spec.ts @@ -2400,19 +2400,11 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { it('should be rejected given requestType:EMAIL_SIGNIN and no ActionCodeSettings', () => { const invalidRequestType = 'EMAIL_SIGNIN'; - const expectedError = new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, - `"ActionCodeSettings" must be a non-null object.`, - ); - const requestHandler = handler.init(mockApp); - return requestHandler.getEmailActionLink(invalidRequestType, email) - .then((resp) => { - throw new Error('Unexpected success'); - }, (error) => { - // Invalid argument error should be thrown. - expect(error).to.deep.equal(expectedError); - }); + + expect(() => { + requestHandler.getEmailActionLink(invalidRequestType, email); + }).to.throw(FirebaseAuthError).with.property('code', 'auth/argument-error'); }); it('should be rejected given an invalid email', () => { diff --git a/test/unit/auth/auth.spec.ts b/test/unit/auth/auth.spec.ts index e44ab9229b..26052cfa9e 100755 --- a/test/unit/auth/auth.spec.ts +++ b/test/unit/auth/auth.spec.ts @@ -411,10 +411,11 @@ AUTH_CONFIGS.forEach((testConfig) => { const tenantId = testConfig.supportsTenantManagement ? undefined : TENANT_ID; const expectedUserRecord = getValidUserRecord(getValidGetAccountInfoResponse(tenantId)); // Set auth_time of token to expected user's tokensValidAfterTime. - if (!expectedUserRecord.tokensValidAfterTime) { - throw new Error("getValidUserRecord didn't properly set tokensValidAfterTime."); - } - const validSince = new Date(expectedUserRecord.tokensValidAfterTime); + expect( + expectedUserRecord.tokensValidAfterTime, + "getValidUserRecord didn't properly set tokensValueAfterTime", + ).to.exist; + const validSince = new Date(expectedUserRecord.tokensValidAfterTime!); // Set expected uid to expected user's. const uid = expectedUserRecord.uid; // Set expected decoded ID token with expected UID and auth time. @@ -2169,8 +2170,9 @@ AUTH_CONFIGS.forEach((testConfig) => { if (emailActionFlow.requiresSettings) { it('should reject when called without actionCodeSettings', () => { - return (auth as any)[emailActionFlow.api](email, undefined) - .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); + expect(() => { + (auth as any)[emailActionFlow.api](email, undefined); + }).to.throw(FirebaseAuthError).with.property('code', 'auth/argument-error'); }); } else { it('should resolve when called without actionCodeSettings with a generated link on success', () => { From f3dcaa0f3c5591d5ade7ca094500991069a12bf0 Mon Sep 17 00:00:00 2001 From: Rich Gowman Date: Mon, 25 Nov 2019 14:27:39 -0500 Subject: [PATCH 4/6] More fixes resulting from merge master --- src/messaging/messaging-types.ts | 7 ++++++- test/unit/messaging/messaging.spec.ts | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/messaging/messaging-types.ts b/src/messaging/messaging-types.ts index 5cdf21ab1c..50bb3a4b37 100644 --- a/src/messaging/messaging-types.ts +++ b/src/messaging/messaging-types.ts @@ -767,7 +767,7 @@ function validateAndroidNotification(notification: AndroidNotification | undefin * * @param {LightSettings} lightSettings An object to be validated. */ -function validateLightSettings(lightSettings: LightSettings) { +function validateLightSettings(lightSettings?: LightSettings) { if (typeof lightSettings === 'undefined') { return; } else if (!validator.isNonNullObject(lightSettings)) { @@ -799,6 +799,11 @@ function validateLightSettings(lightSettings: LightSettings) { } const colorString = lightSettings.color.length === 7 ? lightSettings.color + 'FF' : lightSettings.color; const rgb = /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/i.exec(colorString); + if (!rgb || rgb.length < 4) { + throw new FirebaseMessagingError( + MessagingClientErrorCode.INTERNAL_ERROR, + 'regex to extract rgba values from ' + colorString + ' failed.'); + } const color = { red: parseInt(rgb[1], 16) / 255.0, green: parseInt(rgb[2], 16) / 255.0, diff --git a/test/unit/messaging/messaging.spec.ts b/test/unit/messaging/messaging.spec.ts index 740e5ed1cc..9be5612b22 100644 --- a/test/unit/messaging/messaging.spec.ts +++ b/test/unit/messaging/messaging.spec.ts @@ -2417,7 +2417,8 @@ describe('Messaging', () => { }); const invalidVibrateTimings = [[null, 500], [-100]]; - invalidVibrateTimings.forEach((vibrateTimingsMillis) => { + invalidVibrateTimings.forEach((vibrateTimingsMillisMaybeNull) => { + const vibrateTimingsMillis = vibrateTimingsMillisMaybeNull as number[]; it(`should throw given an null or negative vibrateTimingsMillis: ${ vibrateTimingsMillis }`, () => { const message: Message = { condition: 'topic-name', From e0aea12482acc6978dad492f66fdc7a7d3f85391 Mon Sep 17 00:00:00 2001 From: Rich Gowman Date: Thu, 5 Dec 2019 16:22:50 -0500 Subject: [PATCH 5/6] review feedback pt2 --- src/auth/auth-api-request.ts | 10 +++++++--- src/utils/api-request.ts | 7 +------ test/unit/auth/auth-api-request.spec.ts | 5 ++--- test/unit/auth/auth.spec.ts | 7 +++---- test/unit/auth/tenant-manager.spec.ts | 2 +- test/unit/messaging/messaging.spec.ts | 22 +++++++++++----------- 6 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/auth/auth-api-request.ts b/src/auth/auth-api-request.ts index b0a21e158c..49836721e0 100755 --- a/src/auth/auth-api-request.ts +++ b/src/auth/auth-api-request.ts @@ -708,6 +708,8 @@ export abstract class AbstractAuthRequestHandler { ); } + // TODO(rsgowman): Trace utils.getProjectId() throughout and figure out where a null return + // value will cause troubles. (Such as AuthResourceUrlBuilder::getUrl()). this.projectId = utils.getProjectId(app); this.httpClient = new AuthorizedHttpClient(app); } @@ -1076,9 +1078,11 @@ export abstract class AbstractAuthRequestHandler { // ActionCodeSettings required for email link sign-in to determine the url where the sign-in will // be completed. if (typeof actionCodeSettings === 'undefined' && requestType === 'EMAIL_SIGNIN') { - throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, - "`actionCodeSettings` is required when `requestType` === 'EMAIL_SIGNIN'", + return Promise.reject( + new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + "`actionCodeSettings` is required when `requestType` === 'EMAIL_SIGNIN'", + ), ); } if (typeof actionCodeSettings !== 'undefined' || requestType === 'EMAIL_SIGNIN') { diff --git a/src/utils/api-request.ts b/src/utils/api-request.ts index 462443467e..b0575d6288 100644 --- a/src/utils/api-request.ts +++ b/src/utils/api-request.ts @@ -470,12 +470,7 @@ class AsyncHttpCall { try { this.config = new HttpRequestConfigImpl(config); this.options = this.config.buildRequestOptions(); - if (!this.options.headers) { - throw new FirebaseAppError( - AppErrorCodes.INTERNAL_ERROR, - 'Expected headers to be present in built request options object'); - } - this.entity = this.config.buildEntity(this.options.headers); + this.entity = this.config.buildEntity(this.options.headers!); this.promise = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; diff --git a/test/unit/auth/auth-api-request.spec.ts b/test/unit/auth/auth-api-request.spec.ts index cc9386065a..deacf9fc03 100755 --- a/test/unit/auth/auth-api-request.spec.ts +++ b/test/unit/auth/auth-api-request.spec.ts @@ -2402,9 +2402,8 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { const invalidRequestType = 'EMAIL_SIGNIN'; const requestHandler = handler.init(mockApp); - expect(() => { - requestHandler.getEmailActionLink(invalidRequestType, email); - }).to.throw(FirebaseAuthError).with.property('code', 'auth/argument-error'); + return requestHandler.getEmailActionLink(invalidRequestType, email) + .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); }); it('should be rejected given an invalid email', () => { diff --git a/test/unit/auth/auth.spec.ts b/test/unit/auth/auth.spec.ts index 26052cfa9e..c777fe05e0 100755 --- a/test/unit/auth/auth.spec.ts +++ b/test/unit/auth/auth.spec.ts @@ -1243,7 +1243,7 @@ AUTH_CONFIGS.forEach((testConfig) => { }); it('should be rejected given invalid properties', () => { - return auth.createUser(null as unknown as CreateRequest) + return auth.createUser(null as any as CreateRequest) .then(() => { throw new Error('Unexpected success'); }) @@ -2170,9 +2170,8 @@ AUTH_CONFIGS.forEach((testConfig) => { if (emailActionFlow.requiresSettings) { it('should reject when called without actionCodeSettings', () => { - expect(() => { - (auth as any)[emailActionFlow.api](email, undefined); - }).to.throw(FirebaseAuthError).with.property('code', 'auth/argument-error'); + return (auth as any)[emailActionFlow.api](email, undefined) + .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); }); } else { it('should resolve when called without actionCodeSettings with a generated link on success', () => { diff --git a/test/unit/auth/tenant-manager.spec.ts b/test/unit/auth/tenant-manager.spec.ts index 3149e92cb5..4e91bf2ced 100644 --- a/test/unit/auth/tenant-manager.spec.ts +++ b/test/unit/auth/tenant-manager.spec.ts @@ -401,7 +401,7 @@ describe('TenantManager', () => { }); it('should be rejected given invalid TenantOptions', () => { - return tenantManager.createTenant(null as unknown as TenantOptions) + return tenantManager.createTenant(null as any as TenantOptions) .then(() => { throw new Error('Unexpected success'); }) diff --git a/test/unit/messaging/messaging.spec.ts b/test/unit/messaging/messaging.spec.ts index 9be5612b22..71bc471166 100644 --- a/test/unit/messaging/messaging.spec.ts +++ b/test/unit/messaging/messaging.spec.ts @@ -398,10 +398,10 @@ describe('Messaging', () => { describe('send()', () => { it('should throw given no message', () => { expect(() => { - messaging.send(undefined as unknown as Message); + messaging.send(undefined as any as Message); }).to.throw('Message must be a non-null object'); expect(() => { - messaging.send(null as unknown as Message); + messaging.send(null as any as Message); }).to.throw('Message must be a non-null object'); }); @@ -577,10 +577,10 @@ describe('Messaging', () => { it('should throw given no messages', () => { expect(() => { - messaging.sendAll(undefined as unknown as Message[]); + messaging.sendAll(undefined as any as Message[]); }).to.throw('messages must be a non-empty array'); expect(() => { - messaging.sendAll(null as unknown as Message[]); + messaging.sendAll(null as any as Message[]); }).to.throw('messages must be a non-empty array'); expect(() => { messaging.sendAll([]); @@ -837,7 +837,7 @@ describe('Messaging', () => { it('should throw given no messages', () => { expect(() => { - messaging.sendMulticast(undefined as unknown as MulticastMessage); + messaging.sendMulticast(undefined as any as MulticastMessage); }).to.throw('MulticastMessage must be a non-null object'); expect(() => { messaging.sendMulticast({} as any); @@ -1138,7 +1138,7 @@ describe('Messaging', () => { it('should throw given no registration token(s) argument', () => { expect(() => { - messaging.sendToDevice(undefined as unknown as string, mocks.messaging.payloadDataOnly); + messaging.sendToDevice(undefined as any as string, mocks.messaging.payloadDataOnly); }).to.throw(invalidArgumentError); }); @@ -1460,7 +1460,7 @@ describe('Messaging', () => { it('should throw given no notification key argument', () => { expect(() => { - messaging.sendToDeviceGroup(undefined as unknown as string, mocks.messaging.payloadDataOnly); + messaging.sendToDeviceGroup(undefined as any as string, mocks.messaging.payloadDataOnly); }).to.throw(invalidArgumentError); }); @@ -1708,7 +1708,7 @@ describe('Messaging', () => { it('should throw given no topic argument', () => { expect(() => { - messaging.sendToTopic(undefined as unknown as string, mocks.messaging.payload); + messaging.sendToTopic(undefined as any as string, mocks.messaging.payload); }).to.throw(invalidArgumentError); }); @@ -1935,7 +1935,7 @@ describe('Messaging', () => { it('should throw given no condition argument', () => { expect(() => { - messaging.sendToCondition(undefined as unknown as string, mocks.messaging.payloadDataOnly); + messaging.sendToCondition(undefined as any as string, mocks.messaging.payloadDataOnly); }).to.throw(invalidArgumentError); }); @@ -3610,7 +3610,7 @@ describe('Messaging', () => { it('should throw given no registration token(s) argument', () => { expect(() => { - messagingService[methodName](undefined as unknown as string, mocks.messaging.topic); + messagingService[methodName](undefined as any as string, mocks.messaging.topic); }).to.throw(invalidRegistrationTokensArgumentError); }); @@ -3665,7 +3665,7 @@ describe('Messaging', () => { it('should throw given no topic argument', () => { expect(() => { - messagingService[methodName](mocks.messaging.registrationToken, undefined as unknown as string); + messagingService[methodName](mocks.messaging.registrationToken, undefined as any as string); }).to.throw(invalidTopicArgumentError); }); From af48b2c72efabaef65cb2eaaab3ad2e6acf520a7 Mon Sep 17 00:00:00 2001 From: Rich Gowman Date: Fri, 6 Dec 2019 10:57:36 -0500 Subject: [PATCH 6/6] s/as any as/as any/ in a few of the test files --- test/unit/auth/auth.spec.ts | 4 ++-- test/unit/auth/tenant-manager.spec.ts | 2 +- test/unit/messaging/messaging.spec.ts | 22 +++++++++++----------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/test/unit/auth/auth.spec.ts b/test/unit/auth/auth.spec.ts index c777fe05e0..8ed8c11758 100755 --- a/test/unit/auth/auth.spec.ts +++ b/test/unit/auth/auth.spec.ts @@ -26,7 +26,7 @@ import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; import {Auth, TenantAwareAuth, BaseAuth, DecodedIdToken} from '../../../src/auth/auth'; -import {UserRecord, CreateRequest, UpdateRequest} from '../../../src/auth/user-record'; +import {UserRecord, UpdateRequest} from '../../../src/auth/user-record'; import {FirebaseApp} from '../../../src/firebase-app'; import {FirebaseTokenGenerator} from '../../../src/auth/token-generator'; import { @@ -1243,7 +1243,7 @@ AUTH_CONFIGS.forEach((testConfig) => { }); it('should be rejected given invalid properties', () => { - return auth.createUser(null as any as CreateRequest) + return auth.createUser(null as any) .then(() => { throw new Error('Unexpected success'); }) diff --git a/test/unit/auth/tenant-manager.spec.ts b/test/unit/auth/tenant-manager.spec.ts index 4e91bf2ced..bf1c937e63 100644 --- a/test/unit/auth/tenant-manager.spec.ts +++ b/test/unit/auth/tenant-manager.spec.ts @@ -401,7 +401,7 @@ describe('TenantManager', () => { }); it('should be rejected given invalid TenantOptions', () => { - return tenantManager.createTenant(null as any as TenantOptions) + return tenantManager.createTenant(null as any) .then(() => { throw new Error('Unexpected success'); }) diff --git a/test/unit/messaging/messaging.spec.ts b/test/unit/messaging/messaging.spec.ts index 71bc471166..dc861b02b6 100644 --- a/test/unit/messaging/messaging.spec.ts +++ b/test/unit/messaging/messaging.spec.ts @@ -398,10 +398,10 @@ describe('Messaging', () => { describe('send()', () => { it('should throw given no message', () => { expect(() => { - messaging.send(undefined as any as Message); + messaging.send(undefined as any); }).to.throw('Message must be a non-null object'); expect(() => { - messaging.send(null as any as Message); + messaging.send(null as any); }).to.throw('Message must be a non-null object'); }); @@ -577,10 +577,10 @@ describe('Messaging', () => { it('should throw given no messages', () => { expect(() => { - messaging.sendAll(undefined as any as Message[]); + messaging.sendAll(undefined as any); }).to.throw('messages must be a non-empty array'); expect(() => { - messaging.sendAll(null as any as Message[]); + messaging.sendAll(null as any); }).to.throw('messages must be a non-empty array'); expect(() => { messaging.sendAll([]); @@ -837,7 +837,7 @@ describe('Messaging', () => { it('should throw given no messages', () => { expect(() => { - messaging.sendMulticast(undefined as any as MulticastMessage); + messaging.sendMulticast(undefined as any); }).to.throw('MulticastMessage must be a non-null object'); expect(() => { messaging.sendMulticast({} as any); @@ -1138,7 +1138,7 @@ describe('Messaging', () => { it('should throw given no registration token(s) argument', () => { expect(() => { - messaging.sendToDevice(undefined as any as string, mocks.messaging.payloadDataOnly); + messaging.sendToDevice(undefined as any, mocks.messaging.payloadDataOnly); }).to.throw(invalidArgumentError); }); @@ -1460,7 +1460,7 @@ describe('Messaging', () => { it('should throw given no notification key argument', () => { expect(() => { - messaging.sendToDeviceGroup(undefined as any as string, mocks.messaging.payloadDataOnly); + messaging.sendToDeviceGroup(undefined as any, mocks.messaging.payloadDataOnly); }).to.throw(invalidArgumentError); }); @@ -1708,7 +1708,7 @@ describe('Messaging', () => { it('should throw given no topic argument', () => { expect(() => { - messaging.sendToTopic(undefined as any as string, mocks.messaging.payload); + messaging.sendToTopic(undefined as any, mocks.messaging.payload); }).to.throw(invalidArgumentError); }); @@ -1935,7 +1935,7 @@ describe('Messaging', () => { it('should throw given no condition argument', () => { expect(() => { - messaging.sendToCondition(undefined as any as string, mocks.messaging.payloadDataOnly); + messaging.sendToCondition(undefined as any, mocks.messaging.payloadDataOnly); }).to.throw(invalidArgumentError); }); @@ -3610,7 +3610,7 @@ describe('Messaging', () => { it('should throw given no registration token(s) argument', () => { expect(() => { - messagingService[methodName](undefined as any as string, mocks.messaging.topic); + messagingService[methodName](undefined as any, mocks.messaging.topic); }).to.throw(invalidRegistrationTokensArgumentError); }); @@ -3665,7 +3665,7 @@ describe('Messaging', () => { it('should throw given no topic argument', () => { expect(() => { - messagingService[methodName](mocks.messaging.registrationToken, undefined as any as string); + messagingService[methodName](mocks.messaging.registrationToken, undefined as any); }).to.throw(invalidTopicArgumentError); });