Add password reset route. Closes #29
This commit is contained in:
parent
4901e63599
commit
fd1ec2b475
@ -298,6 +298,7 @@ public final class APIv3 extends AbstractVerticle {
|
|||||||
http.post("/users/:id/leave").handler(new POSTUserLeave());
|
http.post("/users/:id/leave").handler(new POSTUserLeave());
|
||||||
http.post("/users/:id/login").blockingHandler(new POSTUserLogin());
|
http.post("/users/:id/login").blockingHandler(new POSTUserLogin());
|
||||||
http.post("/users/:id/notify").blockingHandler(new POSTUserNotify(), false);
|
http.post("/users/:id/notify").blockingHandler(new POSTUserNotify(), false);
|
||||||
|
http.post("/users/:id/passwordReset").blockingHandler(new POSTUserPasswordReset(), false);
|
||||||
http.post("/users/:id/register").blockingHandler(new POSTUserRegister(), false);
|
http.post("/users/:id/register").blockingHandler(new POSTUserRegister(), false);
|
||||||
http.post("/users/:id/setupTotp").blockingHandler(new POSTUserSetupTotp(), false);
|
http.post("/users/:id/setupTotp").blockingHandler(new POSTUserSetupTotp(), false);
|
||||||
http.post("/users/:id/verifyTotp").blockingHandler(new POSTUserVerifyTotp(), false);
|
http.post("/users/:id/verifyTotp").blockingHandler(new POSTUserVerifyTotp(), false);
|
||||||
|
@ -43,6 +43,8 @@ public final class User {
|
|||||||
@Getter @ExcludeFromReplies private Map<String, Instant> aliases = new HashMap<>();
|
@Getter @ExcludeFromReplies private Map<String, Instant> aliases = new HashMap<>();
|
||||||
@Getter @ExcludeFromReplies @Setter private String totpSecret;
|
@Getter @ExcludeFromReplies @Setter private String totpSecret;
|
||||||
@Getter @ExcludeFromReplies private String password;
|
@Getter @ExcludeFromReplies private String password;
|
||||||
|
@Getter @ExcludeFromReplies private String passwordResetToken;
|
||||||
|
@Getter @ExcludeFromReplies private Instant passwordResetTokenSetAt;
|
||||||
@Getter private String email;
|
@Getter private String email;
|
||||||
@Getter private Instant registeredAt;
|
@Getter private Instant registeredAt;
|
||||||
@Getter @ExcludeFromReplies private String pendingEmail;
|
@Getter @ExcludeFromReplies private String pendingEmail;
|
||||||
@ -317,10 +319,17 @@ public final class User {
|
|||||||
this.online = false;
|
this.online = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPassword(String input) {
|
public void startPasswordReset() {
|
||||||
|
this.passwordResetToken = UUID.randomUUID().toString().replaceAll("-", "");
|
||||||
|
this.passwordResetTokenSetAt = Instant.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updatePassword(String password) {
|
||||||
|
this.passwordResetToken = null;
|
||||||
|
this.passwordResetTokenSetAt = null;
|
||||||
this.password = Hashing
|
this.password = Hashing
|
||||||
.sha256()
|
.sha256()
|
||||||
.hashString(input + "$" + id.toString(), Charsets.UTF_8)
|
.hashString(password + "$" + id.toString(), Charsets.UTF_8)
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ public final class POSTEmailTokensConfirm implements Handler<RoutingContext> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((System.currentTimeMillis() - user.getPendingEmailTokenSetAt().toEpochMilli()) > TimeUnit.DAYS.toMillis(2)) {
|
if ((System.currentTimeMillis() - user.getPendingEmailTokenSetAt().toEpochMilli()) > TimeUnit.DAYS.toMillis(2)) {
|
||||||
ErrorUtils.respondGeneric(ctx, 200, "Email token is expired");
|
ErrorUtils.respondInvalidInput(ctx, "Email token is expired");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,12 +40,12 @@ public final class POSTEmailTokensConfirm implements Handler<RoutingContext> {
|
|||||||
String password = requestBody.getString("password");
|
String password = requestBody.getString("password");
|
||||||
|
|
||||||
if (password.length() < 8) {
|
if (password.length() < 8) {
|
||||||
ErrorUtils.respondGeneric(ctx, 200, "Your password is too short.");
|
ErrorUtils.respondInvalidInput(ctx, "Your password is too short.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
user.completeRegistration(user.getPendingEmail());
|
user.completeRegistration(user.getPendingEmail());
|
||||||
user.setPassword(password);
|
user.updateUsername(password);
|
||||||
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
|
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
|
||||||
user.save(callback);
|
user.save(callback);
|
||||||
callback.get();
|
callback.get();
|
||||||
|
@ -11,6 +11,8 @@ import net.frozenorb.apiv3.unsorted.BlockingCallback;
|
|||||||
import net.frozenorb.apiv3.unsorted.RequiresTotpResult;
|
import net.frozenorb.apiv3.unsorted.RequiresTotpResult;
|
||||||
import net.frozenorb.apiv3.util.ErrorUtils;
|
import net.frozenorb.apiv3.util.ErrorUtils;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public final class POSTUserChangePassword implements Handler<RoutingContext> {
|
public final class POSTUserChangePassword implements Handler<RoutingContext> {
|
||||||
|
|
||||||
public void handle(RoutingContext ctx) {
|
public void handle(RoutingContext ctx) {
|
||||||
@ -30,19 +32,30 @@ public final class POSTUserChangePassword implements Handler<RoutingContext> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean authorized = user.checkPassword(requestBody.getString("currentPassword"));
|
boolean authorized = false;
|
||||||
|
|
||||||
if (!authorized) {
|
if (user.checkPassword(requestBody.getString("currentPassword"))) {
|
||||||
ErrorUtils.respondInvalidInput(ctx, "Current password is not correct.");
|
BlockingCallback<RequiresTotpResult> totpRequiredCallback = new BlockingCallback<>();
|
||||||
return;
|
user.requiresTotpAuthorization(null, totpRequiredCallback);
|
||||||
|
RequiresTotpResult requiresTotp = totpRequiredCallback.get();
|
||||||
|
|
||||||
|
if (requiresTotp == RequiresTotpResult.REQUIRED_NO_EXEMPTIONS) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
authorized = true;
|
||||||
|
} else if (user.getPasswordResetToken() != null && user.getPasswordResetToken().equals(requestBody.getString("passwordResetToken"))) {
|
||||||
|
if ((System.currentTimeMillis() - user.getPasswordResetTokenSetAt().toEpochMilli()) > TimeUnit.DAYS.toMillis(2)) {
|
||||||
|
ErrorUtils.respondGeneric(ctx, 200, "Password reset token is expired");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
authorized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockingCallback<RequiresTotpResult> totpRequiredCallback = new BlockingCallback<>();
|
if (!authorized) {
|
||||||
user.requiresTotpAuthorization(null, totpRequiredCallback);
|
ErrorUtils.respondInvalidInput(ctx, "Could not authorize password change.");
|
||||||
RequiresTotpResult requiresTotp = totpRequiredCallback.get();
|
return;
|
||||||
|
|
||||||
if (requiresTotp == RequiresTotpResult.REQUIRED_NO_EXEMPTIONS) {
|
|
||||||
// TODO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String newPassword = requestBody.getString("newPassword");
|
String newPassword = requestBody.getString("newPassword");
|
||||||
@ -52,7 +65,7 @@ public final class POSTUserChangePassword implements Handler<RoutingContext> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
user.setPassword(newPassword);
|
user.updatePassword(newPassword);
|
||||||
BlockingCallback<UpdateResult> saveCallback = new BlockingCallback<>();
|
BlockingCallback<UpdateResult> saveCallback = new BlockingCallback<>();
|
||||||
user.save(saveCallback);
|
user.save(saveCallback);
|
||||||
saveCallback.get();
|
saveCallback.get();
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
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.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 java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public final class POSTUserPasswordReset 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.getPasswordResetToken() != null && (System.currentTimeMillis() - user.getPasswordResetTokenSetAt().toEpochMilli()) < TimeUnit.DAYS.toMillis(2)) {
|
||||||
|
ErrorUtils.respondInvalidInput(ctx, "User provided already has password reset token set.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
user.startPasswordReset();
|
||||||
|
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
|
||||||
|
user.save(callback);
|
||||||
|
callback.get();
|
||||||
|
|
||||||
|
Map<String, Object> replacements = ImmutableMap.of(
|
||||||
|
"username", user.getLastUsername(),
|
||||||
|
"passwordResetToken", user.getPasswordResetToken()
|
||||||
|
);
|
||||||
|
|
||||||
|
BlockingCallback<NotificationTemplate> notificationTemplateCallback = new BlockingCallback<>();
|
||||||
|
NotificationTemplate.findById("password-reset", notificationTemplateCallback);
|
||||||
|
Notification notification = new Notification(notificationTemplateCallback.get(), replacements, replacements);
|
||||||
|
|
||||||
|
notification.sendAsEmail(user.getEmail(), (ignored, error) -> {
|
||||||
|
if (error != null) {
|
||||||
|
ErrorUtils.respondInternalError(ctx, error);
|
||||||
|
} else {
|
||||||
|
APIv3.respondJson(ctx, ImmutableMap.of(
|
||||||
|
"success", true
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -50,7 +50,7 @@ public final class POSTUserVerifyTotp implements Handler<RoutingContext> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean authorized = TotpUtils.authorizeUser(user, providedCode);
|
boolean authorized = TotpUtils.authorizeUser(user.getTotpSecret(), providedCode);
|
||||||
|
|
||||||
if (authorized) {
|
if (authorized) {
|
||||||
BlockingCallback<Void> markPreAuthorizedCallback = new BlockingCallback<>();
|
BlockingCallback<Void> markPreAuthorizedCallback = new BlockingCallback<>();
|
||||||
|
Loading…
Reference in New Issue
Block a user