├── .travis.yml ├── LICENSE ├── README.md ├── pom.xml ├── renovate.json ├── settings.xml └── src └── main ├── java └── org │ └── inventivetalent │ └── mcauth │ ├── DatabaseClient.java │ ├── MCAuthServer.java │ └── data │ ├── Request.java │ └── Status.java └── resources ├── bungee.yml └── config.yml /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | 5 | script: "mvn deploy --settings settings.xml" 6 | env: 7 | global: 8 | - secure: "gFrKLMjIuyw0kaMAiHaYeuwTVbhboSTVyzsN152MNNwCydjXKsgIAuFRngRtQvXcXAfInBp+2nlI/NuyfQ7Ztfwos9SmsI4dX0cXYtnmICfRCIDOEGHzRnoJAXb9D7ASa3JvipHCOXCQ+xi68XecsfihJy771u7C4OOs+Lz/0NHrEab7TFmiDkCEFWSTyB78+pfLtkZnKzbArkQHc7MVQBiiPs+wObYd1mZpktCwiBlx/zDfFy+WLV+R7iQZC0mVSzWdK4YZRdIpFg3gTyj+QF9pDSE096HqeBMDe1XNWiLNz7DJqGkp5yMtPjUp2IZnTGqj3Xrt7f388ftBFZqxpdITNn4JmdS9XH3KOBMp7js1zkS8sLN6C5aFPFrhOaPnmPLibUThomFUb9OuXVr7WGcHq+nZvZYuiAU51nJJOGjCy4POtc1qan3OZ+3ax7iiuLmh1ZmfJYwT3T4KuPztjzVQMm6xX9aAVYtQ/nKhuFioKEf/7bNOGuoSAjQaRqjmCyQxx2ZvC7ukX+wS7YhDIQ4xxsKnZw1J5uQMPUfyZjeSEIilruNlIDxwZfifGxPwQkQibLA+XrQ7x0v472fX3mwENyP/Hm97ZxNpgslU6eTFErYZXcyampFrWqQPBCpMbI4GTdHJWAasz9aR9kzSafbJCYQiUvMrOFEGClcK4HI=" 9 | - secure: "ptv3CRQyQ2xiJ4utQLPPKHCD4TiXsXWJ1WK87AcDw5mbO8w4YixeZyGEK3H5lLjmPGYdLKSQIPN+0ouTm/kkmI2Bjf0MJtZtmvpTs1TYCVGJ8GdyUGyIB3treRlM/HEfyV68fCRzPDdcpDq3IUShTi+Lu8V8noueL8TK9VJWL2eWjdprw8Tg724vZpOheXwneYRSUKJyrWyPs9M/gAtCD655AqpDOBtj3aHL1OvSTVNaqFtD1mcxXezgjMBVgXAzbEFRUm5e+hpTmAr69kd1WmNhBHNVRxOMZN7D0TqMrieyd3vinuvHZz3S3K9aXa4wYHWWDdSOLpWxqUCg0bADeTDWmkDo3rHUMcvgVseB0R/PQFIi4woQqYyjEE3tN4RzCrPHUyCb6/5qy3gzcAXm6JrtBIwyHqLCidR9+HZp2wPBUlHlMr+8Ezwn3sJgwFd76OUtKEhcYLbPzpPYrm3uRG7mjvYAAk6mkn8taRmEZXhx2BL+zDdI9GB1rYyicb5rk+jyaJ5KGlTPNNUrZxMPcGHC9dXL7kTUhajgzOKkt8Hi+frTXwRSu1jyk2wBUXNQUOMmiK23SBLMCnlVMo6jCUd7YJv30czzdgtBvrvnN3iuLrxMpXr+Y43OCT4BNCwFzAYH9hWHDTpIN2YWunvBiOm/kXNrTkyFUPqesapvK6c=" 10 | deploy: 11 | provider: releases 12 | api_key: 13 | secure: "BXT0k69K89PL50YyoTOVqDzB/HiRuXb5Em/vAEpZmNF3ldZTj1B/j/GsqI8RHQdP5S9yDapO/s0ets4muOx3Sbsb+qoB0J79wUhRswxhlJMV/6U3JuNs1BcA6cc4elkuIo7d2/PTQr9WV8ivlodfTJdUdDP6ZFcjLbNX9nvkZHOOXxWoSBci9qJ/ZX1Qx7ASa07a2iBjTjr4XWDKN2zqLynJ0mQG/oBWcPa7XJOVDMxPgWssXEA99i5y4JGhQNW3LatS8MC4HBJtqXQwKYCoXxTtG+JfwzeQe26GYdRqLKr5xKy7AyGjCWtUe9C1cxy3npYhDOYsEpvoXJ45DY02wwE6AbniYb5e7rHYRpglFlBsFtlcoJx5M9beuxcfmDHeLbtCb2CDHau5zffxqreO6WBVKAaBomyoW9R/tdeBsVQAOPOy9CozIAJBJGZm6XW8Id6Ic9w/OPC56nJgxjk09cLc9ltec/oH4HCBZKp8w9hcQpjCCb/oHmsaBjBfgiFgFx7afpsjtZGdQp8A+l6wSE5iuMBW3TaPc0ekcqjU3l2XViwEgSZPL+slvgSYAWqLPj5wwE5TuieIjmLd3bf/hF9kLVqtsWd06tB1S2VWgZ+ilpertQUAynG4ErxuQ/5ndGqpY4ziK6lXMN1QbF5i0kU6CSQT8eRfRX1XuJfLeW0=" 14 | file_glob: true 15 | file: 16 | - "target/mcauth-server-*.jar" 17 | skip_cleanup: true 18 | on: 19 | tags: true 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Haylee Schäfer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MinecraftID Server 2 | 3 | [![Build Status](https://travis-ci.org/MC-Auth/MinecraftID-Server.svg?branch=master)](https://travis-ci.org/MC-Auth/MinecraftID-Server) 4 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.inventivetalent 8 | mcauth-server 9 | 1.1.0-SNAPSHOT 10 | 11 | 12 | 13 | 14 | src/main/java 15 | 16 | **/*.java 17 | 18 | 19 | 20 | src/main/resources 21 | true 22 | 23 | bungee.yml 24 | config.yml 25 | 26 | 27 | 28 | 29 | 30 | maven-compiler-plugin 31 | 3.3 32 | 33 | 1.8 34 | 1.8 35 | 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-shade-plugin 40 | 2.4 41 | 42 | 43 | package 44 | 45 | shade 46 | 47 | 48 | 49 | 50 | org.inventivetalent:mcauth** 51 | org.mongodb:** 52 | org.jongo:** 53 | com.fasterxml.jackson.core:** 54 | de.undercouch:** 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | net.md-5 67 | bungeecord-api 68 | 1.10-SNAPSHOT 69 | 70 | 71 | net.md-5 72 | bungeecord-event 73 | 1.10-SNAPSHOT 74 | 75 | 76 | net.md-5 77 | bungeecord-config 78 | 1.10-SNAPSHOT 79 | 80 | 81 | 82 | org.mongodb 83 | mongodb-driver 84 | 3.12.0 85 | 86 | 87 | org.mongodb 88 | bson 89 | 3.12.0 90 | 91 | 92 | org.jongo 93 | jongo 94 | 1.3.0 95 | 96 | 97 | com.google.code.gson 98 | gson 99 | 2.6.2 100 | 101 | 102 | 103 | org.projectlombok 104 | lombok 105 | 1.16.8 106 | 107 | 108 | 109 | 110 | 111 | bungeecord-repo 112 | https://oss.sonatype.org/content/repositories/snapshots 113 | 114 | 115 | 116 | 117 | 118 | sonatype-nexus-releases 119 | http://repo.inventivetalent.org/content/repositories/releases/ 120 | 121 | 122 | sonatype-nexus-snapshots 123 | http://repo.inventivetalent.org/content/repositories/snapshots/ 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "maven": { 6 | "enabled": true 7 | }, 8 | "ignoreUnstable": false, 9 | "hostRules": [{ 10 | "hostType": "maven", 11 | "endpoint": "https://repo.inventivetalent.org/content/groups/public/" 12 | }] 13 | } 14 | -------------------------------------------------------------------------------- /settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sonatype-nexus-releases 5 | ${env.CI_DEPLOY_USERNAME} 6 | ${env.CI_DEPLOY_PASSWORD} 7 | 8 | 9 | sonatype-nexus-snapshots 10 | ${env.CI_DEPLOY_USERNAME} 11 | ${env.CI_DEPLOY_PASSWORD} 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/mcauth/DatabaseClient.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mcauth; 2 | 3 | import com.mongodb.MongoClient; 4 | import com.mongodb.MongoClientOptions; 5 | import com.mongodb.MongoCredential; 6 | import com.mongodb.ServerAddress; 7 | import com.mongodb.client.MongoDatabase; 8 | import org.jongo.Jongo; 9 | 10 | import java.io.IOException; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | public class DatabaseClient { 15 | 16 | private List hosts; 17 | 18 | private String dbName; 19 | private String host; 20 | private int port; 21 | private MongoCredential credential; 22 | 23 | private MongoClient mongoClient; 24 | private MongoDatabase mongoDatabase; 25 | 26 | public DatabaseClient(String dbName, List hosts, String user, char[] pass, String authDatabase) { 27 | this.dbName = dbName; 28 | this.hosts = hosts; 29 | this.credential = MongoCredential.createScramSha1Credential(user, authDatabase, pass); 30 | } 31 | 32 | public DatabaseClient(String dbName, String host, int port, String user, char[] pass, String authDatabase) { 33 | this.dbName = dbName; 34 | this.host = host; 35 | this.port = port; 36 | this.credential = MongoCredential.createScramSha1Credential(user, authDatabase, pass); 37 | } 38 | 39 | public int collectionCount() { 40 | int c = 0; 41 | for (String ignored : db().listCollectionNames()) { 42 | c++; 43 | } 44 | return c; 45 | } 46 | 47 | public ServerAddress connect(int timeout) throws IOException { 48 | if (mongoClient == null) { 49 | if (hosts != null && !hosts.isEmpty()) { 50 | System.out.println("Connecting to MongoDB..."); 51 | mongoClient = new MongoClient(this.hosts, this.credential, MongoClientOptions.builder().connectTimeout(timeout).build()); 52 | } else { 53 | System.out.println("Connecting to MongoDB " + this.host + ":" + this.port + "..."); 54 | mongoClient = new MongoClient(new ServerAddress(this.host, this.port), Collections.singletonList(this.credential), MongoClientOptions.builder().connectTimeout(timeout).build()); 55 | } 56 | } 57 | return mongoClient.getAddress(); 58 | } 59 | 60 | public void disconnect() throws IOException { 61 | if (mongoClient != null) { 62 | mongoClient.close(); 63 | } 64 | } 65 | 66 | public MongoDatabase db() { 67 | if (mongoDatabase == null) { 68 | System.out.println("Initializing database '" + dbName + "'"); 69 | mongoDatabase = mongoClient.getDatabase(dbName); 70 | } 71 | return mongoDatabase; 72 | } 73 | 74 | public Jongo jongo() { 75 | return new Jongo(mongoClient.getDB(dbName)); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/mcauth/MCAuthServer.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mcauth; 2 | 3 | import com.mongodb.ServerAddress; 4 | import net.md_5.bungee.api.ProxyServer; 5 | import net.md_5.bungee.api.ServerPing; 6 | import net.md_5.bungee.api.chat.TextComponent; 7 | import net.md_5.bungee.api.event.LoginEvent; 8 | import net.md_5.bungee.api.event.ProxyPingEvent; 9 | import net.md_5.bungee.api.plugin.Listener; 10 | import net.md_5.bungee.api.plugin.Plugin; 11 | import net.md_5.bungee.config.Configuration; 12 | import net.md_5.bungee.config.ConfigurationProvider; 13 | import net.md_5.bungee.config.YamlConfiguration; 14 | import net.md_5.bungee.event.EventHandler; 15 | import org.inventivetalent.mcauth.data.Request; 16 | import org.inventivetalent.mcauth.data.Status; 17 | import org.jongo.Jongo; 18 | import org.jongo.MongoCollection; 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | import java.nio.file.Files; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | import java.util.Random; 27 | 28 | public class MCAuthServer extends Plugin implements Listener { 29 | 30 | Configuration config; 31 | DatabaseClient databaseClient; 32 | 33 | Jongo jongo; 34 | MongoCollection requestsCollection; 35 | MongoCollection accountsCollection; 36 | 37 | @Override 38 | public void onEnable() { 39 | ProxyServer.getInstance().getPluginManager().registerListener(this, this); 40 | 41 | if (!getDataFolder().exists()) { getDataFolder().mkdir(); } 42 | File file = new File(getDataFolder(), "config.yml"); 43 | if (!file.exists()) { 44 | try (InputStream in = getResourceAsStream("config.yml")) { 45 | Files.copy(in, file.toPath()); 46 | throw new RuntimeException("Default config saved! Please edit and restart."); 47 | } catch (IOException e) { 48 | throw new RuntimeException("Failed to save default config", e); 49 | } 50 | } 51 | 52 | try { 53 | config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(new File(getDataFolder(), "config.yml")); 54 | } catch (IOException e) { 55 | throw new RuntimeException("Failed to load config", e); 56 | } 57 | 58 | if (config.contains("mongodb.hosts")) { 59 | List hosts = new ArrayList<>(); 60 | for (String s : config.getStringList("mongodb.hosts")) { 61 | String[] split = s.split(":"); 62 | hosts.add(new ServerAddress(split[0], split.length > 1 ? Integer.parseInt(split[1]) : ServerAddress.defaultPort())); 63 | } 64 | databaseClient = new DatabaseClient(config.getString("mongodb.database"), hosts, config.getString("mongodb.login.user"), config.getString("mongodb.login.pass").toCharArray(), config.getString("mongodb.login.db")); 65 | } else { 66 | databaseClient = new DatabaseClient(config.getString("mongodb.database"), config.getString("mongodb.host"), config.getInt("mongodb.port"), config.getString("mongodb.login.user"), config.getString("mongodb.login.pass").toCharArray(), config.getString("mongodb.login.db")); 67 | } 68 | try { 69 | databaseClient.connect(10000); 70 | databaseClient.collectionCount(); 71 | } catch (IOException e) { 72 | throw new RuntimeException("Failed to connect to database", e); 73 | } 74 | 75 | jongo = databaseClient.jongo(); 76 | requestsCollection = jongo.getCollection("requests"); 77 | accountsCollection = jongo.getCollection("accounts"); 78 | } 79 | 80 | @EventHandler 81 | public void onLogin(LoginEvent event) { 82 | String ip = event.getConnection().getAddress().getAddress().getHostAddress(); 83 | getLogger().info(event.getConnection().getName() + " connecting (" + ip + ")..."); 84 | 85 | event.registerIntent(this); 86 | getProxy().getScheduler().runAsync(this, () -> { 87 | Request request = requestsCollection.findOne("{username: #, status: #}", event.getConnection().getName(), Status.REQUESTED).as(Request.class); 88 | 89 | if (request == null) { 90 | event.setCancelReason("§cThere is no authentication request for your username"); 91 | getLogger().info("No request found"); 92 | event.setCancelled(true); 93 | event.completeIntent(this); 94 | return; 95 | } 96 | getLogger().info("Handling request #" + request.get_id()); 97 | 98 | if (System.currentTimeMillis() - (request.getCreated().getTime()) > 5 * 60 * 1000) {// 5 Minutes timeout 99 | event.setCancelReason("§cYour authentication request timed out"); 100 | event.setCancelled(true); 101 | 102 | request.setStatus(Status.TIMEOUT_LOGIN); 103 | requestsCollection.update("{_id: #}", request.get_id()).upsert().with(request); 104 | 105 | event.completeIntent(this); 106 | return; 107 | } 108 | 109 | request.setToken(generateToken(6)); 110 | request.setTokenTime(System.currentTimeMillis()); 111 | request.setStatus(Status.TOKEN_GENERATED); 112 | request.setUuid(event.getConnection().getUniqueId().toString().replaceAll("-", "")); 113 | 114 | requestsCollection.update("{_id: #}", request.get_id()).upsert().with(request); 115 | 116 | // Kick player 117 | event.setCancelReason("§aYour account has been authenticated.\n" 118 | + "§aPlease enter this code on the website: §b" + request.getToken()); 119 | event.setCancelled(true); 120 | event.completeIntent(this); 121 | }); 122 | } 123 | 124 | @EventHandler 125 | public void onPing(ProxyPingEvent event) { 126 | String ip = event.getConnection().getAddress().getAddress().getHostAddress(); 127 | 128 | event.registerIntent(this); 129 | getProxy().getScheduler().runAsync(this, () -> { 130 | Request request = requestsCollection.findOne("{request_ip: #, status: #}", ip, Status.REQUESTED).as(Request.class); 131 | 132 | if (request != null) { 133 | if (System.currentTimeMillis() - (request.getCreated().getTime()) < 5 * 60 * 1000) { 134 | event.setResponse(new ServerPing( 135 | new ServerPing.Protocol("MinecraftID", event.getConnection().getVersion()), 136 | new ServerPing.Players(1, 0, new ServerPing.PlayerInfo[0]), 137 | new TextComponent("§8MinecraftID Server - §7minecraft.id\n" 138 | + "§aJoin now to verify your account!"), 139 | getProxy().getConfig().getFaviconObject() 140 | )); 141 | event.completeIntent(this); 142 | return; 143 | } 144 | } 145 | event.setResponse(new ServerPing( 146 | new ServerPing.Protocol("MinecraftID", event.getConnection().getVersion()), 147 | new ServerPing.Players(0, 0, new ServerPing.PlayerInfo[0]), 148 | new TextComponent("§8MinecraftID Server - §7minecraft.id\n" 149 | + "§6There is no request to verify your account"), 150 | getProxy().getConfig().getFaviconObject() 151 | )); 152 | event.completeIntent(this); 153 | }); 154 | } 155 | 156 | String generateToken(int length) { 157 | String string = ""; 158 | Random random = new Random(); 159 | for (int i = 0; i < length; i++) { 160 | string += String.valueOf(random.nextInt(10)); 161 | } 162 | return string; 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/mcauth/data/Request.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mcauth.data; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Date; 6 | 7 | @Data 8 | public class Request { 9 | 10 | String _id; 11 | String request_id; 12 | String request_ip; 13 | String username; 14 | String uuid; 15 | Status status; 16 | String token; 17 | long tokenTime; 18 | Date created; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/inventivetalent/mcauth/data/Status.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mcauth.data; 2 | 3 | public enum Status { 4 | STARTED, 5 | REQUESTED, 6 | TOKEN_GENERATED, 7 | TIMEOUT_LOGIN, 8 | TIMEOUT_TOKEN, 9 | VERIFIED, 10 | FAILED; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/resources/bungee.yml: -------------------------------------------------------------------------------- 1 | name: MCAuth-Server 2 | main: org.inventivetalent.mcauth.MCAuthServer 3 | version: 1.1.0 4 | author: inventivetalent 5 | -------------------------------------------------------------------------------- /src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | mongodb: 2 | host: "127.0.0.1" 3 | port: 27017 4 | database: "" 5 | login: 6 | user: "" 7 | pass: "" 8 | db: "" --------------------------------------------------------------------------------