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.GETBannedCellCarriersId;
|
||||||
import net.frozenorb.apiv3.route.bannedCellCarriers.POSTBannedCellCarriers;
|
import net.frozenorb.apiv3.route.bannedCellCarriers.POSTBannedCellCarriers;
|
||||||
import net.frozenorb.apiv3.route.chatFilterList.GETChatFilter;
|
import net.frozenorb.apiv3.route.chatFilterList.GETChatFilter;
|
||||||
import net.frozenorb.apiv3.route.emailToken.GETEmailTokensIdOwner;
|
import net.frozenorb.apiv3.route.emailTokens.GETEmailTokensIdOwner;
|
||||||
import net.frozenorb.apiv3.route.emailToken.POSTEmailTokensIdConfirm;
|
import net.frozenorb.apiv3.route.emailTokens.POSTEmailTokensIdConfirm;
|
||||||
import net.frozenorb.apiv3.route.grants.DELETEGrantsId;
|
import net.frozenorb.apiv3.route.grants.DELETEGrantsId;
|
||||||
import net.frozenorb.apiv3.route.grants.GETGrants;
|
import net.frozenorb.apiv3.route.grants.GETGrants;
|
||||||
import net.frozenorb.apiv3.route.grants.GETGrantsId;
|
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.UuidJsonDeserializer;
|
||||||
import net.frozenorb.apiv3.serialization.jackson.UuidJsonSerializer;
|
import net.frozenorb.apiv3.serialization.jackson.UuidJsonSerializer;
|
||||||
import net.frozenorb.apiv3.serialization.mongodb.UuidCodecProvider;
|
import net.frozenorb.apiv3.serialization.mongodb.UuidCodecProvider;
|
||||||
|
import net.frozenorb.apiv3.util.EmailUtils;
|
||||||
import org.bson.Document;
|
import org.bson.Document;
|
||||||
import org.bson.codecs.BsonValueCodecProvider;
|
import org.bson.codecs.BsonValueCodecProvider;
|
||||||
import org.bson.codecs.DocumentCodecProvider;
|
import org.bson.codecs.DocumentCodecProvider;
|
||||||
@ -205,6 +206,7 @@ public final class APIv3 extends AbstractVerticle {
|
|||||||
Rank.updateCache();
|
Rank.updateCache();
|
||||||
Server.updateCache();
|
Server.updateCache();
|
||||||
ServerGroup.updateCache();
|
ServerGroup.updateCache();
|
||||||
|
EmailUtils.updateBannedEmailDomains();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObjectMapper createMongoJacksonMapper() {
|
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/login").blockingHandler(new POSTUsersIdLogin());
|
||||||
http.post("/users/:id/notify").blockingHandler(new POSTUsersIdNotify(), false);
|
http.post("/users/:id/notify").blockingHandler(new POSTUsersIdNotify(), false);
|
||||||
http.post("/users/:id/passwordReset").blockingHandler(new POSTUsersIdPasswordReset(), 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/setupTotp").blockingHandler(new POSTUsersIdSetupTotp(), false);
|
||||||
http.post("/users/:id/verifyTotp").handler(new POSTUsersIdVerifyTotp());
|
http.post("/users/:id/verifyTotp").handler(new POSTUsersIdVerifyTotp());
|
||||||
|
|
||||||
|
@ -36,8 +36,10 @@ public enum AuditLogActionType {
|
|||||||
SERVER_DELETE(false),
|
SERVER_DELETE(false),
|
||||||
USER_CHANGE_PASSWORD(false),
|
USER_CHANGE_PASSWORD(false),
|
||||||
USER_PASSWORD_RESET(false),
|
USER_PASSWORD_RESET(false),
|
||||||
USER_REGISTER(false),
|
USER_REGISTER_EMAIL(false),
|
||||||
|
USER_REGISTER_PHONE(false),
|
||||||
USER_CONFIRM_EMAIL(false),
|
USER_CONFIRM_EMAIL(false),
|
||||||
|
USER_CONFIRM_PHONE(false),
|
||||||
USER_SETUP_TOTP(false),
|
USER_SETUP_TOTP(false),
|
||||||
USER_VERIFY_TOTP(false);
|
USER_VERIFY_TOTP(false);
|
||||||
|
|
||||||
|
@ -55,7 +55,11 @@ public final class User {
|
|||||||
@Getter @ExcludeFromReplies private String pendingEmail;
|
@Getter @ExcludeFromReplies private String pendingEmail;
|
||||||
@Getter @ExcludeFromReplies private String pendingEmailToken;
|
@Getter @ExcludeFromReplies private String pendingEmailToken;
|
||||||
@Getter @ExcludeFromReplies private Instant pendingEmailTokenSetAt;
|
@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 String lastSeenOn;
|
||||||
@Getter private Instant lastSeenAt;
|
@Getter private Instant lastSeenAt;
|
||||||
@Getter private Instant firstSeenAt;
|
@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) {
|
public static void findByEmail(String email, SingleResultCallback<User> callback) {
|
||||||
usersCollection.find(new Document("email", email)).first(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.email = email;
|
||||||
this.registeredAt = Instant.now();
|
this.registeredAt = Instant.now();
|
||||||
this.pendingEmail = null;
|
this.pendingEmail = null;
|
||||||
@ -468,12 +482,20 @@ public final class User {
|
|||||||
this.pendingEmailTokenSetAt = null;
|
this.pendingEmailTokenSetAt = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startRegistration(String pendingEmail) {
|
public void startPhoneRegistration(String phoneNumber) {
|
||||||
this.pendingEmail = pendingEmail;
|
this.pendingPhone = phoneNumber;
|
||||||
this.pendingEmailToken = UUID.randomUUID().toString().replace("-", "");
|
this.pendingEmailToken = UUID.randomUUID().toString().replace("-", "");
|
||||||
this.pendingEmailTokenSetAt = Instant.now();
|
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) {
|
public void hasPermissionAnywhere(String permission, SingleResultCallback<Boolean> callback) {
|
||||||
getCompoundedPermissions((permissions, error) -> {
|
getCompoundedPermissions((permissions, error) -> {
|
||||||
if (error != null) {
|
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.core.Handler;
|
||||||
import io.vertx.ext.web.RoutingContext;
|
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.google.common.collect.ImmutableMap;
|
||||||
import com.mongodb.client.result.UpdateResult;
|
import com.mongodb.client.result.UpdateResult;
|
||||||
@ -46,7 +46,7 @@ public final class POSTEmailTokensIdConfirm implements Handler<RoutingContext> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
user.completeRegistration(user.getPendingEmail());
|
user.completeEmailRegistration(user.getPendingEmail());
|
||||||
user.updatePassword(password);
|
user.updatePassword(password);
|
||||||
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
|
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
|
||||||
user.save(callback);
|
user.save(callback);
|
@ -18,7 +18,7 @@ import net.frozenorb.apiv3.util.ErrorUtils;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public final class POSTUsersIdRegister implements Handler<RoutingContext> {
|
public final class POSTUsersIdRegisterEmail implements Handler<RoutingContext> {
|
||||||
|
|
||||||
public void handle(RoutingContext ctx) {
|
public void handle(RoutingContext ctx) {
|
||||||
BlockingCallback<User> userCallback = new BlockingCallback<>();
|
BlockingCallback<User> userCallback = new BlockingCallback<>();
|
||||||
@ -61,7 +61,7 @@ public final class POSTUsersIdRegister implements Handler<RoutingContext> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
user.startRegistration(email);
|
user.startEmailRegistration(email);
|
||||||
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
|
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
|
||||||
user.save(callback);
|
user.save(callback);
|
||||||
callback.get();
|
callback.get();
|
||||||
@ -80,7 +80,7 @@ public final class POSTUsersIdRegister implements Handler<RoutingContext> {
|
|||||||
if (error != null) {
|
if (error != null) {
|
||||||
ErrorUtils.respondInternalError(ctx, error);
|
ErrorUtils.respondInternalError(ctx, error);
|
||||||
} else {
|
} 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) {
|
if (error2 != null) {
|
||||||
ErrorUtils.respondInternalError(ctx, error2);
|
ErrorUtils.respondInternalError(ctx, error2);
|
||||||
} else {
|
} 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.concurrent.TimeUnit;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@UtilityClass
|
@UtilityClass
|
||||||
public class EmailUtils {
|
public class EmailUtils {
|
||||||
|
|
||||||
@ -23,11 +22,10 @@ public class EmailUtils {
|
|||||||
private static Set<String> bannedEmailDomains = ImmutableSet.of();
|
private static Set<String> bannedEmailDomains = ImmutableSet.of();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
updateBannedEmailDomains();
|
|
||||||
APIv3.getVertxInstance().setPeriodic(TimeUnit.MINUTES.toMillis(10), (id) -> 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) -> {
|
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.bodyHandler((body) -> bannedEmailDomains = ImmutableSet.copyOf(body.toString().split("\n")));
|
||||||
response.exceptionHandler(Throwable::printStackTrace);
|
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