Add POST /users/usePasswordResetToken to use password reset token without user uuid

This commit is contained in:
Colin McDonald 2016-08-11 13:53:47 -04:00
parent e0090cdeb5
commit 831348b4f8
4 changed files with 99 additions and 38 deletions

View File

@ -409,6 +409,7 @@ public final class APIv3 extends AbstractVerticle {
http.post("/users/:userId/passwordReset").blockingHandler(new POSTUsersIdPasswordReset(), false); http.post("/users/:userId/passwordReset").blockingHandler(new POSTUsersIdPasswordReset(), false);
http.post("/users/:userId/registerEmail").blockingHandler(new POSTUsersIdRegisterEmail(), false); http.post("/users/:userId/registerEmail").blockingHandler(new POSTUsersIdRegisterEmail(), false);
http.post("/users/:userId/registerPhone").blockingHandler(new POSTUsersIdRegisterPhone(), false); http.post("/users/:userId/registerPhone").blockingHandler(new POSTUsersIdRegisterPhone(), false);
http.post("/users/usePasswordResetToken").blockingHandler(new POSTUsersUsePasswordResetToken(), false);
http.post("/users/:userId/setupTotp").blockingHandler(new POSTUsersIdSetupTotp(), false); http.post("/users/:userId/setupTotp").blockingHandler(new POSTUsersIdSetupTotp(), false);
http.post("/users/:userId/verifyTotp").handler(new POSTUsersIdVerifyTotp()); http.post("/users/:userId/verifyTotp").handler(new POSTUsersIdVerifyTotp());

View File

@ -136,6 +136,10 @@ public final class User {
usersCollection.find(new Document("email", email)).first(SyncUtils.vertxWrap(callback)); usersCollection.find(new Document("email", email)).first(SyncUtils.vertxWrap(callback));
} }
public static void findByPasswordResetToken(String passwordResetToken, SingleResultCallback<User> callback) {
usersCollection.find(new Document("passwordResetToken", passwordResetToken)).first(SyncUtils.vertxWrap(callback));
}
public static void findByEmailToken(String emailToken, SingleResultCallback<User> callback) { public static void findByEmailToken(String emailToken, SingleResultCallback<User> callback) {
usersCollection.find(new Document("pendingEmailToken", emailToken)).first(SyncUtils.vertxWrap(callback)); usersCollection.find(new Document("pendingEmailToken", emailToken)).first(SyncUtils.vertxWrap(callback));
} }

View File

@ -18,56 +18,40 @@ public final class POSTUsersIdChangePassword implements Handler<RoutingContext>
public void handle(RoutingContext ctx) { public void handle(RoutingContext ctx) {
User user = SyncUtils.runBlocking(v -> User.findById(ctx.request().getParam("userId"), v)); User user = SyncUtils.runBlocking(v -> User.findById(ctx.request().getParam("userId"), v));
JsonObject requestBody = ctx.getBodyAsJson();
if (user == null) { if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("userId")); ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("userId"));
return; return;
} }
JsonObject requestBody = ctx.getBodyAsJson(); if (user.getPassword() == null) {
ErrorUtils.respondInvalidInput(ctx, "User provided does not have password set.");
return;
}
if (requestBody.containsKey("currentPassword")) { if (!requestBody.containsKey("currentPassword")) {
if (user.getPassword() == null) { ErrorUtils.respondRequiredInput(ctx, "currentPassword");
ErrorUtils.respondInvalidInput(ctx, "User provided does not have password set."); return;
return; }
}
if (!user.checkPassword(requestBody.getString("currentPassword"))) { if (!user.checkPassword(requestBody.getString("currentPassword"))) {
ErrorUtils.respondInvalidInput(ctx, "Could not authorize password change.");
return;
}
RequiresTotpResult requiresTotp = SyncUtils.runBlocking(v -> user.requiresTotpAuthorization(null, v));
if (requiresTotp == RequiresTotpResult.REQUIRED_NO_EXEMPTIONS) {
int code = requestBody.getInteger("totpCode");
TotpAuthorizationResult totpAuthorizationResult = SyncUtils.runBlocking(v -> user.checkTotpAuthorization(code, null, v));
if (!totpAuthorizationResult.isAuthorized()) {
ErrorUtils.respondInvalidInput(ctx, "Totp authorization failed: " + totpAuthorizationResult.name());
return;
}
}
} else if (requestBody.containsKey("passwordResetToken")) {
if (user.getPasswordResetToken() == null) {
ErrorUtils.respondInvalidInput(ctx, "User provided does not have password reset token set.");
return;
}
if (!user.getPasswordResetToken().equals(requestBody.getString("passwordResetToken"))) {
ErrorUtils.respondInvalidInput(ctx, "Could not authorize password change.");
return;
}
if ((System.currentTimeMillis() - user.getPasswordResetTokenSetAt().toEpochMilli()) > TimeUnit.DAYS.toMillis(2)) {
ErrorUtils.respondOther(ctx, 409, "Password reset token is expired.", "passwordTokenExpired", ImmutableMap.of());
return;
}
} else {
ErrorUtils.respondInvalidInput(ctx, "Could not authorize password change."); ErrorUtils.respondInvalidInput(ctx, "Could not authorize password change.");
return; return;
} }
RequiresTotpResult requiresTotp = SyncUtils.runBlocking(v -> user.requiresTotpAuthorization(null, v));
if (requiresTotp == RequiresTotpResult.REQUIRED_NO_EXEMPTIONS) {
int code = requestBody.getInteger("totpCode");
TotpAuthorizationResult totpAuthorizationResult = SyncUtils.runBlocking(v -> user.checkTotpAuthorization(code, null, v));
if (!totpAuthorizationResult.isAuthorized()) {
ErrorUtils.respondInvalidInput(ctx, "Totp authorization failed: " + totpAuthorizationResult.name());
return;
}
}
String newPassword = requestBody.getString("newPassword"); String newPassword = requestBody.getString("newPassword");
if (PasswordUtils.isTooShort(newPassword)) { if (PasswordUtils.isTooShort(newPassword)) {

View File

@ -0,0 +1,72 @@
package net.frozenorb.apiv3.route.users;
import com.google.common.collect.ImmutableMap;
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.User;
import net.frozenorb.apiv3.util.*;
import java.util.concurrent.TimeUnit;
public final class POSTUsersUsePasswordResetToken implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
JsonObject requestBody = ctx.getBodyAsJson();
if (!requestBody.containsKey("passwordResetToken")) {
ErrorUtils.respondRequiredInput(ctx, "passwordResetToken");
return;
}
String passwordResetToken = requestBody.getString("passwordResetToken");
User user = SyncUtils.runBlocking(v -> User.findByPasswordResetToken(passwordResetToken, v));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "Password reset token", passwordResetToken);
return;
}
if ((System.currentTimeMillis() - user.getPasswordResetTokenSetAt().toEpochMilli()) > TimeUnit.DAYS.toMillis(2)) {
ErrorUtils.respondOther(ctx, 409, "Password reset token is expired.", "passwordTokenExpired", ImmutableMap.of());
return;
}
String newPassword = requestBody.getString("newPassword");
if (PasswordUtils.isTooShort(newPassword)) {
ErrorUtils.respondOther(ctx, 409, "Your password is too short.", "passwordTooShort", ImmutableMap.of());
return;
}
if (PasswordUtils.isTooSimple(newPassword)) {
ErrorUtils.respondOther(ctx, 409, "Your password is too simple.", "passwordTooSimple", ImmutableMap.of());
return;
}
user.updatePassword(newPassword);
SyncUtils.<Void>runBlocking(v -> user.save(v));
SyncUtils.<Void>runBlocking(v -> UserSessionUtils.invalidateAllSessions(user.getId(), v));
String userIp = requestBody.getString("userIp");
if (!IpUtils.isValidIp(userIp)) {
ErrorUtils.respondInvalidInput(ctx, "Ip address \"" + userIp + "\" is not valid.");
return;
}
AuditLog.log(user.getId(), userIp, ctx, AuditLogActionType.USER_CHANGE_PASSWORD, (ignored, error) -> {
if (error != null) {
ErrorUtils.respondInternalError(ctx, error);
} else {
APIv3.respondJson(ctx, 200, ImmutableMap.of(
"success", true,
"uuid", user.getId()
));
}
});
}
}