Add disposable login tokens feature. Documentation is available at https://github.com/FrozenOrb/APIv3/wiki/Disposable-Login-Token-Routes
This commit is contained in:
parent
ea70e1b347
commit
3e78262cd5
@ -62,6 +62,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.disposableLoginTokens.POSTDisposableLoginTokens;
|
||||||
|
import net.frozenorb.apiv3.route.disposableLoginTokens.POSTDisposableLoginTokensIdUse;
|
||||||
import net.frozenorb.apiv3.route.emailTokens.GETEmailTokensIdOwner;
|
import net.frozenorb.apiv3.route.emailTokens.GETEmailTokensIdOwner;
|
||||||
import net.frozenorb.apiv3.route.emailTokens.POSTEmailTokensIdConfirm;
|
import net.frozenorb.apiv3.route.emailTokens.POSTEmailTokensIdConfirm;
|
||||||
import net.frozenorb.apiv3.route.grants.DELETEGrantsId;
|
import net.frozenorb.apiv3.route.grants.DELETEGrantsId;
|
||||||
@ -306,6 +308,9 @@ public final class APIv3 extends AbstractVerticle {
|
|||||||
|
|
||||||
http.get("/chatFilter").handler(new GETChatFilter());
|
http.get("/chatFilter").handler(new GETChatFilter());
|
||||||
|
|
||||||
|
http.post("/disposableLoginTokens").blockingHandler(new POSTDisposableLoginTokens(), false);
|
||||||
|
http.post("/disposableLoginTokens/:disposableLoginToken/use").blockingHandler(new POSTDisposableLoginTokensIdUse(), false);
|
||||||
|
|
||||||
http.get("/emailTokens/:emailToken/owner").blockingHandler(new GETEmailTokensIdOwner(), false);
|
http.get("/emailTokens/:emailToken/owner").blockingHandler(new GETEmailTokensIdOwner(), false);
|
||||||
http.post("/emailTokens/:emailToken/confirm").blockingHandler(new POSTEmailTokensIdConfirm(), false);
|
http.post("/emailTokens/:emailToken/confirm").blockingHandler(new POSTEmailTokensIdConfirm(), false);
|
||||||
|
|
||||||
|
@ -10,6 +10,8 @@ import java.time.Instant;
|
|||||||
public enum AuditLogActionType {
|
public enum AuditLogActionType {
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
DISPOSABLE_LOGIN_TOKEN_USE(false),
|
||||||
|
DISPOSABLE_LOGIN_TOKEN_CREATE(false),
|
||||||
ACCESS_TOKEN_CREATE(false),
|
ACCESS_TOKEN_CREATE(false),
|
||||||
ACCESS_TOKEN_UPDATE(false),
|
ACCESS_TOKEN_UPDATE(false),
|
||||||
ACCESS_TOKEN_DELETE(false),
|
ACCESS_TOKEN_DELETE(false),
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
package net.frozenorb.apiv3.route.disposableLoginTokens;
|
||||||
|
|
||||||
|
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.model.User;
|
||||||
|
import net.frozenorb.apiv3.util.DisposableLoginTokenUtils;
|
||||||
|
import net.frozenorb.apiv3.util.ErrorUtils;
|
||||||
|
import net.frozenorb.apiv3.util.IpUtils;
|
||||||
|
import net.frozenorb.apiv3.util.SyncUtils;
|
||||||
|
|
||||||
|
public final class POSTDisposableLoginTokens implements Handler<RoutingContext> {
|
||||||
|
|
||||||
|
public void handle(RoutingContext ctx) {
|
||||||
|
JsonObject requestBody = ctx.getBodyAsJson();
|
||||||
|
User user = SyncUtils.runBlocking(v -> User.findById(requestBody.getString("user"), v));
|
||||||
|
String userIp = requestBody.getString("userIp");
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
ErrorUtils.respondNotFound(ctx, "User", requestBody.getString("user"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IpUtils.isValidIp(userIp)) {
|
||||||
|
ErrorUtils.respondInvalidInput(ctx, "Ip address \"" + userIp + "\" is not valid.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposableLoginTokenUtils.createToken(user.getId(), userIp, (token, error) -> {
|
||||||
|
if (error != null) {
|
||||||
|
ErrorUtils.respondInternalError(ctx, error);
|
||||||
|
} else {
|
||||||
|
APIv3.respondJson(ctx, 200, ImmutableMap.of(
|
||||||
|
"success", true,
|
||||||
|
"token", token
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package net.frozenorb.apiv3.route.disposableLoginTokens;
|
||||||
|
|
||||||
|
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.util.*;
|
||||||
|
|
||||||
|
public final class POSTDisposableLoginTokensIdUse implements Handler<RoutingContext> {
|
||||||
|
|
||||||
|
public void handle(RoutingContext ctx) {
|
||||||
|
JsonObject requestBody = ctx.getBodyAsJson();
|
||||||
|
String disposableLoginToken = ctx.request().getParam("disposableLoginToken");
|
||||||
|
String userIp = requestBody.getString("userip");
|
||||||
|
|
||||||
|
if (disposableLoginToken == null || disposableLoginToken.isEmpty()) {
|
||||||
|
ErrorUtils.respondRequiredInput(ctx, "disposableLoginToken");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IpUtils.isValidIp(userIp)) {
|
||||||
|
ErrorUtils.respondInvalidInput(ctx, "Ip address \"" + userIp + "\" is not valid.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposableLoginTokenUtils.useToken(disposableLoginToken, userIp, (user, error) -> {
|
||||||
|
if (error != null) {
|
||||||
|
ErrorUtils.respondInternalError(ctx, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
ErrorUtils.respondOther(ctx, 409, "Disposable login token provided is not valid", "disposableLoginTokenNotValid", ImmutableMap.of());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuditLog.log(user.getId(), userIp, ctx, AuditLogActionType.DISPOSABLE_LOGIN_TOKEN_USE, (ignored, error2) -> {
|
||||||
|
if (error2 != null) {
|
||||||
|
ErrorUtils.respondInternalError(ctx, error2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
APIv3.respondJson(ctx, 200, ImmutableMap.of(
|
||||||
|
"authorized", true,
|
||||||
|
"uuid", user.getId(),
|
||||||
|
"session", SyncUtils.<String>runBlocking(v -> UserSessionUtils.createSession(user.getId(), userIp, v))
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
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 net.frozenorb.apiv3.model.User;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class DisposableLoginTokenUtils {
|
||||||
|
|
||||||
|
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 useToken(String token, String userIp, SingleResultCallback<User> callback) {
|
||||||
|
if (token == null || token.isEmpty()) {
|
||||||
|
callback.onResult(null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
redisClient.get("apiv3:disposableLoginTokens:" + userIp + ":" + token, (result) -> {
|
||||||
|
if (result.failed()) {
|
||||||
|
callback.onResult(null, result.cause());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.result() == null) {
|
||||||
|
callback.onResult(null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
User.findById(result.result(), (user, error) -> {
|
||||||
|
if (error != null) {
|
||||||
|
callback.onResult(null, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
redisClient.del("apiv3:disposableLoginTokens:" + userIp + ":" + token, (result2) -> {
|
||||||
|
if (result2.failed()) {
|
||||||
|
callback.onResult(null, result2.cause());
|
||||||
|
} else {
|
||||||
|
callback.onResult(user, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void createToken(UUID user, String userIp, SingleResultCallback<String> callback) {
|
||||||
|
String token = UUID.randomUUID().toString().replaceAll("-", "");
|
||||||
|
|
||||||
|
redisClient.setex("apiv3:disposableLoginTokens:" + userIp + ":" + token, TimeUnit.MINUTES.toSeconds(5), user.toString(), (result) -> {
|
||||||
|
if (result.succeeded()) {
|
||||||
|
callback.onResult(token, null);
|
||||||
|
} else {
|
||||||
|
callback.onResult(null, result.cause());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user