diff --git a/application.properties b/application.properties deleted file mode 100644 index b952ae8..0000000 --- a/application.properties +++ /dev/null @@ -1,23 +0,0 @@ -mongo.address=158.69.26.208 -mongo.port=27027 -mongo.database=MineHQDev -mongo.username= -mongo.password= - -redis.address=209.222.96.50 -redis.port=6379 - -http.port=80 -http.keystoreFile= -http.keystorePassword= - -mandrill.apiKey=0OYtwymqJP6oqvszeJu0vQ -mandrill.fromEmail=no-reply@minehq.com -mandrill.fromName=MineHQ Network - -maxMind.userId=66817 -maxMind.licenseKey=8Aw9NsOUeOp7 - -zang.accountSid=ACf18890845596403e330944d98886440c -zang.authToken=dc70bbd1fbd8411ba133fa93813a461b -zang.fromNumber=339-337-5300 \ No newline at end of file diff --git a/application.yml b/application.yml new file mode 100644 index 0000000..b1fe996 --- /dev/null +++ b/application.yml @@ -0,0 +1,35 @@ +mongoUri: mongodb://158.69.26.208:27027/MineHQ +redisUri: redis://209.222.96.50:6379 + +http: + port: 80 + keystoreFile: + keystorePassword: + +disposableLoginToken: + tokenLifetimeSeconds: 300 + +ipHashing: + salt: J$gMsq6#!sWTK^JvB!px + +userSession: + sessionExpirationTimeDays: 30 + +totp: + windowSize: 10 + recentlyUsedPeriodSeconds: 300 + ipAuthorizationDays: 5 + +maxMind: + userId: 66817 + licenseKey: 8Aw9NsOUeOp7 + +mandrill: + apiKey: 0OYtwymqJP6oqvszeJu0vQ + fromEmail: no-reply@minehq.com + fromName: MineHQ Network + +zang: + accountSid: ACf18890845596403e330944d98886440c + authToken: dc70bbd1fbd8411ba133fa93813a461b + fromNumber: 339-337-5300 \ No newline at end of file diff --git a/src/main/java/net/frozenorb/apiv3/Main.java b/src/main/java/net/frozenorb/apiv3/Main.java index 2053b4f..974ff36 100644 --- a/src/main/java/net/frozenorb/apiv3/Main.java +++ b/src/main/java/net/frozenorb/apiv3/Main.java @@ -3,6 +3,7 @@ package net.frozenorb.apiv3; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.core.env.Environment; import org.springframework.scheduling.annotation.EnableScheduling; import javax.annotation.PostConstruct; @@ -14,6 +15,7 @@ import io.vertx.core.Vertx; class Main { @Autowired private APIv3 verticle; + @Autowired private Environment environment; public static void main(String[] args) { System.setProperty("vertx.logger-delegate-factory-class-name", "io.vertx.core.logging.SLF4JLogDelegateFactory"); diff --git a/src/main/java/net/frozenorb/apiv3/config/MongoConfig.java b/src/main/java/net/frozenorb/apiv3/config/MongoConfig.java index b91ac68..303cefc 100644 --- a/src/main/java/net/frozenorb/apiv3/config/MongoConfig.java +++ b/src/main/java/net/frozenorb/apiv3/config/MongoConfig.java @@ -8,13 +8,16 @@ import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.mongodb.ConnectionString; -import com.mongodb.MongoCredential; import com.mongodb.async.client.MongoClient; import com.mongodb.async.client.MongoClientSettings; import com.mongodb.async.client.MongoClients; import com.mongodb.async.client.MongoDatabase; import com.mongodb.client.model.IndexModel; import com.mongodb.connection.ClusterSettings; +import com.mongodb.connection.ConnectionPoolSettings; +import com.mongodb.connection.ServerSettings; +import com.mongodb.connection.SocketSettings; +import com.mongodb.connection.SslSettings; import net.frozenorb.apiv3.serialization.jackson.InstantJsonDeserializer; import net.frozenorb.apiv3.serialization.jackson.InstantJsonSerializer; @@ -32,7 +35,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.time.Instant; -import java.util.List; import java.util.UUID; import fr.javatic.mongo.jacksonCodec.JacksonCodecProvider; @@ -42,82 +44,68 @@ import fr.javatic.mongo.jacksonCodec.ObjectMapperFactory; public class MongoConfig { @Bean - public MongoDatabase mongoDatabase( - @Value("${mongo.username}") String username, - @Value("${mongo.database}") String database, - @Value("${mongo.password}") String password, - @Value("${mongo.address}") String address, - @Value("${mongo.port}") int port - ) { - List credentials = ImmutableList.of(); + public MongoDatabase mongoDatabase(@Value("${mongoUri}") String mongoUri) { + ConnectionString connStr = new ConnectionString(mongoUri); - if (!username.isEmpty()) { - credentials = ImmutableList.of( - MongoCredential.createCredential(username, database, password.toCharArray()) - ); - } - - ConnectionString connectionString = new ConnectionString("mongodb://" + address + ":" + port); - - MongoClient mongoClient = MongoClients.create(MongoClientSettings - .builder() - .codecRegistry(CodecRegistries.fromProviders(ImmutableList.of( - new UuidCodecProvider(), // MHQ, fixes uuid serialization - new ValueCodecProvider(), - new DocumentCodecProvider(), - new BsonValueCodecProvider(), - new JacksonCodecProvider(createMongoJacksonMapper()) // Jackson codec, provides serialization/deserialization - ))) - .credentialList(credentials) - .clusterSettings(ClusterSettings.builder() - .applyConnectionString(connectionString) - .build() - ) - .build() + // all of these lines except for .codecRegistry are copied from MongoClients#create(ConnectionString) + MongoClient mongoClient = MongoClients.create(MongoClientSettings.builder() + .clusterSettings(ClusterSettings.builder().applyConnectionString(connStr).build()) + .connectionPoolSettings(ConnectionPoolSettings.builder().applyConnectionString(connStr).build()) + .serverSettings(ServerSettings.builder().build()).credentialList(connStr.getCredentialList()) + .sslSettings(SslSettings.builder().applyConnectionString(connStr).build()) + .socketSettings(SocketSettings.builder().applyConnectionString(connStr).build()) + .codecRegistry(CodecRegistries.fromProviders(ImmutableList.of( + new UuidCodecProvider(), // MHQ, fixes uuid serialization + new ValueCodecProvider(), + new DocumentCodecProvider(), + new BsonValueCodecProvider(), + new JacksonCodecProvider(createMongoJacksonMapper()) // Jackson codec, provides serialization/deserialization + ))) + .build() ); - MongoDatabase db = mongoClient.getDatabase(database); + MongoDatabase database = mongoClient.getDatabase(connStr.getDatabase()); - db.getCollection("auditLog").createIndexes(ImmutableList.of( - new IndexModel(new Document("user", 1)), - new IndexModel(new Document("performedAt", 1)), - new IndexModel(new Document("type", 1)) + database.getCollection("auditLog").createIndexes(ImmutableList.of( + new IndexModel(new Document("user", 1)), + new IndexModel(new Document("performedAt", 1)), + new IndexModel(new Document("type", 1)) ), (a, b) -> {}); - db.getCollection("grants").createIndexes(ImmutableList.of( - new IndexModel(new Document("user", 1)), - new IndexModel(new Document("rank", 1)), - new IndexModel(new Document("addedAt", 1)) + database.getCollection("grants").createIndexes(ImmutableList.of( + new IndexModel(new Document("user", 1)), + new IndexModel(new Document("rank", 1)), + new IndexModel(new Document("addedAt", 1)) ), (a, b) -> {}); - db.getCollection("ipLog").createIndexes(ImmutableList.of( - new IndexModel(new Document("user", 1)), - new IndexModel(new Document("user", 1).append("userIp", 1)), - new IndexModel(new Document("hashedUserIp", 1)) + database.getCollection("ipLog").createIndexes(ImmutableList.of( + new IndexModel(new Document("user", 1)), + new IndexModel(new Document("user", 1).append("userIp", 1)), + new IndexModel(new Document("hashedUserIp", 1)) ), (a, b) -> {}); - db.getCollection("ipBans").createIndexes(ImmutableList.of( - new IndexModel(new Document("userIp", 1)) + database.getCollection("ipBans").createIndexes(ImmutableList.of( + new IndexModel(new Document("userIp", 1)) ), (a, b) -> {}); - db.getCollection("ipIntel").createIndexes(ImmutableList.of( - new IndexModel(new Document("hashedIp", 1)), - new IndexModel(new Document("location", "2dsphere")) + database.getCollection("ipIntel").createIndexes(ImmutableList.of( + new IndexModel(new Document("hashedIp", 1)), + new IndexModel(new Document("location", "2dsphere")) ), (a, b) -> {}); - db.getCollection("punishments").createIndexes(ImmutableList.of( - new IndexModel(new Document("user", 1)), - new IndexModel(new Document("type", 1)), - new IndexModel(new Document("addedAt", 1)), - new IndexModel(new Document("addedBy", 1)), - new IndexModel(new Document("linkedIpBanId", 1)) + database.getCollection("punishments").createIndexes(ImmutableList.of( + new IndexModel(new Document("user", 1)), + new IndexModel(new Document("type", 1)), + new IndexModel(new Document("addedAt", 1)), + new IndexModel(new Document("addedBy", 1)), + new IndexModel(new Document("linkedIpBanId", 1)) ), (a, b) -> {}); - db.getCollection("users").createIndexes(ImmutableList.of( - new IndexModel(new Document("lastUsername", 1)), - new IndexModel(new Document("lastUsernameLower", 1)), - new IndexModel(new Document("emailToken", 1)), - new IndexModel(new Document("totpSecret", 1)) + database.getCollection("users").createIndexes(ImmutableList.of( + new IndexModel(new Document("lastUsername", 1)), + new IndexModel(new Document("lastUsernameLower", 1)), + new IndexModel(new Document("emailToken", 1)), + new IndexModel(new Document("totpSecret", 1)) ), (a, b) -> {}); - db.getCollection("userMeta").createIndexes(ImmutableList.of( - new IndexModel(new Document("user", 1).append("serverGroup", 1)) + database.getCollection("userMeta").createIndexes(ImmutableList.of( + new IndexModel(new Document("user", 1).append("serverGroup", 1)) ), (a, b) -> {}); - return db; + return database; } private ObjectMapper createMongoJacksonMapper() { diff --git a/src/main/java/net/frozenorb/apiv3/config/RedisConfig.java b/src/main/java/net/frozenorb/apiv3/config/RedisConfig.java index be25b4a..5a4f504 100644 --- a/src/main/java/net/frozenorb/apiv3/config/RedisConfig.java +++ b/src/main/java/net/frozenorb/apiv3/config/RedisConfig.java @@ -4,6 +4,8 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import java.net.URI; + import io.vertx.core.Vertx; import io.vertx.redis.RedisClient; import io.vertx.redis.RedisOptions; @@ -17,11 +19,10 @@ public class RedisConfig { } @Bean - public RedisOptions redisOptions( - @Value("${redis.address}") String address, - @Value("${redis.port}") int port - ) { - return new RedisOptions().setAddress(address).setPort(port); + public RedisOptions redisOptions(@Value("${redisUri}") URI redisUri) { + return new RedisOptions() + .setAddress(redisUri.getHost()) + .setPort(redisUri.getPort()); } } \ No newline at end of file diff --git a/src/main/java/net/frozenorb/apiv3/disposablelogintoken/RedisDisposableLoginTokenService.java b/src/main/java/net/frozenorb/apiv3/disposablelogintoken/RedisDisposableLoginTokenService.java index 9f7dec1..4338e3c 100644 --- a/src/main/java/net/frozenorb/apiv3/disposablelogintoken/RedisDisposableLoginTokenService.java +++ b/src/main/java/net/frozenorb/apiv3/disposablelogintoken/RedisDisposableLoginTokenService.java @@ -5,10 +5,10 @@ import com.mongodb.async.SingleResultCallback; import net.frozenorb.apiv3.model.User; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.UUID; -import java.util.concurrent.TimeUnit; import io.vertx.redis.RedisClient; @@ -16,6 +16,7 @@ import io.vertx.redis.RedisClient; public final class RedisDisposableLoginTokenService implements DisposableLoginTokenService { @Autowired private RedisClient redisClient; + @Value("${disposableLoginToken.tokenLifetimeSeconds}") private int tokenLifetimeSeconds; @Override public void attemptLogin(String token, String userIp, SingleResultCallback callback) { @@ -56,7 +57,7 @@ public final class RedisDisposableLoginTokenService implements DisposableLoginTo public void createToken(UUID user, String userIp, SingleResultCallback callback) { String token = UUID.randomUUID().toString().replaceAll("-", ""); - redisClient.setex("apiv3:disposableLoginTokens:" + userIp + ":" + token, TimeUnit.MINUTES.toSeconds(5), user.toString(), (result) -> { + redisClient.setex("apiv3:disposableLoginTokens:" + userIp + ":" + token, tokenLifetimeSeconds, user.toString(), (result) -> { if (result.succeeded()) { callback.onResult(token, null); } else { diff --git a/src/main/java/net/frozenorb/apiv3/model/User.java b/src/main/java/net/frozenorb/apiv3/model/User.java index 01b044e..d711a71 100644 --- a/src/main/java/net/frozenorb/apiv3/model/User.java +++ b/src/main/java/net/frozenorb/apiv3/model/User.java @@ -47,7 +47,6 @@ import java.util.Random; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; import fr.javatic.mongo.jacksonCodec.Entity; import fr.javatic.mongo.jacksonCodec.objectId.Id; @@ -585,7 +584,7 @@ public final class User { Future markPreAuthFuture = Future.future(); Future markRecentlyUsedFuture = Future.future(); - totpService.markPreAuthorized(this, ip, 3, TimeUnit.DAYS, new MongoToVertxCallback<>(markPreAuthFuture)); + totpService.markPreAuthorized(this, ip, new MongoToVertxCallback<>(markPreAuthFuture)); totpService.markRecentlyUsed(this, code, new MongoToVertxCallback<>(markRecentlyUsedFuture)); CompositeFuture.all(markPreAuthFuture, markRecentlyUsedFuture).setHandler((result) -> { diff --git a/src/main/java/net/frozenorb/apiv3/totp/RedisTotpService.java b/src/main/java/net/frozenorb/apiv3/totp/RedisTotpService.java index 87e7f50..8f9b539 100644 --- a/src/main/java/net/frozenorb/apiv3/totp/RedisTotpService.java +++ b/src/main/java/net/frozenorb/apiv3/totp/RedisTotpService.java @@ -8,17 +8,34 @@ import net.frozenorb.apiv3.model.User; import net.frozenorb.apiv3.util.IpUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; +import javax.annotation.PostConstruct; + import io.vertx.redis.RedisClient; @Component public final class RedisTotpService implements TotpService { - private final GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator(new GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder().setWindowSize(10).build()); @Autowired private RedisClient redisClient; + @Value("${totp.windowSize}") int windowSize; + @Value("${totp.recentlyUsedPeriodSeconds}") int recentlyUsedPeriodSeconds; + @Value("${totp.ipAuthorizationDays}") int ipAuthorizationDays; + private GoogleAuthenticator googleAuthenticator; + + // has to be ran after construction (or else windowSize won't be defined when we go to + // create this object) + @PostConstruct + private void setupGoogleAuth() { + googleAuthenticator = new GoogleAuthenticator( + new GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder() + .setWindowSize(windowSize) + .build() + ); + } @Override public boolean authorizeUser(String secret, int code) { @@ -42,7 +59,7 @@ public final class RedisTotpService implements TotpService { } @Override - public void markPreAuthorized(User user, String ip, long duration, TimeUnit unit, SingleResultCallback callback) { + public void markPreAuthorized(User user, String ip, SingleResultCallback callback) { if (!IpUtils.isValidIp(ip)) { callback.onResult(null, null); return; @@ -50,7 +67,7 @@ public final class RedisTotpService implements TotpService { String key = user.getId() + ":preAuthorizedIp:" + ip.toLowerCase(); - redisClient.setex(key, unit.toSeconds(duration), "", (result) -> { + redisClient.setex(key, TimeUnit.DAYS.toSeconds(ipAuthorizationDays), "", (result) -> { if (result.succeeded()) { callback.onResult(null, null); } else { @@ -74,7 +91,7 @@ public final class RedisTotpService implements TotpService { public void markRecentlyUsed(User user, int code, SingleResultCallback callback) { String key = user.getId() + ":recentTotpCodes:" + code; - redisClient.setex(key, TimeUnit.MINUTES.toSeconds(5), "", (result) -> { + redisClient.setex(key, recentlyUsedPeriodSeconds, "", (result) -> { if (result.succeeded()) { callback.onResult(null, null); } else { diff --git a/src/main/java/net/frozenorb/apiv3/totp/TotpService.java b/src/main/java/net/frozenorb/apiv3/totp/TotpService.java index 6c973d2..4715c9f 100644 --- a/src/main/java/net/frozenorb/apiv3/totp/TotpService.java +++ b/src/main/java/net/frozenorb/apiv3/totp/TotpService.java @@ -6,8 +6,6 @@ import net.frozenorb.apiv3.model.User; import org.springframework.stereotype.Service; -import java.util.concurrent.TimeUnit; - @Service public interface TotpService { @@ -15,7 +13,7 @@ public interface TotpService { void isPreAuthorized(User user, String ip, SingleResultCallback callback); - void markPreAuthorized(User user, String ip, long duration, TimeUnit unit, SingleResultCallback callback); + void markPreAuthorized(User user, String ip, SingleResultCallback callback); void wasRecentlyUsed(User user, int code, SingleResultCallback callback); diff --git a/src/main/java/net/frozenorb/apiv3/usersession/RedisUserSessionService.java b/src/main/java/net/frozenorb/apiv3/usersession/RedisUserSessionService.java index 61b4f66..7640ef4 100644 --- a/src/main/java/net/frozenorb/apiv3/usersession/RedisUserSessionService.java +++ b/src/main/java/net/frozenorb/apiv3/usersession/RedisUserSessionService.java @@ -3,6 +3,7 @@ package net.frozenorb.apiv3.usersession; import com.mongodb.async.SingleResultCallback; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.List; @@ -15,6 +16,7 @@ import io.vertx.redis.RedisClient; public final class RedisUserSessionService implements UserSessionService { @Autowired private RedisClient redisClient; + @Value("${userSession.sessionExpirationTimeDays}") private int sessionExpirationTimeDays; @Override public void sessionExists(String userIp, String userSession, SingleResultCallback callback) { @@ -37,7 +39,7 @@ public final class RedisUserSessionService implements UserSessionService { String userSession = UUID.randomUUID().toString().replaceAll("-", ""); String key = "apiv3:sessions:" + userIp + ":" + userSession; - redisClient.setex(key, TimeUnit.DAYS.toSeconds(30), "", (result) -> { + redisClient.setex(key, TimeUnit.DAYS.toSeconds(sessionExpirationTimeDays), "", (result) -> { if (result.succeeded()) { redisClient.sadd("apiv3:sessions:" + user, key, (result2) -> { if (result2.succeeded()) {