in src/auth/auth-api-request.ts [373:565]
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)) {
invalidClaims.push(blacklistedClaim);
}
});
// Throw an error if an invalid claim is detected.
if (invalidClaims.length > 0) {
throw new FirebaseAuthError(
AuthClientErrorCode.FORBIDDEN_CLAIM,
invalidClaims.length > 1 ?
`Developer claims "${invalidClaims.join('", "')}" are reserved and cannot be specified.` :
`Developer claim "${invalidClaims[0]}" is reserved and cannot be specified.`,
);
}
// Check claims payload does not exceed maxmimum size.
if (request.customAttributes.length > MAX_CLAIMS_PAYLOAD_SIZE) {
throw new FirebaseAuthError(
AuthClientErrorCode.CLAIMS_TOO_LARGE,
`Developer claims payload should not exceed ${MAX_CLAIMS_PAYLOAD_SIZE} characters.`,
);
}
}
// passwordHash has to be a base64 encoded string.
if (typeof request.passwordHash !== 'undefined' &&
!validator.isString(request.passwordHash)) {
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PASSWORD_HASH);
}
// salt has to be a base64 encoded string.
if (typeof request.salt !== 'undefined' &&
!validator.isString(request.salt)) {
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PASSWORD_SALT);
}
// providerUserInfo has to be an array of valid UserInfo requests.
if (typeof request.providerUserInfo !== 'undefined' &&
!validator.isArray(request.providerUserInfo)) {
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_DATA);
} else if (validator.isArray(request.providerUserInfo)) {
request.providerUserInfo.forEach((providerUserInfoEntry: any) => {
validateProviderUserInfo(providerUserInfoEntry);
});
}
// linkProviderUserInfo must be a (single) UserProvider value.
if (typeof request.linkProviderUserInfo !== 'undefined') {
validateProviderUserInfo(request.linkProviderUserInfo);
}
// mfaInfo is used for importUsers.
// mfa.enrollments is used for setAccountInfo.
// enrollments has to be an array of valid AuthFactorInfo requests.
let enrollments: AuthFactorInfo[] | null = null;
if (request.mfaInfo) {
enrollments = request.mfaInfo;
} else if (request.mfa && request.mfa.enrollments) {
enrollments = request.mfa.enrollments;
}
if (enrollments) {
if (!validator.isArray(enrollments)) {
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_ENROLLED_FACTORS);
}
enrollments.forEach((authFactorInfoEntry: AuthFactorInfo) => {
validateAuthFactorInfo(authFactorInfoEntry);
});
}
}