Use Google's libphonenumber to perform more robust phone number validation and to convert all phone numbers to E164 before storing them. Closes #45

This commit is contained in:
Colin McDonald 2016-07-16 17:23:31 -04:00
parent 628c8d656b
commit 5f322824ac
4 changed files with 78 additions and 32 deletions

View File

@ -101,6 +101,11 @@
<artifactId>gson</artifactId> <artifactId>gson</artifactId>
<version>2.7</version> <version>2.7</version>
</dependency> </dependency>
<dependency>
<groupId>com.googlecode.libphonenumber</groupId>
<artifactId>libphonenumber</artifactId>
<version>7.4.5</version>
</dependency>
<!-- Mongo --> <!-- Mongo -->
<dependency> <dependency>

View File

@ -7,6 +7,7 @@ import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.util.PhoneUtils;
import net.frozenorb.apiv3.util.SyncUtils; import net.frozenorb.apiv3.util.SyncUtils;
import net.frozenorb.apiv3.util.ZangUtils; import net.frozenorb.apiv3.util.ZangUtils;
import net.frozenorb.apiv3.zang.ZangResult; import net.frozenorb.apiv3.zang.ZangResult;
@ -31,32 +32,50 @@ public final class PhoneIntel {
} }
public static void findById(String id, SingleResultCallback<PhoneIntel> callback) { public static void findById(String id, SingleResultCallback<PhoneIntel> callback) {
phoneIntelCollection.find(new Document("_id", id)).first(SyncUtils.vertxWrap(callback)); String e164Phone = PhoneUtils.toE164(id);
if (e164Phone == null) {
callback.onResult(null, null);
} else {
phoneIntelCollection.find(new Document("_id", id)).first(SyncUtils.vertxWrap(callback));
}
} }
public static void findOrCreateById(String id, SingleResultCallback<PhoneIntel> callback) { public static void findOrCreateById(String id, SingleResultCallback<PhoneIntel> callback) {
findById(id, (existingPhoneIntel, error) -> { String e164Phone = PhoneUtils.toE164(id);
if (e164Phone == null) {
callback.onResult(null, null);
return;
}
findById(e164Phone, (existingPhoneIntel, error) -> {
if (error != null) { if (error != null) {
callback.onResult(null, error); callback.onResult(null, error);
} else if (existingPhoneIntel != null) { return;
callback.onResult(existingPhoneIntel, null);
} else {
ZangUtils.getCarrierInfo(id, (zangResult, error2) -> {
if (error2 != null) {
callback.onResult(null, error2);
} else {
PhoneIntel newPhoneIntel = new PhoneIntel(id, zangResult);
phoneIntelCollection.insertOne(newPhoneIntel, SyncUtils.vertxWrap((ignored, error3) -> {
if (error3 != null) {
callback.onResult(null, error3);
} else {
callback.onResult(newPhoneIntel, null);
}
}));
}
});
} }
if (existingPhoneIntel != null) {
callback.onResult(existingPhoneIntel, null);
return;
}
ZangUtils.getCarrierInfo(e164Phone, (zangResult, error2) -> {
if (error2 != null) {
callback.onResult(null, error2);
return;
}
PhoneIntel newPhoneIntel = new PhoneIntel(e164Phone, zangResult);
phoneIntelCollection.insertOne(newPhoneIntel, SyncUtils.vertxWrap((ignored, error3) -> {
if (error3 != null) {
callback.onResult(null, error3);
} else {
callback.onResult(newPhoneIntel, null);
}
}));
});
}); });
} }

View File

@ -117,10 +117,16 @@ public final class User {
} }
public static void findByPhone(String phoneNumber, SingleResultCallback<User> callback) { public static void findByPhone(String phoneNumber, SingleResultCallback<User> callback) {
usersCollection.find(new Document("$or", ImmutableList.of( String e164Phone = PhoneUtils.toE164(phoneNumber);
new Document("phone", phoneNumber),
new Document("pendingPhone", phoneNumber) if (e164Phone == null) {
))).first(SyncUtils.vertxWrap(callback)); callback.onResult(null, null);
} else {
usersCollection.find(new Document("$or", ImmutableList.of(
new Document("phone", phoneNumber),
new Document("pendingPhone", phoneNumber)
))).first(SyncUtils.vertxWrap(callback));
}
} }
public static void findByEmail(String email, SingleResultCallback<User> callback) { public static void findByEmail(String email, SingleResultCallback<User> callback) {
@ -536,7 +542,11 @@ public final class User {
} }
public void startPhoneRegistration(String phoneNumber) { public void startPhoneRegistration(String phoneNumber) {
this.pendingPhone = phoneNumber; String e164Phone = PhoneUtils.toE164(phoneNumber);
if (e164Phone == null) return;
this.pendingPhone = e164Phone;
this.pendingPhoneToken = String.valueOf(new Random().nextInt(999999 - 100000) + 100000); this.pendingPhoneToken = String.valueOf(new Random().nextInt(999999 - 100000) + 100000);
this.pendingPhoneTokenSetAt = Instant.now(); this.pendingPhoneTokenSetAt = Instant.now();
} }

View File

@ -1,19 +1,31 @@
package net.frozenorb.apiv3.util; package net.frozenorb.apiv3.util;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import java.util.regex.Pattern;
@UtilityClass @UtilityClass
public class PhoneUtils { public class PhoneUtils {
private static final Pattern VALID_PHONE_PATTERN = Pattern.compile( public static final String DEFAULT_COUNTRY_CODE = "US";
"^\\+?[1-9]\\d{1,14}$", private static final PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
Pattern.CASE_INSENSITIVE
);
public static boolean isValidPhone(String phoneNumber) { public static boolean isValidPhone(String phoneNumber) {
return phoneNumber != null && VALID_PHONE_PATTERN.matcher(phoneNumber).matches(); try {
return phoneNumber != null && phoneUtil.isValidNumber(phoneUtil.parse(phoneNumber, DEFAULT_COUNTRY_CODE));
} catch (NumberParseException ex) {
return false;
}
}
public static String toE164(String phoneNumber) {
try {
Phonenumber.PhoneNumber number = phoneUtil.parse(phoneNumber, DEFAULT_COUNTRY_CODE);
return phoneUtil.format(number, PhoneNumberUtil.PhoneNumberFormat.E164);
} catch (NumberParseException ex) {
return "";
}
} }
} }