├── .classpath
├── .dockerignore
├── .gitignore
├── .project
├── .settings
├── org.eclipse.core.resources.prefs
├── org.eclipse.jdt.core.prefs
└── org.eclipse.m2e.core.prefs
├── README.md
├── docker
├── Dockerfile
├── docker-compose.yml
├── frontend
│ ├── .htaccess
│ ├── config.json
│ └── httpd.conf
└── launcher_config.json
├── pom.xml
├── resources
└── .gitkeep
└── src
└── me
└── mrletsplay
└── shittyauth
├── ShittyAuth.java
├── auth
├── AccessToken.java
├── AccessTokenStorage.java
├── FileAccessTokenStorage.java
├── SQLAccessTokenStorage.java
└── StoredAccessToken.java
├── config
└── ShittyAuthSettings.java
├── page
├── AccountPage.java
├── AdminPage.java
├── CreateAccountPage.java
├── SettingsPage.java
└── api
│ ├── UserCapeDocument.java
│ ├── UserSkinDocument.java
│ ├── authlibinjector
│ ├── AuthLibInjectorDocument.java
│ └── AuthLibInjectorMetadataDocument.java
│ ├── legacy
│ ├── LegacyCheckServerDocument.java
│ ├── LegacyJoinServerDocument.java
│ ├── LegacyUserCapeDocument.java
│ └── LegacyUserSkinDocument.java
│ ├── services
│ ├── PlayerAttributesDocument.java
│ ├── PlayerCertificatesDocument.java
│ └── PlayerReportDocument.java
│ ├── shittyauth
│ ├── ShittyAuthAPI.java
│ └── ShittyAuthAdminAPI.java
│ └── yggdrasil
│ ├── AuthenticatePage.java
│ ├── HasJoinedPage.java
│ ├── InvalidatePage.java
│ ├── JoinPage.java
│ ├── ProfilePage.java
│ ├── PublicKeysPage.java
│ ├── RefreshPage.java
│ ├── SignoutPage.java
│ └── ValidatePage.java
├── textures
├── SkinType.java
└── TexturesHelper.java
├── user
├── FileUserDataStorage.java
├── SQLUserDataStorage.java
├── UserData.java
└── UserDataStorage.java
├── util
├── CryptoHelper.java
├── DefaultTexture.java
├── InvalidSkinException.java
├── InvalidUsernameException.java
└── UUIDHelper.java
└── webinterface
└── ShittyAuthWIHandler.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | /bin/
2 | /target/
3 | dependency-reduced-pom.xml
4 | /run/
5 | /cfg/
6 | /data/
7 | /include/
8 | /logs/
9 | /shittyauth/
10 |
11 | /docker/frontend
12 | /docker/docker-compose.yml
13 | /docker/Dockerfile
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /bin/
2 | /target/
3 | dependency-reduced-pom.xml
4 | /run/
5 | /cfg/
6 | /data/
7 | /include/
8 | /logs/
9 | /shittyauth/
10 | /docker/shittyauth/
11 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | ShittyAuthServer
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.m2e.core.maven2Builder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.m2e.core.maven2Nature
21 | org.eclipse.jdt.core.javanature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.core.resources.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | encoding/src=UTF-8
3 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
5 | org.eclipse.jdt.core.compiler.compliance=11
6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate
7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate
8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate
9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
10 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
11 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
12 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
13 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
14 | org.eclipse.jdt.core.compiler.release=enabled
15 | org.eclipse.jdt.core.compiler.source=11
16 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.m2e.core.prefs:
--------------------------------------------------------------------------------
1 | activeProfiles=
2 | eclipse.preferences.version=1
3 | resolveWorkspaceProjects=true
4 | version=1
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ShittyAuthServer
2 | A shitty implementation of the Yggdrasil authentication scheme
3 |
4 | This project implements Mojang's Yggdrasil authentication scheme (which was used before the transition to Microsoft accounts).
5 |
6 | It is intended to be used in conjunction with the [ShittyAuthLauncher](https://github.com/MrLetsplay2003/ShittyAuthLauncher)
7 |
8 | # Compiling the server
9 | The server uses Maven for building.
10 |
11 | To compile the server, use
12 | ```
13 | $ mvn package
14 | ```
15 | which will generate a `ShittyAuthServer-VERSION.jar` in the `target` folder
16 |
17 | # Running the server
18 | First, compile the server or download a prebuilt JAR file from [here](https://ci.graphite-official.com/job/ShittyAuthServer/), then, run it using any Java 11+ VM.
19 |
20 | Afterwards, navigate to `http://your.server.ip:8880` in your web browser. You will be prompted to set up the WebinterfaceAPI server the authentication server uses.
21 |
22 | Once you're done, your players will be able to create accounts (using password authentication) and use their credentials to login in the launcher (after having changing the auth server URL to the correct domain/IP).
23 |
24 | Optional: If you have a setup using an HTTP(S) proxy server (e.g. Apache), make sure to change the Skin base URL in the `Minecraft > Settings` tab to your public domain.
25 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM maven:latest as builder
2 |
3 | COPY . /workspace
4 |
5 | WORKDIR /workspace
6 |
7 | RUN mvn clean package
8 |
9 |
10 |
11 | FROM alpine:latest
12 |
13 | ENV UID=1000
14 | ENV GID=1000
15 |
16 | RUN apk update && apk add openjdk11 shadow sudo
17 |
18 | COPY --from=mrletsplay/docker_launcher /usr/local/bin/docker_launcher /usr/local/bin/docker_launcher
19 | COPY ./docker/launcher_config.json /shittyauth/launcher_config.json
20 | COPY --from=builder /workspace/target/ShittyAuthServer-*.jar /shittyauth/ShittyAuthServer.jar
21 |
22 | RUN useradd shittyauth
23 |
24 | RUN mkdir /shittyauth/data && chown -R shittyauth /shittyauth
25 |
26 | VOLUME ["/shittyauth/data"]
27 |
28 | WORKDIR /shittyauth/data
29 |
30 | EXPOSE 8880
31 |
32 | ENTRYPOINT [ "docker_launcher", "--config", "/shittyauth/launcher_config.json", "sudo", "-E", "-u", "shittyauth" ]
33 | CMD [ "java", "-jar", "/shittyauth/ShittyAuthServer.jar" ]
34 |
--------------------------------------------------------------------------------
/docker/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | server:
3 | image: mrletsplay/shittyauth
4 | environment:
5 | - "UID=1000"
6 | - "GID=1000"
7 | - "SHITTYAUTH_SKIP_SETUP=true"
8 | - "SHITTYAUTH_ADMIN_USER=admin"
9 | - "SHITTYAUTH_ADMIN_PASSWORD=admin"
10 | volumes:
11 | - "./shittyauth/:/shittyauth/data"
12 | frontend:
13 | image: mrletsplay/shittyauth_frontend
14 | ports:
15 | - 80:80
16 | - 443:443
17 | volumes:
18 | - ./frontend/config.json:/usr/local/apache2/htdocs/config.json
19 | - ./frontend/httpd.conf:/usr/local/apache2/conf/extra/include-shittyauth.conf
20 | - ./frontend/.htaccess:/usr/local/apache2/htdocs/.htaccess
21 |
--------------------------------------------------------------------------------
/docker/frontend/.htaccess:
--------------------------------------------------------------------------------
1 | RewriteEngine On
2 | RewriteCond %{REQUEST_URI} !^/server
3 | RewriteCond %{REQUEST_FILENAME} !-f
4 | RewriteRule ^(.*)$ /server/$1 [P,QSA,L]
5 |
--------------------------------------------------------------------------------
/docker/frontend/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiBaseURL": "/api/shittyauth"
3 | }
4 |
--------------------------------------------------------------------------------
/docker/frontend/httpd.conf:
--------------------------------------------------------------------------------
1 | LoadModule proxy_module modules/mod_proxy.so
2 | LoadModule proxy_http_module modules/mod_proxy_http.so
3 | LoadModule rewrite_module modules/mod_rewrite.so
4 |
5 |
6 | ProxyPass /server http://server:8880
7 | ProxyPassReverse /server http://server:8880
8 |
9 |
--------------------------------------------------------------------------------
/docker/launcher_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "updateUID": true,
3 | "user": "shittyauth",
4 |
5 | "updateGID": true,
6 | "group": "shittyauth",
7 |
8 | "runBefore": [
9 | [ "chown", "-R", "shittyauth:shittyauth", "/shittyauth" ]
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
4 | 4.0.0
5 | me.mrletsplay
6 | ShittyAuthServer
7 | 1.7-SNAPSHOT
8 |
9 | src
10 |
11 |
12 | maven-compiler-plugin
13 | 3.8.1
14 |
15 | 11
16 | UTF-8
17 |
18 |
19 |
20 | org.apache.maven.plugins
21 | maven-jar-plugin
22 | 3.2.0
23 |
24 |
25 |
26 | me.mrletsplay.shittyauth.ShittyAuth
27 |
28 |
29 |
30 |
31 |
32 | org.apache.maven.plugins
33 | maven-shade-plugin
34 | 2.4.3
35 |
36 |
37 | package
38 |
39 | shade
40 |
41 |
42 |
43 |
44 |
45 |
46 | *:*
47 |
48 | META-INF/*.SF
49 | META-INF/*.DSA
50 | META-INF/*.RSA
51 |
52 |
53 |
54 |
55 |
56 |
57 | me.mrletsplay
58 | resource-index-maven-plugin
59 | 1.2.1
60 |
61 |
62 | compile
63 |
64 | generate-index
65 |
66 |
67 |
68 |
69 | shittyauth-resources.list
70 |
71 |
72 |
73 |
74 |
75 | ${project.basedir}/resources
76 | include
77 |
78 |
79 |
80 |
81 |
82 | Graphite-Official
83 | https://maven.graphite-official.com/releases
84 |
85 |
86 |
87 |
88 | Graphite-Official
89 | https://maven.graphite-official.com/releases
90 |
91 |
92 |
93 |
94 | me.mrletsplay
95 | WebinterfaceAPI
96 | 3.5-SNAPSHOT
97 |
98 |
99 | me.mrletsplay
100 | MrCore
101 | 4.4
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/resources/.gitkeep:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/ShittyAuth.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth;
2 |
3 | import java.awt.image.BufferedImage;
4 | import java.io.ByteArrayInputStream;
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.io.OutputStream;
8 | import java.nio.file.Files;
9 | import java.nio.file.Path;
10 | import java.nio.file.Paths;
11 | import java.nio.file.StandardOpenOption;
12 | import java.security.KeyFactory;
13 | import java.security.KeyPair;
14 | import java.security.KeyPairGenerator;
15 | import java.security.NoSuchAlgorithmException;
16 | import java.security.PrivateKey;
17 | import java.security.PublicKey;
18 | import java.security.spec.InvalidKeySpecException;
19 | import java.security.spec.PKCS8EncodedKeySpec;
20 | import java.security.spec.X509EncodedKeySpec;
21 | import java.util.Arrays;
22 | import java.util.HashMap;
23 | import java.util.List;
24 | import java.util.Map;
25 | import java.util.UUID;
26 |
27 | import javax.imageio.ImageIO;
28 |
29 | import me.mrletsplay.mrcore.http.HttpRequest;
30 | import me.mrletsplay.shittyauth.auth.AccessTokenStorage;
31 | import me.mrletsplay.shittyauth.auth.FileAccessTokenStorage;
32 | import me.mrletsplay.shittyauth.auth.SQLAccessTokenStorage;
33 | import me.mrletsplay.shittyauth.config.ShittyAuthSettings;
34 | import me.mrletsplay.shittyauth.page.AccountPage;
35 | import me.mrletsplay.shittyauth.page.AdminPage;
36 | import me.mrletsplay.shittyauth.page.CreateAccountPage;
37 | import me.mrletsplay.shittyauth.page.SettingsPage;
38 | import me.mrletsplay.shittyauth.page.api.UserCapeDocument;
39 | import me.mrletsplay.shittyauth.page.api.UserSkinDocument;
40 | import me.mrletsplay.shittyauth.page.api.authlibinjector.AuthLibInjectorDocument;
41 | import me.mrletsplay.shittyauth.page.api.authlibinjector.AuthLibInjectorMetadataDocument;
42 | import me.mrletsplay.shittyauth.page.api.legacy.LegacyCheckServerDocument;
43 | import me.mrletsplay.shittyauth.page.api.legacy.LegacyJoinServerDocument;
44 | import me.mrletsplay.shittyauth.page.api.legacy.LegacyUserCapeDocument;
45 | import me.mrletsplay.shittyauth.page.api.legacy.LegacyUserSkinDocument;
46 | import me.mrletsplay.shittyauth.page.api.services.PlayerAttributesDocument;
47 | import me.mrletsplay.shittyauth.page.api.services.PlayerCertificatesDocument;
48 | import me.mrletsplay.shittyauth.page.api.services.PlayerReportDocument;
49 | import me.mrletsplay.shittyauth.page.api.shittyauth.ShittyAuthAPI;
50 | import me.mrletsplay.shittyauth.page.api.shittyauth.ShittyAuthAdminAPI;
51 | import me.mrletsplay.shittyauth.page.api.yggdrasil.AuthenticatePage;
52 | import me.mrletsplay.shittyauth.page.api.yggdrasil.HasJoinedPage;
53 | import me.mrletsplay.shittyauth.page.api.yggdrasil.InvalidatePage;
54 | import me.mrletsplay.shittyauth.page.api.yggdrasil.JoinPage;
55 | import me.mrletsplay.shittyauth.page.api.yggdrasil.ProfilePage;
56 | import me.mrletsplay.shittyauth.page.api.yggdrasil.PublicKeysPage;
57 | import me.mrletsplay.shittyauth.page.api.yggdrasil.RefreshPage;
58 | import me.mrletsplay.shittyauth.page.api.yggdrasil.ValidatePage;
59 | import me.mrletsplay.shittyauth.user.FileUserDataStorage;
60 | import me.mrletsplay.shittyauth.user.SQLUserDataStorage;
61 | import me.mrletsplay.shittyauth.user.UserData;
62 | import me.mrletsplay.shittyauth.user.UserDataStorage;
63 | import me.mrletsplay.shittyauth.util.DefaultTexture;
64 | import me.mrletsplay.shittyauth.util.InvalidSkinException;
65 | import me.mrletsplay.shittyauth.util.InvalidUsernameException;
66 | import me.mrletsplay.shittyauth.webinterface.ShittyAuthWIHandler;
67 | import me.mrletsplay.simplehttpserver.http.HttpRequestMethod;
68 | import me.mrletsplay.simplehttpserver.http.document.DocumentProvider;
69 | import me.mrletsplay.simplehttpserver.http.document.FileDocument;
70 | import me.mrletsplay.webinterfaceapi.Webinterface;
71 | import me.mrletsplay.webinterfaceapi.auth.Account;
72 | import me.mrletsplay.webinterfaceapi.auth.AccountConnection;
73 | import me.mrletsplay.webinterfaceapi.config.DefaultSettings;
74 | import me.mrletsplay.webinterfaceapi.config.FileConfig;
75 | import me.mrletsplay.webinterfaceapi.page.PageCategory;
76 | import me.mrletsplay.webinterfaceapi.setup.Setup;
77 | import me.mrletsplay.webinterfaceapi.sql.SQLHelper;
78 |
79 | public class ShittyAuth {
80 |
81 | public static final String ACCOUNT_CONNECTION_NAME = "shittyauth";
82 |
83 | private static final Path
84 | SKINS_PATH = Paths.get("shittyauth/skins/"),
85 | CAPES_PATH = Paths.get("shittyauth/capes/"),
86 | DEFAULT_SKIN_PATH = DefaultTexture.SKIN_STEVE.getPath(),
87 | DEFAULT_CAPE_PATH = DefaultTexture.CAPE_MIGRATOR.getPath();
88 |
89 | public static PublicKey publicKey;
90 | public static PrivateKey privateKey;
91 | public static AccessTokenStorage tokenStorage;
92 | public static UserDataStorage dataStorage;
93 | public static FileConfig config;
94 | public static Map userServers = new HashMap<>();
95 |
96 | public static void main(String[] args) throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
97 | DefaultSettings.HOME_PAGE_PATH.defaultValue(AccountPage.PATH);
98 | DefaultSettings.CORS_ALLOW_ALL_ORIGINS.defaultValue(true);
99 | DefaultSettings.CORS_ALLOWED_HEADERS.defaultValue(Arrays.asList("*"));
100 | DefaultSettings.CORS_ALLOW_CREDENTIALS.defaultValue(true);
101 | DefaultSettings.ALLOW_ANONYMOUS.defaultValue(false);
102 |
103 | if(!Webinterface.getSetup().isDone() && "true".equals(System.getenv("SHITTYAUTH_SKIP_SETUP"))) {
104 | Webinterface.getLogger().info("Skipping setup");
105 |
106 | Setup setup = Webinterface.getSetup();
107 | setup.getSteps().forEach(s -> {
108 | if(setup.isStepDone(s.getID())) return;
109 | setup.addCompletedStep(s.getID());
110 | });
111 | }
112 |
113 | Webinterface.start();
114 | Webinterface.extractResources("/shittyauth-resources.list");
115 |
116 | Webinterface.getLogger().info("Downloading default textures");
117 | for(DefaultTexture texture : DefaultTexture.values()) {
118 | Path path = texture.getPath();
119 | if(!Files.exists(path)) {
120 | Webinterface.getLogger().info("Downloading " + texture.name());
121 | HttpRequest.createGet(texture.getURL()).execute().transferTo(path.toFile());
122 | }
123 | }
124 |
125 | if(SQLHelper.isAvailable()) {
126 | tokenStorage = new SQLAccessTokenStorage();
127 | dataStorage = new SQLUserDataStorage();
128 | }else {
129 | tokenStorage = new FileAccessTokenStorage();
130 | dataStorage = new FileUserDataStorage();
131 | }
132 |
133 | tokenStorage.initialize();
134 | dataStorage.initialize();
135 |
136 | config = new FileConfig(new File("shittyauth/shittyauth.yml"));
137 | config.registerSettings(ShittyAuthSettings.INSTANCE);
138 |
139 | File publicKeyFile = new File("shittyauth/public_key.der");
140 | File privateKeyFile = new File("shittyauth/private_key.der");
141 | if(!privateKeyFile.exists() || !publicKeyFile.exists()) {
142 | KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
143 | gen.initialize(4096);
144 |
145 | KeyPair pair = gen.generateKeyPair();
146 | PrivateKey priv = pair.getPrivate();
147 | PublicKey pub = pair.getPublic();
148 |
149 | Files.write(publicKeyFile.toPath(), pub.getEncoded(), StandardOpenOption.CREATE);
150 | Files.write(privateKeyFile.toPath(), priv.getEncoded(), StandardOpenOption.CREATE);
151 |
152 | Webinterface.getLogger().info("Generated a new key pair");
153 | }
154 |
155 | KeyFactory keyFactory = KeyFactory.getInstance("RSA");
156 |
157 | X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(Files.readAllBytes(publicKeyFile.toPath()));
158 | publicKey = keyFactory.generatePublic(pubSpec);
159 | PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(Files.readAllBytes(privateKeyFile.toPath()));
160 | privateKey = keyFactory.generatePrivate(privSpec);
161 |
162 | DocumentProvider provider = Webinterface.getDocumentProvider();
163 |
164 | provider.register(HttpRequestMethod.GET, "/session/minecraft/hasJoined", new HasJoinedPage());
165 | provider.register(HttpRequestMethod.POST, "/session/minecraft/join", new JoinPage());
166 | provider.registerPattern(HttpRequestMethod.GET, "/session/minecraft/profile/{uuid}", ProfilePage.INSTANCE);
167 | provider.register(HttpRequestMethod.POST, "/authenticate", new AuthenticatePage());
168 | provider.register(HttpRequestMethod.POST, "/validate", new ValidatePage());
169 | provider.register(HttpRequestMethod.POST, "/invalidate", new InvalidatePage());
170 | provider.register(HttpRequestMethod.POST, "/signout", new InvalidatePage());
171 | provider.register(HttpRequestMethod.POST, "/refresh", new RefreshPage());
172 | provider.register(HttpRequestMethod.GET, "/publickeys", new PublicKeysPage());
173 |
174 | PlayerAttributesDocument doc = new PlayerAttributesDocument();
175 | provider.register(HttpRequestMethod.GET, "/player/attributes", doc);
176 | provider.register(HttpRequestMethod.GET, "/privileges", doc); // for MC 1.16 or older
177 | provider.register(HttpRequestMethod.POST, "/player/certificates", new PlayerCertificatesDocument());
178 | provider.register(HttpRequestMethod.POST, "/player/report", new PlayerReportDocument());
179 | provider.register(HttpRequestMethod.GET, "/yggdrasil_session_pubkey.der", new FileDocument(Paths.get("shittyauth/public_key.der")));
180 |
181 | provider.register(HttpRequestMethod.GET, "/game/joinserver.jsp", new LegacyJoinServerDocument());
182 | provider.register(HttpRequestMethod.GET, "/game/checkserver.jsp", new LegacyCheckServerDocument());
183 | provider.registerPattern(HttpRequestMethod.GET, "/cape/{uuid}", UserCapeDocument.INSTANCE);
184 | provider.registerPattern(HttpRequestMethod.GET, "/skin/{uuid}", UserSkinDocument.INSTANCE);
185 | provider.registerPattern(HttpRequestMethod.GET, "/MinecraftSkins/{name}", LegacyUserSkinDocument.INSTANCE);
186 | provider.registerPattern(HttpRequestMethod.GET, "/MinecraftCapes/{name}", LegacyUserCapeDocument.INSTANCE);
187 |
188 | if(ShittyAuth.config.getSetting(ShittyAuthSettings.AUTHLIB_INJECTOR_COMPAT)) {
189 | // Provide authlib metadata
190 | provider.register(HttpRequestMethod.GET, "/authserver", new AuthLibInjectorMetadataDocument());
191 |
192 | // Redirect all of the authlib mappings to their respective ShittyAuth mappings
193 | // See https://github.com/yushijinhun/authlib-injector/blob/961366e012c26849810a744897bf41b2e926b734/src/main/java/moe/yushi/authlibinjector/httpd/DefaultURLRedirector.java#L36
194 | AuthLibInjectorDocument authlibDoc = new AuthLibInjectorDocument();
195 | List authlibPaths = Arrays.asList("/api", "/authserver", "/sessionserver", "/skins", "/minecraftservices");
196 | for(String path : authlibPaths) {
197 | String p = path + "/{page...}";
198 | provider.registerPattern(HttpRequestMethod.GET, p, authlibDoc);
199 | provider.registerPattern(HttpRequestMethod.POST, p, authlibDoc);
200 | }
201 | }
202 |
203 | new ShittyAuthAPI().register(provider);
204 | new ShittyAuthAdminAPI().register(provider);
205 |
206 | Webinterface.registerActionHandler(new ShittyAuthWIHandler());
207 |
208 | initFromEnvvars();
209 |
210 | PageCategory cat = Webinterface.createCategory("Minecraft");
211 | cat.addPage(new AccountPage());
212 | cat.addPage(new CreateAccountPage());
213 | cat.addPage(new AdminPage());
214 | cat.addPage(new SettingsPage());
215 | }
216 |
217 | private static void initFromEnvvars() {
218 | String adminUser = System.getenv("SHITTYAUTH_ADMIN_USER");
219 | String adminPass = System.getenv("SHITTYAUTH_ADMIN_PASSWORD");
220 | if(adminUser != null && adminPass != null) {
221 | Webinterface.getLogger().info("Creating admin user");
222 |
223 | Account acc = getAccountByUsername(adminUser);
224 | if(acc != null) {
225 | Webinterface.getLogger().info("Account with username '" + adminUser + "' already exists. Skipping");
226 | }else {
227 | try {
228 | acc = createAccount(adminUser, adminPass);
229 | acc.addPermission("*");
230 | } catch (InvalidUsernameException e) {
231 | Webinterface.getLogger().error("Failed to create account", e);
232 | }
233 | }
234 | }
235 | }
236 |
237 | public static Account createAccount(String username, String password) throws InvalidUsernameException {
238 | if(!ShittyAuthWIHandler.USERNAME_PATTERN.matcher(username).matches()) throw new InvalidUsernameException("Invalid username");
239 |
240 | String uuid = UUID.randomUUID().toString();
241 | AccountConnection conn = new AccountConnection(ACCOUNT_CONNECTION_NAME, uuid, username, null, null);
242 | Account acc = Webinterface.getAccountStorage().createAccount(conn);
243 | Webinterface.getCredentialsStorage().storeCredentials(ACCOUNT_CONNECTION_NAME, uuid, password);
244 | ShittyAuth.dataStorage.updateUserData(uuid, UserData.createNew());
245 | return acc;
246 | }
247 |
248 | public static Account getAccountByUsername(String username) {
249 | // TODO: more efficient implementation?
250 | for(Account a : Webinterface.getAccountStorage().getAccounts()) {
251 | AccountConnection con = a.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
252 | if(con != null && con.getUserName().equalsIgnoreCase(username)) {
253 | return a;
254 | }
255 | }
256 | return null;
257 | }
258 |
259 | public static void updateUserSkin(String userID, DefaultTexture texture) throws IOException {
260 | Path texPath = texture.getPath();
261 | try {
262 | BufferedImage image = ImageIO.read(texPath.toFile());
263 | updateUserSkin(userID, image);
264 | }catch(InvalidSkinException ignored) {}
265 | }
266 |
267 | public static void updateUserSkin(String userID, BufferedImage image) throws IOException, InvalidSkinException {
268 | if(image.getWidth() != 64 || (image.getHeight() != 64 && image.getHeight() != 32)) throw new InvalidSkinException("Skin must be 64x64 or 64x32 pixels");
269 | BufferedImage copy = new BufferedImage(64, image.getHeight(), BufferedImage.TYPE_INT_ARGB);
270 | copy.createGraphics().drawImage(image, 0, 0, null);
271 |
272 | if(!Files.exists(SKINS_PATH)) Files.createDirectories(SKINS_PATH);
273 | Path skinPath = SKINS_PATH.resolve(userID + ".png");
274 | try(OutputStream out = Files.newOutputStream(skinPath)) {
275 | ImageIO.write(copy, "PNG", out);
276 | }
277 |
278 | UserData d = ShittyAuth.dataStorage.getUserData(userID);
279 | d.setSkinLastChanged(System.currentTimeMillis());
280 | ShittyAuth.dataStorage.updateUserData(userID, d);
281 | }
282 |
283 | public static BufferedImage loadUserSkin(String userID) throws IOException {
284 | return ImageIO.read(new ByteArrayInputStream(loadUserSkinRaw(userID)));
285 | }
286 |
287 | public static byte[] loadUserSkinRaw(String userID) throws IOException {
288 | Path skinPath = SKINS_PATH.resolve(userID + ".png");
289 | if(!skinPath.normalize().startsWith(SKINS_PATH)) throw new IOException("Invalid path");
290 | if(!Files.exists(skinPath)) skinPath = DEFAULT_SKIN_PATH;
291 | return Files.readAllBytes(skinPath);
292 | }
293 |
294 | public static void updateUserCape(String userID, DefaultTexture texture) throws IOException {
295 | Path texPath = texture.getPath();
296 | try {
297 | BufferedImage image = ImageIO.read(texPath.toFile());
298 | updateUserCape(userID, image);
299 | }catch(InvalidSkinException ignored) {}
300 | }
301 |
302 | public static void updateUserCape(String userID, BufferedImage image) throws IOException, InvalidSkinException{
303 | if(image.getWidth() != 64 || image.getHeight() != 32) throw new InvalidSkinException("Cape must be 64x32");
304 | BufferedImage copy = new BufferedImage(64, 32, BufferedImage.TYPE_INT_ARGB);
305 | copy.createGraphics().drawImage(image, 0, 0, null);
306 |
307 | if(!Files.exists(CAPES_PATH)) Files.createDirectories(CAPES_PATH);
308 | Path skinPath = CAPES_PATH.resolve(userID + ".png");
309 | try(OutputStream out = Files.newOutputStream(skinPath)) {
310 | ImageIO.write(copy, "PNG", out);
311 | }
312 |
313 | UserData d = ShittyAuth.dataStorage.getUserData(userID);
314 | d.setCapeLastChanged(System.currentTimeMillis());
315 | ShittyAuth.dataStorage.updateUserData(userID, d);
316 | }
317 |
318 | public static BufferedImage loadUserCape(String userID) throws IOException {
319 | return ImageIO.read(new ByteArrayInputStream(loadUserCapeRaw(userID)));
320 | }
321 |
322 | public static byte[] loadUserCapeRaw(String userID) throws IOException {
323 | Path capePath = CAPES_PATH.resolve(userID + ".png");
324 | if(!capePath.normalize().startsWith(CAPES_PATH)) throw new IOException("Invalid path");
325 | if(!Files.exists(capePath)) capePath = DEFAULT_CAPE_PATH;
326 | return Files.readAllBytes(capePath);
327 | }
328 |
329 | }
330 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/auth/AccessToken.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.auth;
2 |
3 | import java.util.Objects;
4 |
5 | public class AccessToken extends StoredAccessToken {
6 |
7 | private String accessToken;
8 |
9 | public AccessToken(String clientToken, String accountID, long softExpiresAt, long expiresAt, String accessToken) {
10 | super(clientToken, accountID, softExpiresAt, expiresAt);
11 | this.accessToken = accessToken;
12 | }
13 |
14 | public String getAccessToken() {
15 | return accessToken;
16 | }
17 |
18 | @Override
19 | public int hashCode() {
20 | final int prime = 31;
21 | int result = super.hashCode();
22 | result = prime * result + Objects.hash(accessToken);
23 | return result;
24 | }
25 |
26 | @Override
27 | public boolean equals(Object obj) {
28 | if (this == obj)
29 | return true;
30 | if (!super.equals(obj))
31 | return false;
32 | if (getClass() != obj.getClass())
33 | return false;
34 | AccessToken other = (AccessToken) obj;
35 | return Objects.equals(accessToken, other.accessToken);
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/auth/AccessTokenStorage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.auth;
2 |
3 | import java.time.Instant;
4 | import java.time.temporal.ChronoUnit;
5 | import java.util.UUID;
6 |
7 | public interface AccessTokenStorage {
8 |
9 | public void initialize();
10 |
11 | public AccessToken generateToken(String accID, String clientToken);
12 |
13 | public String getAccountID(String token);
14 |
15 | public StoredAccessToken getStoredToken(String token);
16 |
17 | public boolean checkValid(String token, String clientToken);
18 |
19 | public void removeToken(String token);
20 |
21 | public void removeTokensByAccountID(String accID);
22 |
23 | public void cleanUp();
24 |
25 | public default AccessToken createNewToken(String accountID, String clientToken) {
26 | String tok = newToken();
27 | Instant softExpires = Instant.now().plus(7, ChronoUnit.DAYS);
28 | Instant expires = Instant.now().plus(30, ChronoUnit.DAYS);
29 | return new AccessToken(clientToken, accountID, softExpires.toEpochMilli(), expires.toEpochMilli(), tok);
30 | }
31 |
32 | private static String newToken() {
33 | return UUID.randomUUID().toString();
34 | }
35 |
36 | }
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/auth/FileAccessTokenStorage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.auth;
2 |
3 | import java.io.File;
4 | import java.security.NoSuchAlgorithmException;
5 | import java.security.spec.InvalidKeySpecException;
6 | import java.security.spec.KeySpec;
7 | import java.util.Base64;
8 | import java.util.UUID;
9 |
10 | import javax.crypto.SecretKeyFactory;
11 | import javax.crypto.spec.PBEKeySpec;
12 |
13 | import me.mrletsplay.mrcore.config.ConfigLoader;
14 | import me.mrletsplay.mrcore.config.FileCustomConfig;
15 | import me.mrletsplay.mrcore.misc.FriendlyException;
16 | import me.mrletsplay.webinterfaceapi.Webinterface;
17 |
18 | public class FileAccessTokenStorage implements AccessTokenStorage {
19 |
20 | // TODO: Expire tokens, keep only one token per clientToken, add a way for users to manually invalidate tokens?
21 |
22 | private FileCustomConfig config;
23 |
24 | @Override
25 | public void initialize() {
26 | config = ConfigLoader.loadFileConfig(new File(Webinterface.getDataDirectory(), "shittyauth/token-storage.yml"));
27 | }
28 |
29 | @Override
30 | public AccessToken generateToken(String accID, String clientToken) {
31 | if(clientToken == null) clientToken = UUID.randomUUID().toString();
32 | AccessToken token = createNewToken(accID, clientToken);
33 |
34 | String tokenHash = hash(token.getAccessToken());
35 | config.set(tokenHash + ".client-token", token.getClientToken());
36 | config.set(tokenHash + ".account-id", token.getAccountID());
37 | config.set(tokenHash + ".soft-expires-at", token.getSoftExpiresAt());
38 | config.set(tokenHash + ".expires-at", token.getExpiresAt());
39 | config.saveToFile();
40 | return token;
41 | }
42 |
43 | @Override
44 | public String getAccountID(String token) {
45 | return config.getString(hash(token) + ".account-id");
46 | }
47 |
48 | @Override
49 | public StoredAccessToken getStoredToken(String token) {
50 | String tokenHash = hash(token);
51 | return new StoredAccessToken(
52 | config.getString(tokenHash + ".client-token"),
53 | config.getString(tokenHash + ".account-id"),
54 | config.getLong(tokenHash + ".soft-expires-at"),
55 | config.getLong(tokenHash + ".expires-at"));
56 | }
57 |
58 | @Override
59 | public boolean checkValid(String token, String clientToken) {
60 | String clientTok = config.getString(hash(token) + ".client-token");
61 | if(clientTok == null) return false;
62 | return clientTok.equals(clientToken);
63 | }
64 |
65 | @Override
66 | public void removeToken(String token) {
67 | config.unset(hash(token));
68 | config.saveToFile();
69 | }
70 |
71 | @Override
72 | public void removeTokensByAccountID(String accID) {
73 | for(String k : config.getKeys()) {
74 | if(accID.equals(config.getString(k + ".account-id"))) {
75 | config.unset(k);
76 | }
77 | }
78 | config.saveToFile();
79 | }
80 |
81 | @Override
82 | public void cleanUp() {
83 | long currentTime = System.currentTimeMillis();
84 | for(String k : config.getKeys()) {
85 | if(config.getLong(k + ".expires-at") > currentTime) {
86 | config.unset(k);
87 | }
88 | }
89 | config.saveToFile();
90 | }
91 |
92 | private String hash(String raw) {
93 | try {
94 | byte[] salt = new byte[16]; // No salt initialization needed, token is already random
95 | KeySpec spec = new PBEKeySpec(raw.toCharArray(), salt, 65536, 128);
96 | SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
97 | byte[] hash = f.generateSecret(spec).getEncoded();
98 | return Base64.getEncoder().encodeToString(hash);
99 | }catch(NoSuchAlgorithmException | InvalidKeySpecException e) {
100 | throw new FriendlyException(e);
101 | }
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/auth/SQLAccessTokenStorage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.auth;
2 |
3 | import java.security.NoSuchAlgorithmException;
4 | import java.security.spec.InvalidKeySpecException;
5 | import java.security.spec.KeySpec;
6 | import java.sql.PreparedStatement;
7 | import java.sql.ResultSet;
8 | import java.util.Base64;
9 | import java.util.UUID;
10 |
11 | import javax.crypto.SecretKeyFactory;
12 | import javax.crypto.spec.PBEKeySpec;
13 |
14 | import me.mrletsplay.mrcore.misc.FriendlyException;
15 | import me.mrletsplay.webinterfaceapi.sql.SQLHelper;
16 |
17 | public class SQLAccessTokenStorage implements AccessTokenStorage {
18 |
19 | // TODO: Expire tokens, keep only one token per clientToken, add a way for users to manually invalidate tokens?
20 |
21 | @Override
22 | public void initialize() {
23 | SQLHelper.run(c -> {
24 | try(PreparedStatement st = c.prepareStatement("CREATE TABLE IF NOT EXISTS " + SQLHelper.tableName("shittyauth_tokens") + "(TokenHash VARCHAR(255) PRIMARY KEY, AccountId VARCHAR(255), ClientToken VARCHAR(255), SoftExpiresAt BIGINT, ExpiresAt BIGINT)")) {
25 | st.execute();
26 | }
27 | });
28 | }
29 |
30 | @Override
31 | public AccessToken generateToken(String accID, String clientToken) {
32 | if(clientToken == null) clientToken = UUID.randomUUID().toString();
33 | AccessToken token = createNewToken(accID, clientToken);
34 | SQLHelper.run(c -> {
35 | try(PreparedStatement st = c.prepareStatement("INSERT INTO " + SQLHelper.tableName("shittyauth_tokens") + "(TokenHash, AccountId, ClientToken, SoftExpiresAt, ExpiresAt) VALUES(?, ?, ?, ?, ?)")) {
36 | st.setString(1, hash(token.getAccessToken()));
37 | st.setString(2, accID);
38 | st.setString(3, token.getClientToken());
39 | st.setLong(4, token.getSoftExpiresAt());
40 | st.setLong(5, token.getExpiresAt());
41 | st.execute();
42 | }
43 | });
44 | return token;
45 | }
46 |
47 | @Override
48 | public String getAccountID(String token) {
49 | return SQLHelper.run(c -> {
50 | try(PreparedStatement st = c.prepareStatement("SELECT AccountId FROM " + SQLHelper.tableName("shittyauth_tokens") + " WHERE TokenHash = ?")) {
51 | st.setString(1, hash(token));
52 | try(ResultSet r = st.executeQuery()) {
53 | if(!r.next()) return null;
54 | return r.getString("AccountId");
55 | }
56 | }
57 | });
58 | }
59 |
60 | @Override
61 | public StoredAccessToken getStoredToken(String token) {
62 | return SQLHelper.run(c -> {
63 | try(PreparedStatement st = c.prepareStatement("SELECT * FROM " + SQLHelper.tableName("shittyauth_tokens") + " WHERE TokenHash = ?")) {
64 | st.setString(1, hash(token));
65 | try(ResultSet r = st.executeQuery()) {
66 | if(!r.next()) return null;
67 | return new StoredAccessToken(r.getString("ClientToken"), r.getString("AccountId"), r.getLong("SoftExpiresAt"), r.getLong("ExpiresAt"));
68 | }
69 | }
70 | });
71 | }
72 |
73 | @Override
74 | public boolean checkValid(String token, String clientToken) {
75 | return SQLHelper.run(c -> {
76 | try(PreparedStatement st = c.prepareStatement("SELECT ClientToken FROM " + SQLHelper.tableName("shittyauth_tokens") + " WHERE TokenHash = ?")) {
77 | st.setString(1, hash(token));
78 | try(ResultSet r = st.executeQuery()) {
79 | if(!r.next()) return false;
80 | return r.getString("ClientToken").equals(clientToken);
81 | }
82 | }
83 | });
84 | }
85 |
86 | @Override
87 | public void removeToken(String token) {
88 | SQLHelper.run(c -> {
89 | try(PreparedStatement st = c.prepareStatement("DELETE FROM " + SQLHelper.tableName("shittyauth_tokens") + " WHERE TokenHash = ?")) {
90 | st.setString(1, hash(token));
91 | st.execute();
92 | }
93 | });
94 | }
95 |
96 | @Override
97 | public void removeTokensByAccountID(String accID) {
98 | SQLHelper.run(c -> {
99 | try(PreparedStatement st = c.prepareStatement("DELETE FROM " + SQLHelper.tableName("shittyauth_tokens") + " WHERE AccountId = ?")) {
100 | st.setString(1, accID);
101 | st.execute();
102 | }
103 | });
104 | }
105 |
106 | @Override
107 | public void cleanUp() {
108 | long currentTime = System.currentTimeMillis();
109 | SQLHelper.run(c -> {
110 | try(PreparedStatement st = c.prepareStatement("DELETE FROM " + SQLHelper.tableName("shittyauth_tokens") + " WHERE ExpiresAt > ?")) {
111 | st.setLong(1, currentTime);
112 | st.execute();
113 | }
114 | });
115 | }
116 |
117 | private String hash(String raw) {
118 | try {
119 | byte[] salt = new byte[16]; // No salt initialization needed, token is already random
120 | KeySpec spec = new PBEKeySpec(raw.toCharArray(), salt, 65536, 128);
121 | SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
122 | byte[] hash = f.generateSecret(spec).getEncoded();
123 | return Base64.getEncoder().encodeToString(hash);
124 | }catch(NoSuchAlgorithmException | InvalidKeySpecException e) {
125 | throw new FriendlyException(e);
126 | }
127 | }
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/auth/StoredAccessToken.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.auth;
2 |
3 | public class StoredAccessToken {
4 |
5 | protected String clientToken;
6 | protected String accountID;
7 |
8 | /**
9 | * Time when the token becomes unusable for authentication when joining a server, but can still be refreshed
10 | */
11 | protected long softExpiresAt;
12 |
13 | /**
14 | * Time when the token fully expires and cannot be refreshed anymore
15 | */
16 | protected long expiresAt;
17 |
18 | public StoredAccessToken(String clientToken, String accountID, long softExpiresAt, long expiresAt) {
19 | this.clientToken = clientToken;
20 | this.accountID = accountID;
21 | this.softExpiresAt = softExpiresAt;
22 | this.expiresAt = expiresAt;
23 | }
24 |
25 | public String getClientToken() {
26 | return clientToken;
27 | }
28 |
29 | public String getAccountID() {
30 | return accountID;
31 | }
32 |
33 | public long getSoftExpiresAt() {
34 | return softExpiresAt;
35 | }
36 |
37 | public long getExpiresAt() {
38 | return expiresAt;
39 | }
40 |
41 | @Override
42 | public int hashCode() {
43 | final int prime = 31;
44 | int result = 1;
45 | result = prime * result + ((accountID == null) ? 0 : accountID.hashCode());
46 | result = prime * result + ((clientToken == null) ? 0 : clientToken.hashCode());
47 | return result;
48 | }
49 |
50 | @Override
51 | public boolean equals(Object obj) {
52 | if (this == obj)
53 | return true;
54 | if (obj == null)
55 | return false;
56 | if (getClass() != obj.getClass())
57 | return false;
58 | StoredAccessToken other = (StoredAccessToken) obj;
59 | if (accountID == null) {
60 | if (other.accountID != null)
61 | return false;
62 | } else if (!accountID.equals(other.accountID))
63 | return false;
64 | if (clientToken == null) {
65 | if (other.clientToken != null)
66 | return false;
67 | } else if (!clientToken.equals(other.clientToken))
68 | return false;
69 | return true;
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/config/ShittyAuthSettings.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.config;
2 |
3 | import me.mrletsplay.webinterfaceapi.config.setting.AutoSetting;
4 | import me.mrletsplay.webinterfaceapi.config.setting.AutoSettings;
5 | import me.mrletsplay.webinterfaceapi.config.setting.SettingsCategory;
6 | import me.mrletsplay.webinterfaceapi.config.setting.impl.BooleanSetting;
7 |
8 | public class ShittyAuthSettings implements AutoSettings {
9 |
10 | public static final ShittyAuthSettings INSTANCE = new ShittyAuthSettings();
11 |
12 | @AutoSetting
13 | private static SettingsCategory
14 | general = new SettingsCategory("General"),
15 | http = new SettingsCategory("HTTP");
16 |
17 | // General
18 | public static final BooleanSetting
19 | ALLOW_REGISTRATION = general.addBoolean("general.allow-registration", true, "Allow creation of Minecraft account", "Does not affect registration of WIAPI accounts");
20 |
21 | // HTTP
22 | public static final BooleanSetting
23 | AUTHLIB_INJECTOR_COMPAT = http.addBoolean("http.authlib-injector-compat", false, "Authlib-injector compat (Requires restart)", "Enable compatibility for authlib-injector");
24 |
25 | private ShittyAuthSettings() {}
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/AccountPage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page;
2 |
3 | import me.mrletsplay.shittyauth.ShittyAuth;
4 | import me.mrletsplay.shittyauth.textures.SkinType;
5 | import me.mrletsplay.shittyauth.user.UserData;
6 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
7 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
8 | import me.mrletsplay.webinterfaceapi.auth.Account;
9 | import me.mrletsplay.webinterfaceapi.auth.AccountConnection;
10 | import me.mrletsplay.webinterfaceapi.page.Page;
11 | import me.mrletsplay.webinterfaceapi.page.PageSection;
12 | import me.mrletsplay.webinterfaceapi.page.action.MultiAction;
13 | import me.mrletsplay.webinterfaceapi.page.action.SendJSAction;
14 | import me.mrletsplay.webinterfaceapi.page.action.SetValueAction;
15 | import me.mrletsplay.webinterfaceapi.page.action.ShowToastAction;
16 | import me.mrletsplay.webinterfaceapi.page.action.SubmitUploadAction;
17 | import me.mrletsplay.webinterfaceapi.page.action.value.ActionValue;
18 | import me.mrletsplay.webinterfaceapi.page.element.Button;
19 | import me.mrletsplay.webinterfaceapi.page.element.CheckBox;
20 | import me.mrletsplay.webinterfaceapi.page.element.FileUpload;
21 | import me.mrletsplay.webinterfaceapi.page.element.Heading;
22 | import me.mrletsplay.webinterfaceapi.page.element.PasswordField;
23 | import me.mrletsplay.webinterfaceapi.page.element.Select;
24 | import me.mrletsplay.webinterfaceapi.page.element.Text;
25 | import me.mrletsplay.webinterfaceapi.page.element.TitleText;
26 | import me.mrletsplay.webinterfaceapi.page.element.VerticalSpacer;
27 | import me.mrletsplay.webinterfaceapi.page.element.layout.DefaultLayoutOption;
28 | import me.mrletsplay.webinterfaceapi.page.element.layout.Grid;
29 | import me.mrletsplay.webinterfaceapi.session.Session;
30 |
31 | public class AccountPage extends Page {
32 |
33 | public static final String PATH = "/shittyauth/account";
34 |
35 | public AccountPage() {
36 | super("Account", PATH);
37 | setIcon("mdi:minecraft");
38 |
39 | PageSection s = new PageSection();
40 | s.setSlimLayout(true);
41 | s.setGrid(new Grid().setColumns("75fr", "25fr"));
42 | s.dynamic(els -> {
43 | AccountConnection con = Session.getCurrentSession().getAccount().getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
44 | UserData d = ShittyAuth.dataStorage.getUserData(con.getUserID());
45 |
46 | els.add(Heading.builder()
47 | .level(2)
48 | .leftboundText()
49 | .text(con.getUserName() + " (" + con.getUserID() + ")")
50 | .fullWidth()
51 | .create());
52 |
53 | els.add(TitleText.builder()
54 | .leftboundText()
55 | .text("Skin Type")
56 | .fullWidth()
57 | .create());
58 |
59 | Select skinTypeSel = Select.builder()
60 | .addOption("Steve", "STEVE", d.getSkinType() == SkinType.STEVE)
61 | .addOption("Alex", "ALEX", d.getSkinType() == SkinType.ALEX)
62 | .create();
63 | els.add(skinTypeSel);
64 |
65 | els.add(Button.builder()
66 | .text("Update skin type")
67 | .onClick(SendJSAction.of("shittyauth", "setSkinType", ActionValue.object().put("type", ActionValue.elementValue(skinTypeSel))).onSuccess(ShowToastAction.info("Updated!")))
68 | .create());
69 | });
70 |
71 | VerticalSpacer sp1 = new VerticalSpacer("30px");
72 | sp1.addLayoutOptions(DefaultLayoutOption.FULL_WIDTH);
73 | s.addElement(sp1);
74 |
75 | s.addElement(TitleText.builder()
76 | .leftboundText()
77 | .text("Skin")
78 | .fullWidth()
79 | .create());
80 |
81 | FileUpload uploadSkin = FileUpload.builder()
82 | .uploadHandler("shittyauth", "uploadSkin")
83 | .create();
84 | s.addElement(uploadSkin);
85 |
86 | s.addElement(Button.builder()
87 | .text("Upload Skin")
88 | .onClick(SubmitUploadAction.of(uploadSkin).onSuccess(ShowToastAction.info("Skin uploaded!")))
89 | .create());
90 |
91 | VerticalSpacer sp2 = new VerticalSpacer("30px");
92 | sp2.addLayoutOptions(DefaultLayoutOption.FULL_WIDTH);
93 | s.addElement(sp2);
94 |
95 | s.addElement(TitleText.builder()
96 | .leftboundText()
97 | .text("Cape")
98 | .fullWidth()
99 | .create());
100 |
101 | FileUpload uploadCape = FileUpload.builder()
102 | .uploadHandler("shittyauth", "uploadCape")
103 | .create();
104 | s.addElement(uploadCape);
105 |
106 | s.addElement(Button.builder()
107 | .text("Upload Cape")
108 | .onClick(SubmitUploadAction.of(uploadCape).onSuccess(ShowToastAction.info("Cape uploaded!")))
109 | .create());
110 |
111 | s.addElement(Text.builder()
112 | .leftboundText()
113 | .text("Enable cape?")
114 | .create());
115 |
116 | s.dynamic(() -> {
117 | UserData d = ShittyAuth.dataStorage.getUserData(Session.getCurrentSession().getAccount().getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME).getUserID());
118 |
119 | return CheckBox.builder()
120 | .initialState(d.hasCape())
121 | .onChange(ch -> SendJSAction.of("shittyauth", "setEnableCape", ActionValue.object().put("enable", ActionValue.checkboxValue(ch))).onSuccess(ShowToastAction.info("Updated!")))
122 | .create();
123 | });
124 |
125 | VerticalSpacer sp3 = new VerticalSpacer("30px");
126 | sp3.addLayoutOptions(DefaultLayoutOption.FULL_WIDTH);
127 | s.addElement(sp3);
128 |
129 | s.addElement(TitleText.builder()
130 | .leftboundText()
131 | .text("Reset password")
132 | .fullWidth()
133 | .create());
134 |
135 | PasswordField passInput = PasswordField.builder().create();
136 | s.addElement(passInput);
137 |
138 | s.addElement(Button.builder()
139 | .text("Change Password")
140 | .onClick(MultiAction.of(
141 | SendJSAction.of("shittyauth", "resetPassword", ActionValue.object().put("password", ActionValue.elementValue(passInput)))
142 | .onSuccess(ShowToastAction.info("Successfully changed password!")),
143 | SetValueAction.of(passInput, ActionValue.nullValue())))
144 | .create());
145 |
146 | addSection(s);
147 | }
148 |
149 | @Override
150 | public void createContent() {
151 | if(!Session.requireSession()) return;
152 |
153 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
154 | Account acc = Session.getCurrentSession().getAccount();
155 | AccountConnection mcCon = acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
156 | if(mcCon == null) {
157 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.FOUND_302);
158 | ctx.getServerHeader().getFields().set("Location", CreateAccountPage.PATH);
159 | return;
160 | }
161 |
162 | super.createContent();
163 | }
164 |
165 | }
166 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/AdminPage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page;
2 |
3 | import me.mrletsplay.shittyauth.ShittyAuth;
4 | import me.mrletsplay.webinterfaceapi.DefaultPermissions;
5 | import me.mrletsplay.webinterfaceapi.Webinterface;
6 | import me.mrletsplay.webinterfaceapi.auth.Account;
7 | import me.mrletsplay.webinterfaceapi.auth.AccountConnection;
8 | import me.mrletsplay.webinterfaceapi.page.Page;
9 | import me.mrletsplay.webinterfaceapi.page.PageSection;
10 | import me.mrletsplay.webinterfaceapi.page.action.ConfirmAction;
11 | import me.mrletsplay.webinterfaceapi.page.action.ReloadPageAction;
12 | import me.mrletsplay.webinterfaceapi.page.action.SendJSAction;
13 | import me.mrletsplay.webinterfaceapi.page.action.value.ActionValue;
14 | import me.mrletsplay.webinterfaceapi.page.element.Button;
15 | import me.mrletsplay.webinterfaceapi.page.element.Group;
16 | import me.mrletsplay.webinterfaceapi.page.element.Image;
17 | import me.mrletsplay.webinterfaceapi.page.element.InputField;
18 | import me.mrletsplay.webinterfaceapi.page.element.PasswordField;
19 | import me.mrletsplay.webinterfaceapi.page.element.Text;
20 | import me.mrletsplay.webinterfaceapi.page.element.TitleText;
21 | import me.mrletsplay.webinterfaceapi.page.element.VerticalSpacer;
22 | import me.mrletsplay.webinterfaceapi.page.element.builder.Align;
23 | import me.mrletsplay.webinterfaceapi.page.element.layout.DefaultLayoutOption;
24 | import me.mrletsplay.webinterfaceapi.page.element.layout.Grid;
25 |
26 | public class AdminPage extends Page {
27 |
28 | public static final String PATH = "/shittyauth/admin";
29 |
30 | public AdminPage() {
31 | super("Admin", PATH);
32 | setIcon("mdi:security");
33 | setPermission(DefaultPermissions.MODIFY_USERS);
34 |
35 | PageSection sc = new PageSection();
36 | sc.setSlimLayout(true);
37 |
38 | sc.addHeading("Accounts", 2);
39 | sc.getStyle().setProperty("grid-template-columns", "1fr");
40 | sc.getMobileStyle().setProperty("grid-template-columns", "1fr");
41 |
42 | sc.dynamic(els -> {
43 | for(Account acc : Webinterface.getAccountStorage().getAccounts()) {
44 | AccountConnection conn = acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
45 | if(conn == null) continue;
46 |
47 | Group grp = new Group();
48 | grp.getStyle().setProperty("border", "1px solid var(--theme-color-content-border)");
49 | grp.getStyle().setProperty("border-radius", "5px");
50 | grp.getStyle().setProperty("margin", "5px 0px");
51 | grp.setGrid(new Grid().setColumns("min-content", "auto"));
52 |
53 | TitleText tt = TitleText.builder()
54 | .text(conn.getUserName())
55 | .leftboundText()
56 | .noLineBreaks()
57 | .create();
58 | tt.getStyle().setProperty("font-size", "24px");
59 | grp.addElement(tt);
60 |
61 | grp.addElement(Image.builder()
62 | .src("/api/shittyauth/avatar/" + conn.getUserID())
63 | .width("64px")
64 | .height("64px")
65 | .withLayoutOptions((container, element) -> element.appendAttribute("style", "image-rendering: pixelated;"))
66 | .align(Align.LEFT_CENTER)
67 | .create());
68 |
69 | grp.addElement(TitleText.builder()
70 | .text("User ID")
71 | .noLineBreaks()
72 | .leftboundText()
73 | .withLayoutOptions(DefaultLayoutOption.NEW_LINE)
74 | .create());
75 | grp.addElement(Text.builder()
76 | .text(conn.getUserID())
77 | .leftboundText()
78 | .create());
79 |
80 | grp.addElement(TitleText.builder()
81 | .text("WIAPI account name")
82 | .noLineBreaks()
83 | .leftboundText()
84 | .withLayoutOptions(DefaultLayoutOption.NEW_LINE)
85 | .create());
86 | grp.addElement(Text.builder()
87 | .text(acc.getUsername())
88 | .leftboundText()
89 | .create());
90 |
91 | grp.addElement(TitleText.builder()
92 | .text("WIAPI account ID")
93 | .noLineBreaks()
94 | .leftboundText()
95 | .withLayoutOptions(DefaultLayoutOption.NEW_LINE)
96 | .create());
97 | grp.addElement(Text.builder()
98 | .text(acc.getID())
99 | .leftboundText()
100 | .create());
101 |
102 | Group actions = new Group();
103 | actions.addLayoutOptions(new Grid().setColumns("1fr auto"), DefaultLayoutOption.FULL_WIDTH);
104 |
105 | InputField usernameField = InputField.builder()
106 | .placeholder("Username")
107 | .create();
108 | actions.addElement(usernameField);
109 |
110 | actions.addElement(Button.builder()
111 | .text("Change MC username")
112 | .onClick(ConfirmAction.of(SendJSAction.of(
113 | "shittyauth", "changeMCUsername",
114 | ActionValue.object()
115 | .put("account", ActionValue.string(acc.getID()))
116 | .put("username", ActionValue.elementValue(usernameField))
117 | )
118 | .onSuccess(ReloadPageAction.delayed(100))))
119 | .create());
120 |
121 | PasswordField passwordField = PasswordField.builder()
122 | .placeholder("Password")
123 | .create();
124 | actions.addElement(passwordField);
125 |
126 | actions.addElement(Button.builder()
127 | .text("Change MC password")
128 | .onClick(ConfirmAction.of(SendJSAction.of(
129 | "shittyauth", "changeMCPassword",
130 | ActionValue.object()
131 | .put("account", ActionValue.string(acc.getID()))
132 | .put("password", ActionValue.elementValue(passwordField))
133 | )
134 | .onSuccess(ReloadPageAction.delayed(100))))
135 | .create());
136 | grp.addElement(actions);
137 |
138 | // TODO: this should be in the main WIAPI users page, not here
139 | PasswordField wiapiPasswordField = PasswordField.builder()
140 | .placeholder("Password")
141 | .create();
142 | actions.addElement(wiapiPasswordField);
143 |
144 | actions.addElement(Button.builder()
145 | .text("Change WIAPI password")
146 | .onClick(ConfirmAction.of(SendJSAction.of(
147 | "shittyauth", "changeWIAPIPassword",
148 | ActionValue.object()
149 | .put("account", ActionValue.string(acc.getID()))
150 | .put("password", ActionValue.elementValue(wiapiPasswordField))
151 | )
152 | .onSuccess(ReloadPageAction.delayed(100))))
153 | .create());
154 |
155 | grp.addElement(Button.builder()
156 | .text("Delete account")
157 | .width("auto")
158 | .align(Align.LEFT_CENTER)
159 | .withLayoutOptions(DefaultLayoutOption.FULL_WIDTH)
160 | .onClick(ConfirmAction.of(SendJSAction.of("webinterface", "deleteAccount", ActionValue.object().put("account", ActionValue.string(acc.getID()))).onSuccess(ReloadPageAction.delayed(100))))
161 | .create());
162 | grp.addElement(new VerticalSpacer("30px"));
163 |
164 | els.add(grp);
165 | }
166 | });
167 |
168 | addSection(sc);
169 | }
170 |
171 | }
172 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/CreateAccountPage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page;
2 |
3 | import me.mrletsplay.shittyauth.ShittyAuth;
4 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
5 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
6 | import me.mrletsplay.webinterfaceapi.auth.Account;
7 | import me.mrletsplay.webinterfaceapi.auth.AccountConnection;
8 | import me.mrletsplay.webinterfaceapi.page.Page;
9 | import me.mrletsplay.webinterfaceapi.page.PageSection;
10 | import me.mrletsplay.webinterfaceapi.page.action.RedirectAction;
11 | import me.mrletsplay.webinterfaceapi.page.action.SendJSAction;
12 | import me.mrletsplay.webinterfaceapi.page.action.value.ActionValue;
13 | import me.mrletsplay.webinterfaceapi.page.element.Button;
14 | import me.mrletsplay.webinterfaceapi.page.element.InputField;
15 | import me.mrletsplay.webinterfaceapi.page.element.TitleText;
16 | import me.mrletsplay.webinterfaceapi.page.element.layout.Grid;
17 | import me.mrletsplay.webinterfaceapi.session.Session;
18 |
19 | public class CreateAccountPage extends Page {
20 |
21 | public static final String PATH = "/shittyauth/create";
22 |
23 | public CreateAccountPage() {
24 | super("Create Account", PATH, true);
25 |
26 | PageSection s = new PageSection();
27 | s.setSlimLayout(true);
28 | addSection(s);
29 |
30 | s.addTitle("Create your Minecraft account");
31 | s.setGrid(new Grid().setColumns("25fr", "75fr"));
32 |
33 | s.addElement(TitleText.builder().text("Username").leftboundText().create());
34 | InputField username = InputField.builder().placeholder("Minecraft Username").create();
35 | username.setMinLength(3);
36 | username.setMaxLength(16);
37 | s.addElement(username);
38 |
39 | s.addElement(TitleText.builder().text("Password").leftboundText().create());
40 | InputField password = InputField.builder().placeholder("Minecraft Password").create();
41 | s.addElement(password);
42 |
43 | s.addElement(Button.builder()
44 | .text("Create")
45 | .fullWidth()
46 | .onClick(SendJSAction.of("shittyauth", "createAccount", ActionValue.object()
47 | .put("username", ActionValue.elementValue(username))
48 | .put("password", ActionValue.elementValue(password)))
49 | .onSuccess(RedirectAction.to(AccountPage.PATH)))
50 | .create());
51 | }
52 |
53 | @Override
54 | public void createContent() {
55 | if(!Session.requireSession()) return;
56 |
57 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
58 | Account acc = Session.getCurrentSession().getAccount();
59 | AccountConnection mcCon = acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
60 | if(mcCon != null) {
61 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.FOUND_302);
62 | ctx.getServerHeader().getFields().set("Location", AccountPage.PATH);
63 | return;
64 | }
65 |
66 | super.createContent();
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/SettingsPage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page;
2 |
3 | import me.mrletsplay.shittyauth.ShittyAuth;
4 | import me.mrletsplay.shittyauth.config.ShittyAuthSettings;
5 | import me.mrletsplay.webinterfaceapi.DefaultPermissions;
6 | import me.mrletsplay.webinterfaceapi.page.Page;
7 | import me.mrletsplay.webinterfaceapi.page.PageSection;
8 | import me.mrletsplay.webinterfaceapi.page.element.SettingsPane;
9 |
10 | public class SettingsPage extends Page {
11 |
12 | public static final String PATH = "/shittyauth/settings";
13 |
14 | public SettingsPage() {
15 | super("Settings", PATH, DefaultPermissions.SETTINGS);
16 | setIcon("mdi:cog");
17 |
18 | PageSection sc2 = new PageSection();
19 | sc2.setSlimLayout(true);
20 | sc2.addTitle("Settings");
21 | sc2.addElement(new SettingsPane(() -> ShittyAuth.config, ShittyAuthSettings.INSTANCE.getSettingsCategories(), "shittyauth", "setSetting"));
22 |
23 | addSection(sc2);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/UserCapeDocument.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api;
2 |
3 | import java.io.IOException;
4 | import java.nio.charset.StandardCharsets;
5 |
6 | import me.mrletsplay.shittyauth.ShittyAuth;
7 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
8 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
9 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
10 | import me.mrletsplay.simplehttpserver.http.util.MimeType;
11 | import me.mrletsplay.webinterfaceapi.Webinterface;
12 | import me.mrletsplay.webinterfaceapi.auth.Account;
13 |
14 | public class UserCapeDocument implements HttpDocument {
15 |
16 | public static final UserCapeDocument INSTANCE = new UserCapeDocument();
17 |
18 | @Override
19 | public void createContent() {
20 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
21 | String accID = ctx.getPathParameters().get("uuid");
22 | if(accID.contains("_")) accID = accID.substring(0, accID.indexOf("_"));
23 | Account acc = Webinterface.getAccountStorage().getAccountByConnectionSpecificID(ShittyAuth.ACCOUNT_CONNECTION_NAME, accID);
24 | if(acc == null) {
25 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.NOT_FOUND_404);
26 | ctx.getServerHeader().setContent(MimeType.TEXT, "404 Not Found".getBytes(StandardCharsets.UTF_8));
27 | return;
28 | }
29 |
30 | try {
31 | byte[] bytes = ShittyAuth.loadUserCapeRaw(accID);
32 | ctx.getServerHeader().setContent(MimeType.PNG, bytes);
33 | } catch (IOException e) {
34 | Webinterface.getLogger().error("Failed to load cape", e);
35 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.INTERNAL_SERVER_ERROR_500);
36 | ctx.getServerHeader().setContent(MimeType.TEXT, "500 Internal Server error".getBytes(StandardCharsets.UTF_8));
37 | return;
38 | }
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/UserSkinDocument.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api;
2 |
3 | import java.io.IOException;
4 | import java.nio.charset.StandardCharsets;
5 |
6 | import me.mrletsplay.shittyauth.ShittyAuth;
7 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
8 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
9 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
10 | import me.mrletsplay.simplehttpserver.http.util.MimeType;
11 | import me.mrletsplay.webinterfaceapi.Webinterface;
12 | import me.mrletsplay.webinterfaceapi.auth.Account;
13 |
14 | public class UserSkinDocument implements HttpDocument {
15 |
16 | public static final UserSkinDocument INSTANCE = new UserSkinDocument();
17 |
18 | @Override
19 | public void createContent() {
20 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
21 | String accID = ctx.getPathParameters().get("uuid");
22 | if(accID.contains("_")) accID = accID.substring(0, accID.indexOf("_"));
23 | Account acc = Webinterface.getAccountStorage().getAccountByConnectionSpecificID(ShittyAuth.ACCOUNT_CONNECTION_NAME, accID);
24 | if(acc == null) {
25 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.NOT_FOUND_404);
26 | ctx.getServerHeader().setContent(MimeType.TEXT, "404 Not Found".getBytes(StandardCharsets.UTF_8));
27 | return;
28 | }
29 |
30 | try {
31 | byte[] bytes = ShittyAuth.loadUserSkinRaw(accID);
32 | ctx.getServerHeader().setContent(MimeType.PNG, bytes);
33 | ctx.getServerHeader().getFields().set("Content-Type", "image/png");
34 | } catch (IOException e) {
35 | Webinterface.getLogger().error("Failed to load skin", e);
36 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.INTERNAL_SERVER_ERROR_500);
37 | ctx.getServerHeader().setContent(MimeType.TEXT, "500 Internal Server error".getBytes(StandardCharsets.UTF_8));
38 | return;
39 | }
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/authlibinjector/AuthLibInjectorDocument.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.authlibinjector;
2 |
3 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
4 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
5 | import me.mrletsplay.webinterfaceapi.Webinterface;
6 |
7 | public class AuthLibInjectorDocument implements HttpDocument {
8 |
9 | @Override
10 | public void createContent() {
11 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
12 | String page = ctx.getPathParameters().get("page");
13 | HttpDocument doc = Webinterface.getDocumentProvider().get(ctx.getClientHeader().getMethod(), "/" + page);
14 | if(doc == null) {
15 | Webinterface.getDocumentProvider().getNotFoundDocument().createContent();
16 | return;
17 | }
18 |
19 | doc.createContent();
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/authlibinjector/AuthLibInjectorMetadataDocument.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.authlibinjector;
2 |
3 | import java.net.URI;
4 | import java.nio.charset.StandardCharsets;
5 |
6 | import me.mrletsplay.mrcore.json.JSONArray;
7 | import me.mrletsplay.mrcore.json.JSONObject;
8 | import me.mrletsplay.shittyauth.ShittyAuth;
9 | import me.mrletsplay.shittyauth.util.CryptoHelper;
10 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
11 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
12 | import me.mrletsplay.simplehttpserver.http.util.MimeType;
13 | import me.mrletsplay.webinterfaceapi.Webinterface;
14 | import me.mrletsplay.webinterfaceapi.config.DefaultSettings;
15 |
16 | public class AuthLibInjectorMetadataDocument implements HttpDocument {
17 |
18 | @Override
19 | public void createContent() {
20 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
21 |
22 | JSONObject obj = new JSONObject();
23 | JSONObject meta = new JSONObject();
24 | meta.put("implementationName", "ShittyAuthServer");
25 | meta.put("implementationVersion", "69.420");
26 | meta.put("serverName", "ShittyAuthServer");
27 | JSONObject links = new JSONObject();
28 | links.put("homepage", Webinterface.getConfig().getSetting(DefaultSettings.HTTP_BASE_URL));
29 | links.put("register", Webinterface.getConfig().getSetting(DefaultSettings.HTTP_BASE_URL));
30 | meta.put("links", links);
31 |
32 | meta.put("feature.legacy_skin_api", true);
33 | meta.put("feature.no_mojang_namespace", true);
34 | meta.put("feature.enable_profile_key", true);
35 | meta.put("feature.username_check", true);
36 | obj.put("meta", meta);
37 |
38 | JSONArray skinDomains = new JSONArray();
39 | skinDomains.add(URI.create(Webinterface.getConfig().getSetting(DefaultSettings.HTTP_BASE_URL)).getHost());
40 | obj.put("skinDomains", skinDomains);
41 |
42 | obj.put("signaturePublicKey", CryptoHelper.encodeRSAPublicKey(ShittyAuth.publicKey));
43 | ctx.getServerHeader().setContent(MimeType.JSON, obj.toString().getBytes(StandardCharsets.UTF_8));
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/legacy/LegacyCheckServerDocument.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.legacy;
2 |
3 | import java.nio.charset.StandardCharsets;
4 |
5 | import me.mrletsplay.shittyauth.ShittyAuth;
6 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
7 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
8 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
9 | import me.mrletsplay.simplehttpserver.http.request.urlencoded.UrlEncoded;
10 | import me.mrletsplay.simplehttpserver.http.util.MimeType;
11 | import me.mrletsplay.webinterfaceapi.auth.Account;
12 | import me.mrletsplay.webinterfaceapi.auth.AccountConnection;
13 |
14 | public class LegacyCheckServerDocument implements HttpDocument {
15 |
16 | @Override
17 | public void createContent() {
18 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
19 | UrlEncoded query = ctx.getClientHeader().getPath().getQuery();
20 |
21 | if(!query.has("user") || !query.has("serverId")) {
22 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.BAD_REQUEST_400);
23 | ctx.getServerHeader().setContent(MimeType.TEXT, "Bad Request".getBytes(StandardCharsets.UTF_8));
24 | return;
25 | }
26 |
27 | String accName = query.getFirst("user");
28 | String serverId = query.getFirst("serverId");
29 |
30 | Account acc = ShittyAuth.getAccountByUsername(accName);
31 | AccountConnection con = acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
32 | String joinedServer = ShittyAuth.userServers.get(con.getUserID());
33 | if(joinedServer == null || !joinedServer.equals(serverId)) {
34 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.UNAUTHORIZED_401);
35 | return;
36 | }
37 |
38 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.OK_200);
39 | ctx.getServerHeader().setContent(MimeType.TEXT, "YES".getBytes(StandardCharsets.UTF_8));
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/legacy/LegacyJoinServerDocument.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.legacy;
2 |
3 | import java.nio.charset.StandardCharsets;
4 |
5 | import me.mrletsplay.shittyauth.ShittyAuth;
6 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
7 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
8 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
9 | import me.mrletsplay.simplehttpserver.http.request.urlencoded.UrlEncoded;
10 | import me.mrletsplay.simplehttpserver.http.util.MimeType;
11 | import me.mrletsplay.webinterfaceapi.auth.Account;
12 | import me.mrletsplay.webinterfaceapi.auth.AccountConnection;
13 |
14 | public class LegacyJoinServerDocument implements HttpDocument {
15 |
16 | @Override
17 | public void createContent() {
18 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
19 | UrlEncoded query = ctx.getClientHeader().getPath().getQuery();
20 |
21 | if(!query.has("sessionId") || !query.has("user") || !query.has("serverId")) {
22 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.BAD_REQUEST_400);
23 | ctx.getServerHeader().setContent(MimeType.TEXT, "Bad Request".getBytes(StandardCharsets.UTF_8));
24 | return;
25 | }
26 |
27 | String accessToken = query.getFirst("sessionId");
28 | String accName = query.getFirst("user");
29 | String serverId = query.getFirst("serverId");
30 |
31 | String accID = ShittyAuth.tokenStorage.getAccountID(accessToken);
32 |
33 | if(accID == null) {
34 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.UNAUTHORIZED_401);
35 | return;
36 | }
37 |
38 | Account acc = ShittyAuth.getAccountByUsername(accName);
39 | AccountConnection con = acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
40 | if(!con.getUserID().equals(accID)) {
41 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.UNAUTHORIZED_401);
42 | return;
43 | }
44 |
45 | ShittyAuth.userServers.put(con.getUserID(), serverId);
46 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.OK_200);
47 | ctx.getServerHeader().setContent(MimeType.TEXT, "ok".getBytes(StandardCharsets.UTF_8));
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/legacy/LegacyUserCapeDocument.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.legacy;
2 |
3 | import java.io.IOException;
4 | import java.nio.charset.StandardCharsets;
5 |
6 | import me.mrletsplay.shittyauth.ShittyAuth;
7 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
8 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
9 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
10 | import me.mrletsplay.simplehttpserver.http.util.MimeType;
11 | import me.mrletsplay.webinterfaceapi.Webinterface;
12 | import me.mrletsplay.webinterfaceapi.auth.Account;
13 | import me.mrletsplay.webinterfaceapi.auth.AccountConnection;
14 |
15 | public class LegacyUserCapeDocument implements HttpDocument {
16 |
17 | public static final String PATH_PREFIX = "/MinecraftCloaks/";
18 | public static final LegacyUserCapeDocument INSTANCE = new LegacyUserCapeDocument();
19 |
20 | @Override
21 | public void createContent() {
22 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
23 | String username = ctx.getPathParameters().get("name");
24 | if(username.endsWith(".png")) username = username.substring(0, username.length() - ".png".length());
25 | Account acc = ShittyAuth.getAccountByUsername(username);
26 | if(acc == null) {
27 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.NOT_FOUND_404);
28 | ctx.getServerHeader().setContent(MimeType.TEXT, "404 Not Found".getBytes(StandardCharsets.UTF_8));
29 | return;
30 | }
31 |
32 | AccountConnection con = acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
33 |
34 | try {
35 | byte[] bytes = ShittyAuth.loadUserCapeRaw(con.getUserID());
36 | ctx.getServerHeader().setContent(bytes);
37 | ctx.getServerHeader().getFields().set("Content-Type", "image/png");
38 | } catch (IOException e) {
39 | Webinterface.getLogger().error("Failed to load cape", e);
40 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.INTERNAL_SERVER_ERROR_500);
41 | ctx.getServerHeader().setContent(MimeType.TEXT, "500 Internal Server error".getBytes(StandardCharsets.UTF_8));
42 | return;
43 | }
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/legacy/LegacyUserSkinDocument.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.legacy;
2 |
3 | import java.io.IOException;
4 | import java.nio.charset.StandardCharsets;
5 |
6 | import me.mrletsplay.shittyauth.ShittyAuth;
7 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
8 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
9 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
10 | import me.mrletsplay.simplehttpserver.http.util.MimeType;
11 | import me.mrletsplay.webinterfaceapi.Webinterface;
12 | import me.mrletsplay.webinterfaceapi.auth.Account;
13 | import me.mrletsplay.webinterfaceapi.auth.AccountConnection;
14 |
15 | public class LegacyUserSkinDocument implements HttpDocument {
16 |
17 | public static final String PATH_PREFIX = "/MinecraftSkins/";
18 | public static final LegacyUserSkinDocument INSTANCE = new LegacyUserSkinDocument();
19 |
20 | @Override
21 | public void createContent() {
22 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
23 | String username = ctx.getPathParameters().get("name");
24 | if(username.endsWith(".png")) username = username.substring(0, username.length() - ".png".length());
25 | Account acc = ShittyAuth.getAccountByUsername(username);
26 | if(acc == null) {
27 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.NOT_FOUND_404);
28 | ctx.getServerHeader().setContent(MimeType.TEXT, "404 Not Found".getBytes(StandardCharsets.UTF_8));
29 | return;
30 | }
31 |
32 | AccountConnection con = acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
33 |
34 | try {
35 | byte[] bytes = ShittyAuth.loadUserSkinRaw(con.getUserID());
36 | ctx.getServerHeader().setContent(bytes);
37 | ctx.getServerHeader().getFields().set("Content-Type", "image/png");
38 | } catch (IOException e) {
39 | Webinterface.getLogger().error("Failed to load skin", e);
40 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.INTERNAL_SERVER_ERROR_500);
41 | ctx.getServerHeader().setContent(MimeType.TEXT, "500 Internal Server error".getBytes(StandardCharsets.UTF_8));
42 | return;
43 | }
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/services/PlayerAttributesDocument.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.services;
2 |
3 | import java.nio.charset.StandardCharsets;
4 |
5 | import me.mrletsplay.mrcore.json.JSONObject;
6 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
7 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
8 | import me.mrletsplay.simplehttpserver.http.util.MimeType;
9 |
10 | public class PlayerAttributesDocument implements HttpDocument {
11 |
12 | @Override
13 | public void createContent() {
14 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
15 | // String auth = ctx.getClientHeader().getFields().getFieldValue("Authorization"); TODO: ignored for now
16 |
17 | JSONObject obj = new JSONObject();
18 | JSONObject privileges = new JSONObject();
19 | JSONObject enabled = new JSONObject();
20 | enabled.set("enabled", true);
21 | JSONObject disabled = new JSONObject();
22 | disabled.set("enabled", false);
23 | privileges.set("onlineChat", enabled);
24 | privileges.set("multiplayerServer", enabled);
25 | privileges.set("multiplayerRealms", enabled);
26 | privileges.set("telemetry", disabled);
27 | obj.put("privileges", privileges);
28 |
29 | JSONObject prof = new JSONObject();
30 | prof.put("profanityFilterOn", false);
31 | obj.put("profanityFilterPreferences", prof);
32 | ctx.getServerHeader().setContent(MimeType.JSON, obj.toString().getBytes(StandardCharsets.UTF_8));
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/services/PlayerCertificatesDocument.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.services;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import java.nio.charset.StandardCharsets;
7 | import java.security.PublicKey;
8 | import java.security.Signature;
9 | import java.time.Instant;
10 | import java.time.temporal.ChronoUnit;
11 | import java.util.Base64;
12 | import java.util.UUID;
13 |
14 | import me.mrletsplay.mrcore.json.JSONObject;
15 | import me.mrletsplay.shittyauth.ShittyAuth;
16 | import me.mrletsplay.shittyauth.auth.StoredAccessToken;
17 | import me.mrletsplay.shittyauth.user.UserData;
18 | import me.mrletsplay.shittyauth.util.CryptoHelper;
19 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
20 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
21 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
22 | import me.mrletsplay.simplehttpserver.http.util.MimeType;
23 |
24 | public class PlayerCertificatesDocument implements HttpDocument {
25 |
26 | @Override
27 | public void createContent() {
28 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
29 | String auth = ctx.getClientHeader().getFields().getFirst("Authorization");
30 | if(!auth.startsWith("Bearer ")) {
31 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.ACCESS_DENIED_403);
32 | return;
33 | }
34 |
35 | auth = auth.substring("Bearer ".length());
36 | StoredAccessToken tok = ShittyAuth.tokenStorage.getStoredToken(auth);
37 | if(tok == null) {
38 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.ACCESS_DENIED_403);
39 | return;
40 | }
41 |
42 | Instant expAt = Instant.now().plus(7, ChronoUnit.DAYS); // TODO: Use correct timestamps
43 | UserData userData = ShittyAuth.dataStorage.getUserData(tok.getAccountID());
44 |
45 | JSONObject obj = new JSONObject();
46 | JSONObject keyPair = new JSONObject();
47 | keyPair.put("privateKey", CryptoHelper.encodeRSAPrivateKey(userData.getPrivateKey()));
48 | keyPair.put("publicKey", CryptoHelper.encodeRSAPublicKey(userData.getPublicKey()));
49 | obj.put("keyPair", keyPair);
50 |
51 | try {
52 | Signature sig = Signature.getInstance("SHA1withRSA");
53 | sig.initSign(ShittyAuth.privateKey);
54 | sig.update(getSignaturePayload(UUID.fromString(tok.getAccountID()), expAt, userData.getPublicKey()));
55 | byte[] sign = sig.sign();
56 | // obj.put("publicKeySignature", Base64.getEncoder().encodeToString(sign)); TODO: v1 signature?
57 | obj.put("publicKeySignatureV2", Base64.getEncoder().encodeToString(sign));
58 | }catch (Exception e) {
59 | e.printStackTrace();
60 | }
61 |
62 | obj.put("refreshedAfter", Instant.now().plus(7, ChronoUnit.DAYS).toString());
63 | obj.put("expiresAt", expAt.toString());
64 |
65 | ctx.getServerHeader().setContent(MimeType.JSON, obj.toString().getBytes(StandardCharsets.UTF_8));
66 | }
67 |
68 | private byte[] getSignaturePayload(UUID playerUUID, Instant expiresAt, PublicKey key) {
69 | ByteArrayOutputStream bOut = new ByteArrayOutputStream();
70 | DataOutputStream dOut = new DataOutputStream(bOut);
71 | try {
72 | dOut.writeLong(playerUUID.getMostSignificantBits());
73 | dOut.writeLong(playerUUID.getLeastSignificantBits());
74 | dOut.writeLong(expiresAt.toEpochMilli());
75 | dOut.write(key.getEncoded());
76 | } catch (IOException ignored) {}
77 | return bOut.toByteArray();
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/services/PlayerReportDocument.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.services;
2 |
3 | import java.nio.charset.StandardCharsets;
4 |
5 | import me.mrletsplay.mrcore.json.JSONObject;
6 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
7 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
8 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
9 | import me.mrletsplay.simplehttpserver.http.util.MimeType;
10 |
11 | public class PlayerReportDocument implements HttpDocument {
12 |
13 | @Override
14 | public void createContent() {
15 | // TODO: currently not supported
16 | HttpRequestContext.getCurrentContext().getServerHeader().setStatusCode(HttpStatusCodes.BAD_REQUEST_400);
17 | JSONObject error = new JSONObject();
18 | error.put("errorMessage", "Chat reporting is not supported by ShittyAuthServer");
19 | HttpRequestContext.getCurrentContext().getServerHeader().setContent(MimeType.JSON, error.toString().getBytes(StandardCharsets.UTF_8));
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/shittyauth/ShittyAuthAPI.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.shittyauth;
2 |
3 | import java.awt.image.BufferedImage;
4 | import java.io.ByteArrayInputStream;
5 | import java.io.ByteArrayOutputStream;
6 | import java.io.IOException;
7 | import java.util.Base64;
8 |
9 | import javax.imageio.ImageIO;
10 |
11 | import me.mrletsplay.mrcore.json.JSONArray;
12 | import me.mrletsplay.mrcore.json.JSONObject;
13 | import me.mrletsplay.mrcore.json.JSONType;
14 | import me.mrletsplay.shittyauth.ShittyAuth;
15 | import me.mrletsplay.shittyauth.config.ShittyAuthSettings;
16 | import me.mrletsplay.shittyauth.textures.SkinType;
17 | import me.mrletsplay.shittyauth.textures.TexturesHelper;
18 | import me.mrletsplay.shittyauth.user.UserData;
19 | import me.mrletsplay.shittyauth.util.DefaultTexture;
20 | import me.mrletsplay.shittyauth.util.InvalidSkinException;
21 | import me.mrletsplay.shittyauth.util.InvalidUsernameException;
22 | import me.mrletsplay.shittyauth.webinterface.ShittyAuthWIHandler;
23 | import me.mrletsplay.simplehttpserver.http.HttpRequestMethod;
24 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
25 | import me.mrletsplay.simplehttpserver.http.endpoint.Endpoint;
26 | import me.mrletsplay.simplehttpserver.http.endpoint.EndpointCollection;
27 | import me.mrletsplay.simplehttpserver.http.endpoint.RequestParameter;
28 | import me.mrletsplay.simplehttpserver.http.header.DefaultClientContentTypes;
29 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
30 | import me.mrletsplay.simplehttpserver.http.response.HttpResponse;
31 | import me.mrletsplay.simplehttpserver.http.response.JsonResponse;
32 | import me.mrletsplay.simplehttpserver.http.util.MimeType;
33 | import me.mrletsplay.simplehttpserver.http.validation.JsonObjectValidator;
34 | import me.mrletsplay.simplehttpserver.http.validation.result.ValidationResult;
35 | import me.mrletsplay.webinterfaceapi.Webinterface;
36 | import me.mrletsplay.webinterfaceapi.auth.Account;
37 | import me.mrletsplay.webinterfaceapi.auth.AccountConnection;
38 | import me.mrletsplay.webinterfaceapi.session.Session;
39 |
40 | public class ShittyAuthAPI implements EndpointCollection {
41 |
42 | private static final JsonObjectValidator LOGIN_VALIDATOR = new JsonObjectValidator()
43 | .require("username", JSONType.STRING)
44 | .require("password", JSONType.STRING);
45 |
46 | private static final JsonObjectValidator CHANGE_USERNAME_VALIDATOR = new JsonObjectValidator()
47 | .require("newUsername", JSONType.STRING);
48 |
49 | private static final JsonObjectValidator CHANGE_PASSWORD_VALIDATOR = new JsonObjectValidator()
50 | .require("oldPassword", JSONType.STRING)
51 | .require("newPassword", JSONType.STRING);
52 |
53 | private static final JsonObjectValidator UPDATE_SKIN_SETTINGS_VALIDATOR = new JsonObjectValidator()
54 | .optional("skinType", JSONType.STRING)
55 | .optional("capeEnabled", JSONType.BOOLEAN);
56 |
57 | private static final JsonObjectValidator CHANGE_RESET_SKIN_VALIDATOR = new JsonObjectValidator()
58 | .require("skin", JSONType.STRING);
59 |
60 | private static final JsonObjectValidator CHANGE_RESET_CAPE_VALIDATOR = new JsonObjectValidator()
61 | .require("cape", JSONType.STRING);
62 |
63 | static JSONObject error(String message) {
64 | JSONObject error = new JSONObject();
65 | error.put("error", message);
66 | return error;
67 | }
68 |
69 | static Account requireAuthorization(HttpRequestContext ctx) {
70 | String sessionID = ctx.getClientHeader().getFields().getFirst("Authorization");
71 | if(sessionID == null || !sessionID.startsWith("Bearer ")) {
72 | ctx.respond(HttpStatusCodes.ACCESS_DENIED_403, new JsonResponse(error("Unauthorized")));
73 | return null;
74 | }
75 |
76 | Session session = Session.getSession(sessionID.substring("Bearer ".length()));
77 | if(session == null) {
78 | ctx.respond(HttpStatusCodes.ACCESS_DENIED_403, new JsonResponse(error("Unauthorized")));
79 | return null;
80 | }
81 |
82 | Account account = session.getAccount();
83 | AccountConnection connection = account.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
84 | if(connection == null) {
85 | ctx.respond(HttpStatusCodes.NOT_FOUND_404, new JsonResponse(error("No account")));
86 | return null;
87 | }
88 |
89 | return account;
90 | }
91 |
92 | @Endpoint(method = HttpRequestMethod.GET, path = "/meta")
93 | public void version() {
94 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
95 |
96 | JSONObject meta = new JSONObject();
97 | meta.put("version", 1);
98 |
99 | ctx.respond(HttpStatusCodes.OK_200, new JsonResponse(meta));
100 | }
101 |
102 | @Endpoint(method = HttpRequestMethod.POST, path = "/login")
103 | public void login() {
104 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
105 |
106 | JSONObject object;
107 | if((object = ctx.expectContent(DefaultClientContentTypes.JSON_OBJECT)) == null){
108 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Bad JSON")));
109 | return;
110 | }
111 |
112 | ValidationResult result = LOGIN_VALIDATOR.validate(object);
113 | if(!result.isOk()) {
114 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, result.asJsonResponse());
115 | return;
116 | }
117 |
118 | String username = object.getString("username");
119 | String password = object.getString("password");
120 | Account account = ShittyAuth.getAccountByUsername(username);
121 | if(account == null) {
122 | ctx.respond(HttpStatusCodes.ACCESS_DENIED_403, new JsonResponse(error("Invalid credentials")));
123 | return;
124 | }
125 |
126 | AccountConnection connection = account.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
127 | if(!Webinterface.getCredentialsStorage().checkCredentials(ShittyAuth.ACCOUNT_CONNECTION_NAME, connection.getUserID(), password)) {
128 | ctx.respond(HttpStatusCodes.ACCESS_DENIED_403, new JsonResponse(error("Invalid credentials")));
129 | return;
130 | }
131 |
132 | Session session = Session.startSession(account);
133 | JSONObject response = new JSONObject();
134 | response.put("token", session.getSessionID());
135 | ctx.respond(HttpStatusCodes.OK_200, new JsonResponse(response));
136 | }
137 |
138 | @Endpoint(method = HttpRequestMethod.POST, path = "/register")
139 | public void register() {
140 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
141 |
142 | if(!ShittyAuth.config.getSetting(ShittyAuthSettings.ALLOW_REGISTRATION)) {
143 | ctx.respond(HttpStatusCodes.ACCESS_DENIED_403, new JsonResponse(error("Creation of Minecraft accounts disabled")));
144 | return;
145 | }
146 |
147 | JSONObject object;
148 | if((object = ctx.expectContent(DefaultClientContentTypes.JSON_OBJECT)) == null){
149 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Bad JSON")));
150 | return;
151 | }
152 |
153 | ValidationResult result = LOGIN_VALIDATOR.validate(object);
154 | if(!result.isOk()) {
155 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, result.asJsonResponse());
156 | return;
157 | }
158 |
159 | String username = object.getString("username");
160 | String password = object.getString("password");
161 | if(ShittyAuth.getAccountByUsername(username) != null) {
162 | ctx.respond(HttpStatusCodes.ACCESS_DENIED_403, new JsonResponse(error("A user with that username already exists")));
163 | return;
164 | }
165 |
166 | try {
167 | Account account = ShittyAuth.createAccount(username, password);
168 | Session session = Session.startSession(account);
169 | JSONObject response = new JSONObject();
170 | response.put("token", session.getSessionID());
171 | ctx.respond(HttpStatusCodes.OK_200, new JsonResponse(response));
172 | } catch (InvalidUsernameException e) {
173 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Invalid username")));
174 | }
175 | }
176 |
177 | @Endpoint(method = HttpRequestMethod.GET, path = "/me")
178 | public void me() {
179 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
180 |
181 | Account account = requireAuthorization(ctx);
182 | if(account == null) return;
183 |
184 | AccountConnection connection = account.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
185 |
186 | JSONObject accountInfo = new JSONObject();
187 | accountInfo.put("id", connection.getUserID());
188 | accountInfo.put("username", connection.getUserName());
189 | accountInfo.put("isAdmin", account.hasPermission("*"));
190 | ctx.respond(HttpStatusCodes.OK_200, new JsonResponse(accountInfo));
191 | }
192 |
193 | @Endpoint(method = HttpRequestMethod.GET, path = "/skin")
194 | public void skin() {
195 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
196 |
197 | Account account = requireAuthorization(ctx);
198 | if(account == null) return;
199 |
200 | AccountConnection connection = account.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
201 |
202 | UserData data = ShittyAuth.dataStorage.getUserData(connection.getUserID());
203 |
204 | JSONObject skinInfo = new JSONObject();
205 | skinInfo.put("skinURL", String.format(TexturesHelper.getSkinBaseURL() + TexturesHelper.SKIN_PATH, connection.getUserID(), data.getSkinLastChanged()));
206 | skinInfo.put("skinType", data.getSkinType().name().toLowerCase());
207 | skinInfo.put("capeURL", String.format(TexturesHelper.getSkinBaseURL() + TexturesHelper.CAPE_PATH, connection.getUserID(), data.getCapeLastChanged()));
208 | skinInfo.put("capeEnabled", data.hasCape());
209 | ctx.respond(HttpStatusCodes.OK_200, new JsonResponse(skinInfo));
210 | }
211 |
212 | @Endpoint(method = HttpRequestMethod.POST, path = "/skin")
213 | public void changeSkin() {
214 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
215 |
216 | JSONObject object;
217 | if((object = ctx.expectContent(DefaultClientContentTypes.JSON_OBJECT)) == null){
218 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Bad JSON")));
219 | return;
220 | }
221 |
222 | ValidationResult result = CHANGE_RESET_SKIN_VALIDATOR.validate(object);
223 | if(!result.isOk()) {
224 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, result.asJsonResponse());
225 | return;
226 | }
227 |
228 | Account account = requireAuthorization(ctx);
229 | if(account == null) return;
230 |
231 | AccountConnection connection = account.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
232 |
233 | byte[] skinBytes;
234 | try {
235 | skinBytes = Base64.getDecoder().decode(object.getString("skin"));
236 | }catch(IllegalArgumentException e) {
237 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Invalid base64")));
238 | return;
239 | }
240 |
241 | try {
242 | ShittyAuth.updateUserSkin(connection.getUserID(), ImageIO.read(new ByteArrayInputStream(skinBytes)));
243 | ctx.respond(HttpStatusCodes.OK_200, JsonResponse.EMPTY_OBJECT);
244 | } catch (IOException e) {
245 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Invalid skin file")));
246 | }catch(InvalidSkinException e) {
247 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error(e.getMessage())));
248 | }
249 | }
250 |
251 | @Endpoint(method = HttpRequestMethod.POST, path = "/resetSkin")
252 | public void resetSkin() {
253 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
254 |
255 | JSONObject object;
256 | if((object = ctx.expectContent(DefaultClientContentTypes.JSON_OBJECT)) == null){
257 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Bad JSON")));
258 | return;
259 | }
260 |
261 | ValidationResult result = CHANGE_RESET_SKIN_VALIDATOR.validate(object);
262 | if(!result.isOk()) {
263 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, result.asJsonResponse());
264 | return;
265 | }
266 |
267 | Account account = requireAuthorization(ctx);
268 | if(account == null) return;
269 |
270 | AccountConnection connection = account.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
271 |
272 | DefaultTexture texture;
273 | boolean slim = false;
274 | try {
275 | texture = DefaultTexture.valueOf(object.getString("skin").toUpperCase());
276 | if(!DefaultTexture.getSkins().contains(texture) && !(slim = DefaultTexture.getSlimSkins().contains(texture))) {
277 | throw new IllegalArgumentException();
278 | }
279 | }catch(IllegalArgumentException e) {
280 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Not a valid skin")));
281 | return;
282 | }
283 |
284 | try {
285 | ShittyAuth.updateUserSkin(connection.getUserID(), texture);
286 | UserData d = ShittyAuth.dataStorage.getUserData(connection.getUserID());
287 | d.setSkinType(slim ? SkinType.ALEX : SkinType.STEVE);
288 | ShittyAuth.dataStorage.updateUserData(connection.getUserID(), d);
289 | ctx.respond(HttpStatusCodes.OK_200, JsonResponse.EMPTY_OBJECT);
290 | }catch(IOException e) {
291 | ctx.respond(HttpStatusCodes.INTERNAL_SERVER_ERROR_500, new JsonResponse(error("Failed to update skin")));
292 | return;
293 | }
294 | }
295 |
296 | @Endpoint(method = HttpRequestMethod.POST, path = "/cape")
297 | public void changeCape() {
298 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
299 |
300 | JSONObject object;
301 | if((object = ctx.expectContent(DefaultClientContentTypes.JSON_OBJECT)) == null){
302 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Bad JSON")));
303 | return;
304 | }
305 |
306 | ValidationResult result = CHANGE_RESET_CAPE_VALIDATOR.validate(object);
307 | if(!result.isOk()) {
308 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, result.asJsonResponse());
309 | return;
310 | }
311 |
312 | Account account = requireAuthorization(ctx);
313 | if(account == null) return;
314 |
315 | AccountConnection connection = account.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
316 |
317 | byte[] skinBytes;
318 | try {
319 | skinBytes = Base64.getDecoder().decode(object.getString("cape"));
320 | }catch(IllegalArgumentException e) {
321 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Invalid base64")));
322 | return;
323 | }
324 |
325 | try {
326 | ShittyAuth.updateUserCape(connection.getUserID(), ImageIO.read(new ByteArrayInputStream(skinBytes)));
327 | ctx.respond(HttpStatusCodes.OK_200, JsonResponse.EMPTY_OBJECT);
328 | } catch (IOException e) {
329 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Invalid cape file")));
330 | }catch(InvalidSkinException e) {
331 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error(e.getMessage())));
332 | }
333 | }
334 |
335 | @Endpoint(method = HttpRequestMethod.POST, path = "/resetCape")
336 | public void resetCape() {
337 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
338 |
339 | JSONObject object;
340 | if((object = ctx.expectContent(DefaultClientContentTypes.JSON_OBJECT)) == null){
341 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Bad JSON")));
342 | return;
343 | }
344 |
345 | ValidationResult result = CHANGE_RESET_CAPE_VALIDATOR.validate(object);
346 | if(!result.isOk()) {
347 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, result.asJsonResponse());
348 | return;
349 | }
350 |
351 | Account account = requireAuthorization(ctx);
352 | if(account == null) return;
353 |
354 | AccountConnection connection = account.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
355 |
356 | DefaultTexture texture;
357 | try {
358 | texture = DefaultTexture.valueOf(object.getString("cape").toUpperCase());
359 | if(!DefaultTexture.getCapes().contains(texture)) {
360 | throw new IllegalArgumentException();
361 | }
362 | }catch(IllegalArgumentException e) {
363 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Not a valid cape")));
364 | return;
365 | }
366 |
367 | try {
368 | ShittyAuth.updateUserCape(connection.getUserID(), texture);
369 | ctx.respond(HttpStatusCodes.OK_200, JsonResponse.EMPTY_OBJECT);
370 | }catch(IOException e) {
371 | ctx.respond(HttpStatusCodes.INTERNAL_SERVER_ERROR_500, new JsonResponse(error("Failed to update cape")));
372 | return;
373 | }
374 | }
375 |
376 | @Endpoint(method = HttpRequestMethod.POST, path = "/changeUsername")
377 | public void changeUsername() {
378 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
379 |
380 | JSONObject object;
381 | if((object = ctx.expectContent(DefaultClientContentTypes.JSON_OBJECT)) == null){
382 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Bad JSON")));
383 | return;
384 | }
385 |
386 | ValidationResult result = CHANGE_USERNAME_VALIDATOR.validate(object);
387 | if(!result.isOk()) {
388 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, result.asJsonResponse());
389 | return;
390 | }
391 |
392 | Account account = requireAuthorization(ctx);
393 | if(account == null) return;
394 |
395 | String newUsername = object.getString("newUsername");
396 | if(!ShittyAuthWIHandler.USERNAME_PATTERN.matcher(newUsername).matches()) {
397 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Invalid username")));
398 | return;
399 | }
400 |
401 | if(ShittyAuth.getAccountByUsername(newUsername) != null) {
402 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Username is already taken")));
403 | return;
404 | }
405 |
406 | AccountConnection connection = account.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
407 | AccountConnection newConnection = new AccountConnection(connection.getConnectionName(), connection.getUserID(), newUsername, connection.getUserEmail(), connection.getUserAvatar());
408 | account.removeConnection(connection);
409 | account.addConnection(newConnection);
410 |
411 | ctx.respond(HttpStatusCodes.OK_200, JsonResponse.EMPTY_OBJECT);
412 | }
413 |
414 | @Endpoint(method = HttpRequestMethod.POST, path = "/changePassword")
415 | public void changePassword() {
416 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
417 |
418 | JSONObject object;
419 | if((object = ctx.expectContent(DefaultClientContentTypes.JSON_OBJECT)) == null){
420 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Bad JSON")));
421 | return;
422 | }
423 |
424 | ValidationResult result = CHANGE_PASSWORD_VALIDATOR.validate(object);
425 | if(!result.isOk()) {
426 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, result.asJsonResponse());
427 | return;
428 | }
429 |
430 | Account account = requireAuthorization(ctx);
431 | if(account == null) return;
432 |
433 | String oldPassword = object.getString("oldPassword");
434 | String newPassword = object.getString("newPassword");
435 |
436 | if(newPassword.isEmpty()) {
437 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("New password can't be empty")));
438 | return;
439 | }
440 |
441 | AccountConnection connection = account.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
442 | if(!Webinterface.getCredentialsStorage().checkCredentials(ShittyAuth.ACCOUNT_CONNECTION_NAME, connection.getUserID(), oldPassword)) {
443 | ctx.respond(HttpStatusCodes.ACCESS_DENIED_403, new JsonResponse(error("Invalid credentials")));
444 | return;
445 | }
446 |
447 | Webinterface.getCredentialsStorage().storeCredentials(ShittyAuth.ACCOUNT_CONNECTION_NAME, connection.getUserID(), newPassword);
448 | ctx.respond(HttpStatusCodes.OK_200, JsonResponse.EMPTY_OBJECT);
449 | }
450 |
451 | @Endpoint(method = HttpRequestMethod.PUT, path = "/updateSkinSettings")
452 | public void updateSkinSettings() {
453 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
454 |
455 | JSONObject object;
456 | if((object = ctx.expectContent(DefaultClientContentTypes.JSON_OBJECT)) == null){
457 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Bad JSON")));
458 | return;
459 | }
460 |
461 | ValidationResult result = UPDATE_SKIN_SETTINGS_VALIDATOR.validate(object);
462 | if(!result.isOk()) {
463 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, result.asJsonResponse());
464 | return;
465 | }
466 |
467 | Account account = requireAuthorization(ctx);
468 | if(account == null) return;
469 |
470 | AccountConnection connection = account.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
471 |
472 | SkinType skinType = null;
473 |
474 | if(object.has("skinType")) {
475 | try {
476 | skinType = SkinType.valueOf(object.getString("skinType").toUpperCase());
477 | }catch(IllegalArgumentException e) {
478 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Invalid skin type")));
479 | return;
480 | }
481 | }
482 |
483 | Boolean capeEnabled = object.optBoolean("capeEnabled").orElse(null);
484 |
485 | if(skinType != null || capeEnabled != null) {
486 | UserData data = ShittyAuth.dataStorage.getUserData(connection.getUserID());
487 | if(skinType != null) data.setSkinType(skinType);
488 | if(capeEnabled != null) data.setHasCape(capeEnabled);
489 | ShittyAuth.dataStorage.updateUserData(connection.getUserID(), data);
490 | }
491 |
492 | ctx.respond(HttpStatusCodes.OK_200, JsonResponse.EMPTY_OBJECT);
493 | }
494 |
495 | @Endpoint(method = HttpRequestMethod.GET, path = "/avatar/{userID}", pathPattern = true)
496 | public void avatar(@RequestParameter("userID") String userID) {
497 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
498 |
499 | Account account = Webinterface.getAccountStorage().getAccountByConnectionSpecificID(ShittyAuth.ACCOUNT_CONNECTION_NAME, userID);
500 | if(account == null) {
501 | ctx.respond(HttpStatusCodes.NOT_FOUND_404, new JsonResponse(error("Account not found")));
502 | return;
503 | }
504 |
505 | byte[] headBytes;
506 | try {
507 | BufferedImage skinImage = ShittyAuth.loadUserSkin(userID);
508 | BufferedImage headImage = skinImage.getSubimage(8, 8, 8, 8);
509 | ByteArrayOutputStream bOut = new ByteArrayOutputStream();
510 | ImageIO.write(headImage, "PNG", bOut);
511 | headBytes = bOut.toByteArray();
512 | } catch (IOException e) {
513 | ctx.respond(HttpStatusCodes.INTERNAL_SERVER_ERROR_500, new JsonResponse(error("Failed to load skin")));
514 | return;
515 | }
516 |
517 | ctx.respond(HttpStatusCodes.OK_200, new HttpResponse() {
518 |
519 | @Override
520 | public MimeType getContentType() {
521 | return MimeType.PNG;
522 | }
523 |
524 | @Override
525 | public byte[] getContent() {
526 | return headBytes;
527 | }
528 | });
529 | }
530 |
531 | @Endpoint(method = HttpRequestMethod.GET, path = "/defaultSkins")
532 | public void defaultSkins() {
533 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
534 |
535 | JSONObject res = new JSONObject();
536 |
537 | JSONArray skins = new JSONArray();
538 | for(DefaultTexture t : DefaultTexture.getSkins()) {
539 | JSONObject skin = new JSONObject();
540 | skin.put("id", t.name().toLowerCase());
541 | skin.put("name", t.getName());
542 | skin.put("url", t.getURL()); // TODO: provide url on ShittyAuthServer
543 | skins.add(skin);
544 | }
545 | res.put("skins", skins);
546 |
547 | JSONArray slimSkins = new JSONArray();
548 | for(DefaultTexture t : DefaultTexture.getSlimSkins()) {
549 | JSONObject skin = new JSONObject();
550 | skin.put("id", t.name().toLowerCase());
551 | skin.put("name", t.getName());
552 | skin.put("url", t.getURL()); // TODO: provide url on ShittyAuthServer
553 | slimSkins.add(skin);
554 | }
555 | res.put("slimSkins", slimSkins);
556 |
557 | ctx.respond(HttpStatusCodes.OK_200, new JsonResponse(res));
558 | }
559 |
560 | @Endpoint(method = HttpRequestMethod.GET, path = "/defaultCapes")
561 | public void defaultCapes() {
562 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
563 |
564 | JSONArray capes = new JSONArray();
565 | for(DefaultTexture t : DefaultTexture.getCapes()) {
566 | JSONObject cape = new JSONObject();
567 | cape.put("id", t.name().toLowerCase());
568 | cape.put("name", t.getName());
569 | cape.put("url", t.getURL()); // TODO: provide url on ShittyAuthServer
570 | capes.add(cape);
571 | }
572 |
573 | ctx.respond(HttpStatusCodes.OK_200, new JsonResponse(capes));
574 | }
575 |
576 | @Override
577 | public String getBasePath() {
578 | return "/api/shittyauth";
579 | }
580 |
581 | }
582 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/shittyauth/ShittyAuthAdminAPI.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.shittyauth;
2 |
3 | import static me.mrletsplay.shittyauth.page.api.shittyauth.ShittyAuthAPI.error;
4 | import static me.mrletsplay.shittyauth.page.api.shittyauth.ShittyAuthAPI.requireAuthorization;
5 |
6 | import java.util.List;
7 |
8 | import me.mrletsplay.mrcore.json.JSONArray;
9 | import me.mrletsplay.mrcore.json.JSONObject;
10 | import me.mrletsplay.mrcore.json.JSONType;
11 | import me.mrletsplay.shittyauth.ShittyAuth;
12 | import me.mrletsplay.shittyauth.config.ShittyAuthSettings;
13 | import me.mrletsplay.shittyauth.webinterface.ShittyAuthWIHandler;
14 | import me.mrletsplay.simplehttpserver.http.HttpRequestMethod;
15 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
16 | import me.mrletsplay.simplehttpserver.http.endpoint.Endpoint;
17 | import me.mrletsplay.simplehttpserver.http.endpoint.EndpointCollection;
18 | import me.mrletsplay.simplehttpserver.http.header.DefaultClientContentTypes;
19 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
20 | import me.mrletsplay.simplehttpserver.http.response.JsonResponse;
21 | import me.mrletsplay.simplehttpserver.http.validation.JsonObjectValidator;
22 | import me.mrletsplay.simplehttpserver.http.validation.result.ValidationResult;
23 | import me.mrletsplay.webinterfaceapi.Webinterface;
24 | import me.mrletsplay.webinterfaceapi.auth.Account;
25 | import me.mrletsplay.webinterfaceapi.auth.AccountConnection;
26 |
27 | public class ShittyAuthAdminAPI implements EndpointCollection {
28 |
29 | private static final JsonObjectValidator CHANGE_USERNAME_VALIDATOR = new JsonObjectValidator()
30 | .require("userID", JSONType.STRING)
31 | .require("newUsername", JSONType.STRING);
32 |
33 | private static final JsonObjectValidator CHANGE_PASSWORD_VALIDATOR = new JsonObjectValidator()
34 | .require("userID", JSONType.STRING)
35 | .require("newPassword", JSONType.STRING);
36 |
37 | private static final JsonObjectValidator DELETE_ACCOUNT_VALIDATOR = new JsonObjectValidator()
38 | .require("userID", JSONType.STRING);
39 |
40 | private static final JsonObjectValidator CHANGE_ADMIN_VALIDATOR = new JsonObjectValidator()
41 | .require("userID", JSONType.STRING)
42 | .require("admin", JSONType.BOOLEAN);
43 |
44 | private static final JsonObjectValidator GLOBAL_SETTINGS_VALIDATOR = new JsonObjectValidator()
45 | .optional("allowRegistration", JSONType.BOOLEAN)
46 | .optional("authlibCompat", JSONType.BOOLEAN);
47 |
48 | private static Account requireAdmin(HttpRequestContext ctx) {
49 | Account account = requireAuthorization(ctx);
50 | if(account == null) return null;
51 |
52 | if(!account.hasPermission("*")) {
53 | ctx.respond(HttpStatusCodes.ACCESS_DENIED_403, new JsonResponse(error("Unauthorized")));
54 | return null;
55 | }
56 |
57 | return account;
58 | }
59 |
60 | @Endpoint(method = HttpRequestMethod.GET, path = "/accounts")
61 | public void accounts() {
62 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
63 |
64 | Account account = requireAdmin(ctx);
65 | if(account == null) return;
66 |
67 | List allAccounts = Webinterface.getAccountStorage().getAccounts();
68 | JSONArray accounts = new JSONArray();
69 | for(Account a : allAccounts) {
70 | AccountConnection connection = a.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
71 | if(connection == null) continue;
72 |
73 | JSONObject obj = new JSONObject();
74 | obj.put("id", connection.getUserID());
75 | obj.put("username", connection.getUserName());
76 | obj.put("isAdmin", a.hasPermission("*"));
77 | accounts.add(obj);
78 | }
79 |
80 | ctx.respond(HttpStatusCodes.OK_200, new JsonResponse(accounts));
81 | }
82 |
83 | @Endpoint(method = HttpRequestMethod.POST, path = "/changeUsername")
84 | public void changeUsername() {
85 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
86 |
87 | JSONObject object;
88 | if((object = ctx.expectContent(DefaultClientContentTypes.JSON_OBJECT)) == null){
89 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Bad JSON")));
90 | return;
91 | }
92 |
93 | ValidationResult result = CHANGE_USERNAME_VALIDATOR.validate(object);
94 | if(!result.isOk()) {
95 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, result.asJsonResponse());
96 | return;
97 | }
98 |
99 | String newUsername = object.getString("newUsername");
100 | if(!ShittyAuthWIHandler.USERNAME_PATTERN.matcher(newUsername).matches()) {
101 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("New username is invalid")));
102 | return;
103 | }
104 |
105 | Account account = requireAdmin(ctx);
106 | if(account == null) return;
107 |
108 | Account otherAccount = Webinterface.getAccountStorage().getAccountByConnectionSpecificID(ShittyAuth.ACCOUNT_CONNECTION_NAME, object.getString("userID"));
109 | if(otherAccount == null) {
110 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Account doesn't exist")));
111 | return;
112 | }
113 |
114 | if(account.getID().equals(otherAccount.getID())) {
115 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Can't edit own account via admin API")));
116 | return;
117 | }
118 |
119 | if(ShittyAuth.getAccountByUsername(newUsername) != null) {
120 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Username is already taken")));
121 | return;
122 | }
123 |
124 | AccountConnection connection = otherAccount.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
125 | AccountConnection newConnection = new AccountConnection(connection.getConnectionName(), connection.getUserID(), newUsername, connection.getUserEmail(), connection.getUserAvatar());
126 | otherAccount.removeConnection(connection);
127 | otherAccount.addConnection(newConnection);
128 |
129 | ctx.respond(HttpStatusCodes.OK_200, JsonResponse.EMPTY_OBJECT);
130 | }
131 |
132 | @Endpoint(method = HttpRequestMethod.POST, path = "/changePassword")
133 | public void changePassword() {
134 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
135 |
136 | JSONObject object;
137 | if((object = ctx.expectContent(DefaultClientContentTypes.JSON_OBJECT)) == null){
138 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Bad JSON")));
139 | return;
140 | }
141 |
142 | ValidationResult result = CHANGE_PASSWORD_VALIDATOR.validate(object);
143 | if(!result.isOk()) {
144 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, result.asJsonResponse());
145 | return;
146 | }
147 |
148 | String newPassword = object.getString("newPassword");
149 | if(newPassword.isEmpty()) {
150 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("New password can't be empty")));
151 | return;
152 | }
153 |
154 | Account account = requireAdmin(ctx);
155 | if(account == null) return;
156 |
157 | String userID = object.getString("userID");
158 | Account otherAccount = Webinterface.getAccountStorage().getAccountByConnectionSpecificID(ShittyAuth.ACCOUNT_CONNECTION_NAME, userID);
159 | if(otherAccount == null) {
160 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Account doesn't exist")));
161 | return;
162 | }
163 |
164 | if(account.getID().equals(otherAccount.getID())) {
165 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Can't edit own account via admin API")));
166 | return;
167 | }
168 |
169 | Webinterface.getCredentialsStorage().storeCredentials(ShittyAuth.ACCOUNT_CONNECTION_NAME, userID, newPassword);
170 | ctx.respond(HttpStatusCodes.OK_200, JsonResponse.EMPTY_OBJECT);
171 | }
172 |
173 | @Endpoint(method = HttpRequestMethod.POST, path = "/deleteAccount")
174 | public void deleteAccount() {
175 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
176 |
177 | JSONObject object;
178 | if((object = ctx.expectContent(DefaultClientContentTypes.JSON_OBJECT)) == null){
179 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Bad JSON")));
180 | return;
181 | }
182 |
183 | ValidationResult result = DELETE_ACCOUNT_VALIDATOR.validate(object);
184 | if(!result.isOk()) {
185 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, result.asJsonResponse());
186 | return;
187 | }
188 |
189 | Account account = requireAdmin(ctx);
190 | if(account == null) return;
191 |
192 | Account otherAccount = Webinterface.getAccountStorage().getAccountByConnectionSpecificID(ShittyAuth.ACCOUNT_CONNECTION_NAME, object.getString("userID"));
193 | if(otherAccount == null) {
194 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Account doesn't exist")));
195 | return;
196 | }
197 |
198 | if(account.getID().equals(otherAccount.getID())) {
199 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Can't edit own account via admin API")));
200 | return;
201 | }
202 |
203 | Webinterface.getAccountStorage().deleteAccount(otherAccount.getID());
204 | ctx.respond(HttpStatusCodes.OK_200, JsonResponse.EMPTY_OBJECT);
205 | }
206 |
207 | @Endpoint(method = HttpRequestMethod.POST, path = "/changeAdmin")
208 | public void changeAdmin() {
209 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
210 |
211 | JSONObject object;
212 | if((object = ctx.expectContent(DefaultClientContentTypes.JSON_OBJECT)) == null){
213 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Bad JSON")));
214 | return;
215 | }
216 |
217 | ValidationResult result = CHANGE_ADMIN_VALIDATOR.validate(object);
218 | if(!result.isOk()) {
219 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, result.asJsonResponse());
220 | return;
221 | }
222 |
223 | Account account = requireAdmin(ctx);
224 | if(account == null) return;
225 |
226 | Account otherAccount = Webinterface.getAccountStorage().getAccountByConnectionSpecificID(ShittyAuth.ACCOUNT_CONNECTION_NAME, object.getString("userID"));
227 | if(otherAccount == null) {
228 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Account doesn't exist")));
229 | return;
230 | }
231 |
232 | if(account.getID().equals(otherAccount.getID())) {
233 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Can't edit own account via admin API")));
234 | return;
235 | }
236 |
237 | boolean admin = object.getBoolean("admin");
238 | if(admin) {
239 | if(!otherAccount.hasPermission("*")) otherAccount.addPermission("*");
240 | }else {
241 | otherAccount.removePermission("*");
242 | }
243 |
244 | ctx.respond(HttpStatusCodes.OK_200, JsonResponse.EMPTY_OBJECT);
245 | }
246 |
247 | @Endpoint(method = HttpRequestMethod.GET, path = "/globalSettings")
248 | public void globalSettings() {
249 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
250 |
251 | Account account = requireAdmin(ctx);
252 | if(account == null) return;
253 |
254 | JSONObject obj = new JSONObject();
255 | obj.put("allowRegistration", ShittyAuth.config.getSetting(ShittyAuthSettings.ALLOW_REGISTRATION));
256 | obj.put("authlibCompat", ShittyAuth.config.getSetting(ShittyAuthSettings.AUTHLIB_INJECTOR_COMPAT));
257 |
258 | ctx.respond(HttpStatusCodes.OK_200, new JsonResponse(obj));
259 | }
260 |
261 | @Endpoint(method = HttpRequestMethod.PATCH, path = "/updateGlobalSettings")
262 | public void updateGlobalSettings() {
263 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
264 |
265 | JSONObject object;
266 | if((object = ctx.expectContent(DefaultClientContentTypes.JSON_OBJECT)) == null){
267 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, new JsonResponse(error("Bad JSON")));
268 | return;
269 | }
270 |
271 | ValidationResult result = GLOBAL_SETTINGS_VALIDATOR.validate(object);
272 | if(!result.isOk()) {
273 | ctx.respond(HttpStatusCodes.BAD_REQUEST_400, result.asJsonResponse());
274 | return;
275 | }
276 |
277 | Account account = requireAdmin(ctx);
278 | if(account == null) return;
279 |
280 | if(object.has("allowRegistration")) {
281 | boolean allowRegistration = object.getBoolean("allowRegistration");
282 | ShittyAuth.config.setSetting(ShittyAuthSettings.ALLOW_REGISTRATION, allowRegistration);
283 | }
284 |
285 | if(object.has("authlibCompat")) {
286 | boolean authlibCompat = object.getBoolean("authlibCompat");
287 | ShittyAuth.config.setSetting(ShittyAuthSettings.AUTHLIB_INJECTOR_COMPAT, authlibCompat);
288 | }
289 |
290 | ctx.respond(HttpStatusCodes.OK_200, JsonResponse.EMPTY_OBJECT);
291 | }
292 |
293 | @Override
294 | public String getBasePath() {
295 | return "/api/shittyauth/admin";
296 | }
297 |
298 | }
299 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/yggdrasil/AuthenticatePage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.yggdrasil;
2 |
3 | import java.nio.charset.StandardCharsets;
4 | import java.util.UUID;
5 |
6 | import me.mrletsplay.mrcore.json.JSONArray;
7 | import me.mrletsplay.mrcore.json.JSONObject;
8 | import me.mrletsplay.mrcore.json.JSONParseException;
9 | import me.mrletsplay.shittyauth.ShittyAuth;
10 | import me.mrletsplay.shittyauth.auth.AccessToken;
11 | import me.mrletsplay.shittyauth.util.UUIDHelper;
12 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
13 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
14 | import me.mrletsplay.simplehttpserver.http.header.DefaultClientContentTypes;
15 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
16 | import me.mrletsplay.simplehttpserver.http.util.MimeType;
17 | import me.mrletsplay.webinterfaceapi.Webinterface;
18 | import me.mrletsplay.webinterfaceapi.auth.Account;
19 | import me.mrletsplay.webinterfaceapi.auth.AccountConnection;
20 |
21 | public class AuthenticatePage implements HttpDocument {
22 |
23 | // https://wiki.vg/Authentication
24 |
25 | @Override
26 | public void createContent() {
27 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
28 |
29 | JSONObject obj;
30 | try {
31 | obj = ctx.getClientHeader().getPostData().getParsedAs(DefaultClientContentTypes.JSON_OBJECT);
32 | }catch(JSONParseException e) {
33 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.UNAUTHORIZED_401);
34 | JSONObject response = new JSONObject();
35 | response.put("error", "ForbiddenOperationException");
36 | response.put("errorMessage", "Forbidden");
37 | ctx.getServerHeader().setContent(MimeType.JSON, response.toString().getBytes(StandardCharsets.UTF_8));
38 | return;
39 | }
40 |
41 | if(!obj.has("username") || !obj.has("password")) {
42 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.UNAUTHORIZED_401);
43 | JSONObject response = new JSONObject();
44 | response.put("error", "ForbiddenOperationException");
45 | response.put("errorMessage", "Forbidden");
46 | ctx.getServerHeader().setContent(MimeType.JSON, response.toString().getBytes(StandardCharsets.UTF_8));
47 | return;
48 | }
49 |
50 | String
51 | username = obj.getString("username"),
52 | password = obj.getString("password"),
53 | clientToken = obj.optString("clientToken").orElse(null);
54 |
55 | Account acc = ShittyAuth.getAccountByUsername(username);
56 | AccountConnection con;
57 | if(acc == null
58 | || (con = acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME)) == null
59 | || !Webinterface.getCredentialsStorage().checkCredentials(ShittyAuth.ACCOUNT_CONNECTION_NAME, con.getUserID(), password)) {
60 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.UNAUTHORIZED_401);
61 | JSONObject response = new JSONObject();
62 | response.put("error", "ForbiddenOperationException");
63 | response.put("errorMessage", "Invalid credentials. Invalid username or password.");
64 | ctx.getServerHeader().setContent(MimeType.JSON, response.toString().getBytes(StandardCharsets.UTF_8));
65 | return;
66 | }
67 |
68 | JSONObject response = new JSONObject();
69 |
70 | if(obj.optBoolean("requestUser").orElse(false)) {
71 | JSONObject user = new JSONObject();
72 | user.put("username", username);
73 | JSONArray properties = new JSONArray();
74 | user.put("properties", properties);
75 | user.put("id", con.getUserID());
76 | response.put("user", user);
77 | }
78 |
79 | JSONObject profile = new JSONObject();
80 | profile.put("name", username);
81 | profile.put("id", UUIDHelper.toShortUUID(UUID.fromString(con.getUserID())));
82 |
83 | JSONArray availableProfiles = new JSONArray();
84 | availableProfiles.add(profile);
85 | response.put("availableProfiles", availableProfiles);
86 |
87 | response.put("selectedProfile", profile);
88 |
89 | AccessToken tok = ShittyAuth.tokenStorage.generateToken(con.getUserID(), clientToken);
90 | response.put("accessToken", tok.getAccessToken());
91 | response.put("clientToken", tok.getClientToken());
92 |
93 | ctx.getServerHeader().setContent(MimeType.JSON, response.toString().getBytes(StandardCharsets.UTF_8));
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/yggdrasil/HasJoinedPage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.yggdrasil;
2 |
3 | import java.nio.charset.StandardCharsets;
4 | import java.security.Signature;
5 | import java.util.Base64;
6 | import java.util.UUID;
7 |
8 | import me.mrletsplay.mrcore.json.JSONArray;
9 | import me.mrletsplay.mrcore.json.JSONObject;
10 | import me.mrletsplay.shittyauth.ShittyAuth;
11 | import me.mrletsplay.shittyauth.textures.TexturesHelper;
12 | import me.mrletsplay.shittyauth.user.UserData;
13 | import me.mrletsplay.shittyauth.util.UUIDHelper;
14 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
15 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
16 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
17 | import me.mrletsplay.simplehttpserver.http.util.MimeType;
18 | import me.mrletsplay.webinterfaceapi.auth.Account;
19 | import me.mrletsplay.webinterfaceapi.auth.AccountConnection;
20 |
21 | public class HasJoinedPage implements HttpDocument {
22 |
23 | @Override
24 | public void createContent() {
25 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
26 |
27 | String username = ctx.getClientHeader().getPath().getQuery().getFirst("username");
28 | if(username == null) {
29 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.NOT_FOUND_404);
30 | return;
31 | }
32 |
33 | Account acc = ShittyAuth.getAccountByUsername(username);
34 | if(acc == null) {
35 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.NOT_FOUND_404);
36 | return;
37 | }
38 |
39 | AccountConnection con = acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
40 |
41 | String serverHash = ShittyAuth.userServers.get(con.getUserID());
42 | String hash = ctx.getClientHeader().getPath().getQuery().getFirst("serverId");
43 | if(hash == null || serverHash == null || !serverHash.equals(hash)) {
44 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.NO_CONTENT_204);
45 | return;
46 | }
47 |
48 | ShittyAuth.userServers.remove(con.getUserID());
49 |
50 | JSONObject obj = new JSONObject();
51 | obj.put("id", UUIDHelper.toShortUUID(UUID.fromString(con.getUserID())));
52 | obj.put("name", con.getUserName());
53 |
54 | JSONArray a = new JSONArray();
55 | JSONObject b = new JSONObject();
56 | b.put("name", "textures");
57 |
58 | JSONObject textures = new JSONObject();
59 | textures.put("timestamp", System.currentTimeMillis());
60 | textures.put("profileId", UUIDHelper.toShortUUID(UUID.fromString(con.getUserID())));
61 | textures.put("profileName", con.getUserName());
62 |
63 | UserData d = ShittyAuth.dataStorage.getUserData(con.getUserID());
64 | textures.put("textures", TexturesHelper.getTexturesObject(con.getUserID(), d));
65 |
66 | String b64Value = Base64.getEncoder().encodeToString(textures.toString().getBytes());
67 | b.put("value", b64Value);
68 |
69 | try {
70 | Signature sig = Signature.getInstance("SHA1withRSA");
71 | sig.initSign(ShittyAuth.privateKey);
72 | sig.update(b64Value.getBytes(StandardCharsets.UTF_8));
73 | b.put("signature", Base64.getEncoder().encodeToString(sig.sign()));
74 | }catch (Exception e) {
75 | e.printStackTrace();
76 | }
77 | a.add(b);
78 | obj.put("properties", a);
79 |
80 | ctx.getServerHeader().setContent(MimeType.JSON, obj.toString().getBytes(StandardCharsets.UTF_8));
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/yggdrasil/InvalidatePage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.yggdrasil;
2 |
3 | import me.mrletsplay.mrcore.json.JSONObject;
4 | import me.mrletsplay.shittyauth.ShittyAuth;
5 | import me.mrletsplay.shittyauth.auth.StoredAccessToken;
6 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
7 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
8 | import me.mrletsplay.simplehttpserver.http.header.DefaultClientContentTypes;
9 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
10 | import me.mrletsplay.webinterfaceapi.Webinterface;
11 | import me.mrletsplay.webinterfaceapi.auth.Account;
12 |
13 | public class InvalidatePage implements HttpDocument {
14 |
15 | @Override
16 | public void createContent() {
17 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
18 | JSONObject obj = ctx.getClientHeader().getPostData().getParsedAs(DefaultClientContentTypes.JSON_OBJECT);
19 | String
20 | accessToken = obj.getString("accessToken"),
21 | clientToken = obj.optString("clientToken").orElse(null);
22 |
23 | // Check the token
24 | StoredAccessToken tok = ShittyAuth.tokenStorage.getStoredToken(accessToken);
25 | if(tok == null || (clientToken != null && !clientToken.equals(tok.getClientToken()))) {
26 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.ACCESS_DENIED_403);
27 | JSONObject err = new JSONObject();
28 | err.put("error", "ForbiddenOperationException");
29 | err.put("errorMessage", "Invalid token.");
30 | return;
31 | }
32 |
33 | Account acc = Webinterface.getAccountStorage().getAccountByID(tok.getAccountID());
34 | if(acc == null) {
35 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.ACCESS_DENIED_403);
36 | JSONObject err = new JSONObject();
37 | err.put("error", "ForbiddenOperationException");
38 | err.put("errorMessage", "Invalid token.");
39 | return;
40 | }
41 |
42 | // Invalidate the token
43 | ShittyAuth.tokenStorage.removeToken(accessToken);
44 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.NO_CONTENT_204);
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/yggdrasil/JoinPage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.yggdrasil;
2 |
3 | import java.util.UUID;
4 |
5 | import me.mrletsplay.mrcore.json.JSONObject;
6 | import me.mrletsplay.shittyauth.ShittyAuth;
7 | import me.mrletsplay.shittyauth.util.UUIDHelper;
8 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
9 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
10 | import me.mrletsplay.simplehttpserver.http.header.DefaultClientContentTypes;
11 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
12 |
13 | public class JoinPage implements HttpDocument {
14 |
15 | @Override
16 | public void createContent() {
17 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
18 |
19 | JSONObject authData = ctx.getClientHeader().getPostData().getParsedAs(DefaultClientContentTypes.JSON_OBJECT);
20 | String accessToken = authData.getString("accessToken");
21 |
22 | String accID = ShittyAuth.tokenStorage.getAccountID(accessToken);
23 | String shortUUID = accID == null ? null : UUIDHelper.toShortUUID(UUID.fromString(accID));
24 | if(accID == null || !shortUUID.equals(authData.getString("selectedProfile"))) {
25 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.UNAUTHORIZED_401);
26 | return;
27 | }
28 |
29 | ShittyAuth.userServers.put(accID, authData.getString("serverId"));
30 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.NO_CONTENT_204);
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/yggdrasil/ProfilePage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.yggdrasil;
2 |
3 | import java.nio.charset.StandardCharsets;
4 | import java.security.Signature;
5 | import java.util.Base64;
6 | import java.util.UUID;
7 |
8 | import me.mrletsplay.mrcore.json.JSONArray;
9 | import me.mrletsplay.mrcore.json.JSONObject;
10 | import me.mrletsplay.shittyauth.ShittyAuth;
11 | import me.mrletsplay.shittyauth.textures.TexturesHelper;
12 | import me.mrletsplay.shittyauth.user.UserData;
13 | import me.mrletsplay.shittyauth.util.UUIDHelper;
14 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
15 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
16 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
17 | import me.mrletsplay.simplehttpserver.http.util.MimeType;
18 | import me.mrletsplay.webinterfaceapi.Webinterface;
19 | import me.mrletsplay.webinterfaceapi.auth.Account;
20 | import me.mrletsplay.webinterfaceapi.auth.AccountConnection;
21 |
22 | public class ProfilePage implements HttpDocument {
23 |
24 | public static final ProfilePage INSTANCE = new ProfilePage();
25 |
26 | @Override
27 | public void createContent() {
28 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
29 | String uuid = ctx.getPathParameters().get("uuid");
30 | UUID uuidU = UUIDHelper.parseShortUUID(uuid);
31 | if(uuidU == null) {
32 | ctx.getServerHeader().setContent(MimeType.TEXT, "404 Not Found".getBytes(StandardCharsets.UTF_8));
33 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.NOT_FOUND_404);
34 | return;
35 | }
36 |
37 | Account acc = Webinterface.getAccountStorage().getAccountByConnectionSpecificID(ShittyAuth.ACCOUNT_CONNECTION_NAME, uuidU.toString());
38 | if(acc == null) {
39 | ctx.getServerHeader().setContent(MimeType.TEXT, "404 Not Found".getBytes(StandardCharsets.UTF_8));
40 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.NOT_FOUND_404);
41 | return;
42 | }
43 |
44 | AccountConnection con = acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
45 |
46 | JSONObject obj = new JSONObject();
47 | obj.put("id", UUIDHelper.toShortUUID(UUID.fromString(con.getUserID())));
48 | obj.put("name", con.getUserName());
49 |
50 | JSONArray a = new JSONArray();
51 | JSONObject b = new JSONObject();
52 | b.put("name", "textures");
53 |
54 | JSONObject textures = new JSONObject();
55 | textures.put("timestamp", System.currentTimeMillis());
56 | textures.put("profileId", UUIDHelper.toShortUUID(UUID.fromString(con.getUserID())));
57 | textures.put("profileName", con.getUserName());
58 |
59 | String unsigned = ctx.getRequestedPath().getQuery().getFirst("unsigned");
60 | boolean signed = "false".equals(unsigned) || "0".equals(unsigned);
61 | if(signed) {
62 | textures.put("signatureRequired", true);
63 | }
64 |
65 | UserData d = ShittyAuth.dataStorage.getUserData(con.getUserID());
66 | textures.put("textures", TexturesHelper.getTexturesObject(con.getUserID(), d));
67 |
68 | String value = Base64.getEncoder().encodeToString(textures.toString().getBytes(StandardCharsets.UTF_8));
69 | b.put("value", value);
70 |
71 | if(signed) {
72 | try {
73 | Signature sig = Signature.getInstance("SHA1withRSA");
74 | sig.initSign(ShittyAuth.privateKey);
75 | sig.update(value.getBytes(StandardCharsets.US_ASCII));
76 | byte[] sign = sig.sign();
77 | b.put("signature", Base64.getEncoder().encodeToString(sign));
78 | }catch (Exception e) {
79 | e.printStackTrace();
80 | }
81 | }
82 |
83 | a.add(b);
84 | obj.put("properties", a);
85 |
86 | ctx.getServerHeader().setContent(MimeType.JSON, obj.toString().getBytes(StandardCharsets.UTF_8));
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/yggdrasil/PublicKeysPage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.yggdrasil;
2 |
3 | import java.nio.charset.StandardCharsets;
4 | import java.util.Base64;
5 |
6 | import me.mrletsplay.mrcore.json.JSONArray;
7 | import me.mrletsplay.mrcore.json.JSONObject;
8 | import me.mrletsplay.shittyauth.ShittyAuth;
9 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
10 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
11 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
12 |
13 | public class PublicKeysPage implements HttpDocument {
14 |
15 | @Override
16 | public void createContent() {
17 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
18 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.OK_200);
19 |
20 | JSONObject obj = new JSONObject();
21 |
22 | // playerCertificateKeys is not currently used by clients using the ShittyAuthServer. It might be used for signed chat messages and the connected player certificates
23 | obj.put("playerCertificateKeys", new JSONArray());
24 |
25 | // profilePropertyKeys is used for signing properties on the profile page. ShittyAuthServer just uses the normal private key for that
26 | JSONArray profileKeys = new JSONArray();
27 | JSONObject key = new JSONObject();
28 | key.put("publicKey", Base64.getEncoder().encodeToString(ShittyAuth.publicKey.getEncoded()));
29 | profileKeys.add(key);
30 | obj.put("profilePropertyKeys", profileKeys);
31 |
32 | ctx.getServerHeader().setContent(obj.toString().getBytes(StandardCharsets.UTF_8));
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/yggdrasil/RefreshPage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.yggdrasil;
2 |
3 | import java.nio.charset.StandardCharsets;
4 | import java.util.UUID;
5 |
6 | import me.mrletsplay.mrcore.json.JSONArray;
7 | import me.mrletsplay.mrcore.json.JSONObject;
8 | import me.mrletsplay.shittyauth.ShittyAuth;
9 | import me.mrletsplay.shittyauth.auth.AccessToken;
10 | import me.mrletsplay.shittyauth.auth.StoredAccessToken;
11 | import me.mrletsplay.shittyauth.util.UUIDHelper;
12 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
13 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
14 | import me.mrletsplay.simplehttpserver.http.header.DefaultClientContentTypes;
15 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
16 | import me.mrletsplay.simplehttpserver.http.util.MimeType;
17 | import me.mrletsplay.webinterfaceapi.Webinterface;
18 | import me.mrletsplay.webinterfaceapi.auth.Account;
19 | import me.mrletsplay.webinterfaceapi.auth.AccountConnection;
20 |
21 | public class RefreshPage implements HttpDocument {
22 |
23 | @Override
24 | public void createContent() {
25 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
26 | JSONObject obj = ctx.getClientHeader().getPostData().getParsedAs(DefaultClientContentTypes.JSON_OBJECT);
27 | String
28 | accessToken = obj.getString("accessToken"),
29 | clientToken = obj.optString("clientToken").orElse(null);
30 | boolean requestUser = obj.optBoolean("requestUser").orElse(false);
31 |
32 | // Check the old token
33 | StoredAccessToken tok = ShittyAuth.tokenStorage.getStoredToken(accessToken);
34 | if(tok == null || (clientToken != null && !clientToken.equals(tok.getClientToken()))) {
35 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.ACCESS_DENIED_403);
36 | JSONObject err = new JSONObject();
37 | err.put("error", "ForbiddenOperationException");
38 | err.put("errorMessage", "Invalid token.");
39 | return;
40 | }
41 |
42 | Account acc = Webinterface.getAccountStorage().getAccountByConnectionSpecificID(ShittyAuth.ACCOUNT_CONNECTION_NAME, tok.getAccountID());
43 | if(acc == null) {
44 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.ACCESS_DENIED_403);
45 | JSONObject err = new JSONObject();
46 | err.put("error", "ForbiddenOperationException");
47 | err.put("errorMessage", "Invalid token.");
48 | return;
49 | }
50 |
51 | // Invalidate the old token
52 | ShittyAuth.tokenStorage.removeToken(accessToken);
53 |
54 | AccountConnection con = acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
55 |
56 | // Then create a new token
57 | AccessToken newTok = ShittyAuth.tokenStorage.generateToken(con.getUserID(), clientToken);
58 |
59 | JSONObject r = new JSONObject();
60 | r.put("accessToken", newTok.getAccessToken());
61 | r.put("clientToken", newTok.getClientToken());
62 |
63 | JSONObject selectedProfile = new JSONObject();
64 | selectedProfile.put("id", UUIDHelper.toShortUUID(UUID.fromString(con.getUserID())));
65 | selectedProfile.put("name", con.getUserName());
66 | r.put("selectedProfile", selectedProfile);
67 |
68 | if(requestUser) { // To be compliant with Yggdrasil (https://wiki.vg/Authentication#Refresh)
69 | JSONObject user = new JSONObject();
70 | user.put("username", con.getUserName());
71 | user.put("id", acc.getID());
72 |
73 | user.put("properties", new JSONArray()); // TODO: Maybe add preferredLanguage?
74 | }
75 |
76 | ctx.getServerHeader().setContent(MimeType.JSON, r.toString().getBytes(StandardCharsets.UTF_8));
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/yggdrasil/SignoutPage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.yggdrasil;
2 |
3 | import java.nio.charset.StandardCharsets;
4 |
5 | import me.mrletsplay.mrcore.json.JSONObject;
6 | import me.mrletsplay.shittyauth.ShittyAuth;
7 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
8 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
9 | import me.mrletsplay.simplehttpserver.http.header.DefaultClientContentTypes;
10 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
11 | import me.mrletsplay.simplehttpserver.http.util.MimeType;
12 | import me.mrletsplay.webinterfaceapi.Webinterface;
13 | import me.mrletsplay.webinterfaceapi.auth.Account;
14 | import me.mrletsplay.webinterfaceapi.auth.AccountConnection;
15 |
16 | public class SignoutPage implements HttpDocument {
17 |
18 | // https://wiki.vg/Authentication
19 |
20 | @Override
21 | public void createContent() {
22 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
23 | JSONObject obj = ctx.getClientHeader().getPostData().getParsedAs(DefaultClientContentTypes.JSON_OBJECT);
24 |
25 | if(!obj.has("username") || !obj.has("password")) {
26 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.UNAUTHORIZED_401);
27 | JSONObject response = new JSONObject();
28 | response.put("error", "ForbiddenOperationException");
29 | response.put("errorMessage", "Forbidden");
30 | ctx.getServerHeader().setContent(MimeType.JSON, response.toString().getBytes(StandardCharsets.UTF_8));
31 | return;
32 | }
33 |
34 | String
35 | username = obj.getString("username"),
36 | password = obj.getString("password");
37 |
38 | Account acc = ShittyAuth.getAccountByUsername(username);
39 | AccountConnection con = acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
40 | if(acc == null || !Webinterface.getCredentialsStorage().checkCredentials(ShittyAuth.ACCOUNT_CONNECTION_NAME, con.getUserID(), password)) {
41 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.UNAUTHORIZED_401);
42 | JSONObject response = new JSONObject();
43 | response.put("error", "ForbiddenOperationException");
44 | response.put("errorMessage", "Invalid credentials. Invalid username or password.");
45 | ctx.getServerHeader().setContent(MimeType.JSON, response.toString().getBytes(StandardCharsets.UTF_8));
46 | return;
47 | }
48 |
49 | ShittyAuth.tokenStorage.removeTokensByAccountID(con.getUserID());
50 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.NO_CONTENT_204);
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/page/api/yggdrasil/ValidatePage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.page.api.yggdrasil;
2 |
3 | import me.mrletsplay.mrcore.json.JSONObject;
4 | import me.mrletsplay.shittyauth.ShittyAuth;
5 | import me.mrletsplay.shittyauth.auth.StoredAccessToken;
6 | import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
7 | import me.mrletsplay.simplehttpserver.http.document.HttpDocument;
8 | import me.mrletsplay.simplehttpserver.http.header.DefaultClientContentTypes;
9 | import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
10 |
11 | public class ValidatePage implements HttpDocument {
12 |
13 | @Override
14 | public void createContent() {
15 | HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
16 | JSONObject obj = ctx.getClientHeader().getPostData().getParsedAs(DefaultClientContentTypes.JSON_OBJECT);
17 | String
18 | accessToken = obj.getString("accessToken"),
19 | clientToken = obj.optString("clientToken").orElse(null);
20 |
21 |
22 | StoredAccessToken tok = ShittyAuth.tokenStorage.getStoredToken(accessToken);
23 | if(tok == null || (clientToken != null && !clientToken.equals(tok.getClientToken()))) {
24 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.ACCESS_DENIED_403);
25 | JSONObject err = new JSONObject();
26 | err.put("error", "ForbiddenOperationException");
27 | err.put("errorMessage", "Invalid token.");
28 | return;
29 | }
30 |
31 | ctx.getServerHeader().setStatusCode(HttpStatusCodes.NO_CONTENT_204);
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/textures/SkinType.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.textures;
2 |
3 | import me.mrletsplay.mrcore.json.converter.JSONEnum;
4 |
5 | public enum SkinType implements JSONEnum {
6 |
7 | STEVE,
8 | ALEX;
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/textures/TexturesHelper.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.textures;
2 |
3 | import me.mrletsplay.mrcore.json.JSONObject;
4 | import me.mrletsplay.shittyauth.user.UserData;
5 | import me.mrletsplay.webinterfaceapi.Webinterface;
6 | import me.mrletsplay.webinterfaceapi.config.DefaultSettings;
7 |
8 | public class TexturesHelper {
9 |
10 | public static final String
11 | SKIN_PATH = "/skin/%s_%s",
12 | CAPE_PATH = "/cape/%s_%s";
13 |
14 | public static String getSkinBaseURL() {
15 | return Webinterface.getConfig().getSetting(DefaultSettings.HTTP_BASE_URL);
16 | }
17 |
18 | public static JSONObject getTexturesObject(String accID, UserData userData) {
19 | JSONObject textures2 = new JSONObject();
20 | JSONObject skin = new JSONObject();
21 | skin.put("url", String.format(getSkinBaseURL() + SKIN_PATH, accID, userData.getSkinLastChanged()));
22 | if(userData.getSkinType() == SkinType.ALEX) {
23 | JSONObject meta = new JSONObject();
24 | meta.put("model", "slim");
25 | skin.put("metadata", meta);
26 | }
27 | textures2.put("SKIN", skin);
28 | if(userData.hasCape()) {
29 | JSONObject cape = new JSONObject();
30 | cape.put("url", String.format(getSkinBaseURL() + CAPE_PATH, accID, userData.getCapeLastChanged()));
31 | textures2.put("CAPE", cape);
32 | }
33 | return textures2;
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/user/FileUserDataStorage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.user;
2 |
3 | import java.io.File;
4 |
5 | import me.mrletsplay.mrcore.config.ConfigLoader;
6 | import me.mrletsplay.mrcore.config.FileCustomConfig;
7 | import me.mrletsplay.mrcore.config.mapper.JSONObjectMapper;
8 | import me.mrletsplay.webinterfaceapi.Webinterface;
9 |
10 | public class FileUserDataStorage implements UserDataStorage {
11 |
12 | private FileCustomConfig config;
13 |
14 | @Override
15 | public void initialize() {
16 | config = ConfigLoader.loadFileConfig(new File(Webinterface.getDataDirectory(), "shittyauth/user-data.yml"));
17 | config.registerMapper(JSONObjectMapper.create(UserData.class));
18 | }
19 |
20 | @Override
21 | public void updateUserData(String accID, UserData userData) {
22 | config.set(accID, userData);
23 | config.saveToFile();
24 | }
25 |
26 | @Override
27 | public UserData getUserData(String accID) {
28 | return config.getGeneric(accID, UserData.class, null, false);
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/user/SQLUserDataStorage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.user;
2 |
3 | import java.sql.PreparedStatement;
4 | import java.sql.ResultSet;
5 |
6 | import me.mrletsplay.shittyauth.textures.SkinType;
7 | import me.mrletsplay.shittyauth.util.CryptoHelper;
8 | import me.mrletsplay.webinterfaceapi.sql.SQLHelper;
9 |
10 | public class SQLUserDataStorage implements UserDataStorage {
11 |
12 | @Override
13 | public void initialize() {
14 | SQLHelper.run(c -> {
15 | try(PreparedStatement st = c.prepareStatement("CREATE TABLE IF NOT EXISTS " + SQLHelper.tableName("shittyauth_user_data") + "(UserId VARCHAR(255) PRIMARY KEY, HasCape BOOLEAN, SkinType VARCHAR(255), SkinLastChanged BIGINT, CapeLastChanged BIGINT, PublicKey BLOB, PrivateKey BLOB)")) {
16 | st.execute();
17 | }
18 | });
19 | }
20 |
21 | @Override
22 | public void updateUserData(String accID, UserData userData) {
23 | SQLHelper.run(c -> {
24 | try(PreparedStatement st = c.prepareStatement("INSERT INTO " + SQLHelper.tableName("shittyauth_user_data") + "(UserId, HasCape, SkinType, SkinLastChanged, CapeLastChanged, PublicKey, PrivateKey) VALUES(?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE HasCape = VALUES(HasCape), SkinType = VALUES(SkinType), SkinLastChanged = VALUES(SkinLastChanged), CapeLastChanged = VALUES(CapeLastChanged), PublicKey = VALUES(PublicKey), PrivateKey = VALUES(PrivateKey)")) {
25 | st.setString(1, accID);
26 | st.setBoolean(2, userData.hasCape());
27 | st.setString(3, userData.getSkinType().name());
28 | st.setLong(4, userData.getSkinLastChanged());
29 | st.setLong(5, userData.getCapeLastChanged());
30 | st.setBytes(6, userData.getPublicKey().getEncoded());
31 | st.setBytes(7, userData.getPrivateKey().getEncoded());
32 | st.execute();
33 | }
34 | });
35 | }
36 |
37 | @Override
38 | public UserData getUserData(String accID) {
39 | return SQLHelper.run(c -> {
40 | try(PreparedStatement st = c.prepareStatement("SELECT * FROM " + SQLHelper.tableName("shittyauth_user_data") + " WHERE UserId = ?")) {
41 | st.setString(1, accID);
42 | try(ResultSet r = st.executeQuery()) {
43 | if(!r.next()) return null;
44 | return new UserData(
45 | r.getBoolean("HasCape"),
46 | SkinType.valueOf(r.getString("SkinType")),
47 | r.getLong("SkinLastChanged"),
48 | r.getLong("CapeLastChanged"),
49 | CryptoHelper.parseRSAPublicKey(r.getBytes("PublicKey")),
50 | CryptoHelper.parseRSAPrivateKey(r.getBytes("PrivateKey"))
51 | );
52 | }
53 | }
54 | });
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/user/UserData.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.user;
2 |
3 | import java.security.KeyPair;
4 | import java.security.PrivateKey;
5 | import java.security.PublicKey;
6 | import java.util.Base64;
7 |
8 | import me.mrletsplay.mrcore.json.JSONObject;
9 | import me.mrletsplay.mrcore.json.converter.JSONConstructor;
10 | import me.mrletsplay.mrcore.json.converter.JSONConvertible;
11 | import me.mrletsplay.mrcore.json.converter.JSONValue;
12 | import me.mrletsplay.shittyauth.textures.SkinType;
13 | import me.mrletsplay.shittyauth.util.CryptoHelper;
14 |
15 | public class UserData implements JSONConvertible {
16 |
17 | @JSONValue
18 | private boolean hasCape;
19 |
20 | @JSONValue
21 | private SkinType skinType;
22 |
23 | @JSONValue
24 | private long skinLastChanged; // To update skin cache in client after change
25 |
26 | @JSONValue
27 | private long capeLastChanged; // To update cape cache in client after change
28 |
29 | private PublicKey publicKey;
30 |
31 | private PrivateKey privateKey;
32 |
33 | @JSONConstructor
34 | private UserData() {}
35 |
36 | public UserData(boolean hasCape, SkinType skinType, long skinLastChanged, long capeLastChanged, PublicKey publicKey, PrivateKey privateKey) {
37 | this.hasCape = hasCape;
38 | this.skinType = skinType;
39 | this.skinLastChanged = skinLastChanged;
40 | this.capeLastChanged = capeLastChanged;
41 | this.publicKey = publicKey;
42 | this.privateKey = privateKey;
43 | }
44 |
45 | public void setHasCape(boolean hasCape) {
46 | this.hasCape = hasCape;
47 | }
48 |
49 | public boolean hasCape() {
50 | return hasCape;
51 | }
52 |
53 | public void setSkinType(SkinType skinType) {
54 | this.skinType = skinType;
55 | }
56 |
57 | public SkinType getSkinType() {
58 | return skinType;
59 | }
60 |
61 | public void setSkinLastChanged(long skinLastChanged) {
62 | this.skinLastChanged = skinLastChanged;
63 | }
64 |
65 | public long getSkinLastChanged() {
66 | return skinLastChanged;
67 | }
68 |
69 | public void setCapeLastChanged(long capeLastChanged) {
70 | this.capeLastChanged = capeLastChanged;
71 | }
72 |
73 | public long getCapeLastChanged() {
74 | return capeLastChanged;
75 | }
76 |
77 | public PublicKey getPublicKey() {
78 | return publicKey;
79 | }
80 |
81 | public PrivateKey getPrivateKey() {
82 | return privateKey;
83 | }
84 |
85 | public boolean hasKeyPair() {
86 | return publicKey != null && privateKey != null;
87 | }
88 |
89 | public void generateNewKeyPair() {
90 | KeyPair pair = CryptoHelper.generateRSAKeyPair();
91 | this.publicKey = pair.getPublic();
92 | this.privateKey = pair.getPrivate();
93 | }
94 |
95 | @Override
96 | public void preSerialize(JSONObject object) {
97 | object.put("publicKey" ,Base64.getEncoder().encodeToString(publicKey.getEncoded()));
98 | object.put("privateKey" ,Base64.getEncoder().encodeToString(privateKey.getEncoded()));
99 | }
100 |
101 | @Override
102 | public void preDeserialize(JSONObject object) {
103 | publicKey = CryptoHelper.parseRSAPublicKey(Base64.getDecoder().decode(object.getString("publicKey")));
104 | privateKey = CryptoHelper.parseRSAPrivateKey(Base64.getDecoder().decode(object.getString("privateKey")));
105 | }
106 |
107 | public static UserData createNew() {
108 | UserData ud = new UserData();
109 | ud.setSkinType(SkinType.STEVE);
110 | ud.generateNewKeyPair();
111 | return ud;
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/user/UserDataStorage.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.user;
2 |
3 | public interface UserDataStorage {
4 |
5 | public void initialize();
6 |
7 | public void updateUserData(String accID, UserData userData);
8 |
9 | public UserData getUserData(String accID);
10 |
11 | }
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/util/CryptoHelper.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.util;
2 |
3 | import java.nio.charset.StandardCharsets;
4 | import java.security.KeyFactory;
5 | import java.security.KeyPair;
6 | import java.security.KeyPairGenerator;
7 | import java.security.NoSuchAlgorithmException;
8 | import java.security.PrivateKey;
9 | import java.security.PublicKey;
10 | import java.security.spec.InvalidKeySpecException;
11 | import java.security.spec.PKCS8EncodedKeySpec;
12 | import java.security.spec.X509EncodedKeySpec;
13 | import java.util.Base64;
14 |
15 | import me.mrletsplay.mrcore.misc.FriendlyException;
16 |
17 | public class CryptoHelper {
18 |
19 | private static final String
20 | PUBKEY_HEADER = "-----BEGIN RSA PUBLIC KEY-----",
21 | PUBKEY_FOOTER = "-----END RSA PUBLIC KEY-----",
22 | PRIVKEY_HEADER = "-----BEGIN RSA PRIVATE KEY-----",
23 | PRIVKEY_FOOTER = "-----END RSA PRIVATE KEY-----";
24 |
25 | private static final KeyFactory RSA;
26 | private static final Base64.Encoder ENCODER = Base64.getMimeEncoder(64, "\n".getBytes(StandardCharsets.UTF_8));
27 |
28 | static {
29 | try {
30 | RSA = KeyFactory.getInstance("RSA");
31 | } catch (NoSuchAlgorithmException e) {
32 | throw new FriendlyException(e);
33 | }
34 | }
35 |
36 | public static KeyPair generateRSAKeyPair() {
37 | try {
38 | return KeyPairGenerator.getInstance("RSA").generateKeyPair();
39 | } catch (NoSuchAlgorithmException e) {
40 | throw new FriendlyException(e);
41 | }
42 | }
43 |
44 | public static PublicKey parseRSAPublicKey(byte[] bytes) {
45 | X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
46 | try {
47 | return RSA.generatePublic(spec);
48 | } catch (InvalidKeySpecException e) {
49 | throw new FriendlyException(e);
50 | }
51 | }
52 |
53 | public static PrivateKey parseRSAPrivateKey(byte[] bytes) {
54 | PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
55 | try {
56 | return RSA.generatePrivate(spec);
57 | } catch (InvalidKeySpecException e) {
58 | throw new FriendlyException(e);
59 | }
60 | }
61 |
62 | public static PublicKey parseRSAPublicKey(String str) {
63 | byte[] bs = Base64.getMimeDecoder().decode(str.substring(PUBKEY_HEADER.length(), str.length() - PUBKEY_FOOTER.length()));
64 | return parseRSAPublicKey(bs);
65 | }
66 |
67 | public static PrivateKey parseRSAPrivateKey(String str) {
68 | byte[] bs = Base64.getMimeDecoder().decode(str.substring(PRIVKEY_HEADER.length(), str.length() - PRIVKEY_FOOTER.length()));
69 | return parseRSAPrivateKey(bs);
70 | }
71 |
72 | public static String encodeRSAPublicKey(PublicKey key) {
73 | byte[] bs = key.getEncoded();
74 | return PUBKEY_HEADER + "\n" + ENCODER.encodeToString(bs) + "\n" + PUBKEY_FOOTER;
75 | }
76 |
77 | public static String encodeRSAPrivateKey(PrivateKey key) {
78 | byte[] bs = key.getEncoded();
79 | return PRIVKEY_HEADER + "\n" + ENCODER.encodeToString(bs) + "\n" + PRIVKEY_FOOTER;
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/util/DefaultTexture.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.util;
2 |
3 | import java.nio.file.Path;
4 | import java.nio.file.Paths;
5 | import java.util.ArrayList;
6 | import java.util.Collections;
7 | import java.util.List;
8 |
9 | public enum DefaultTexture {
10 |
11 | SKIN_STEVE_CLASSIC("Steve (Classic)", "1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b"),
12 |
13 | SKIN_STEVE("Steve", "31f477eb1a7beee631c2ca64d06f8f68fa93a3386d04452ab27f43acdf1b60cb"),
14 | SKIN_ALEX("Alex", "1abc803022d8300ab7578b189294cce39622d9a404cdc00d3feacfdf45be6981"),
15 | SKIN_ZURI("Zuri", "f5dddb41dcafef616e959c2817808e0be741c89ffbfed39134a13e75b811863d"),
16 | SKIN_SUNNY("Sunny", "a3bd16079f764cd541e072e888fe43885e711f98658323db0f9a6045da91ee7a"),
17 | SKIN_NOOR("Noor", "90e75cd429ba6331cd210b9bd19399527ee3bab467b5a9f61cb8a27b177f6789"),
18 | SKIN_MAKENA("Makena", "dc0fcfaf2aa040a83dc0de4e56058d1bbb2ea40157501f3e7d15dc245e493095"),
19 | SKIN_KAI("Kai", "e5cdc3243b2153ab28a159861be643a4fc1e3c17d291cdd3e57a7f370ad676f3"),
20 | SKIN_EFE("Efe", "daf3d88ccb38f11f74814e92053d92f7728ddb1a7955652a60e30cb27ae6659f"),
21 | SKIN_ARI("Ari", "4c05ab9e07b3505dc3ec11370c3bdce5570ad2fb2b562e9b9dd9cf271f81aa44"),
22 |
23 | SKIN_STEVE_SLIM("Steve", "d5c4ee5ce20aed9e33e866c66caa37178606234b3721084bf01d13320fb2eb3f"),
24 | SKIN_ALEX_SLIM("Alex", "46acd06e8483b176e8ea39fc12fe105eb3a2a4970f5100057e9d84d4b60bdfa7"),
25 | SKIN_ZURI_SLIM("Zuri", "eee522611005acf256dbd152e992c60c0bb7978cb0f3127807700e478ad97664"),
26 | SKIN_SUNNY_SLIM("Sunny", "b66bc80f002b10371e2fa23de6f230dd5e2f3affc2e15786f65bc9be4c6eb71a"),
27 | SKIN_NOOR_SLIM("Noor", "6c160fbd16adbc4bff2409e70180d911002aebcfa811eb6ec3d1040761aea6dd"),
28 | SKIN_MAKENA_SLIM("Makena", "7cb3ba52ddd5cc82c0b050c3f920f87da36add80165846f479079663805433db"),
29 | SKIN_KAI_SLIM("Kai", "226c617fde5b1ba569aa08bd2cb6fd84c93337532a872b3eb7bf66bdd5b395f8"),
30 | SKIN_EFE_SLIM("Efe", "fece7017b1bb13926d1158864b283b8b930271f80a90482f174cca6a17e88236"),
31 | SKIN_ARI_SLIM("Ari", "6ac6ca262d67bcfb3dbc924ba8215a18195497c780058a5749de674217721892"),
32 |
33 | CAPE_MOJANG_OLD("Mojang (Classic)", "8f120319222a9f4a104e2f5cb97b2cda93199a2ee9e1585cb8d09d6f687cb761"),
34 | CAPE_MOJANG("Mojang", "5786fe99be377dfb6858859f926c4dbc995751e91cee373468c5fbf4865e7151"),
35 | CAPE_MOJANG_NEW("Mojang Studios", "9e507afc56359978a3eb3e32367042b853cddd0995d17d0da995662913fb00f7"),
36 |
37 | CAPE_MINECON_2011("MINECON 2011", "953cac8b779fe41383e675ee2b86071a71658f2180f56fbce8aa315ea70e2ed6"),
38 | CAPE_MINECON_2012("MINECON 2012", "a2e8d97ec79100e90a75d369d1b3ba81273c4f82bc1b737e934eed4a854be1b6"),
39 | CAPE_MINECON_2013("MINECON 2013", "153b1a0dfcbae953cdeb6f2c2bf6bf79943239b1372780da44bcbb29273131da"),
40 | CAPE_MINECON_2015("MINECON 2015", "b0cc08840700447322d953a02b965f1d65a13a603bf64b17c803c21446fe1635"),
41 | CAPE_MINECON_2016("MINECON 2016", "e7dfea16dc83c97df01a12fabbd1216359c0cd0ea42f9999b6e97c584963e980"),
42 |
43 | CAPE_MILLIONTH_SALE("Millionth Customer", "70efffaf86fe5bc089608d3cb297d3e276b9eb7a8f9f2fe6659c23a2d8b18edf"),
44 | CAPE_DANNYBSTYLE("dB (dannyBstyle)", "bcfbe84c6542a4a5c213c1cacf8979b5e913dcb4ad783a8b80e3c4a7d5c8bdac"),
45 | CAPE_JULIANCLARK("Snowman (JulianClark)", "23ec737f18bfe4b547c95935fc297dd767bb84ee55bfd855144d279ac9bfd9fe"),
46 | CAPE_CHEAPSH0T("Japanese Translator (cheapsh0t)", "ca29f5dd9e94fb1748203b92e36b66fda80750c87ebc18d6eafdb0e28cc1d05f"),
47 | CAPE_MRMESSIAH("Spade (MrMessiah)", "2e002d5e1758e79ba51d08d92a0f3a95119f2f435ae7704916507b6c565a7da8"),
48 | CAPE_PRISMARINE("Prismarine (Drullkus)", "d8f8d13a1adf9636a16c31d47f3ecc9bb8d8533108aa5ad2a01b13b1a0c55eac"),
49 | CAPE_BIRTHDAY("Birthday (Gr8Bizzo)", "2056f2eebd759cce93460907186ef44e9192954ae12b227d817eb4b55627a7fc"),
50 | CAPE_VALENTINE("Valentine", "6a7cf0eb5cfe7e7c508b364e32916dfd28d164e7bf6d92c6ea7811b82451e760"),
51 |
52 | CAPE_TRANSLATOR("Translator", "1bf91499701404e21bd46b0191d63239a4ef76ebde88d27e4d430ac211df681e"),
53 | CAPE_TRANSLATOR_CHINESE("Chinese Translator", "2262fb1d24912209490586ecae98aca8500df3eff91f2a07da37ee524e7e3cb6"),
54 | CAPE_SCROLLS_CHAMP("Scrolls Champion", "3efadf6510961830f9fcc077f19b4daf286d502b5f5aafbd807c7bbffcaca245"),
55 | CAPE_COBALT("Cobalt", "ca35c56efe71ed290385f4ab5346a1826b546a54d519e6a3ff01efa01acce81"),
56 | CAPE_MODERATOR("Mojira Moderator", "ae677f7d98ac70a533713518416df4452fe5700365c09cf45d0d156ea9396551"),
57 | CAPE_MAP_MAKER("Realms MapMaker", "17912790ff164b93196f08ba71d0e62129304776d0f347334f8a6eae509f8a56"),
58 | CAPE_TURTLE("Turtle", "5048ea61566353397247d2b7d946034de926b997d5e66c86483dfb1e031aee95"),
59 |
60 | CAPE_MIGRATOR("Migrator", "2340c0e03dd24a11b15a8b33c2a7e9e32abb2051b2481d0ba7defd635ca7a933"),
61 | CAPE_VANILLA("Vanilla", "f9a76537647989f9a0b6d001e320dac591c359e9e61a31f4ce11c88f207f0ad4"),
62 |
63 | CAPE_15_YEAR_ANNIVERSARY("15 Year Anniversary", "cd9d82ab17fd92022dbd4a86cde4c382a7540e117fae7b9a2853658505a80625"),
64 | CAPE_CHERRY_BLOSSOM("Cherry Blossom", "afd553b39358a24edfe3b8a9a939fa5fa4faa4d9a9c3d6af8eafb377fa05c2bb"),
65 | CAPE_PURPLE_HEART("Purple Heart", "cb40a92e32b57fd732a00fc325e7afb00a7ca74936ad50d8e860152e482cfbde"),
66 | CAPE_FOLLOWERS("Follower's", "569b7f2a1d00d26f30efe3f9ab9ac817b1e6d35f4f3cfb0324ef2d328223d350"),
67 |
68 | // Christmas 2010
69 | // New Year 2011
70 | // Sniffer
71 | // Snail
72 | // ICU
73 | // ICU2
74 | // Frog
75 | // Pancape
76 | // Founder's
77 | // Pride
78 | // MCC 15th Year
79 |
80 | // Microsoft Xbox
81 | // 1st birthday
82 |
83 | // Awesom
84 | // Blonk
85 | // No-Circle
86 | // Nyan
87 | // Squid
88 | // Veterinarian
89 |
90 | // Minecon 2011 alternatives
91 |
92 | // BE skin packs
93 | ;
94 |
95 | public static final String TEXTURE_URL = "https://textures.minecraft.net/texture/%s";
96 | public static final Path DEFAULT_TEXTURES_PATH = Paths.get("shittyauth/textures");
97 |
98 | private static final List
99 | SKINS,
100 | SLIM_SKINS,
101 | CAPES;
102 |
103 | static {
104 | List
105 | skins = new ArrayList<>(),
106 | slimSkins = new ArrayList<>(),
107 | capes = new ArrayList<>();
108 |
109 | for(DefaultTexture texture : values()) {
110 | if(texture.name().startsWith("SKIN_")) {
111 | (texture.name().endsWith("_SLIM") ? slimSkins : skins).add(texture);
112 | }else {
113 | capes.add(texture);
114 | }
115 | }
116 |
117 | SKINS = Collections.unmodifiableList(skins);
118 | SLIM_SKINS = Collections.unmodifiableList(slimSkins);
119 | CAPES = Collections.unmodifiableList(capes);
120 | }
121 |
122 | private final String name;
123 | private final String textureID;
124 |
125 | private DefaultTexture(String name, String textureID) {
126 | this.name = name;
127 | this.textureID = textureID;
128 | }
129 |
130 | public String getName() {
131 | return name;
132 | }
133 |
134 | public String getTextureID() {
135 | return textureID;
136 | }
137 |
138 | public String getURL() {
139 | return String.format(TEXTURE_URL, textureID);
140 | }
141 |
142 | public Path getPath() {
143 | return DEFAULT_TEXTURES_PATH.resolve(name().toLowerCase() + ".png");
144 | }
145 |
146 | public static List getSkins() {
147 | return SKINS;
148 | }
149 |
150 | public static List getSlimSkins() {
151 | return SLIM_SKINS;
152 | }
153 |
154 | public static List getCapes() {
155 | return CAPES;
156 | }
157 |
158 | }
159 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/util/InvalidSkinException.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.util;
2 |
3 | public class InvalidSkinException extends Exception {
4 |
5 | private static final long serialVersionUID = 2581532239295743608L;
6 |
7 | public InvalidSkinException() {
8 | super();
9 | }
10 |
11 | public InvalidSkinException(String message, Throwable cause) {
12 | super(message, cause);
13 | }
14 |
15 | public InvalidSkinException(String message) {
16 | super(message);
17 | }
18 |
19 | public InvalidSkinException(Throwable cause) {
20 | super(cause);
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/util/InvalidUsernameException.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.util;
2 |
3 | public class InvalidUsernameException extends Exception {
4 |
5 | private static final long serialVersionUID = -827594972986252869L;
6 |
7 | public InvalidUsernameException() {
8 | super();
9 | }
10 |
11 | public InvalidUsernameException(String message, Throwable cause) {
12 | super(message, cause);
13 | }
14 |
15 | public InvalidUsernameException(String message) {
16 | super(message);
17 | }
18 |
19 | public InvalidUsernameException(Throwable cause) {
20 | super(cause);
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/util/UUIDHelper.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.util;
2 |
3 | import java.nio.ByteBuffer;
4 | import java.util.UUID;
5 |
6 | import me.mrletsplay.mrcore.misc.ByteUtils;
7 |
8 | public class UUIDHelper {
9 |
10 | public static String toShortUUID(UUID uuid) {
11 | return ByteUtils.bytesToHex(getBytesFromUUID(uuid));
12 | }
13 |
14 | public static UUID parseShortUUID(String shortUUID) {
15 | try {
16 | return getUUIDFromBytes(ByteUtils.hexToBytes(shortUUID));
17 | }catch(IllegalArgumentException e) {
18 | return null;
19 | }
20 | }
21 |
22 | private static byte[] getBytesFromUUID(UUID uuid) {
23 | ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
24 | bb.putLong(uuid.getMostSignificantBits());
25 | bb.putLong(uuid.getLeastSignificantBits());
26 | return bb.array();
27 | }
28 |
29 | private static UUID getUUIDFromBytes(byte[] bytes) {
30 | ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
31 | Long high = byteBuffer.getLong();
32 | Long low = byteBuffer.getLong();
33 | return new UUID(high, low);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/me/mrletsplay/shittyauth/webinterface/ShittyAuthWIHandler.java:
--------------------------------------------------------------------------------
1 | package me.mrletsplay.shittyauth.webinterface;
2 |
3 | import java.awt.image.BufferedImage;
4 | import java.io.ByteArrayInputStream;
5 | import java.io.IOException;
6 | import java.util.UUID;
7 | import java.util.regex.Pattern;
8 |
9 | import javax.imageio.ImageIO;
10 |
11 | import me.mrletsplay.shittyauth.ShittyAuth;
12 | import me.mrletsplay.shittyauth.config.ShittyAuthSettings;
13 | import me.mrletsplay.shittyauth.textures.SkinType;
14 | import me.mrletsplay.shittyauth.user.UserData;
15 | import me.mrletsplay.shittyauth.util.InvalidSkinException;
16 | import me.mrletsplay.webinterfaceapi.DefaultPermissions;
17 | import me.mrletsplay.webinterfaceapi.Webinterface;
18 | import me.mrletsplay.webinterfaceapi.auth.Account;
19 | import me.mrletsplay.webinterfaceapi.auth.AccountConnection;
20 | import me.mrletsplay.webinterfaceapi.auth.impl.PasswordAuth;
21 | import me.mrletsplay.webinterfaceapi.page.SettingsPage;
22 | import me.mrletsplay.webinterfaceapi.page.action.ActionEvent;
23 | import me.mrletsplay.webinterfaceapi.page.action.ActionHandler;
24 | import me.mrletsplay.webinterfaceapi.page.action.ActionResponse;
25 | import me.mrletsplay.webinterfaceapi.page.action.WebinterfaceHandler;
26 | import me.mrletsplay.webinterfaceapi.page.element.FileUpload;
27 |
28 | public class ShittyAuthWIHandler implements ActionHandler {
29 |
30 | public static final Pattern USERNAME_PATTERN = Pattern.compile("[a-zA-Z0-9_]{3,16}");
31 |
32 | @WebinterfaceHandler(requestTarget = "shittyauth", requestTypes = "createAccount")
33 | public ActionResponse createAccount(ActionEvent event) {
34 | if(ShittyAuth.config.getSetting(ShittyAuthSettings.ALLOW_REGISTRATION)) return ActionResponse.error("Creation of Minecraft accounts disabled");
35 |
36 | Account acc = event.getAccount();
37 | if(acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME) != null) return ActionResponse.error("Account already has a Minecraft account");
38 |
39 | String username = event.getData().getString("username");
40 | String password = event.getData().getString("password");
41 |
42 | if(!USERNAME_PATTERN.matcher(username).matches()) return ActionResponse.error("Invalid username");
43 |
44 | if(ShittyAuth.getAccountByUsername(username) != null) return ActionResponse.error("An account with that username already exists");
45 |
46 | UUID uuid = UUID.randomUUID();
47 | AccountConnection con = new AccountConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME, uuid.toString(), username, null, null);
48 | acc.addConnection(con);
49 | Webinterface.getCredentialsStorage().storeCredentials(ShittyAuth.ACCOUNT_CONNECTION_NAME, uuid.toString(), password);
50 | ShittyAuth.dataStorage.updateUserData(con.getUserID(), UserData.createNew());
51 |
52 | return ActionResponse.success();
53 | }
54 |
55 | @WebinterfaceHandler(requestTarget = "shittyauth", requestTypes = "uploadSkin")
56 | public ActionResponse uploadSkin(ActionEvent event) {
57 | Account acc = event.getAccount();
58 | AccountConnection con = acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
59 | if(con == null) return ActionResponse.error("No Minecraft account");
60 | byte[] skinBytes = FileUpload.getUploadedFileBytes(event);
61 | if(skinBytes.length == 0) return ActionResponse.error("No file provided");
62 | try {
63 | BufferedImage img = ImageIO.read(new ByteArrayInputStream(skinBytes));
64 | if(img == null) return ActionResponse.error("Invalid image file");
65 | ShittyAuth.updateUserSkin(con.getUserID(), img);
66 | return ActionResponse.success();
67 | }catch(IOException e) {
68 | return ActionResponse.error("Invalid skin file");
69 | } catch (InvalidSkinException e) {
70 | return ActionResponse.error(e.getMessage());
71 | }
72 | }
73 |
74 | @WebinterfaceHandler(requestTarget = "shittyauth", requestTypes = "uploadCape")
75 | public ActionResponse uploadCape(ActionEvent event) {
76 | Account acc = event.getAccount();
77 | AccountConnection con = acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
78 | if(con == null) return ActionResponse.error("No MC account");
79 | byte[] capeBytes = FileUpload.getUploadedFileBytes(event);
80 | if(capeBytes.length == 0) return ActionResponse.error("No file provided");
81 | try {
82 | BufferedImage img = ImageIO.read(new ByteArrayInputStream(capeBytes));
83 | if(img == null) return ActionResponse.error("Invalid image file");
84 | ShittyAuth.updateUserCape(con.getUserID(), img);
85 | return ActionResponse.success();
86 | }catch(IOException e) {
87 | return ActionResponse.error("Invalid cape file");
88 | } catch (InvalidSkinException e) {
89 | return ActionResponse.error(e.getMessage());
90 | }
91 | }
92 |
93 | @WebinterfaceHandler(requestTarget = "shittyauth", requestTypes = "setSkinType")
94 | public ActionResponse setSkinType(ActionEvent event) {
95 | Account acc = event.getAccount();
96 | AccountConnection con = acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
97 | if(con == null) return ActionResponse.error("No Minecraft account");
98 | SkinType type = SkinType.valueOf(event.getData().getString("type"));
99 | UserData d = ShittyAuth.dataStorage.getUserData(con.getUserID());
100 | d.setSkinType(type);
101 | ShittyAuth.dataStorage.updateUserData(con.getUserID(), d);
102 | return ActionResponse.success();
103 | }
104 |
105 | @WebinterfaceHandler(requestTarget = "shittyauth", requestTypes = "setEnableCape")
106 | public ActionResponse setEnableCape(ActionEvent event) {
107 | Account acc = event.getAccount();
108 | AccountConnection con = acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
109 | if(con == null) return ActionResponse.error("No Minecraft account");
110 | boolean enable = event.getData().getBoolean("enable");
111 | UserData d = ShittyAuth.dataStorage.getUserData(con.getUserID());
112 | d.setHasCape(enable);
113 | ShittyAuth.dataStorage.updateUserData(con.getUserID(), d);
114 | return ActionResponse.success();
115 | }
116 |
117 | @WebinterfaceHandler(requestTarget = "shittyauth", requestTypes = "resetPassword")
118 | public ActionResponse resetPassword(ActionEvent event) {
119 | Account acc = event.getAccount();
120 | AccountConnection con = acc.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
121 | if(con == null) return ActionResponse.error("No Minecraft account");
122 |
123 | String password = event.getData().getString("password");
124 | Webinterface.getCredentialsStorage().storeCredentials(ShittyAuth.ACCOUNT_CONNECTION_NAME, con.getUserID(), password);
125 | ShittyAuth.tokenStorage.removeTokensByAccountID(con.getUserID()); // Invalidate all sessions
126 |
127 | return ActionResponse.success();
128 | }
129 |
130 | @WebinterfaceHandler(requestTarget = "shittyauth", requestTypes = "changeMCUsername", permission = DefaultPermissions.MODIFY_USERS)
131 | public ActionResponse changeMCUsername(ActionEvent event) {
132 | String accountID = event.getData().getString("account");
133 | String username = event.getData().getString("username");
134 |
135 | Account account = Webinterface.getAccountStorage().getAccountByID(accountID);
136 | if(account == null) return ActionResponse.error("Account doesn't exist");
137 |
138 | AccountConnection connection = account.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
139 | if(connection == null) return ActionResponse.error("No Minecraft account");
140 |
141 | AccountConnection newConnection = new AccountConnection(connection.getConnectionName(), connection.getUserID(), username, connection.getUserEmail(), connection.getUserAvatar());
142 | account.removeConnection(connection);
143 | account.addConnection(newConnection);
144 |
145 | return ActionResponse.success();
146 | }
147 |
148 | @WebinterfaceHandler(requestTarget = "shittyauth", requestTypes = "changeMCPassword", permission = DefaultPermissions.MODIFY_USERS)
149 | public ActionResponse changeMCPassword(ActionEvent event) {
150 | String accountID = event.getData().getString("account");
151 | String password = event.getData().getString("password");
152 |
153 | Account account = Webinterface.getAccountStorage().getAccountByID(accountID);
154 | if(account == null) return ActionResponse.error("Account doesn't exist");
155 |
156 | AccountConnection connection = account.getConnection(ShittyAuth.ACCOUNT_CONNECTION_NAME);
157 | if(connection == null) return ActionResponse.error("No Minecraft account");
158 |
159 | Webinterface.getCredentialsStorage().storeCredentials(ShittyAuth.ACCOUNT_CONNECTION_NAME, connection.getUserID(), password);
160 | return ActionResponse.success();
161 | }
162 |
163 | @WebinterfaceHandler(requestTarget = "shittyauth", requestTypes = "changeWIAPIPassword", permission = DefaultPermissions.MODIFY_USERS)
164 | public ActionResponse changeWIAPIPassword(ActionEvent event) {
165 | String accountID = event.getData().getString("account");
166 | String password = event.getData().getString("password");
167 |
168 | Account account = Webinterface.getAccountStorage().getAccountByID(accountID);
169 | if(account == null) return ActionResponse.error("Account doesn't exist");
170 |
171 | AccountConnection connection = account.getConnection(PasswordAuth.ID);
172 | if(connection == null) return ActionResponse.error("Not a password-based account");
173 |
174 | Webinterface.getCredentialsStorage().storeCredentials(PasswordAuth.ID, connection.getUserID(), password);
175 | return ActionResponse.success();
176 | }
177 |
178 | @WebinterfaceHandler(requestTarget = "shittyauth", requestTypes = "setSetting", permission = DefaultPermissions.SETTINGS)
179 | public ActionResponse setSetting(ActionEvent event) {
180 | return SettingsPage.handleSetSettingRequest(ShittyAuth.config, event);
181 | }
182 |
183 | }
184 |
--------------------------------------------------------------------------------