DefaultAuthenticators.java 15.6 KB
Newer Older
KingRainbow44's avatar
KingRainbow44 committed
1
2
package emu.grasscutter.auth;

3
import at.favre.lib.crypto.bcrypt.BCrypt;
KingRainbow44's avatar
KingRainbow44 committed
4
5
6
7
8
import emu.grasscutter.Grasscutter;
import emu.grasscutter.auth.AuthenticationSystem.AuthenticationRequest;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account;
import emu.grasscutter.server.http.objects.*;
9
10
11
12
13
14
15
16
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils;

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
KingRainbow44's avatar
KingRainbow44 committed
17
18
19
20
21
22
23
24

import static emu.grasscutter.Configuration.*;
import static emu.grasscutter.utils.Language.translate;

/**
 * A class containing default authenticators.
 */
public final class DefaultAuthenticators {
25

KingRainbow44's avatar
KingRainbow44 committed
26
    /**
KingRainbow44's avatar
KingRainbow44 committed
27
     * Handles the authentication request from the username and password form.
KingRainbow44's avatar
KingRainbow44 committed
28
29
     */
    public static class PasswordAuthenticator implements Authenticator<LoginResultJson> {
30
31
        @Override
        public LoginResultJson authenticate(AuthenticationRequest request) {
KingRainbow44's avatar
KingRainbow44 committed
32
            var response = new LoginResultJson();
33

KingRainbow44's avatar
KingRainbow44 committed
34
35
            var requestData = request.getPasswordRequest();
            assert requestData != null; // This should never be null.
36
37
            int playerCount = Grasscutter.getGameServer().getPlayers().size();

38
            boolean successfulLogin = false;
KingRainbow44's avatar
KingRainbow44 committed
39
40
            String address = request.getRequest().ip();
            String responseMessage = translate("messages.dispatch.account.username_error");
41
42
            String loggerMessage = "";

KingRainbow44's avatar
KingRainbow44 committed
43
44
            // Get account from database.
            Account account = DatabaseHelper.getAccountByName(requestData.account);
45
46
            if (ACCOUNT.maxPlayer <= -1 || playerCount < ACCOUNT.maxPlayer) {
                // Check if account exists.
47
                if (account == null && ACCOUNT.autoCreate) {
48
                    // This account has been created AUTOMATICALLY. There will be no permissions added.
49
                    account = DatabaseHelper.createAccountWithUid(requestData.account, 0);
50
51

                    // Check if the account was created successfully.
52
                    if (account == null) {
53
54
55
56
57
58
59
60
61
                        responseMessage = translate("messages.dispatch.account.username_create_error");
                        Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_error", address));
                    } else {
                        // Continue with login.
                        successfulLogin = true;

                        // Log the creation.
                        Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_success", address, response.data.account.uid));
                    }
62
                } else if (account != null)
KingRainbow44's avatar
KingRainbow44 committed
63
                    successfulLogin = true;
64
                else
65
66
67
68
69
70
                    loggerMessage = translate("messages.dispatch.account.account_login_exist_error", address);

            } else {
                responseMessage = translate("messages.dispatch.account.server_max_player_limit");
                loggerMessage = translate("messages.dispatch.account.login_max_player_limit", address);
            }
KingRainbow44's avatar
KingRainbow44 committed
71

72

KingRainbow44's avatar
KingRainbow44 committed
73
            // Set response data.
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
            if (successfulLogin) {
                response.message = "OK";
                response.data.account.uid = account.getId();
                response.data.account.token = account.generateSessionKey();
                response.data.account.email = account.getEmail();

                loggerMessage = translate("messages.dispatch.account.login_success", address, account.getId());
            } else {
                response.retcode = -201;
                response.message = responseMessage;

            }
            Grasscutter.getLogger().info(loggerMessage);

            return response;
        }
    }

    public static class ExperimentalPasswordAuthenticator implements Authenticator<LoginResultJson> {
        @Override
        public LoginResultJson authenticate(AuthenticationRequest request) {
            var response = new LoginResultJson();

            var requestData = request.getPasswordRequest();
            assert requestData != null; // This should never be null.
            int playerCount = Grasscutter.getGameServer().getPlayers().size();

            boolean successfulLogin = false;
            String address = request.getRequest().ip();
            String responseMessage = translate("messages.dispatch.account.username_error");
            String loggerMessage = "";
            String decryptedPassword = "";
Yazawazi's avatar
Yazawazi committed
106
107
108
109
110
            try {
                byte[] key = FileUtils.readResource("/keys/auth_private-key.der");
                PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key);
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                RSAPrivateKey private_key = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
111

Yazawazi's avatar
Yazawazi committed
112
                Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
113

Yazawazi's avatar
Yazawazi committed
114
                cipher.init(Cipher.DECRYPT_MODE, private_key);
115

Yazawazi's avatar
Yazawazi committed
116
117
118
                decryptedPassword = new String(cipher.doFinal(Utils.base64Decode(request.getPasswordRequest().password)), StandardCharsets.UTF_8);
            } catch (Exception ignored) {
                decryptedPassword = request.getPasswordRequest().password;
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
            }

            if (decryptedPassword == null) {
                successfulLogin = false;
                loggerMessage = translate("messages.dispatch.account.login_password_error", address);
                responseMessage = translate("messages.dispatch.account.password_error");
            }

            // Get account from database.
            Account account = DatabaseHelper.getAccountByName(requestData.account);
            if (ACCOUNT.maxPlayer <= -1 || playerCount < ACCOUNT.maxPlayer) {
                // Check if account exists.
                if (account == null && ACCOUNT.autoCreate) {
                    // This account has been created AUTOMATICALLY. There will be no permissions added.
                    if (decryptedPassword.length() >= 8) {
                        account = DatabaseHelper.createAccountWithUid(requestData.account, 0);
                        account.setPassword(BCrypt.withDefaults().hashToString(12, decryptedPassword.toCharArray()));
                        account.save();

                        // Check if the account was created successfully.
                        if (account == null) {
                            responseMessage = translate("messages.dispatch.account.username_create_error");
                            loggerMessage = translate("messages.dispatch.account.account_login_create_error", address);
                        } else {
                            // Continue with login.
                            successfulLogin = true;

                            // Log the creation.
                            Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_success", address, response.data.account.uid));
                        }
                    } else {
                        successfulLogin = false;
                        loggerMessage = translate("messages.dispatch.account.login_password_error", address);
                        responseMessage = translate("messages.dispatch.account.password_length_error");
                    }
                } else if (account != null) {
                    if (account.getPassword() != null && !account.getPassword().isEmpty()) {
                        if (BCrypt.verifyer().verify(decryptedPassword.toCharArray(), account.getPassword()).verified) {
                            successfulLogin = true;
                        } else {
                            successfulLogin = false;
                            loggerMessage = translate("messages.dispatch.account.login_password_error", address);
                            responseMessage = translate("messages.dispatch.account.password_error");
                        }
                    } else {
                        successfulLogin = false;
                        loggerMessage = translate("messages.dispatch.account.login_password_storage_error", address);
                        responseMessage = translate("password_storage_error");
                    }
                } else {
                    loggerMessage = translate("messages.dispatch.account.account_login_exist_error", address);
                }
            } else {
                responseMessage = translate("messages.dispatch.account.server_max_player_limit");
                loggerMessage = translate("messages.dispatch.account.login_max_player_limit", address);
            }


            // Set response data.
            if (successfulLogin) {
KingRainbow44's avatar
KingRainbow44 committed
179
180
181
182
                response.message = "OK";
                response.data.account.uid = account.getId();
                response.data.account.token = account.generateSessionKey();
                response.data.account.email = account.getEmail();
183
184

                loggerMessage = translate("messages.dispatch.account.login_success", address, account.getId());
KingRainbow44's avatar
KingRainbow44 committed
185
186
187
            } else {
                response.retcode = -201;
                response.message = responseMessage;
188

KingRainbow44's avatar
KingRainbow44 committed
189
            }
190
191
            Grasscutter.getLogger().info(loggerMessage);

KingRainbow44's avatar
KingRainbow44 committed
192
193
194
195
196
197
198
199
            return response;
        }
    }

    /**
     * Handles the authentication request from the game when using a registry token.
     */
    public static class TokenAuthenticator implements Authenticator<LoginResultJson> {
200
201
        @Override
        public LoginResultJson authenticate(AuthenticationRequest request) {
KingRainbow44's avatar
KingRainbow44 committed
202
            var response = new LoginResultJson();
203

KingRainbow44's avatar
KingRainbow44 committed
204
205
            var requestData = request.getTokenRequest();
            assert requestData != null;
206

KingRainbow44's avatar
KingRainbow44 committed
207
208
            boolean successfulLogin;
            String address = request.getRequest().ip();
209
210
211
            String loggerMessage;
            int playerCount = Grasscutter.getGameServer().getPlayers().size();

KingRainbow44's avatar
KingRainbow44 committed
212
213
            // Log the attempt.
            Grasscutter.getLogger().info(translate("messages.dispatch.account.login_token_attempt", address));
214
215
216
217
218
219
220
221
222
223

            if (ACCOUNT.maxPlayer <= -1 || playerCount < ACCOUNT.maxPlayer) {

                // Get account from database.
                Account account = DatabaseHelper.getAccountById(requestData.uid);

                // Check if account exists/token is valid.
                successfulLogin = account != null && account.getSessionKey().equals(requestData.token);

                // Set response data.
224
                if (successfulLogin) {
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
                    response.message = "OK";
                    response.data.account.uid = account.getId();
                    response.data.account.token = account.getSessionKey();
                    response.data.account.email = account.getEmail();

                    // Log the login.
                    loggerMessage = translate("messages.dispatch.account.login_token_success", address, requestData.uid);
                } else {
                    response.retcode = -201;
                    response.message = translate("messages.dispatch.account.account_cache_error");

                    // Log the failure.
                    loggerMessage = translate("messages.dispatch.account.login_token_error", address);
                }

KingRainbow44's avatar
KingRainbow44 committed
240
241
            } else {
                response.retcode = -201;
242
243
244
                response.message = translate("messages.dispatch.account.server_max_player_limit");

                loggerMessage = translate("messages.dispatch.account.login_max_player_limit", address);
KingRainbow44's avatar
KingRainbow44 committed
245
            }
246
247

            Grasscutter.getLogger().info(loggerMessage);
KingRainbow44's avatar
KingRainbow44 committed
248
249
250
251
252
253
254
255
            return response;
        }
    }

    /**
     * Handles the authentication request from the game when using a combo token/session key.
     */
    public static class SessionKeyAuthenticator implements Authenticator<ComboTokenResJson> {
256
257
258
259
        @Override
        public ComboTokenResJson authenticate(AuthenticationRequest request) {
            var response = new ComboTokenResJson();

KingRainbow44's avatar
KingRainbow44 committed
260
261
            var requestData = request.getSessionKeyRequest();
            var loginData = request.getSessionKeyData();
262
263
264
            assert requestData != null;
            assert loginData != null;

KingRainbow44's avatar
KingRainbow44 committed
265
266
            boolean successfulLogin;
            String address = request.getRequest().ip();
267
268
269
270
271
272
273
274
275
276
277
            String loggerMessage;
            int playerCount = Grasscutter.getGameServer().getPlayers().size();

            if (ACCOUNT.maxPlayer <= -1 || playerCount < ACCOUNT.maxPlayer) {
                // Get account from database.
                Account account = DatabaseHelper.getAccountById(loginData.uid);

                // Check if account exists/token is valid.
                successfulLogin = account != null && account.getSessionKey().equals(loginData.token);

                // Set response data.
278
                if (successfulLogin) {
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
                    response.message = "OK";
                    response.data.open_id = account.getId();
                    response.data.combo_id = "157795300";
                    response.data.combo_token = account.generateLoginToken();

                    // Log the login.
                    loggerMessage = translate("messages.dispatch.account.combo_token_success", address);

                } else {
                    response.retcode = -201;
                    response.message = translate("messages.dispatch.account.session_key_error");

                    // Log the failure.
                    loggerMessage = translate("messages.dispatch.account.combo_token_error", address);
                }
KingRainbow44's avatar
KingRainbow44 committed
294
295
            } else {
                response.retcode = -201;
296
297
298
                response.message = translate("messages.dispatch.account.server_max_player_limit");

                loggerMessage = translate("messages.dispatch.account.login_max_player_limit", address);
KingRainbow44's avatar
KingRainbow44 committed
299
            }
300
301

            Grasscutter.getLogger().info(loggerMessage);
KingRainbow44's avatar
KingRainbow44 committed
302
303
304
            return response;
        }
    }
KingRainbow44's avatar
KingRainbow44 committed
305
306
307
308
309

    /**
     * Handles authentication requests from external sources.
     */
    public static class ExternalAuthentication implements ExternalAuthenticator {
310
311
        @Override
        public void handleLogin(AuthenticationRequest request) {
KingRainbow44's avatar
KingRainbow44 committed
312
313
314
315
            assert request.getResponse() != null;
            request.getResponse().send("Authentication is not available with the default authentication method.");
        }

316
317
        @Override
        public void handleAccountCreation(AuthenticationRequest request) {
KingRainbow44's avatar
KingRainbow44 committed
318
319
320
321
            assert request.getResponse() != null;
            request.getResponse().send("Authentication is not available with the default authentication method.");
        }

322
323
        @Override
        public void handlePasswordReset(AuthenticationRequest request) {
KingRainbow44's avatar
KingRainbow44 committed
324
325
326
327
            assert request.getResponse() != null;
            request.getResponse().send("Authentication is not available with the default authentication method.");
        }
    }
328
329
330
331
332

    /**
     * Handles authentication requests from OAuth sources.
     */
    public static class OAuthAuthentication implements OAuthAuthenticator {
333
334
        @Override
        public void handleLogin(AuthenticationRequest request) {
335
336
337
338
            assert request.getResponse() != null;
            request.getResponse().send("Authentication is not available with the default authentication method.");
        }

339
340
        @Override
        public void handleRedirection(AuthenticationRequest request, ClientType type) {
341
342
343
344
            assert request.getResponse() != null;
            request.getResponse().send("Authentication is not available with the default authentication method.");
        }

345
346
        @Override
        public void handleTokenProcess(AuthenticationRequest request) {
347
348
349
350
            assert request.getResponse() != null;
            request.getResponse().send("Authentication is not available with the default authentication method.");
        }
    }
KingRainbow44's avatar
KingRainbow44 committed
351
}