More work on Zang phone registration
This commit is contained in:
parent
9f4089a752
commit
1532bee954
@ -51,8 +51,8 @@ import net.frozenorb.apiv3.route.bannedCellCarriers.GETBannedCellCarriers;
|
||||
import net.frozenorb.apiv3.route.bannedCellCarriers.GETBannedCellCarriersId;
|
||||
import net.frozenorb.apiv3.route.bannedCellCarriers.POSTBannedCellCarriers;
|
||||
import net.frozenorb.apiv3.route.chatFilterList.GETChatFilter;
|
||||
import net.frozenorb.apiv3.route.emailToken.GETEmailTokensIdOwner;
|
||||
import net.frozenorb.apiv3.route.emailToken.POSTEmailTokensIdConfirm;
|
||||
import net.frozenorb.apiv3.route.emailTokens.GETEmailTokensIdOwner;
|
||||
import net.frozenorb.apiv3.route.emailTokens.POSTEmailTokensIdConfirm;
|
||||
import net.frozenorb.apiv3.route.grants.DELETEGrantsId;
|
||||
import net.frozenorb.apiv3.route.grants.GETGrants;
|
||||
import net.frozenorb.apiv3.route.grants.GETGrantsId;
|
||||
@ -85,6 +85,7 @@ import net.frozenorb.apiv3.serialization.jackson.InstantJsonSerializer;
|
||||
import net.frozenorb.apiv3.serialization.jackson.UuidJsonDeserializer;
|
||||
import net.frozenorb.apiv3.serialization.jackson.UuidJsonSerializer;
|
||||
import net.frozenorb.apiv3.serialization.mongodb.UuidCodecProvider;
|
||||
import net.frozenorb.apiv3.util.EmailUtils;
|
||||
import org.bson.Document;
|
||||
import org.bson.codecs.BsonValueCodecProvider;
|
||||
import org.bson.codecs.DocumentCodecProvider;
|
||||
@ -205,6 +206,7 @@ public final class APIv3 extends AbstractVerticle {
|
||||
Rank.updateCache();
|
||||
Server.updateCache();
|
||||
ServerGroup.updateCache();
|
||||
EmailUtils.updateBannedEmailDomains();
|
||||
}
|
||||
|
||||
private ObjectMapper createMongoJacksonMapper() {
|
||||
@ -324,7 +326,8 @@ public final class APIv3 extends AbstractVerticle {
|
||||
http.post("/users/:id/login").blockingHandler(new POSTUsersIdLogin());
|
||||
http.post("/users/:id/notify").blockingHandler(new POSTUsersIdNotify(), false);
|
||||
http.post("/users/:id/passwordReset").blockingHandler(new POSTUsersIdPasswordReset(), false);
|
||||
http.post("/users/:id/register").blockingHandler(new POSTUsersIdRegister(), false);
|
||||
http.post("/users/:id/registerEmail").blockingHandler(new POSTUsersIdRegisterEmail(), false);
|
||||
http.post("/users/:id/registerPhone").blockingHandler(new POSTUsersIdRegisterPhone(), false);
|
||||
http.post("/users/:id/setupTotp").blockingHandler(new POSTUsersIdSetupTotp(), false);
|
||||
http.post("/users/:id/verifyTotp").handler(new POSTUsersIdVerifyTotp());
|
||||
|
||||
|
@ -36,8 +36,10 @@ public enum AuditLogActionType {
|
||||
SERVER_DELETE(false),
|
||||
USER_CHANGE_PASSWORD(false),
|
||||
USER_PASSWORD_RESET(false),
|
||||
USER_REGISTER(false),
|
||||
USER_REGISTER_EMAIL(false),
|
||||
USER_REGISTER_PHONE(false),
|
||||
USER_CONFIRM_EMAIL(false),
|
||||
USER_CONFIRM_PHONE(false),
|
||||
USER_SETUP_TOTP(false),
|
||||
USER_VERIFY_TOTP(false);
|
||||
|
||||
|
@ -55,7 +55,11 @@ public final class User {
|
||||
@Getter @ExcludeFromReplies private String pendingEmail;
|
||||
@Getter @ExcludeFromReplies private String pendingEmailToken;
|
||||
@Getter @ExcludeFromReplies private Instant pendingEmailTokenSetAt;
|
||||
@Getter private String phoneNumber;
|
||||
@Getter private String phone;
|
||||
@Getter private Instant phoneRegisteredAt;
|
||||
@Getter @ExcludeFromReplies private String pendingPhone;
|
||||
@Getter @ExcludeFromReplies private String pendingPhoneToken;
|
||||
@Getter @ExcludeFromReplies private Instant pendingPhoneTokenSetAt;
|
||||
@Getter private String lastSeenOn;
|
||||
@Getter private Instant lastSeenAt;
|
||||
@Getter private Instant firstSeenAt;
|
||||
@ -78,6 +82,10 @@ public final class User {
|
||||
}
|
||||
}
|
||||
|
||||
public static void findByPhone(String phoneNumber, SingleResultCallback<User> callback) {
|
||||
usersCollection.find(new Document("phone", phoneNumber)).first(callback);
|
||||
}
|
||||
|
||||
public static void findByEmail(String email, SingleResultCallback<User> callback) {
|
||||
usersCollection.find(new Document("email", email)).first(callback);
|
||||
}
|
||||
@ -460,7 +468,13 @@ public final class User {
|
||||
});
|
||||
}
|
||||
|
||||
public void completeRegistration(String email) {
|
||||
public void startEmailRegistration(String pendingEmail) {
|
||||
this.pendingEmail = pendingEmail;
|
||||
this.pendingEmailToken = UUID.randomUUID().toString().replace("-", "");
|
||||
this.pendingEmailTokenSetAt = Instant.now();
|
||||
}
|
||||
|
||||
public void completeEmailRegistration(String email) {
|
||||
this.email = email;
|
||||
this.registeredAt = Instant.now();
|
||||
this.pendingEmail = null;
|
||||
@ -468,12 +482,20 @@ public final class User {
|
||||
this.pendingEmailTokenSetAt = null;
|
||||
}
|
||||
|
||||
public void startRegistration(String pendingEmail) {
|
||||
this.pendingEmail = pendingEmail;
|
||||
public void startPhoneRegistration(String phoneNumber) {
|
||||
this.pendingPhone = phoneNumber;
|
||||
this.pendingEmailToken = UUID.randomUUID().toString().replace("-", "");
|
||||
this.pendingEmailTokenSetAt = Instant.now();
|
||||
}
|
||||
|
||||
public void completeRegistration(String email) {
|
||||
this.email = email;
|
||||
this.registeredAt = Instant.now();
|
||||
this.pendingEmail = null;
|
||||
this.pendingEmailToken = null;
|
||||
this.pendingEmailTokenSetAt = null;
|
||||
}
|
||||
|
||||
public void hasPermissionAnywhere(String permission, SingleResultCallback<Boolean> callback) {
|
||||
getCompoundedPermissions((permissions, error) -> {
|
||||
if (error != null) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package net.frozenorb.apiv3.route.emailToken;
|
||||
package net.frozenorb.apiv3.route.emailTokens;
|
||||
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.ext.web.RoutingContext;
|
@ -1,4 +1,4 @@
|
||||
package net.frozenorb.apiv3.route.emailToken;
|
||||
package net.frozenorb.apiv3.route.emailTokens;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
@ -46,7 +46,7 @@ public final class POSTEmailTokensIdConfirm implements Handler<RoutingContext> {
|
||||
return;
|
||||
}
|
||||
|
||||
user.completeRegistration(user.getPendingEmail());
|
||||
user.completeEmailRegistration(user.getPendingEmail());
|
||||
user.updatePassword(password);
|
||||
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
|
||||
user.save(callback);
|
@ -18,7 +18,7 @@ import net.frozenorb.apiv3.util.ErrorUtils;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class POSTUsersIdRegister implements Handler<RoutingContext> {
|
||||
public final class POSTUsersIdRegisterEmail implements Handler<RoutingContext> {
|
||||
|
||||
public void handle(RoutingContext ctx) {
|
||||
BlockingCallback<User> userCallback = new BlockingCallback<>();
|
||||
@ -61,7 +61,7 @@ public final class POSTUsersIdRegister implements Handler<RoutingContext> {
|
||||
return;
|
||||
}
|
||||
|
||||
user.startRegistration(email);
|
||||
user.startEmailRegistration(email);
|
||||
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
|
||||
user.save(callback);
|
||||
callback.get();
|
||||
@ -80,7 +80,7 @@ public final class POSTUsersIdRegister implements Handler<RoutingContext> {
|
||||
if (error != null) {
|
||||
ErrorUtils.respondInternalError(ctx, error);
|
||||
} else {
|
||||
AuditLog.log(user.getId(), requestBody.getString("userIp"), ctx, AuditLogActionType.USER_REGISTER, (ignored2, error2) -> {
|
||||
AuditLog.log(user.getId(), requestBody.getString("userIp"), ctx, AuditLogActionType.USER_REGISTER_EMAIL, (ignored2, error2) -> {
|
||||
if (error2 != null) {
|
||||
ErrorUtils.respondInternalError(ctx, error2);
|
||||
} else {
|
@ -0,0 +1,91 @@
|
||||
package net.frozenorb.apiv3.route.users;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import net.frozenorb.apiv3.APIv3;
|
||||
import net.frozenorb.apiv3.auditLog.AuditLog;
|
||||
import net.frozenorb.apiv3.auditLog.AuditLogActionType;
|
||||
import net.frozenorb.apiv3.model.NotificationTemplate;
|
||||
import net.frozenorb.apiv3.model.User;
|
||||
import net.frozenorb.apiv3.unsorted.BlockingCallback;
|
||||
import net.frozenorb.apiv3.unsorted.Notification;
|
||||
import net.frozenorb.apiv3.util.ErrorUtils;
|
||||
import net.frozenorb.apiv3.util.PhoneUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class POSTUsersIdRegisterPhone implements Handler<RoutingContext> {
|
||||
|
||||
public void handle(RoutingContext ctx) {
|
||||
BlockingCallback<User> userCallback = new BlockingCallback<>();
|
||||
User.findById(ctx.request().getParam("id"), userCallback);
|
||||
User user = userCallback.get();
|
||||
|
||||
if (user == null) {
|
||||
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.getPhone() != null) {
|
||||
ErrorUtils.respondInvalidInput(ctx, "User provided already has phone set.");
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject requestBody = ctx.getBodyAsJson();
|
||||
String phone = requestBody.getString("phone");
|
||||
|
||||
if (!PhoneUtils.isValidPhone(phone)) {
|
||||
ErrorUtils.respondInvalidInput(ctx, phone + " is not a valid phone number.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.getPendingPhoneToken() != null && (System.currentTimeMillis() - user.getPendingPhoneTokenSetAt().toEpochMilli()) < TimeUnit.DAYS.toMillis(2)) {
|
||||
ErrorUtils.respondGeneric(ctx, 200, "We just recently sent you a confirmation code. Please wait before trying to register again.");
|
||||
return;
|
||||
}
|
||||
|
||||
BlockingCallback<User> samePhoneCallback = new BlockingCallback<>();
|
||||
User.findByPhone(phone, samePhoneCallback);
|
||||
|
||||
if (samePhoneCallback.get() != null) {
|
||||
ErrorUtils.respondInvalidInput(ctx, phone + " is already in use.");
|
||||
return;
|
||||
}
|
||||
|
||||
user.startPhoneRegistration(phone);
|
||||
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
|
||||
user.save(callback);
|
||||
callback.get();
|
||||
|
||||
Map<String, Object> replacements = ImmutableMap.of(
|
||||
"username", user.getLastUsername(),
|
||||
"phone", user.getPendingPhone(),
|
||||
"phoneToken", user.getPendingPhoneToken()
|
||||
);
|
||||
|
||||
BlockingCallback<NotificationTemplate> notificationTemplateCallback = new BlockingCallback<>();
|
||||
NotificationTemplate.findById("phone-confirmation", notificationTemplateCallback);
|
||||
Notification notification = new Notification(notificationTemplateCallback.get(), replacements, replacements);
|
||||
|
||||
notification.sendAsText(user.getPendingPhone(), (ignored, error) -> {
|
||||
if (error != null) {
|
||||
ErrorUtils.respondInternalError(ctx, error);
|
||||
} else {
|
||||
AuditLog.log(user.getId(), requestBody.getString("userIp"), ctx, AuditLogActionType.USER_REGISTER_PHONE, (ignored2, error2) -> {
|
||||
if (error2 != null) {
|
||||
ErrorUtils.respondInternalError(ctx, error2);
|
||||
} else {
|
||||
APIv3.respondJson(ctx, ImmutableMap.of(
|
||||
"success", true
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -11,7 +11,6 @@ import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Slf4j
|
||||
@UtilityClass
|
||||
public class EmailUtils {
|
||||
|
||||
@ -23,11 +22,10 @@ public class EmailUtils {
|
||||
private static Set<String> bannedEmailDomains = ImmutableSet.of();
|
||||
|
||||
static {
|
||||
updateBannedEmailDomains();
|
||||
APIv3.getVertxInstance().setPeriodic(TimeUnit.MINUTES.toMillis(10), (id) -> updateBannedEmailDomains());
|
||||
}
|
||||
|
||||
private static void updateBannedEmailDomains() {
|
||||
public static void updateBannedEmailDomains() {
|
||||
httpsClient.get(443, "raw.githubusercontent.com", "/martenson/disposable-email-domains/master/disposable_email_blacklist.conf", (response) -> {
|
||||
response.bodyHandler((body) -> bannedEmailDomains = ImmutableSet.copyOf(body.toString().split("\n")));
|
||||
response.exceptionHandler(Throwable::printStackTrace);
|
||||
|
20
src/main/java/net/frozenorb/apiv3/util/PhoneUtils.java
Normal file
20
src/main/java/net/frozenorb/apiv3/util/PhoneUtils.java
Normal file
@ -0,0 +1,20 @@
|
||||
package net.frozenorb.apiv3.util;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@UtilityClass
|
||||
public class PhoneUtils {
|
||||
|
||||
private static final Pattern VALID_PHONE_PATTERN = Pattern.compile(
|
||||
"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
);
|
||||
|
||||
public static boolean isValidPhone(String phoneNumber) {
|
||||
return true; // TODO
|
||||
//return VALID_EMAIL_PATTERN.matcher(email).matches();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user