Add user session integration. We still need to add routes that require auth in our session handler
This commit is contained in:
parent
f42f714863
commit
2502f4a1b2
@ -40,10 +40,12 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import net.frozenorb.apiv3.handler.ActorAttributeHandler;
|
import net.frozenorb.apiv3.handler.ActorAttributeHandler;
|
||||||
import net.frozenorb.apiv3.handler.AuthorizationHandler;
|
import net.frozenorb.apiv3.handler.AuthorizationHandler;
|
||||||
import net.frozenorb.apiv3.handler.MetricsHandler;
|
import net.frozenorb.apiv3.handler.MetricsHandler;
|
||||||
|
import net.frozenorb.apiv3.handler.WebsiteUserSessionHandler;
|
||||||
import net.frozenorb.apiv3.model.*;
|
import net.frozenorb.apiv3.model.*;
|
||||||
import net.frozenorb.apiv3.route.GETDumpsType;
|
import net.frozenorb.apiv3.route.GETDumpsType;
|
||||||
import net.frozenorb.apiv3.route.GETMetrics;
|
import net.frozenorb.apiv3.route.GETMetrics;
|
||||||
import net.frozenorb.apiv3.route.GETWhoAmI;
|
import net.frozenorb.apiv3.route.GETWhoAmI;
|
||||||
|
import net.frozenorb.apiv3.route.POSTLogout;
|
||||||
import net.frozenorb.apiv3.route.accessTokens.DELETEAccessTokensId;
|
import net.frozenorb.apiv3.route.accessTokens.DELETEAccessTokensId;
|
||||||
import net.frozenorb.apiv3.route.accessTokens.GETAccessTokens;
|
import net.frozenorb.apiv3.route.accessTokens.GETAccessTokens;
|
||||||
import net.frozenorb.apiv3.route.accessTokens.GETAccessTokensId;
|
import net.frozenorb.apiv3.route.accessTokens.GETAccessTokensId;
|
||||||
@ -274,6 +276,7 @@ public final class APIv3 extends AbstractVerticle {
|
|||||||
http.route().method(HttpMethod.PUT).method(HttpMethod.POST).method(HttpMethod.DELETE).handler(BodyHandler.create());
|
http.route().method(HttpMethod.PUT).method(HttpMethod.POST).method(HttpMethod.DELETE).handler(BodyHandler.create());
|
||||||
http.route().handler(new ActorAttributeHandler());
|
http.route().handler(new ActorAttributeHandler());
|
||||||
http.route().handler(new MetricsHandler());
|
http.route().handler(new MetricsHandler());
|
||||||
|
http.route().handler(new WebsiteUserSessionHandler());
|
||||||
http.route().handler(new AuthorizationHandler());
|
http.route().handler(new AuthorizationHandler());
|
||||||
http.exceptionHandler(Throwable::printStackTrace);
|
http.exceptionHandler(Throwable::printStackTrace);
|
||||||
|
|
||||||
@ -376,6 +379,7 @@ public final class APIv3 extends AbstractVerticle {
|
|||||||
http.get("/dumps/:dumpType").handler(new GETDumpsType());
|
http.get("/dumps/:dumpType").handler(new GETDumpsType());
|
||||||
http.get("/metrics").handler(new GETMetrics());
|
http.get("/metrics").handler(new GETMetrics());
|
||||||
http.get("/whoami").handler(new GETWhoAmI());
|
http.get("/whoami").handler(new GETWhoAmI());
|
||||||
|
http.post("/logout").handler(new POSTLogout());
|
||||||
|
|
||||||
int port = Integer.parseInt(config.getProperty("http.port"));
|
int port = Integer.parseInt(config.getProperty("http.port"));
|
||||||
webServer.requestHandler(http::accept).listen(port);
|
webServer.requestHandler(http::accept).listen(port);
|
||||||
|
@ -2,6 +2,6 @@ package net.frozenorb.apiv3.actor;
|
|||||||
|
|
||||||
public enum ActorType {
|
public enum ActorType {
|
||||||
|
|
||||||
WEBSITE, BUNGEE_CORD, SERVER, UNKNOWN
|
WEBSITE, STORE, BUNGEE_CORD, SERVER, UNKNOWN
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
package net.frozenorb.apiv3.handler;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import io.vertx.core.Handler;
|
||||||
|
import io.vertx.core.http.HttpMethod;
|
||||||
|
import io.vertx.ext.web.RoutingContext;
|
||||||
|
import net.frozenorb.apiv3.actor.Actor;
|
||||||
|
import net.frozenorb.apiv3.actor.ActorType;
|
||||||
|
import net.frozenorb.apiv3.util.ErrorUtils;
|
||||||
|
import net.frozenorb.apiv3.util.UserSessionUtils;
|
||||||
|
|
||||||
|
public final class WebsiteUserSessionHandler implements Handler<RoutingContext> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(RoutingContext ctx) {
|
||||||
|
Actor actor = ctx.get("actor");
|
||||||
|
|
||||||
|
if (actor.getType() != ActorType.WEBSITE) {
|
||||||
|
ctx.next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isUserSessionRequired(ctx)) {
|
||||||
|
ctx.next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String userSession = ctx.request().getHeader("MHQ-UserSession");
|
||||||
|
String userIp = ctx.request().getHeader("MHQ-UserIp");
|
||||||
|
|
||||||
|
UserSessionUtils.sessionExists(userIp, userSession, (exists, error) -> {
|
||||||
|
if (error != null) {
|
||||||
|
ErrorUtils.respondInternalError(ctx, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exists) {
|
||||||
|
ctx.next();
|
||||||
|
} else {
|
||||||
|
ErrorUtils.respondOther(ctx, 403, "User session invalid.", "userSessionInvalid", ImmutableMap.of());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUserSessionRequired(RoutingContext ctx) {
|
||||||
|
HttpMethod method = ctx.request().method();
|
||||||
|
String path = ctx.request().path().toLowerCase();
|
||||||
|
|
||||||
|
/*if (method == HttpMethod.GET) {
|
||||||
|
switch (path) {
|
||||||
|
case "/grants":
|
||||||
|
case "/servers":
|
||||||
|
case "/servergroups":
|
||||||
|
case "/whoami":
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.contains("/dumps")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (method == HttpMethod.POST) {
|
||||||
|
switch (path) {
|
||||||
|
case "/grants":
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
25
src/main/java/net/frozenorb/apiv3/route/POSTLogout.java
Normal file
25
src/main/java/net/frozenorb/apiv3/route/POSTLogout.java
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package net.frozenorb.apiv3.route;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import io.vertx.core.Handler;
|
||||||
|
import io.vertx.ext.web.RoutingContext;
|
||||||
|
import net.frozenorb.apiv3.APIv3;
|
||||||
|
import net.frozenorb.apiv3.util.ErrorUtils;
|
||||||
|
import net.frozenorb.apiv3.util.UserSessionUtils;
|
||||||
|
|
||||||
|
public final class POSTLogout implements Handler<RoutingContext> {
|
||||||
|
|
||||||
|
public void handle(RoutingContext ctx) {
|
||||||
|
String userSession = ctx.request().getHeader("MHQ-UserSession");
|
||||||
|
String userIp = ctx.request().getHeader("MHQ-UserIp");
|
||||||
|
|
||||||
|
UserSessionUtils.invalidateSession(userIp, userSession, (ignored, error) -> {
|
||||||
|
if (error != null) {
|
||||||
|
ErrorUtils.respondInternalError(ctx, error);
|
||||||
|
} else {
|
||||||
|
APIv3.respondJson(ctx, 200, ImmutableMap.of());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
package net.frozenorb.apiv3.route.users;
|
package net.frozenorb.apiv3.route.users;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import io.vertx.core.Handler;
|
import io.vertx.core.Handler;
|
||||||
import io.vertx.ext.web.RoutingContext;
|
import io.vertx.ext.web.RoutingContext;
|
||||||
import net.frozenorb.apiv3.APIv3;
|
import net.frozenorb.apiv3.APIv3;
|
||||||
@ -9,7 +8,10 @@ import net.frozenorb.apiv3.auditLog.AuditLogActionType;
|
|||||||
import net.frozenorb.apiv3.model.User;
|
import net.frozenorb.apiv3.model.User;
|
||||||
import net.frozenorb.apiv3.util.ErrorUtils;
|
import net.frozenorb.apiv3.util.ErrorUtils;
|
||||||
import net.frozenorb.apiv3.util.SyncUtils;
|
import net.frozenorb.apiv3.util.SyncUtils;
|
||||||
|
import net.frozenorb.apiv3.util.UserSessionUtils;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public final class GETUsersIdVerifyPassword implements Handler<RoutingContext> {
|
public final class GETUsersIdVerifyPassword implements Handler<RoutingContext> {
|
||||||
@ -37,15 +39,23 @@ public final class GETUsersIdVerifyPassword implements Handler<RoutingContext> {
|
|||||||
|
|
||||||
final UUID finalUuid = user.getId();
|
final UUID finalUuid = user.getId();
|
||||||
boolean authorized = user.checkPassword(ctx.request().getParam("password"));
|
boolean authorized = user.checkPassword(ctx.request().getParam("password"));
|
||||||
|
String userIp = ctx.request().getParam("userIp");
|
||||||
|
|
||||||
AuditLog.log(user.getId(), ctx.request().getParam("userIp"), ctx, authorized ? AuditLogActionType.USER_LOGIN_SUCCESS : AuditLogActionType.USER_LOGIN_FAIL, (ignored, error) -> {
|
AuditLog.log(user.getId(), userIp, ctx, authorized ? AuditLogActionType.USER_LOGIN_SUCCESS : AuditLogActionType.USER_LOGIN_FAIL, (ignored, error) -> {
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
ErrorUtils.respondInternalError(ctx, error);
|
ErrorUtils.respondInternalError(ctx, error);
|
||||||
} else {
|
} else {
|
||||||
APIv3.respondJson(ctx, 200, ImmutableMap.of(
|
Map<String, Object> result = new HashMap<>();
|
||||||
"authorized", authorized,
|
|
||||||
"uuid", finalUuid
|
result.put("authorized", authorized);
|
||||||
));
|
result.put("uuid", finalUuid);
|
||||||
|
|
||||||
|
if (authorized) {
|
||||||
|
String session = SyncUtils.runBlocking(v -> UserSessionUtils.createSession(finalUuid, userIp, v));
|
||||||
|
result.put("session", session);
|
||||||
|
}
|
||||||
|
|
||||||
|
APIv3.respondJson(ctx, 200, result);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import net.frozenorb.apiv3.unsorted.RequiresTotpResult;
|
|||||||
import net.frozenorb.apiv3.unsorted.TotpAuthorizationResult;
|
import net.frozenorb.apiv3.unsorted.TotpAuthorizationResult;
|
||||||
import net.frozenorb.apiv3.util.ErrorUtils;
|
import net.frozenorb.apiv3.util.ErrorUtils;
|
||||||
import net.frozenorb.apiv3.util.SyncUtils;
|
import net.frozenorb.apiv3.util.SyncUtils;
|
||||||
|
import net.frozenorb.apiv3.util.UserSessionUtils;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@ -71,6 +72,7 @@ public final class POSTUsersIdChangePassword implements Handler<RoutingContext>
|
|||||||
|
|
||||||
user.updatePassword(newPassword);
|
user.updatePassword(newPassword);
|
||||||
SyncUtils.<Void>runBlocking(v -> user.save(v));
|
SyncUtils.<Void>runBlocking(v -> user.save(v));
|
||||||
|
SyncUtils.<Void>runBlocking(v -> UserSessionUtils.invalidateAllSessions(user.getId(), v));
|
||||||
|
|
||||||
AuditLog.log(user.getId(), requestBody.getString("userIp"), ctx, AuditLogActionType.USER_CHANGE_PASSWORD, (ignored, error) -> {
|
AuditLog.log(user.getId(), requestBody.getString("userIp"), ctx, AuditLogActionType.USER_CHANGE_PASSWORD, (ignored, error) -> {
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
|
84
src/main/java/net/frozenorb/apiv3/util/UserSessionUtils.java
Normal file
84
src/main/java/net/frozenorb/apiv3/util/UserSessionUtils.java
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package net.frozenorb.apiv3.util;
|
||||||
|
|
||||||
|
import com.mongodb.async.SingleResultCallback;
|
||||||
|
import io.vertx.redis.RedisClient;
|
||||||
|
import io.vertx.redis.RedisOptions;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import net.frozenorb.apiv3.APIv3;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class UserSessionUtils {
|
||||||
|
|
||||||
|
private static final RedisClient redisClient = RedisClient.create(APIv3.getVertxInstance(),
|
||||||
|
new RedisOptions()
|
||||||
|
.setAddress(APIv3.getConfig().getProperty("redis.address"))
|
||||||
|
.setPort(Integer.parseInt(APIv3.getConfig().getProperty("redis.port")))
|
||||||
|
);
|
||||||
|
|
||||||
|
public static void sessionExists(String userIp, String userSession, SingleResultCallback<Boolean> callback) {
|
||||||
|
if (userIp == null || userIp.isEmpty() || userSession == null || userSession.isEmpty()) {
|
||||||
|
callback.onResult(false, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
redisClient.exists("apiv3:sessions:" + userIp + ":" + userSession, (result) -> {
|
||||||
|
if (result.succeeded()) {
|
||||||
|
callback.onResult(result.result() == 1, null);
|
||||||
|
} else {
|
||||||
|
callback.onResult(null, result.cause());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void createSession(UUID user, String userIp, SingleResultCallback<String> callback) {
|
||||||
|
String userSession = UUID.randomUUID().toString().replaceAll("-", "");
|
||||||
|
String key = "apiv3:sessions:" + userIp + ":" + userSession;
|
||||||
|
|
||||||
|
redisClient.setex(key, TimeUnit.DAYS.toSeconds(30), "", (result) -> {
|
||||||
|
if (result.succeeded()) {
|
||||||
|
redisClient.sadd("apiv3:sessions:" + user, key, (result2) -> {
|
||||||
|
if (result2.succeeded()) {
|
||||||
|
callback.onResult(null, null);
|
||||||
|
} else {
|
||||||
|
callback.onResult(null, result2.cause());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
callback.onResult(null, null);
|
||||||
|
} else {
|
||||||
|
callback.onResult(null, result.cause());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void invalidateSession(String userIp, String userSession, SingleResultCallback<Void> callback) {
|
||||||
|
redisClient.del("apiv3:sessions:" + userIp + ":" + userSession, (result) -> {
|
||||||
|
if (result.succeeded()) {
|
||||||
|
callback.onResult(null, null);
|
||||||
|
} else {
|
||||||
|
callback.onResult(null, result.cause());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void invalidateAllSessions(UUID user, SingleResultCallback<Void> callback) {
|
||||||
|
redisClient.smembers("apiv3:sessions:" + user, (result) -> {
|
||||||
|
if (result.failed()) {
|
||||||
|
callback.onResult(null, result.cause());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
redisClient.delMany((List<String>) result.result().getList(), (result2) -> {
|
||||||
|
if (result2.succeeded()) {
|
||||||
|
callback.onResult(null, null);
|
||||||
|
} else {
|
||||||
|
callback.onResult(null, result2.cause());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user