diff --git a/pom.xml b/pom.xml index 5fd7fa3..3e28914 100644 --- a/pom.xml +++ b/pom.xml @@ -72,6 +72,11 @@ mandrillClient 1.1 + + org.mindrot + jbcrypt + 0.3m + org.mongodb.morphia morphia diff --git a/src/main/java/net/frozenorb/apiv3/models/User.java b/src/main/java/net/frozenorb/apiv3/models/User.java index be8deca..8eea1a2 100644 --- a/src/main/java/net/frozenorb/apiv3/models/User.java +++ b/src/main/java/net/frozenorb/apiv3/models/User.java @@ -1,9 +1,11 @@ package net.frozenorb.apiv3.models; import lombok.Getter; +import lombok.Setter; import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.weirdStuff.ExcludeFromReplies; import org.bson.Document; +import org.mindrot.jbcrypt.BCrypt; import org.mongodb.morphia.annotations.Entity; import org.mongodb.morphia.annotations.Id; @@ -16,9 +18,10 @@ public final class User { @Getter private String lastName; @Getter @ExcludeFromReplies private Map aliases; @Getter @ExcludeFromReplies private String otpCode; + @Getter @ExcludeFromReplies @Setter private String emailToken; + @Getter @ExcludeFromReplies @Setter private Date emailTokenSet; @Getter @ExcludeFromReplies private String password; - @Getter @ExcludeFromReplies private String passwordSalt; - @Getter private String email; + @Getter @Setter private String email; @Getter private int phoneNumber; @Getter private String lastSeenOn; @Getter private Date lastSeenAt; @@ -52,6 +55,10 @@ public final class User { return APIv3.getDatastore().createQuery(User.class).field("lastName").equalIgnoreCase(name).get(); } + public static User byEmailToken(String name) { + return APIv3.getDatastore().createQuery(User.class).field("emailToken").equal(name).get(); + } + public static List values() { return APIv3.getDatastore().createQuery(User.class).asList(); } @@ -64,7 +71,6 @@ public final class User { this.aliases = new HashMap<>(); this.otpCode = null; this.password = null; - this.passwordSalt = null; this.email = null; this.phoneNumber = -1; this.lastSeenOn = null; @@ -101,8 +107,8 @@ public final class User { return APIv3.getDatastore().createQuery(Punishment.class).field("target").equal(id).asList(); } - public List getPunishments(Punishment.PunishmentType type) { - return APIv3.getDatastore().createQuery(Punishment.class).field("target").equal(id).field("type").equal(type).asList(); + public List getPunishments(Collection types) { + return APIv3.getDatastore().createQuery(Punishment.class).field("target").equal(id).field("type").in(types).asList(); } public UserMetaEntry getMeta(ServerGroup group) { @@ -120,4 +126,12 @@ public final class User { } } + public void setPassword(char[] unencrypted) { + this.password = BCrypt.hashpw(new String(unencrypted), BCrypt.gensalt()); + } + + public boolean checkPassword(char[] unencrypted) { + return BCrypt.checkpw(new String(unencrypted), password); + } + } \ No newline at end of file diff --git a/src/main/java/net/frozenorb/apiv3/routes/GETRoutes.java b/src/main/java/net/frozenorb/apiv3/routes/GETRoutes.java index eb8bbc6..a157e89 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/GETRoutes.java +++ b/src/main/java/net/frozenorb/apiv3/routes/GETRoutes.java @@ -19,6 +19,7 @@ public final class GETRoutes implements Route { private Field pathField; private Field targetField; + @SuppressWarnings("unchecked") // Casting List to List public GETRoutes() { try { Object spark = Spark.getInstance(); diff --git a/src/main/java/net/frozenorb/apiv3/routes/POSTConfirmRegister.java b/src/main/java/net/frozenorb/apiv3/routes/POSTConfirmRegister.java new file mode 100644 index 0000000..106fe87 --- /dev/null +++ b/src/main/java/net/frozenorb/apiv3/routes/POSTConfirmRegister.java @@ -0,0 +1,55 @@ +package net.frozenorb.apiv3.routes; + +import com.google.common.collect.ImmutableList; +import net.frozenorb.apiv3.APIv3; +import net.frozenorb.apiv3.models.User; +import net.frozenorb.apiv3.weirdStuff.ErrorUtils; +import org.bson.Document; +import spark.Request; +import spark.Response; +import spark.Route; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +public final class POSTConfirmRegister implements Route { + + private List commonPasswords = ImmutableList.copyOf(("123456 password 12345678 qwerty 123456789 12345 1234 111111 1234567 dragon " + + "123123 baseball abc123 football monkey letmein 696969 shadow master 666666 qwertyuiop 123321 mustang 1234567890 " + + "michael 654321 pussy superman 1qaz2wsx 7777777 fuckyou 121212 000000 qazwsx 123qwe killer trustno1 jordan jennifer " + + "zxcvbnm asdfgh hunter buster soccer harley batman andrew tigger sunshine iloveyou fuckme 2000 charlie robert thomas " + + "hockey ranger daniel starwars klaster 112233 george asshole computer michelle jessica pepper 1111 zxcvbn 555555 11111111" + + " 131313 freedom 777777 pass fuck maggie 159753 aaaaaa ginger princess joshua cheese amanda summer love ashley 6969 " + + "nicole chelsea biteme matthew access yankees 987654321 dallas austin thunder taylor matrix").split(" ")); + + public Object handle(Request req, Response res) { + User user = User.byEmailToken(req.params("emailToken")); + + if (user == null) { + return ErrorUtils.notFound("Email token", req.params("emailToken")); + } + + if (user.getEmail() != null) { + return ErrorUtils.error("User provided already has email set."); + } + + if ((System.currentTimeMillis() - user.getEmailTokenSet().getTime()) > TimeUnit.DAYS.toMillis(2)) { + return ErrorUtils.error("Email token is expired"); + } + + String password = req.queryParams("password"); + + if (password.length() < 8) { + return ErrorUtils.error("Your password is too short."); + } else if (commonPasswords.contains(password)) { + return ErrorUtils.error("Your password is too common. Please use a more secure password."); + } + + user.setEmailToken(null); + user.setPassword(password.toCharArray()); + APIv3.getDatastore().save(user); + + return new Document("success", true).append("message", "User confirmed"); + } + +} \ No newline at end of file diff --git a/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserNotify.java b/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserNotify.java index 89c1a70..58ccb1d 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserNotify.java +++ b/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserNotify.java @@ -21,7 +21,7 @@ public final class POSTUserNotify implements Route { return ErrorUtils.notFound("User", req.params("id")); } - if (user.getEmail() == null || user.getEmail().isEmpty()) { + if (user.getEmail() == null) { return ErrorUtils.error("User provided does not have email set."); } diff --git a/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserRegister.java b/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserRegister.java index 831ef58..f172535 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserRegister.java +++ b/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserRegister.java @@ -1,13 +1,28 @@ package net.frozenorb.apiv3.routes.users; +import com.google.common.collect.ImmutableMap; +import net.frozenorb.apiv3.APIv3; +import net.frozenorb.apiv3.models.NotificationTemplate; import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.weirdStuff.ErrorUtils; +import net.frozenorb.apiv3.weirdStuff.Notification; +import org.bson.Document; import spark.Request; import spark.Response; import spark.Route; +import java.math.BigInteger; +import java.util.Date; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + public final class POSTUserRegister implements Route { + public static final Pattern VALID_EMAIL_ADDRESS_REGEX = + Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE); + public Object handle(Request req, Response res) { User user = User.byIdOrName(req.params("id")); @@ -15,11 +30,39 @@ public final class POSTUserRegister implements Route { return ErrorUtils.notFound("User", req.params("id")); } - if (user.getEmail() == null || user.getEmail().isEmpty()) { - return ErrorUtils.error("User provided does not have email set."); + if (user.getEmail() != null) { + return ErrorUtils.error("User provided already has email set."); } - return null; + String email = req.queryParams("email"); + + if (!VALID_EMAIL_ADDRESS_REGEX.matcher(email).find()) { + return ErrorUtils.error(email + " is not a valid email."); + } + + if (user.getEmailToken() != null && (System.currentTimeMillis() - user.getEmailTokenSet().getTime()) < TimeUnit.DAYS.toMillis(2)) { + return ErrorUtils.error("We just recently sent you a confirmation email. Please wait before trying to register again."); + } + + user.setEmail(email); + user.setEmailToken(new BigInteger(130, new Random()).toString(32)); + user.setEmailTokenSet(new Date()); + APIv3.getDatastore().save(user); + + Map replacements = ImmutableMap.of( + "username", user.getLastName(), + "email", user.getEmail(), + "emailToken", user.getEmailToken() + ); + + Notification notification = new Notification(NotificationTemplate.byId("email-confirmation"), replacements, replacements); + + try { + notification.sendAsEmail(user.getEmail()); + return new Document("success", true).append("message", "User registered"); + } catch (Exception ex) { + return ErrorUtils.error("Failed to send confirmation email. Please contact a MineHQ staff member."); + } } } \ No newline at end of file diff --git a/src/main/java/net/frozenorb/apiv3/weirdStuff/ErrorUtils.java b/src/main/java/net/frozenorb/apiv3/weirdStuff/ErrorUtils.java index e4d7488..cc0b386 100644 --- a/src/main/java/net/frozenorb/apiv3/weirdStuff/ErrorUtils.java +++ b/src/main/java/net/frozenorb/apiv3/weirdStuff/ErrorUtils.java @@ -14,12 +14,12 @@ public class ErrorUtils { return error("Unauthorized access: Permission \"" + permission + "\" required."); } - public static Document invalidInput(String reason) { - return error("Invalid input: " + reason); + public static Document invalidInput(String message) { + return error("Invalid input: " + message); } - public static Document error(String reason) { - return new Document("success", false).append("reason", reason); + public static Document error(String message) { + return new Document("success", false).append("message", message); } } \ No newline at end of file diff --git a/src/main/java/net/frozenorb/apiv3/weirdStuff/MandrillUtils.java b/src/main/java/net/frozenorb/apiv3/weirdStuff/MandrillUtils.java index 543b0b9..638016d 100644 --- a/src/main/java/net/frozenorb/apiv3/weirdStuff/MandrillUtils.java +++ b/src/main/java/net/frozenorb/apiv3/weirdStuff/MandrillUtils.java @@ -5,7 +5,7 @@ import com.cribbstechnologies.clients.mandrill.request.MandrillRESTRequest; import com.cribbstechnologies.clients.mandrill.util.MandrillConfiguration; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.experimental.UtilityClass; -import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import java.util.Properties; @@ -31,7 +31,7 @@ public class MandrillUtils { request.setConfig(config); request.setObjectMapper(new ObjectMapper()); - request.setHttpClient(new DefaultHttpClient()); + request.setHttpClient(HttpClientBuilder.create().build()); MandrillMessagesRequest messagesRequest = new MandrillMessagesRequest(); messagesRequest.setRequest(request);