Skip to content

Commit 6cd505b

Browse files
authored
Implementing Phone Auth Support (#60)
* Implementing phone auth support in the user management API * Updated error message * Updated javadoc comment * Simplifying the phone number format check; Annotating nullable args in UpdateRequest
1 parent a438e50 commit 6cd505b

File tree

9 files changed

+242
-5
lines changed

9 files changed

+242
-5
lines changed

src/main/java/com/google/firebase/auth/FirebaseAuth.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,26 @@ public UserRecord then(Task<GetTokenResult> task) throws Exception {
243243
});
244244
}
245245

246+
/**
247+
* Gets the user data corresponding to the specified user phone number.
248+
*
249+
* @param phoneNumber A user phone number string.
250+
* @return A {@link Task} which will complete successfully with a {@link UserRecord} instance.
251+
* If an error occurs while retrieving user data or if the phone number does not
252+
* correspond to a user, the task fails with a FirebaseAuthException.
253+
* @throws IllegalArgumentException If the phone number is null or empty.
254+
*/
255+
public Task<UserRecord> getUserByPhoneNumber(final String phoneNumber) {
256+
checkArgument(!Strings.isNullOrEmpty(phoneNumber), "phone number must not be null or empty");
257+
return ImplFirebaseTrampolines.getToken(firebaseApp, false).continueWith(
258+
new Continuation<GetTokenResult, UserRecord>() {
259+
@Override
260+
public UserRecord then(Task<GetTokenResult> task) throws Exception {
261+
return userManager.getUserByPhoneNumber(phoneNumber, task.getResult().getToken());
262+
}
263+
});
264+
}
265+
246266
/**
247267
* Creates a new user account with the attributes contained in the specified
248268
* {@link CreateRequest}.

src/main/java/com/google/firebase/auth/FirebaseUserManager.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,24 @@ UserRecord getUserByEmail(String email, String token) throws FirebaseAuthExcepti
119119
return new UserRecord(response.getUsers().get(0));
120120
}
121121

122+
UserRecord getUserByPhoneNumber(String phoneNumber, String token) throws FirebaseAuthException {
123+
final Map<String, Object> payload = ImmutableMap.<String, Object>of(
124+
"phoneNumber", ImmutableList.of(phoneNumber));
125+
GetAccountInfoResponse response;
126+
try {
127+
response = post("getAccountInfo", token, payload, GetAccountInfoResponse.class);
128+
} catch (IOException e) {
129+
throw new FirebaseAuthException(INTERNAL_ERROR,
130+
"IO error while retrieving user with phone number: " + phoneNumber, e);
131+
}
132+
133+
if (response == null || response.getUsers() == null || response.getUsers().isEmpty()) {
134+
throw new FirebaseAuthException(USER_NOT_FOUND_ERROR,
135+
"No user record found for the provided phone number: " + phoneNumber);
136+
}
137+
return new UserRecord(response.getUsers().get(0));
138+
}
139+
122140
String createUser(CreateRequest request, String token) throws FirebaseAuthException {
123141
GenericJson response;
124142
try {

src/main/java/com/google/firebase/auth/ProviderUserInfo.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ class ProviderUserInfo implements UserInfo {
2828
private final String uid;
2929
private final String displayName;
3030
private final String email;
31+
private final String phoneNumber;
3132
private final String photoUrl;
3233
private final String providerId;
3334

3435
ProviderUserInfo(GetAccountInfoResponse.Provider response) {
3536
this.uid = response.getUid();
3637
this.displayName = response.getDisplayName();
3738
this.email = response.getEmail();
39+
this.phoneNumber = response.getPhoneNumber();
3840
this.photoUrl = response.getPhotoUrl();
3941
this.providerId = response.getProviderId();
4042
}
@@ -56,6 +58,12 @@ public String getEmail() {
5658
return email;
5759
}
5860

61+
@Nullable
62+
@Override
63+
public String getPhoneNumber() {
64+
return phoneNumber;
65+
}
66+
5967
@Nullable
6068
@Override
6169
public String getPhotoUrl() {

src/main/java/com/google/firebase/auth/UserInfo.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ public interface UserInfo {
4747
@Nullable
4848
String getEmail();
4949

50+
/**
51+
* Returns the user's phone number, if available.
52+
*
53+
* @return a phone number string or null.
54+
*/
55+
@Nullable
56+
String getPhoneNumber();
57+
5058
/**
5159
* Returns the user's photo URL, if available.
5260
*

src/main/java/com/google/firebase/auth/UserRecord.java

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static com.google.common.base.Preconditions.checkArgument;
2020
import static com.google.common.base.Preconditions.checkNotNull;
21+
import static com.google.common.base.Preconditions.checkState;
2122

2223
import com.google.common.base.Strings;
2324
import com.google.common.collect.ImmutableList;
@@ -45,6 +46,7 @@ public class UserRecord implements UserInfo {
4546

4647
private final String uid;
4748
private final String email;
49+
private final String phoneNumber;
4850
private final boolean emailVerified;
4951
private final String displayName;
5052
private final String photoUrl;
@@ -57,6 +59,7 @@ public class UserRecord implements UserInfo {
5759
checkArgument(!Strings.isNullOrEmpty(response.getUid()), "uid must not be null or empty");
5860
this.uid = response.getUid();
5961
this.email = response.getEmail();
62+
this.phoneNumber = response.getPhoneNumber();
6063
this.emailVerified = response.isEmailVerified();
6164
this.displayName = response.getDisplayName();
6265
this.photoUrl = response.getPhotoUrl();
@@ -103,6 +106,17 @@ public String getEmail() {
103106
return email;
104107
}
105108

109+
/**
110+
* Returns the phone number associated with this user.
111+
*
112+
* @return a phone number string or null.
113+
*/
114+
@Nullable
115+
@Override
116+
public String getPhoneNumber() {
117+
return phoneNumber;
118+
}
119+
106120
/**
107121
* Returns whether the email address of this user has been verified.
108122
*
@@ -177,6 +191,14 @@ private static void checkEmail(String email) {
177191
checkArgument(email.matches("^[^@]+@[^@]+$"));
178192
}
179193

194+
private static void checkPhoneNumber(String phoneNumber) {
195+
// Phone number verification is very lax here. Backend will enforce E.164 spec compliance, and
196+
// normalize accordingly.
197+
checkArgument(!Strings.isNullOrEmpty(phoneNumber), "phone number cannot be null or empty");
198+
checkState(phoneNumber.startsWith("+"),
199+
"phone number must be a valid, E.164 compliant identifier starting with a '+' sign");
200+
}
201+
180202
private static void checkPassword(String password) {
181203
checkArgument(!Strings.isNullOrEmpty(password), "password cannot be null or empty");
182204
checkArgument(password.length() >= 6, "password must be at least 6 characters long");
@@ -223,6 +245,17 @@ public CreateRequest setEmail(String email) {
223245
return this;
224246
}
225247

248+
/**
249+
* Sets a phone number for the new user.
250+
*
251+
* @param phone a non-null, non-empty phone number string.
252+
*/
253+
public CreateRequest setPhoneNumber(String phone) {
254+
checkPhoneNumber(phone);
255+
properties.put("phoneNumber", phone);
256+
return this;
257+
}
258+
226259
/**
227260
* Sets whether the user email address has been verified or not.
228261
*
@@ -323,6 +356,20 @@ public UpdateRequest setEmail(String email) {
323356
return this;
324357
}
325358

359+
/**
360+
* Updates the phone number associated with this user. Calling this method with a null argument
361+
* removes the phone number from the user account.
362+
*
363+
* @param phone a valid phone number string or null.
364+
*/
365+
public UpdateRequest setPhoneNumber(@Nullable String phone) {
366+
if (phone != null) {
367+
checkPhoneNumber(phone);
368+
}
369+
properties.put("phoneNumber", phone);
370+
return this;
371+
}
372+
326373
/**
327374
* Updates the email verification status of this account.
328375
*
@@ -339,7 +386,7 @@ public UpdateRequest setEmailVerified(boolean emailVerified) {
339386
*
340387
* @param displayName a display name string or null
341388
*/
342-
public UpdateRequest setDisplayName(String displayName) {
389+
public UpdateRequest setDisplayName(@Nullable String displayName) {
343390
properties.put("displayName", displayName);
344391
return this;
345392
}
@@ -350,7 +397,7 @@ public UpdateRequest setDisplayName(String displayName) {
350397
*
351398
* @param photoUrl a valid URL string or null
352399
*/
353-
public UpdateRequest setPhotoUrl(String photoUrl) {
400+
public UpdateRequest setPhotoUrl(@Nullable String photoUrl) {
354401
if (photoUrl != null) {
355402
try {
356403
new URL(photoUrl);
@@ -396,6 +443,11 @@ Map<String, Object> getProperties() {
396443
if (!remove.isEmpty()) {
397444
copy.put("deleteAttribute", ImmutableList.copyOf(remove));
398445
}
446+
447+
if (copy.containsKey("phoneNumber") && copy.get("phoneNumber") == null) {
448+
copy.put("deleteProvider", ImmutableList.of("phone"));
449+
copy.remove("phoneNumber");
450+
}
399451
return ImmutableMap.copyOf(copy);
400452
}
401453
}

src/main/java/com/google/firebase/auth/internal/GetAccountInfoResponse.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ public static final class User {
4949
@Key("email")
5050
private String email;
5151

52+
@Key("phoneNumber")
53+
private String phoneNumber;
54+
5255
@Key("emailVerified")
5356
private boolean emailVerified;
5457

@@ -78,6 +81,10 @@ public String getEmail() {
7881
return email;
7982
}
8083

84+
public String getPhoneNumber() {
85+
return phoneNumber;
86+
}
87+
8188
public boolean isEmailVerified() {
8289
return emailVerified;
8390
}
@@ -121,6 +128,9 @@ public static final class Provider {
121128
@Key("email")
122129
private String email;
123130

131+
@Key("phoneNumber")
132+
private String phoneNumber;
133+
124134
@Key("photoUrl")
125135
private String photoUrl;
126136

@@ -139,6 +149,10 @@ public String getEmail() {
139149
return email;
140150
}
141151

152+
public String getPhoneNumber() {
153+
return phoneNumber;
154+
}
155+
142156
public String getPhotoUrl() {
143157
return photoUrl;
144158
}

src/test/java/com/google/firebase/auth/FirebaseAuthIT.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@
3838
import com.google.firebase.tasks.Tasks;
3939
import com.google.firebase.testing.IntegrationTestUtils;
4040
import java.io.IOException;
41+
import java.util.ArrayList;
42+
import java.util.List;
4143
import java.util.Map;
44+
import java.util.Random;
4245
import java.util.UUID;
4346
import java.util.concurrent.ExecutionException;
4447
import org.junit.BeforeClass;
@@ -107,14 +110,25 @@ public void testDeleteNonExistingUser() throws Exception {
107110
}
108111
}
109112

113+
private String randomPhoneNumber() {
114+
Random random = new Random();
115+
StringBuilder builder = new StringBuilder("+1");
116+
for (int i = 0; i < 10; i++) {
117+
builder.append(random.nextInt(10));
118+
}
119+
return builder.toString();
120+
}
121+
110122
@Test
111123
public void testCreateUserWithParams() throws Exception {
112124
String randomId = UUID.randomUUID().toString().replaceAll("-", "");
113125
String userEmail = ("test" + randomId.substring(0, 12) + "@example." + randomId.substring(12)
114126
+ ".com").toLowerCase();
127+
String phone = randomPhoneNumber();
115128
CreateRequest user = new CreateRequest()
116129
.setUid(randomId)
117130
.setEmail(userEmail)
131+
.setPhoneNumber(phone)
118132
.setDisplayName("Random User")
119133
.setPhotoUrl("https://example.com/photo.png")
120134
.setEmailVerified(true)
@@ -125,10 +139,19 @@ public void testCreateUserWithParams() throws Exception {
125139
assertEquals(randomId, userRecord.getUid());
126140
assertEquals("Random User", userRecord.getDisplayName());
127141
assertEquals(userEmail, userRecord.getEmail());
142+
assertEquals(phone, userRecord.getPhoneNumber());
128143
assertEquals("https://example.com/photo.png", userRecord.getPhotoUrl());
129144
assertTrue(userRecord.isEmailVerified());
130145
assertFalse(userRecord.isDisabled());
131146

147+
assertEquals(2, userRecord.getProviderData().length);
148+
List<String> providers = new ArrayList<>();
149+
for (UserInfo provider : userRecord.getProviderData()) {
150+
providers.add(provider.getProviderId());
151+
}
152+
assertTrue(providers.contains("password"));
153+
assertTrue(providers.contains("phone"));
154+
132155
checkRecreate(randomId);
133156
} finally {
134157
Tasks.await(auth.deleteUser(userRecord.getUid()));
@@ -157,29 +180,35 @@ public void testUserLifecycle() throws Exception {
157180
assertEquals(uid, userRecord.getUid());
158181
assertNull(userRecord.getDisplayName());
159182
assertNull(userRecord.getEmail());
183+
assertNull(userRecord.getPhoneNumber());
160184
assertNull(userRecord.getPhotoUrl());
161185
assertFalse(userRecord.isEmailVerified());
162186
assertFalse(userRecord.isDisabled());
163187
assertTrue(userRecord.getUserMetadata().getCreationTimestamp() > 0);
164188
assertEquals(0, userRecord.getUserMetadata().getLastSignInTimestamp());
189+
assertEquals(0, userRecord.getProviderData().length);
165190

166191
// Update user
167192
String randomId = UUID.randomUUID().toString().replaceAll("-", "");
168193
String userEmail = ("test" + randomId.substring(0, 12) + "@example." + randomId.substring(12)
169194
+ ".com").toLowerCase();
195+
String phone = randomPhoneNumber();
170196
UpdateRequest request = userRecord.updateRequest()
171197
.setDisplayName("Updated Name")
172198
.setEmail(userEmail)
199+
.setPhoneNumber(phone)
173200
.setPhotoUrl("https://example.com/photo.png")
174201
.setEmailVerified(true)
175202
.setPassword("secret");
176203
userRecord = Tasks.await(auth.updateUser(request));
177204
assertEquals(uid, userRecord.getUid());
178205
assertEquals("Updated Name", userRecord.getDisplayName());
179206
assertEquals(userEmail, userRecord.getEmail());
207+
assertEquals(phone, userRecord.getPhoneNumber());
180208
assertEquals("https://example.com/photo.png", userRecord.getPhotoUrl());
181209
assertTrue(userRecord.isEmailVerified());
182210
assertFalse(userRecord.isDisabled());
211+
assertEquals(2, userRecord.getProviderData().length);
183212

184213
// Get user by email
185214
userRecord = Tasks.await(auth.getUserByEmail(userRecord.getEmail()));
@@ -189,14 +218,17 @@ public void testUserLifecycle() throws Exception {
189218
request = userRecord.updateRequest()
190219
.setPhotoUrl(null)
191220
.setDisplayName(null)
221+
.setPhoneNumber(null)
192222
.setDisabled(true);
193223
userRecord = Tasks.await(auth.updateUser(request));
194224
assertEquals(uid, userRecord.getUid());
195225
assertNull(userRecord.getDisplayName());
196226
assertEquals(userEmail, userRecord.getEmail());
227+
assertNull(userRecord.getPhoneNumber());
197228
assertNull(userRecord.getPhotoUrl());
198229
assertTrue(userRecord.isEmailVerified());
199230
assertTrue(userRecord.isDisabled());
231+
assertEquals(1, userRecord.getProviderData().length);
200232

201233
// Delete user
202234
Tasks.await(auth.deleteUser(userRecord.getUid()));

0 commit comments

Comments
 (0)