in src/auth/auth-api-request.ts [202:502]
host: emulatorHost()
});
} else {
this.urlFormat = FIREBASE_AUTH_TENANT_URL_FORMAT;
}
}
/**
* Returns the resource URL corresponding to the provided parameters.
*
* @param api - The backend API name.
* @param params - The optional additional parameters to substitute in the
* URL path.
* @returns The corresponding resource URL.
*/
public getUrl(api?: string, params?: object): Promise<string> {
return super.getUrl(api, params)
.then((url) => {
return utils.formatString(url, { tenantId: this.tenantId });
});
}
}
/**
* Auth-specific HTTP client which uses the special "owner" token
* when communicating with the Auth Emulator.
*/
class AuthHttpClient extends AuthorizedHttpClient {
protected getToken(): Promise<string> {
if (useEmulator()) {
return Promise.resolve('owner');
}
return super.getToken();
}
}
/**
* Validates an AuthFactorInfo object. All unsupported parameters
* are removed from the original request. If an invalid field is passed
* an error is thrown.
*
* @param request - The AuthFactorInfo request object.
*/
function validateAuthFactorInfo(request: AuthFactorInfo): void {
const validKeys = {
mfaEnrollmentId: true,
displayName: true,
phoneInfo: true,
enrolledAt: true,
};
// Remove unsupported keys from the original request.
for (const key in request) {
if (!(key in validKeys)) {
delete request[key];
}
}
// No enrollment ID is available for signupNewUser. Use another identifier.
const authFactorInfoIdentifier =
request.mfaEnrollmentId || request.phoneInfo || JSON.stringify(request);
// Enrollment uid may or may not be specified for update operations.
if (typeof request.mfaEnrollmentId !== 'undefined' &&
!validator.isNonEmptyString(request.mfaEnrollmentId)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_UID,
'The second factor "uid" must be a valid non-empty string.',
);
}
if (typeof request.displayName !== 'undefined' &&
!validator.isString(request.displayName)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_DISPLAY_NAME,
`The second factor "displayName" for "${authFactorInfoIdentifier}" must be a valid string.`,
);
}
// enrolledAt must be a valid UTC date string.
if (typeof request.enrolledAt !== 'undefined' &&
!validator.isISODateString(request.enrolledAt)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_ENROLLMENT_TIME,
`The second factor "enrollmentTime" for "${authFactorInfoIdentifier}" must be a valid ` +
'UTC date string.');
}
// Validate required fields depending on second factor type.
if (typeof request.phoneInfo !== 'undefined') {
// phoneNumber should be a string and a valid phone number.
if (!validator.isPhoneNumber(request.phoneInfo)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_PHONE_NUMBER,
`The second factor "phoneNumber" for "${authFactorInfoIdentifier}" must be a non-empty ` +
'E.164 standard compliant identifier string.');
}
} else {
// Invalid second factor. For example, a phone second factor may have been provided without
// a phone number. A TOTP based second factor may require a secret key, etc.
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_ENROLLED_FACTORS,
'MFAInfo object provided is invalid.');
}
}
/**
* Validates a providerUserInfo object. All unsupported parameters
* are removed from the original request. If an invalid field is passed
* an error is thrown.
*
* @param request - The providerUserInfo request object.
*/
function validateProviderUserInfo(request: any): void {
const validKeys = {
rawId: true,
providerId: true,
email: true,
displayName: true,
photoUrl: true,
};
// Remove invalid keys from original request.
for (const key in request) {
if (!(key in validKeys)) {
delete request[key];
}
}
if (!validator.isNonEmptyString(request.providerId)) {
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID);
}
if (typeof request.displayName !== 'undefined' &&
typeof request.displayName !== 'string') {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_DISPLAY_NAME,
`The provider "displayName" for "${request.providerId}" must be a valid string.`,
);
}
if (!validator.isNonEmptyString(request.rawId)) {
// This is called localId on the backend but the developer specifies this as
// uid externally. So the error message should use the client facing name.
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_UID,
`The provider "uid" for "${request.providerId}" must be a valid non-empty string.`,
);
}
// email should be a string and a valid email.
if (typeof request.email !== 'undefined' && !validator.isEmail(request.email)) {
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_EMAIL,
`The provider "email" for "${request.providerId}" must be a valid email string.`,
);
}
// photoUrl should be a URL.
if (typeof request.photoUrl !== 'undefined' &&
!validator.isURL(request.photoUrl)) {
// This is called photoUrl on the backend but the developer specifies this as
// photoURL externally. So the error message should use the client facing name.
throw new FirebaseAuthError(
AuthClientErrorCode.INVALID_PHOTO_URL,
`The provider "photoURL" for "${request.providerId}" must be a valid URL string.`,
);
}
}
/**
* Validates a create/edit request object. All unsupported parameters
* are removed from the original request. If an invalid field is passed
* an error is thrown.
*
* @param request - The create/edit request object.
* @param writeOperationType - The write operation type.
*/
function validateCreateEditRequest(request: any, writeOperationType: WriteOperationType): void {
const uploadAccountRequest = writeOperationType === WriteOperationType.Upload;
// Hash set of whitelisted parameters.
const validKeys = {
displayName: true,
localId: true,
email: true,
password: true,
rawPassword: true,
emailVerified: true,
photoUrl: true,
disabled: true,
disableUser: true,
deleteAttribute: true,
deleteProvider: true,
sanityCheck: true,
phoneNumber: true,
customAttributes: true,
validSince: true,
// Pass linkProviderUserInfo only for updates (i.e. not for uploads.)
linkProviderUserInfo: !uploadAccountRequest,
// Pass tenantId only for uploadAccount requests.
tenantId: uploadAccountRequest,
passwordHash: uploadAccountRequest,
salt: uploadAccountRequest,
createdAt: uploadAccountRequest,
lastLoginAt: uploadAccountRequest,
providerUserInfo: uploadAccountRequest,
mfaInfo: uploadAccountRequest,
// Only for non-uploadAccount requests.
mfa: !uploadAccountRequest,
};
// Remove invalid keys from original request.
for (const key in request) {
if (!(key in validKeys)) {
delete request[key];
}
}
if (typeof request.tenantId !== 'undefined' &&
!validator.isNonEmptyString(request.tenantId)) {
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_TENANT_ID);
}
// For any invalid parameter, use the external key name in the error description.
// displayName should be a string.
if (typeof request.displayName !== 'undefined' &&
!validator.isString(request.displayName)) {
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_DISPLAY_NAME);
}
if ((typeof request.localId !== 'undefined' || uploadAccountRequest) &&
!validator.isUid(request.localId)) {
// This is called localId on the backend but the developer specifies this as
// uid externally. So the error message should use the client facing name.
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_UID);
}
// email should be a string and a valid email.
if (typeof request.email !== 'undefined' && !validator.isEmail(request.email)) {
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_EMAIL);
}
// phoneNumber should be a string and a valid phone number.
if (typeof request.phoneNumber !== 'undefined' &&
!validator.isPhoneNumber(request.phoneNumber)) {
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PHONE_NUMBER);
}
// password should be a string and a minimum of 6 chars.
if (typeof request.password !== 'undefined' &&
!validator.isPassword(request.password)) {
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PASSWORD);
}
// rawPassword should be a string and a minimum of 6 chars.
if (typeof request.rawPassword !== 'undefined' &&
!validator.isPassword(request.rawPassword)) {
// This is called rawPassword on the backend but the developer specifies this as
// password externally. So the error message should use the client facing name.
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PASSWORD);
}
// emailVerified should be a boolean.
if (typeof request.emailVerified !== 'undefined' &&
typeof request.emailVerified !== 'boolean') {
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_EMAIL_VERIFIED);
}
// photoUrl should be a URL.
if (typeof request.photoUrl !== 'undefined' &&
!validator.isURL(request.photoUrl)) {
// This is called photoUrl on the backend but the developer specifies this as
// photoURL externally. So the error message should use the client facing name.
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PHOTO_URL);
}
// disabled should be a boolean.
if (typeof request.disabled !== 'undefined' &&
typeof request.disabled !== 'boolean') {
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_DISABLED_FIELD);
}
// validSince should be a number.
if (typeof request.validSince !== 'undefined' &&
!validator.isNumber(request.validSince)) {
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_TOKENS_VALID_AFTER_TIME);
}
// createdAt should be a number.
if (typeof request.createdAt !== 'undefined' &&
!validator.isNumber(request.createdAt)) {
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_CREATION_TIME);
}
// lastSignInAt should be a number.
if (typeof request.lastLoginAt !== 'undefined' &&
!validator.isNumber(request.lastLoginAt)) {
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_LAST_SIGN_IN_TIME);
}
// disableUser should be a boolean.
if (typeof request.disableUser !== 'undefined' &&
typeof request.disableUser !== 'boolean') {
// This is called disableUser on the backend but the developer specifies this as
// disabled externally. So the error message should use the client facing name.
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_DISABLED_FIELD);
}
// customAttributes should be stringified JSON with no blacklisted claims.
// The payload should not exceed 1KB.
if (typeof request.customAttributes !== 'undefined') {
let developerClaims: object;
try {
developerClaims = JSON.parse(request.customAttributes);
} catch (error) {
// JSON parsing error. This should never happen as we stringify the claims internally.
// However, we still need to check since setAccountInfo via edit requests could pass
// this field.
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_CLAIMS, error.message);
}
const invalidClaims: string[] = [];
// Check for any invalid claims.
RESERVED_CLAIMS.forEach((blacklistedClaim) => {
if (Object.prototype.hasOwnProperty.call(developerClaims, blacklistedClaim)) {