Compare commits

...

363 Commits

Author SHA1 Message Date
games647
c21c7eaeec Generalize references from Bungee to proxies 2021-03-17 11:24:16 +01:00
games647
804a0aec4b Isolate modules in their packages 2021-03-17 11:19:04 +01:00
games647
13bc92f6c3 Migrate to session manager 2021-03-15 14:22:38 +01:00
games647
e88f2e7f8e Fail safely if BungeeCord implementation specific classes are not found 2021-03-15 13:40:28 +01:00
games647
337e01e537 Put configuration values into extra lines to highlight the environment
They are not configuration options

Related #488
2021-03-10 16:50:16 +01:00
games647
d9f254fbff Merge pull request #473 from games647/dependabot/maven/com.comphenix.protocol-ProtocolLib-4.6.0
Bump ProtocolLib from 4.5.1 to 4.6.0
2021-03-09 20:57:41 +01:00
dependabot-preview[bot]
09ab7288da Bump ProtocolLib from 4.5.1 to 4.6.0
Bumps [ProtocolLib](https://github.com/dmulloy2/ProtocolLib) from 4.5.1 to 4.6.0.
- [Release notes](https://github.com/dmulloy2/ProtocolLib/releases)
- [Commits](https://github.com/dmulloy2/ProtocolLib/compare/4.5.1...4.6.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-22 16:15:32 +00:00
games647
ff2e5c0435 Drop package goal for the core module 2021-02-18 18:25:37 +01:00
games647
30c233c953 Fix workflow version 2021-02-18 18:21:32 +01:00
games647
6fd81e8e29 Sort supported list alphabetically 2021-02-18 18:17:52 +01:00
games647
4765372b0a Add BungeeAuthenticator and UserLogin 2021-02-18 18:16:06 +01:00
games647
303c064416 Check for valid file access first 2021-02-14 17:06:44 +01:00
games647
a15de80c7d Revert "Ignore the correct channel"
This reverts commit 465519ee
Namespace key class did the lowercase operation
2021-02-11 10:22:50 +01:00
games647
25356db175 Clean up CI 2021-02-09 16:33:37 +01:00
games647
465519eece Ignore the correct channel 2021-02-09 16:33:37 +01:00
games647
16f131ee2d Limit expire timer 2021-02-09 16:33:36 +01:00
games647
43046bea97 Fix branch references 2021-02-09 16:33:36 +01:00
xXSchrandXx
aa734853c6 Implement BungeeCordAuthenticator (#454)
* Create BungeeCordAuthenticatorHook

* Update bungee.yml

* Update FastLoginBungee.java

* Some small changes

* Test Hook

* Update pom.xml

* Update pom.xml

* Rebuild with new spigotplugins-repo

* Disable checksum for spigotplugins

* Update pom.xml

* Update pom.xml

* Update pom.xml

* Update pom.xml

* Update pom.xml

* Removed spigotplugins-repo

* Update pom.xml

* Update pom.xml

* Update pom.xml

* Update pom.xml

* Update pom.xml

* Update pom.xml

* Update pom.xml

New url

* Update pom.xml

* Update pom.xml

* Update FastLoginBungee.java

* requested changes

* Delete BungeeCordAuthenticator-0.0.2-SNAPSHOT.jar
2021-02-09 14:41:21 +01:00
dependabot-preview[bot]
28fb40d063 Merge pull request #453 from games647/dependabot/maven/com.zaxxer-HikariCP-4.0.1 2021-02-09 12:36:45 +00:00
games647
29c633f68a Merge pull request #459 from Kamilkime/main
Fix name change detection, allow FastLogin to respect AuthMe registration limits
2021-02-09 10:59:33 +01:00
Kamilkime
c079653d09 Always return true when registering with AuthMe 2021-02-09 10:42:10 +01:00
Kamilkime
7fbfa8240c Allow FastLogin to respect AuthMe pre IP register limit, fixes #458 2021-02-09 01:28:12 +01:00
Kamilkime
1ab346d067 Correct name check on PaperSpigot, fixes #423, fixes #437, fixes #457 2021-02-09 00:28:05 +01:00
dependabot-preview[bot]
50a8fd2f79 Bump HikariCP from 3.4.5 to 4.0.1
Bumps [HikariCP](https://github.com/brettwooldridge/HikariCP) from 3.4.5 to 4.0.1.
- [Release notes](https://github.com/brettwooldridge/HikariCP/releases)
- [Changelog](https://github.com/brettwooldridge/HikariCP/blob/dev/CHANGES)
- [Commits](https://github.com/brettwooldridge/HikariCP/compare/HikariCP-3.4.5...HikariCP-4.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-01 16:25:09 +00:00
games647
9ff404a424 Update CI workflow 2020-11-10 12:33:31 +01:00
games647
5029701f2a Merge pull request #418 from Kamilkime/master
Update encryption to handle MC 1.16.4, fixes #412
2020-11-09 14:38:31 +01:00
Kamilkime
1f63f91f17 Add comments, make cipher variable names clearer 2020-11-09 13:25:16 +01:00
Kamilkime
71d84d1748 Make encryption method reflections static 2020-11-08 17:15:26 +01:00
Kamilkime
64072d84d8 Update encryption to handle MC 1.16.4, fixes #412 2020-11-08 03:52:39 +01:00
dependabot-preview[bot]
f2441ecc15 Merge pull request #408 from games647/dependabot/maven/junit-junit-4.13.1 2020-11-04 11:18:44 +00:00
games647
fe1499f1fb Add support for 1.16.4
Fixes #412
Fixes #411
2020-11-04 11:05:34 +01:00
dependabot-preview[bot]
e74288e676 Bump junit from 4.13 to 4.13.1
Bumps [junit](https://github.com/junit-team/junit4) from 4.13 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.13...r4.13.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-12 16:20:06 +00:00
games647
d034e71331 Do not move and create a new proxy file
Fixes #389
2020-08-06 10:10:00 +02:00
games647
7e43b99f91 [CI-SKIP] Dump placeholderapi to prevent deprecates on register 2020-08-03 21:06:22 +02:00
games647
496a036fb4 Merge pull request #385 from ishlandbukkit/master
Add SodionAuthHook
2020-08-02 17:17:58 +02:00
games647
fa1f85698a Fix NoSuchMethodError for PlaceholderAPI after breaking change
Fixes #386
2020-07-31 17:02:51 +02:00
games647
25b6ca7653 Convert proxy file to the new name 2020-07-31 17:02:51 +02:00
games647
b30eb0de29 Debug verified username usage in ProtocolLib 2020-07-31 17:02:51 +02:00
games647
c96d64597a Clear proxies file name 2020-07-31 17:02:51 +02:00
games647
0ef2000f2a Rename rate limit to join limit 2020-07-31 17:02:51 +02:00
ishland
a36ac7f9e3 Remove unneeded SodionAuth-Libs 2020-07-29 16:11:41 +08:00
ishland
2e38550db0 I figured it out FINALLY 2020-07-29 16:07:24 +08:00
ishland
4e2b6c7810 Use JitPack and update API reference 2020-07-29 10:58:04 +08:00
ishland
4acbcbfd19 Squash previous commits
* Add SodionAuth

* Add Comment

* add SodionAuth

Co-authored-by: logos <1102280066@qq.com>
2020-07-29 09:22:17 +08:00
Gabriele C
cae2f8f58d Implement FastLoginPremiumToggleEvent 2020-06-27 13:58:38 +02:00
games647
de1487b6aa Fix driver warning message not displaying 2020-06-06 18:38:51 +02:00
games647
96cdac1332 Ignore proxied bedrock players (Fixes #328) 2020-06-06 18:32:08 +02:00
games647
2601fea84f Drop no longer needed uuid getter 2020-06-06 16:29:52 +02:00
games647
2bd339b0bf Use username from Mojang for offline IDs
Affected systems: BungeeCord after name change
Effects: Carry on items, permissions, etc. from the old user account without access to the new one

After a name change it could happen that the client still only
knows the old username and will send it to the server. Mojang will provide us with an up-to-date username that we should use instead.

The username is also sent to Mojang, so that they could verify the use. Therefore exploiting this behavior extensively for arbitrary usernames is not possible.

Related #344
2020-05-25 13:41:41 +02:00
games647
286b755ee3 Log player handling
Related #354
2020-05-23 21:40:10 +02:00
games647
5c5b7384a4 Better explain anti bot configuration values 2020-05-21 10:25:01 +02:00
games647
7c125dc0b6 Add more debugging messages for delayed proxy logins
Related #352
2020-05-21 10:03:56 +02:00
games647
103a8320ec Log requested premium logins 2020-05-20 19:50:22 +02:00
games647
49df461643 Reduce work threads, because processing is async 2020-05-19 11:47:08 +02:00
games647
3b69904257 Extract login decryption int utilities 2020-05-19 11:46:10 +02:00
games647
b41d56f835 Fix reflection access 2020-05-19 09:57:39 +02:00
games647
b1797c84d9 Prevent duplicate save requests 2020-05-18 16:10:39 +02:00
games647
9941a69c6d Use invokeExact for better performance 2020-05-18 13:21:07 +02:00
games647
0ee269785a Set UUIDs using MethodHandles 2020-05-18 12:12:51 +02:00
games647
c74c8f5fee Throw exception on unexpected UUIDs 2020-05-15 15:14:14 +02:00
games647
0ad3a853b5 Guard against any potential modifications for UUID changes
Related #344
2020-05-15 14:49:49 +02:00
games647
42637813e8 Add missing cases from last commit 2020-05-15 14:44:31 +02:00
games647
feee64309e Warn if FastLogin doesn't login authenticated players
Related #351
2020-05-15 14:38:22 +02:00
games647
b9cf8f0498 Log setting the offline UUID on Bungee
Related #344
2020-05-15 14:12:18 +02:00
games647
30a763dbc4 Add introduction about closing issues 2020-05-14 15:11:01 +02:00
games647
42790b27f8 Unregister variable on disable 2020-05-14 13:45:23 +02:00
games647
c58eda983a Fix parsing of placeholder status
Fixes #341
2020-05-14 13:41:32 +02:00
games647
477d3df1dd Favor nano time, because it's monotonic 2020-05-13 22:40:26 +02:00
games647
c1dc69845e [Experimental] Enable paranoid to hide MySQL details 2020-05-13 21:27:53 +02:00
games647
c3f6881e4b Add missing default values for the rate limiter 2020-05-13 19:51:05 +02:00
games647
25b380f74a Add a basic rate limiter for incoming connections
Related #347
2020-05-13 19:47:02 +02:00
games647
a5777869c8 Re-order driver specific properties 2020-05-13 19:46:02 +02:00
games647
cf3e8434a2 Prefer more MySQL performance optimizations 2020-05-13 19:46:02 +02:00
dependabot-preview[bot]
5881be9b78 Merge pull request #348 from games647/dependabot/maven/com.zaxxer-HikariCP-3.4.5 2020-05-12 14:22:39 +00:00
dependabot-preview[bot]
58c42f9f7f Bump HikariCP from 3.4.3 to 3.4.5
Bumps [HikariCP](https://github.com/brettwooldridge/HikariCP) from 3.4.3 to 3.4.5.
- [Release notes](https://github.com/brettwooldridge/HikariCP/releases)
- [Changelog](https://github.com/brettwooldridge/HikariCP/blob/dev/CHANGES)
- [Commits](https://github.com/brettwooldridge/HikariCP/compare/HikariCP-3.4.3...HikariCP-3.4.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-11 16:26:14 +00:00
games647
f40e787b55 Delay force action until PlayerJoinEvent is fired
Fixes Paper async chunk loading -> delayed PlayerJoinEvent
(Related #331)
2020-05-09 18:49:43 +02:00
games647
b6a95bb153 Delay force login command for async logins on Paper from Bungee
Related #331
2020-05-07 16:25:10 +02:00
dependabot-preview[bot]
9bae5c3f79 Merge pull request #339 from games647/dependabot/maven/me.clip-placeholderapi-2.10.6 2020-05-05 11:46:47 +00:00
dependabot-preview[bot]
b5ccc1df2e Bump placeholderapi from 2.10.5 to 2.10.6
Bumps placeholderapi from 2.10.5 to 2.10.6.

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-04 16:26:59 +00:00
dependabot-preview[bot]
d829c71438 Merge pull request #334 from games647/dependabot/maven/com.zaxxer-HikariCP-3.4.3 2020-04-29 10:36:11 +00:00
dependabot-preview[bot]
9cf5431d1a Merge pull request #335 from games647/dependabot/maven/com.lenis0012.bukkit-loginsecurity-3.0.2 2020-04-28 08:36:50 +00:00
dependabot-preview[bot]
e0a7f207c5 Bump loginsecurity from 3.0.1 to 3.0.2
Bumps loginsecurity from 3.0.1 to 3.0.2.

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-27 16:21:02 +00:00
dependabot-preview[bot]
efb4c34b50 Bump HikariCP from 3.4.2 to 3.4.3
Bumps [HikariCP](https://github.com/brettwooldridge/HikariCP) from 3.4.2 to 3.4.3.
- [Release notes](https://github.com/brettwooldridge/HikariCP/releases)
- [Changelog](https://github.com/brettwooldridge/HikariCP/blob/dev/CHANGES)
- [Commits](https://github.com/brettwooldridge/HikariCP/compare/HikariCP-3.4.2...HikariCP-3.4.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-27 16:20:09 +00:00
dependabot-preview[bot]
68d35c75c2 Bump maven-shade-plugin from 3.2.2 to 3.2.3
Bumps [maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.2.2 to 3.2.3.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.2.2...maven-shade-plugin-3.2.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-26 11:49:33 +02:00
dependabot-preview[bot]
0d93a2c371 Bump placeholderapi from 2.10.4 to 2.10.5
Bumps placeholderapi from 2.10.4 to 2.10.5.

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-26 11:49:33 +02:00
games647
6091a228ab Don't rely on toString to build unique session keys
Fix IPv6 compatibility which contains brackets

Related #331
2020-04-25 17:05:08 +02:00
games647
6f99b6e9b4 Add explicit Junit dependency 2020-03-20 16:13:45 +01:00
games647
109e19e6da Add missing synchronized modifiers 2020-03-20 16:13:45 +01:00
games647
65469ed579 Make scheduler platform dependent 2020-03-20 13:44:58 +01:00
games647
8ecb5657d3 Drop nullable annotation usage to compile
Thanks CI
2020-03-14 19:06:05 +01:00
games647
b0cf6e39c7 Drop forced dependency 2020-03-14 19:01:09 +01:00
games647
88a526b5bf Drop support for Minecraft 1.7 2020-03-14 18:51:27 +01:00
games647
57b84509da Limit the total number of running threads
(Related #304)
2020-03-14 18:20:34 +01:00
games647
e0bc7d914c Load the after auth plugins
Fixes #306

There seems to be some capability problems
that it doesn't correctly load ProtocolLib before
this plugin. Furthermore if we use
events for our hooks they print out a warning.
2020-03-10 09:59:31 +01:00
games647
f1933f735a Merge pull request #305 from games647/dependabot/maven/pl.project13.maven-git-commit-id-plugin-4.0.0
Bump git-commit-id-plugin from 3.0.0 to 4.0.0
2020-03-09 19:18:30 +01:00
dependabot-preview[bot]
f69154418e Bump git-commit-id-plugin from 3.0.0 to 4.0.0
Bumps [git-commit-id-plugin](https://github.com/git-commit-id/maven-git-commit-id-plugin) from 3.0.0 to 4.0.0.
- [Release notes](https://github.com/git-commit-id/maven-git-commit-id-plugin/releases)
- [Commits](https://github.com/git-commit-id/maven-git-commit-id-plugin/compare/v3.0.0...v4.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-09 16:17:00 +00:00
ㄗㄠˋ ㄑㄧˊ
74b13231f8 bump craftapi to 0.3 (#303) 2020-03-08 12:15:37 +01:00
dependabot-preview[bot]
79627e3b60 Bump slf4j-jdk14 from 1.7.26 to 1.7.30 (#301)
Bumps [slf4j-jdk14](https://github.com/qos-ch/slf4j) from 1.7.26 to 1.7.30.
- [Release notes](https://github.com/qos-ch/slf4j/releases)
- [Commits](https://github.com/qos-ch/slf4j/compare/v_1.7.26...v_1.7.30)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-06 19:28:02 +01:00
dependabot-preview[bot]
1722ab3267 Bump maven-shade-plugin from 3.2.0 to 3.2.2 (#302)
Bumps [maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.2.0 to 3.2.2.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.2.0...maven-shade-plugin-3.2.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-06 19:27:19 +01:00
games647
fdc0485f05 [CI-SKIP] Use HTTPS and newer URLs in Maven 2020-03-03 10:50:48 +01:00
games647
1068ddbadd Enable Maven cache for runner 2020-03-03 10:47:55 +01:00
games647
b0d5bd606c Rework issue templates 2020-03-03 10:47:37 +01:00
games647
1e59101ada Enable MySQL storage caching 2020-03-03 10:46:43 +01:00
games647
46ac3c74e4 Fix Java setup action version 2020-02-17 15:28:58 +01:00
games647
d22af0f42e Fix unique name 2020-02-17 15:24:45 +01:00
games647
2cc078773b Migrate to GitHub actions 2020-02-17 15:18:58 +01:00
games647
c1b8b60bf7 Use HTTPS for code comments too 2020-02-12 10:59:11 +01:00
games647
c54d6eecfc Merge branch 'JLLeitschuh/fix/JLL/use_https_to_resolve_dependencies' 2020-02-12 10:57:30 +01:00
Jonathan Leitschuh
0cf6c4c188 Use HTTPS instead of HTTP to resolve dependencies
This fixes a security vulnerability in this project where the `pom.xml`
files were configuring Maven to resolve dependencies over HTTP instead of
HTTPS.

Signed-off-by: Jonathan Leitschuh <Jonathan.Leitschuh@gmail.com>
2020-02-12 10:55:08 +01:00
games647
282467d21b Set a default kick reason if nothing is specified 2020-01-08 10:44:10 +01:00
games647
e8a5dc7433 Use BungeeCord event methods for kicking the player 2020-01-07 20:35:16 +01:00
games647
4b45932d6a Migrate to asynchronous calls to LoginSecurity
(Fixes #290)
2020-01-06 10:04:44 +01:00
games647
1375cf3997 Fix ProtocolLib version 2019-12-29 13:35:33 +01:00
games647
5a5cf016d9 Remove on blacklist status on join too 2019-12-29 13:33:18 +01:00
games647
2c7e569653 Fix force login events being sync instead of async (Fixes #278) 2019-11-09 13:57:12 +01:00
games647
94c5fe302e Merge pull request #277 from FearGames/master
Fix build and add events
2019-11-02 16:41:43 +01:00
Gabriele C
c80012ddb1 Allow multi-line messages 2019-10-25 11:54:26 +02:00
Gabriele C
17361b1b54 Implement PreLogin and AutoLogin events 2019-10-23 14:45:51 +02:00
Gabriele C
426b458a58 Fix BungeeAuth dependency 2019-10-23 12:50:40 +02:00
games647
62a8b939cc Use 'TEXT' for strings in the configuration file 2019-08-16 14:59:14 +02:00
games647
327c8c4c9d Dump dependencies 2019-07-09 12:56:14 +02:00
games647
fbbe7a735a Initialize the decryption cipher only once
(Related #259)
2019-07-09 12:52:54 +02:00
games647
4110ce2fa2 Fix creating logging session in ProtocolSupport environments
Fixes #251
2019-05-11 21:16:38 +02:00
games647
28743d23bf Remove duplicate repository 2019-05-06 17:39:21 +02:00
games647
145bd95679 Drop usage of deprecated apache lang library 2019-05-06 17:37:13 +02:00
games647
3fe17cf8d9 Merge pull request #250 from lenis0012/patch-2
Update LoginSecurity maven repository
2019-05-06 17:36:15 +02:00
lenis0012
3446d4c443 Update LoginSecurity maven repository 2019-05-06 11:35:39 +02:00
games647
c528433079 Allow username only database logins
Related #247
2019-04-15 20:35:56 +02:00
games647
25858ea11f Enable travis caching 2019-04-14 10:16:53 +02:00
games647
204ffbb2ee Document network requests 2019-04-14 10:16:33 +02:00
games647
9c1ba81cbe Fix running force actions in LoginSecurity thread-safe 2019-04-14 10:16:19 +02:00
games647
15c5857c4f Correctly set the offline UUID in ProtocolSupport (Thanks to @Shevchik) 2018-08-30 12:32:42 +02:00
games647
4b0ad3b186 Update ProtocolSupport hook 2018-08-29 19:49:10 +02:00
games647
101f7207a9 Limit plugin channels to 16 characters for 1.7 support
Fixes #223
2018-08-07 13:41:30 +02:00
games647
64175fe9e8 Update to CraftApi 0.2 2018-07-29 18:47:07 +02:00
games647
f7a10d86eb Don't transform because it's already compatible with 1.13 2018-07-25 16:55:52 +02:00
games647
542aabad73 Lowercase the namespace correctly 2018-07-24 14:33:33 +02:00
games647
260b93a565 Channel names should be lowercase according to the spec
Related #217, #218
2018-07-24 10:17:21 +02:00
games647
6604cca8bd Use Plugin:Subchannel for channel messages
This is required to follow 1.13 spec.
(Related #216, #215)
2018-07-23 14:11:40 +02:00
games647
c172b1ec84 Prevent duplicate message fetching for kick in PLib (Fixes #212) 2018-06-27 18:59:40 +02:00
games647
9b0b8f5fcb Fix repository link of ProtocolLib 2018-06-06 20:17:24 +02:00
games647
20104b2b00 Update ProtocolLib to fix building 2018-06-06 09:33:05 +02:00
games647
fd76d2448e Add advanced options for the connection pool (Fixes #210) 2018-06-04 21:28:19 +02:00
games647
53a1821a9d Fix ProtocolLib repository 2018-06-04 21:27:23 +02:00
games647
1c6f4e82e0 Fix NPE for cracked players on non-bungee environments 2018-05-23 19:14:20 +02:00
games647
084afef899 Update premium UUID on verification (Related #208) 2018-05-04 19:45:43 +02:00
games647
8a9eed3a74 Add /newline variable 2018-05-04 18:54:26 +02:00
games647
1ea6d929b1 Clarify how to configure MariaDB/MySQL correctly 2018-04-26 10:09:40 +02:00
games647
ddc3aa9279 Replace deprecated PropertiesResolveEvent with LoginFinishEvent
affects only ProtocolSupport
2018-04-26 10:08:56 +02:00
games647
2a79a9511b Fix auto register type in BungeeCord not being sent 2018-04-07 16:14:46 +02:00
games647
791df26702 Fix defaults overriding config 2018-04-05 18:42:50 +02:00
games647
cdf1988f2f Fix comping after craftapi update 2018-04-05 17:38:16 +02:00
games647
f476c091bb Fix default message loading, because default values are ignored by .getKeys() 2018-04-05 17:33:32 +02:00
games647
352c72df64 Add note about developments builds 2018-04-02 14:43:43 +02:00
games647
2cd0b194aa We are SNAPSHOT build not a release candidate
Maven versions plugin is great for multi-modules.
Run mvn version:set -DnewVersion=... and that's it.
2018-03-31 10:38:57 +02:00
games647
f2e42019d6 Mention the new FastLogin module names in the setup guide 2018-03-31 10:34:26 +02:00
games647
82ec71e8d0 Update premium status for non-bungeecord setups (Related #200) 2018-03-27 20:43:53 +02:00
games647
6d207d62ba Fix BungeeCord blacklist condition checking 2018-03-25 15:44:51 +02:00
games647
889dab3152 Migrate to PlaceholderExpansion from PlaceholderAPI 2018-03-21 11:02:55 +01:00
games647
71c1f4f12e Remove session in ProtocolSupport directly without expiring 2018-03-18 16:22:03 +01:00
games647
3651c4873c Dump craftapi version 2018-03-17 16:57:05 +01:00
games647
8b613a48cc Forward skin from ProtocolLib verification response 2018-03-16 16:40:12 +01:00
games647
78f8fa1f05 Fix NPE on nullable uuid column 2018-03-16 16:34:31 +01:00
games647
ac5820bb75 Encode enums as integers 2018-03-16 15:15:54 +01:00
games647
c1cb28c996 Add basic API (Fixes #200) 2018-03-16 15:15:03 +01:00
games647
b534765ff8 Always forward premium status to spigot 2018-03-16 15:14:35 +01:00
games647
5bcfdfeb32 Cancel restore session events if it's a premium player (Related #201) 2018-03-16 14:44:35 +01:00
games647
b7c0fd549c Add an explicit warning about the BungeeCord setup guide 2018-03-11 12:27:43 +01:00
games647
61c1364506 Simplify command handling 2018-03-11 11:47:02 +01:00
games647
a29dd849f9 Move shared Mojang client into independant project 2018-03-09 14:39:02 +01:00
games647
3f9eba69ba Generate a public key only for ProtocolLib listener 2018-03-09 13:57:51 +01:00
games647
f250f8071f Optional migration 2018-03-05 21:27:48 +01:00
games647
8272aeac69 Switch to the codemc repo for BungeeCord 2018-03-05 17:37:40 +01:00
games647
4d470be712 Dump AuthMe version 2018-03-05 17:35:33 +01:00
games647
e2c04f2c26 Add isSaved helper 2018-03-02 19:56:17 +01:00
games647
86694982c7 Minor refactoring 2018-03-02 18:29:38 +01:00
games647
04b00f4f22 Add driver available check for more readable error messages 2018-02-24 20:45:22 +01:00
games647
48c2355745 Remove copy-paste misleading package name 2018-02-24 20:45:22 +01:00
games647
cff25c958d Extract BungeeCord message in dedicated classes 2018-02-24 20:45:20 +01:00
games647
06bb4b80dd Add toString methods to all relevant classes 2018-02-24 20:43:18 +01:00
games647
2bdd051a41 Remove universal jar building for a smaller jar footprint and less conflicts with provided dependencies 2018-02-24 20:43:18 +01:00
games647
526a8a9d51 Log invalid proxy id messages 2018-02-24 10:45:52 +01:00
games647
8cbdb66625 Relocate HikariCP and slf4j too to prevent conflicts 2018-02-08 13:24:42 +01:00
games647
e5e815a885 Cancel autologin for AuthMe sessions (Fixes #189, #148, #103) 2018-02-05 15:01:28 +01:00
games647
d0d5bd300b Use static imports for Colectors.* 2018-02-05 12:54:35 +01:00
games647
0c550edb05 Shade the gson dependency to fix compatibility with Minecraft 1.7.10
(Fixes #190)
2018-01-30 13:15:48 +01:00
games647
181ea71222 Readd SSLFactory for rate-limit load balance because direct proxies doesn't work at all 2018-01-28 13:25:10 +01:00
games647
c38692e237 Use ChangeSkins rate-limit message here too 2018-01-28 12:25:25 +01:00
games647
dcef62fa57 Fix FileAlreadyExistsException for sym linked folders 2018-01-27 21:49:32 +01:00
games647
856613a8c7 Update Hikari dependency 2018-01-27 18:47:19 +01:00
games647
3beb8beaeb Migrate tests to assertThat 2018-01-27 18:47:01 +01:00
games647
f3ea7ecbbe Update development builds link 2018-01-27 18:46:43 +01:00
games647
25c725f237 [ci skip] Update LoginSecurity to 2.1.7 to fix compiling 2017-12-01 09:35:37 +01:00
games647
ffe4eb7364 Clarify BungeeCord plugin installation on Spigot 2017-11-25 09:34:38 +01:00
games647
82a258097d Use SecureRandom for passwords 2017-10-30 17:57:01 +01:00
games647
57eff4b3ec Fix NPE for skin apply in ProtocolLib mode (Related #182) 2017-10-15 17:57:24 +02:00
games647
4858049c2a Use direct proxies instead of ssl factories for multiple IP-addresses 2017-10-14 18:25:12 +02:00
games647
bb2cc1b42a Remove local address check (Related #181) 2017-10-12 09:59:16 +02:00
games647
2512c5cf67 Convert local IP addr '-' to . (Related #179) 2017-10-09 10:33:38 +02:00
games647
c7c0782071 Fix address rotating for contacting the Mojang API 2017-10-07 19:48:29 +02:00
games647
df945146b8 Fix debug logging 2017-10-07 19:19:45 +02:00
games647
e32b0232e9 Fix logger init (Fixes #178) 2017-10-04 09:22:21 +02:00
games647
6daa654af8 Fix NPE for Mojang API connector 2017-10-03 15:14:37 +02:00
games647
0f01002564 Optimize issue template 2017-10-03 14:19:34 +02:00
games647
28a20a46fa Fix NPE parsing Mojang uuid 2017-10-03 14:19:02 +02:00
games647
105e00b7e8 Use Instant for timestamps 2017-10-01 17:11:06 +02:00
games647
dce44295d0 Migrate SLF4J logging (Fixes #177) 2017-09-29 16:54:29 +02:00
games647
1f917f3a8d Use Optionals for nullable values 2017-09-24 19:50:42 +02:00
games647
e6c23a4bb5 Use Gson's TypeAdapter for more type safety 2017-09-23 13:56:28 +02:00
games647
66b808c999 Fix compile 2017-09-22 21:41:24 +02:00
games647
2932de5588 Add support for IPv6 proxies 2017-09-22 21:08:24 +02:00
games647
16f7461568 Fix message loading was interacting with the normal config 2017-09-22 20:11:58 +02:00
games647
2f0eb81735 Shade the Bungee-Config implementation because it's platform independent 2017-09-22 20:07:04 +02:00
games647
bb80521ab6 Thermos supports GSON so we could share the json parsing 2017-09-22 18:17:35 +02:00
games647
109508dae6 Clean up using IDE inspections 2017-09-21 15:00:39 +02:00
games647
5bf9b05d30 Fix BungeeAuth Maven repository 2017-09-13 12:34:56 +02:00
games647
7839804a4c Drop support for deprecated AuthMe API 2017-09-12 17:05:18 +02:00
games647
ca58c55eca Remove legacy database migration code 2017-09-08 11:33:14 +02:00
games647
10453fd637 Drop support for RoyalAuth, because it doesn't seem to be supported anymore 2017-09-08 11:30:24 +02:00
games647
d18b734550 Update dependencies 2017-09-08 11:17:05 +02:00
games647
7f51659cc7 Version dump 2017-09-03 20:06:00 +02:00
games647
bb240d3aa0 Refactor encryption implementation
* Simplify utility class and make it more independent from the vendor code
* Create only one cipher object for verification
2017-08-28 12:17:47 +02:00
games647
484855724b Add ip parameter to verify a player doesn't use an authentication proxy.
This doesn't prevent proxy connections in general, but it verifies that
the same IP that is used for connecting to the Minecraft server is also
used for authenticating against the Mojang servers.

This happens if someone uses McLeaks. They use an authentication proxy
in order to hide and control the credentials behind those leaked or
donated accounts. So a user of that service joins the server using
a direct connection, but asks the McLeaks servers to send a relevant
request to the Mojang session-servers in order to pass the premium
verification process.
2017-08-25 13:20:55 +02:00
games647
4ea7968366 Remove Importer to prepare for code refactor 2017-08-24 18:50:37 +02:00
games647
44a47bc97f Set default value for proxies 2017-08-20 21:40:37 +02:00
games647
82cb25f809 Output more informational messages by default 2017-08-19 21:53:07 +02:00
games647
551441cdc4 Add HTTP-proxies support 2017-08-18 16:09:59 +02:00
games647
22a56862b0 Remove mcapi.ca section and fix config typos 2017-08-16 17:18:58 +02:00
games647
edf5933e07 Set the fake offline UUID on lowest priority (-> as soon as possible)
Then every plugin listening on priority level higher than lowest can see that fake UUID

This also fixes race conditions for plugins listening on the same priority as FastLogin before (->low)
(Fixes #167)
2017-08-01 10:29:58 +02:00
games647
c6da04de70 Fix listening for login start packets if ProtocolLib is installed
Another call on ProtocolLib's types removes all previous listening types

Fixes #163
2017-07-25 13:18:08 +02:00
games647
0459b0a5a1 Remove bungee chatcolor for Bukkit to support KCauldron 2017-07-22 08:35:32 +02:00
games647
033333e35c Minor cleanup using inspections + Https
* Use https for maven repositories if possible
* Fix typos
* Merge ProtocolLib listeners into one class
* Upgrade maven plugins and dependencies
2017-07-22 08:27:55 +02:00
games647
6595dc6ac0 Increase hook delay to let ProtocolLib inject the listener 2017-06-30 17:37:57 +02:00
games647
ea44002e91 Update dependencies and format imports 2017-06-30 17:23:46 +02:00
games647
131de8404c Add support for new authme API 2017-06-12 17:26:46 +02:00
games647
fbdd8ffc35 Choose player name casing based on client request.
Since BungeeCord commit 5bc189fbb7e8ca19984544af78a083024404fb2a the name casing is based on
the exact name saved at Mojang. This means it could have breaking effects on FastLogin, because
it performs case-sensitive checks against the database. To provide backwards compatibility with
old data we restore the old implementation access for FastLogin.

Thanks to @Maxetto for pointing this out. This commit basically reverts:
059c3f346e
2017-06-07 21:09:00 +02:00
games647
7db8c78975 Drop support for old authme API 2017-06-04 15:52:01 +02:00
games647
b102f06f8e Update ProtocolLib to fix building 2017-05-27 11:24:43 +02:00
games647
a79e18445a Fix building because the bungee proxy repo is down [ci skip] 2017-05-19 12:01:02 +02:00
games647
cf1a0c1bef Remove ebean util usage to make it compatible with 1.12 2017-05-14 17:11:10 +02:00
games647
059c3f346e Lowercase name inside pendingconnection for comparisons against the database 2017-05-10 17:06:25 +02:00
Leo G. ~ Leoko
47db2c7858 Fixed AuthHook (#144)
* Fixed AuthHook

The setServerStarted()-Method is now also called if an extern AuthHook
hooks into FastLogin via the API

* Simplified if-Statement
2017-04-19 14:39:27 +02:00
games647
5bb8640d78 Do not try to hook into a plugin if auth plugin hook is already set using the FastLogin API 2017-04-17 15:22:09 +02:00
games647
881b2ec7bc Fix changelog markdown syntax 2017-04-15 09:42:17 +02:00
games647
194c67cd6f Fix markdown syntax 2017-04-05 09:24:41 +02:00
games647
863607c9a4 Add optional useSSL config option 2017-02-23 09:16:11 +01:00
games647
f37cc0a0db Add commit id to the version 2017-02-14 14:01:57 +01:00
games647
70a81bfcdf Correctly wait for BungeeAuth loading by using the correct depend tag (Fixes #119) 2017-02-10 19:06:57 +01:00
games647
b8d029d6da Remove third party API 2017-02-04 14:09:38 +01:00
games647
c47dd1df80 Fix FileNotFoundEx if the bungee config doesn't exist 2017-01-28 16:38:48 +01:00
games647
4d5b1787b1 Migrate to Java 7 NIO files 2017-01-26 09:52:45 +01:00
games647
8c764220bd Fix duplicate premium username message 2017-01-21 18:02:45 +01:00
games647
9af076b4c4 Fix premium username logging message at the wrong place 2017-01-09 17:57:50 +01:00
games647
22aa9287e9 Fix NoClassDef errors if the optional PlaceholderAPI is not available (Fixes #108) 2017-01-07 18:42:10 +01:00
games647
f08daa9b72 Update bungee-proxy maven repository 2017-01-06 13:00:17 +01:00
games647
bc53743c6b Add placeholder variables 2017-01-06 12:54:02 +01:00
games647
a430a079c9 Do no print auto login message on authme session reuse (Related #101) 2016-12-23 22:12:55 +01:00
games647
f3ac6090f1 Fix bungee online check (Fixes #101) 2016-12-23 10:01:38 +01:00
games647
5ca9b9c59a Add note about firewalling your spigot server if you use BungeeCord 2016-12-22 09:13:58 +01:00
games647
b886d1501f Update LoginSecurity to make it buildable 2016-12-16 15:56:30 +01:00
games647
0082cc6536 Use static builder to make it independent from ProtocolLib without throwing NoClassDefFoundError 2016-12-16 15:49:40 +01:00
games647
7f96d55084 Convert config values to string if casting fails 2016-11-26 13:27:39 +01:00
games647
3851d539f8 Workaround injector class is package private in older versions of ProtocolLib (Fixes #94) 2016-11-26 11:33:15 +01:00
games647
a25d97879f Fail safetly if there session was started (prevents duplicate errors) 2016-11-26 10:06:27 +01:00
games647
41abffdb08 Fix Spigot console command invocation sends result to ingame players 2016-10-20 14:06:18 +02:00
games647
e69eb70377 Update BungeeAuth dependency and use the new API 2016-10-05 10:06:02 +02:00
games647
e924b7a2fa Automatically register players who are not known to the auth plugin
Fixes #85
2016-10-03 13:46:33 +02:00
games647
157ca04691 Fix timestamp parsing in newer versions of SQLite 2016-09-23 12:26:18 +02:00
games647
ae3e03405d No duplicate login's like auth plugins auto logins if it's the same ip 2016-09-23 10:42:25 +02:00
games647
bebb04bdea Share the same force login mangement for less duplicate code 2016-09-22 10:56:31 +02:00
games647
91f41c55de Finally set a value to the API column 2016-09-21 13:24:26 +02:00
games647
1acc825f81 Remove deprecated API methods 2016-09-21 13:22:48 +02:00
games647
87ca00d75d [SwitchMode] Kick the player only if the player is unknown to us 2016-09-21 09:16:19 +02:00
games647
62ffb1a904 [Bukkit] Fix adding to premium whitelist 2016-09-20 13:55:03 +02:00
games647
5075a71843 A few code styling things 2016-09-20 13:32:06 +02:00
games647
da266c7e91 Fix loading of settings 2016-09-19 17:59:45 +02:00
games647
acab4766b1 Remove database migration logging 2016-09-19 15:59:57 +02:00
games647
bef90d11cd Deploy only the universal jar to the target folder 2016-09-18 11:40:21 +02:00
games647
a02acd2d63 Remove the nasty UltraAuth fakeplayer workaround 2016-09-18 10:38:05 +02:00
games647
ca42a7c19e Refactor more code for more Java 8 and Guava usage 2016-09-17 15:19:07 +02:00
games647
b533197f05 Fix config loading in BungeeCord 2016-09-17 15:19:07 +02:00
Maxetto
c94711f315 Fix verb (#79) 2016-09-17 08:02:33 +02:00
games647
ee7af80bf0 Fix travis 2016-09-16 17:41:23 +02:00
games647
17c2099bf1 Make use of the awesome Java 8 features 2016-09-16 17:40:42 +02:00
games647
31d6b67381 Try to upgrade to Java 8. I hope enough people are using it. 2016-09-16 16:31:47 +02:00
games647
4b423c9ccb Update ProtocolSupport and use a maven repository for it now 2016-09-16 10:45:05 +02:00
games647
4292e9aaa0 Less deprecated warnings + Clean up 2016-09-15 11:10:52 +02:00
games647
07d0aededa Fix loading with unloaded configuration values 2016-09-15 10:33:17 +02:00
games647
218bc50c96 Drop support for LoginSecurity 1.X since 2.X seems to be stable 2016-09-14 17:44:32 +02:00
games647
a3b2e33aad Switch to vik1395 repository for BungeeAuth 2016-09-13 09:53:46 +02:00
games647
76f5ba7ed1 Refactor a lot of code + Add Guava v10 as shared library 2016-09-11 21:26:03 +02:00
games647
2cd50d23ad Revert converting auth hooks to the new format
-> backwards compatibility
2016-09-11 20:07:21 +02:00
games647
9f5f61f1c2 Do the same for the password generator 2016-09-11 19:57:27 +02:00
games647
3e9c8e3a7e More shared project code for less errors and less duplication 2016-09-11 18:59:42 +02:00
games647
8e5da01be0 Added configuration to disable auto logins for 2Factor authentication
(Fixes #65)
2016-09-09 16:52:44 +02:00
games647
5022c9aa7b Add cracked whitelist (Fixes #42)
(switch-mode -> switching to online-mode from offlinemode)
2016-09-09 16:40:24 +02:00
games647
ad1ab22586 Test another locale sqlite fix 2016-09-08 11:48:13 +02:00
games647
99ef5ce726 Fix correct cracked permission for bukkit 2016-09-08 10:06:43 +02:00
games647
9b7634a9f3 Fix LogIt repository 2016-09-04 16:35:57 +02:00
games647
115fc2e7ba A try to fix SQLite timestamp parsing 2016-09-04 12:14:28 +02:00
games647
b660951e1e Fix compatibility with older ProtocolLib versions (for 1.7)
because of the missing getMethodAcccessorOrNull method
2016-09-03 10:21:42 +02:00
games647
e495f70ccd Fix logging exceptions on encryption enabling 2016-09-01 20:09:34 +02:00
games647
b35d67b5c0 Add missing add-premium-other message 2016-09-01 19:41:58 +02:00
games647
58ac73a5a9 Fix update username in FastLogin database after nameChange (Fixes #67) 2016-08-31 13:29:06 +02:00
games647
ebe768f7a2 Fix ProtocolSupport autoRegister 2016-08-30 12:27:02 +02:00
games647
d20db79f46 Add second attemp login -> cracked (Fixes #51) 2016-08-29 17:38:46 +02:00
games647
c28d889c1b Remove complex nameChange convert since we now allow duplicate uuids 2016-08-26 13:32:28 +02:00
games647
ad60397851 Added auto login importers 2016-08-25 17:45:09 +02:00
games647
88fdeff3f1 Update documentation 2016-08-25 17:44:51 +02:00
games647
558ee1c92c Fix console support for cracked/premium commands (Fixes# 62) 2016-08-20 17:38:47 +02:00
games647
3e84ebd787 [BungeeAuth] Do not login the player if it's already logged in using
sessions
2016-08-19 22:00:07 +02:00
games647
36d7564c3a Fix race condition when waiting for bukkit message while
bungee redirects player
2016-08-19 21:07:29 +02:00
games647
596caa0573 Invoke forcelogin in BungeeCord only once 2016-08-19 20:52:51 +02:00
games647
fe4331298f Send a message on BungeeCord if there is only an auth plugin 2016-08-18 20:22:04 +02:00
games647
a67d84ef3f Fix race condition in bungee<->bukkit 2016-08-18 20:15:43 +02:00
games647
71362dfd7d Log bungeeauth exceptions 2016-08-18 09:16:06 +02:00
Maxetto
fcd98fce43 Fix Color Code (#54)
Got it wrong the first time.
2016-08-09 19:17:48 +02:00
games647
6c1c4e7286 Fix third-party not premium player detection 2016-08-09 14:24:57 +02:00
games647
164fb735d6 Fix ProtocolSupport BungeeCord 2016-08-07 11:41:03 +02:00
games647
fa1b0970a5 Dump to 1.7.1 2016-08-01 12:59:04 +02:00
games647
974bf498fc Fix protocollsupport autoregister 2016-07-31 11:56:20 +02:00
games647
27c04ff08f Fix BungeeCord autoRegister (Fixes #46) 2016-07-31 09:57:50 +02:00
games647
fb357424e6 Run the plugin message reader async to prevent the timeout event
warning from BungeeCord
2016-07-26 14:30:11 +02:00
games647
c73bb70256 FIx autoregister bug 2016-07-25 19:29:22 +02:00
games647
dc395cdc3f Remove debug code 2016-07-20 20:03:17 +02:00
games647
f7626ab969 Read the fully input from mcapi.ca instead of just one line 2016-07-20 11:54:00 +02:00
games647
5f9802d589 Fix third party profile parsing 2016-07-19 10:47:47 +02:00
games647
642c1621ad Fine tune timeout length 2016-07-13 13:00:25 +02:00
games647
eb965d5a48 Fix importing 2016-07-13 10:05:22 +02:00
games647
457bc9cf47 Fix SQLite drop index 2016-07-12 13:06:27 +02:00
games647
2ab3c6b77c Update AutoIn importer 2016-07-12 12:39:06 +02:00
games647
f27bad02d3 Remove the uuid index for name change conflicts 2016-07-12 12:23:32 +02:00
games647
9334296beb Fix saving on name change 2016-07-10 19:25:37 +02:00
games647
fd9940e6f0 Fix setting skin on Cauldron (Fixes #36) 2016-07-10 13:34:08 +02:00
games647
0745957e79 Fix BungeeCord not setting an premium uuid (Fixes #35) 2016-07-09 13:30:43 +02:00
games647
bb2e60f6e1 State why choose the loginEvent and fix it 2016-07-08 16:47:50 +02:00
games647
d15861b8e5 Use the correct message key 2016-07-08 16:47:50 +02:00
Maxetto
b84b340a77 Consistency language update (#33)
Messages that aren't targeted to the player invoking the command shouldn't start with "You are", that could be confusing.

Changed Warning message to be more eye-catching to the player. Assuming also that next line doesn't take the color code from previous line (otherwise change "&r" with "&6").
2016-07-07 14:51:14 +02:00
games647
c50249edea Switch to mcapi.ca and add configurable number of requests 2016-07-07 12:20:39 +02:00
games647
757ddb905a Make it buildable again 2016-07-04 21:40:42 +02:00
games647
9914b7f358 Fix player entry is not saved if namechangecheck is enabled 2016-07-04 21:26:03 +02:00
games647
bba4eb4eec Ignore all canceled events 2016-07-03 21:28:44 +02:00
games647
2b16f3341f Use the loginevent to send the client the offline uuid
-> skin applies on deactivated premium uuid
2016-07-03 16:58:23 +02:00
games647
167ce66057 Added note about skin forwarding if premium uuids are disabled 2016-07-03 14:18:27 +02:00
games647
8d1021e44c Update fake player methods 2016-06-29 19:04:12 +02:00
games647
a811a741f5 Change to lenis repository 2016-06-29 19:03:52 +02:00
games647
a6348766b3 Reduce the number of lookups if a cracked player already exists 2016-06-20 16:29:39 +02:00
games647
22dcc50950 I'm stupid (Related #27) 2016-06-20 16:16:31 +02:00
games647
bd3494eed0 Fix recursive method invocation (Related #27) 2016-06-20 15:27:43 +02:00
games647
1aba9a0f3b Added us.mcapi.com as third-party APIs to workaround rate-limits
(Fixes #27)
2016-06-20 14:12:29 +02:00
games647
6faf00e1bf Support for making requests to Mojang from different IPv4 addresses
(Related #27)
2016-06-20 13:52:37 +02:00
games647
0d89614f3c Add support for the new LoginSecurity version 2016-06-18 14:39:47 +02:00
games647
b009658eea Fix typo in BungeeCord message key 2016-06-16 15:10:25 +02:00
games647
2881689f09 Fixed default message copying 2016-06-15 17:35:10 +02:00
games647
6d1a97fd32 Added premium command warning 2016-06-15 13:55:57 +02:00
games647
b74faa2fd5 Fix missing translation 2016-06-15 13:26:20 +02:00
games647
4800a88886 Perform protocollib checks async/non-blocking 2016-06-14 19:36:34 +02:00
games647
92c9ab5b76 Use ProtocolLib as a soft dependency 2016-06-14 17:21:46 +02:00
games647
d90e3fdb44 Load the embed message as default 2016-06-14 17:02:12 +02:00
games647
8abbb8f07c Fix bungeecord support (Fixes #26) 2016-06-13 08:51:47 +02:00
games647
f04a44b1d2 Applies skin earlier to make it visible for other
plugins listening on login events
2016-06-11 17:16:03 +02:00
games647
1a66121977 Do not save players multiple times on server switch 2016-06-11 15:08:44 +02:00
games647
413a0325f8 Fixed BungeeCord force logins if there is a lobby server 2016-06-11 13:24:35 +02:00
138 changed files with 6188 additions and 5476 deletions

41
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,41 @@
---
name: Bug report
about: Something isn't working
title: ''
labels: 'bug'
assignees: ''
---
[//]: # (Lines in this format are considered as comments and will not be displayed.)
[//]: # (Before reporting make sure you're running the **latest build** of the plugin and checked for existing issues!)
### What behaviour is observed:
[//]: # (What happened?)
### What behaviour is expected:
[//]: # (What did you expect?)
### Steps/models to reproduce:
[//]: # (The actions that cause the issue. Please explain it in detail)
### Screenshots (if applicable)
[//]: # (You can drop the files here directly)
### Plugin list:
[//]: # (This can be found by running `/pl`)
### Environment description
[//]: # (Server software with exact version number, Minecraft version, SQLite/MySQL/MariaDB, ...)
### Plugin version or build number (don't write latest):
[//]: # (This can be found by running `/version plugin-name`.)
### Server Log:
[//]: # (No images please - only the textual representation)
[Hastebin](https://hastebin.com/) / [Gist](https://gist.github.com/) link of the error, stacktrace or the complete log (if any)
### Configuration:
[//]: # (No images please - only the textual representation)
[//]: # (remember to delete any sensitive data)
[Hastebin](https://hastebin.com/) / [Gist](https://gist.github.com/) link of your config.yml file

View File

@@ -0,0 +1,22 @@
---
name: Enhancement request
about: New feature or change request
title: ''
labels: 'enhancement'
assignees: ''
---
[//]: # (Lines in this format are considered as comments and will not be displayed.)
### Is your feature request related to a problem? Please describe.
[//]: # (A clear and concise description of what the problem is. Ex. I'm always frustrated when [...])
### Describe the solution you'd like
[//]: # (A clear and concise description of what you want to happen.)
### Describe alternatives you've considered
[//]: # (A clear and concise description of any alternative solutions or features you've considered.)
### Additional context
[//]: # (Add any other context or screenshots about the feature request here.)

10
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@@ -0,0 +1,10 @@
---
name: Question
about: You want to ask something
title: ''
labels: 'question'
assignees: ''
---

8
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,8 @@
[//]: # (Lines in this format are considered as comments and will not be displayed.)
[//]: # (If your work is in progress, please consider making a draft pull request.)
### Summary of your change
[//]: # (Example: motiviation, enhancement)
### Related issue
[//]: # (Reference it using '#NUMBER'. Ex: Fixes/Related #...)

49
.github/workflows/maven.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
# Automatically build, run unit and integration tests to detect errors early (CI provided by GitHub)
# including making pull requests review easier
# Human readable name in the actions tab
name: Java CI
# Build on every push and pull request regardless of the branch
# Wiki: https://help.github.com/en/actions/reference/events-that-trigger-workflows
on:
- push
- pull_request
jobs:
# job id
build_and_test:
# Environment image - always newest OS
runs-on: ubuntu-latest
# Run steps
steps:
# Pull changes
- uses: actions/checkout@v2.3.4
# Cache artifacts - however this has the downside that we don't get notified of
# artifact resolution failures like invalid repository
# Nevertheless the repositories should be more stable and it makes no sense to pull
# a same version every time
# A dry run would make more sense
- uses: actions/cache@v2.1.4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
# Setup Java
- name: Set up JDK
uses: actions/setup-java@v1.4.3
with:
# Use Java 11, because it's minimum required version
java-version: 11
# Build and test (included in package)
- name: Build with Maven and test
# Run non-interactive, package (with compile+test),
# ignore snapshot updates, because they are likely to have breaking changes, enforce checksums to validate
# possible errors in dependencies
run: mvn package test --batch-mode --no-snapshot-updates --strict-checksums --file pom.xml

61
.gitignore vendored
View File

@@ -1,38 +1,22 @@
# Eclipse stuff
/.classpath
/.project
/.settings
# Eclipse
.classpath
.project
.settings/
# netbeans
/nbproject
# NetBeans
nbproject/
nb-configuration.xml
/bukkit/nbproject/
# maven
/target
# vim
.*.sw[a-p]
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# various other potential build files
/build
/bin
/dist
/manifest.mf
*.log
# Mac filesystem dust
.DS_Store
# intellij
# IntelliJ
*.iml
*.ipr
*.iws
.idea/
# Maven
target/
pom.xml.versionsBackup
# Gradle
.gradle
@@ -42,8 +26,21 @@ gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Project module targets
bukkit/target
universal/target
bungee/target
core/target
# various other potential build files
build/
bin/
dist/
manifest.mf
*.log
# Vim
.*.sw[a-p]
# virtual machine crash logs, see https://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# Mac filesystem dust
.DS_Store
# Factorypath from Visual Studio Code
.factorypath

View File

@@ -1,15 +0,0 @@
# Use https://travis-ci.org/ for automatic tests
# speed up testing http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/
sudo: false
# This is a java project
language: java
script: mvn compile test
# We run on 7+
jdk:
- openjdk7
- oraclejdk7
- oraclejdk8

View File

@@ -1,25 +1,138 @@
######1.5.1
### 1.11
* TODO: Replace reflection with methodhandles
* Use direct proxies instead of ssl factories for multiple IP-addresses
* Remove local address check for multiple IP-addresses
* Fix parsing of local IP-addresses
* Fix address rotating for contacting the Mojang API
* Optimize issue template
* Use Instant for timestamps
* Migrate SLF4J logging (Fixes #177)
* Use Gson's TypeAdapter for more type safety
* Add support for IPv6 proxies
* Shared configuration implementation for easier maintained code
* Use Gson for json parsing, because it's supported on all platforms and removes code duplicates
* Clean up project code
* Drop support for deprecated AuthMe API
* Remove legacy database migration code
* Drop support for RoyalAuth, because it doesn't seem to be supported anymore
* Clean up client-server encryption -> use only one cipher per connection, simplify code
### 1.10
* Prevent authentication proxies
* Drop database importer
* More logging by default
* Add support for HTTP proxies
* Set the fake offline UUID on lowest priority (-> as soon as possible)
* Remove bungee chatcolor for Bukkit to support KCauldron
* Minor cleanup using inspections + Https
* Increase hook delay to let ProtocolLib inject the listener
* Drop support for old AuthMe API + Add support for new AuthMe API
* Remove eBean util usage to make it compatible with 1.12
* Do not try to hook into a plugin if auth plugin hook is already set using the FastLogin API
* Automatically register accounts if they are not in the auth plugin database but in the FastLogin database
* Update BungeeAuth dependency and use the new API. Please update your plugin if you still use the old one.
* Remove deprecated API methods from the last version
* Finally update the IP column on every login
* No duplicate session login
* Fix timestamp parsing in newer versions of SQLite
* Fix Spigot console command invocation sends result to in game players
### 1.9
* Added second attempt login -> cracked login
* Added cracked whitelist (switch-mode -> switching to online-mode from offlinemode)
* Added configuration to disable auto logins for 2Factor authentication
* Added missing add-premium-other message
* Upgrade to Java 8 -> Minimize file size
* Refactored/Cleaned up a lot of code
* [API] Deprecated platform specific auth-plugin. Please use AuthPlugin< platform specific player type >
* [API] Deprecated bukkit's password generator. Please use PasswordGenerator< platform specific player type >
* Fix ProtocolSupport autoRegister
* Fix update username in FastLogin database after nameChange
* Fix logging exceptions on encryption enabling
* Fix compatibility with older ProtocolLib versions (for 1.7) because of the missing getMethodAcccessorOrNull method
* Fix correct cracked permission for bukkit
* A try to fix SQLite timestamp parsing
* Drop support for LoginSecurity 1.X since 2.X seems to be stable
* Remove the nasty UltraAuth fakeplayer workaround by using a new api method. You should UltraAuth if you have it
### 1.8
* Added autoIn importer
* Added BFA importer
* Added ElDziAuth importer
* Fix third-party not premium player detection
* Fix ProtocolSupport BungeeCord
* Fix duplicate logins for BungeeAuth users
### 1.7.1
* Fix BungeeCord autoRegister (Fixes #46)
* Fix ProtocolSupport auto-register
### 1.7
* Added support for making requests to Mojang from different IPv4 addresses
* Added us.mcapi.com as third-party APIs to workaround rate-limits
* Fixed NPE in BungeeCord on cracked session
* Fixed skin applies if premium uuid is deactivated
* Fix player entry is not saved if namechangecheck is enabled
* Fix skin applies for third-party plugins
* Switch to mcapi.ca for uuid lookups
* Fix BungeeCord not setting an premium uuid
* Fix setting skin on Cauldron
* Fix saving on name change
### 1.6.2
* Fixed support for new LoginSecurity version
### 1.6.1
* Fix message typo in BungeeCord which created a NPE if premium-warning is activated
### 1.6
* Add a warning message if the user tries to invoke the premium command
* Added missing translation if the server isn't fully started
* Removed ProtocolLib as required dependency. You can use ProtocolSupport or BungeeCord as alternative
* Reduce the number of worker threads from 5 to 3 in ProtocolLib
* Process packets in ProtocolLib async/non-blocking -> better performance
* Fixed missing translation in commands
* Fixed cracked command not working on BungeeCord
* Fix error if forward skins is disabled
### 1.5.2
* Fixed BungeeCord force logins if there is a lobby server
* Removed cache expire in BungeeCord
* Applies skin earlier to make it visible for other plugins listening on login events
### 1.5.1
* Fixed BungeeCord support by correctly saving the proxy ids
######1.5
### 1.5
* Added localization
* Fixed NPE on premium name check if it's pure cracked player
* Fixed NPE in BungeeCord on cracked login for existing players
* Fixed saving of existing cracked players
######1.4
### 1.4
* Added Bungee setAuthPlugin method
* Added nameChangeCheck
* Multiple BungeeCord support
######1.3.1
### 1.3.1
* Prevent thread create violation in BungeeCord
######1.3
### 1.3
* Added support for AuthMe 3.X
* Fixed premium logins if the server is not fully started
@@ -28,11 +141,11 @@
* Fixed 1.7 Minecraft support by removing guava 11+ only features -> Cauldron support
* Fixed BungeeCord support in Cauldron
######1.2.1
### 1.2.1
* Fix premium status change notification message on BungeeCord
######1.2
### 1.2
* Fix race condition in BungeeCord
* Fix dead lock in xAuth
@@ -40,20 +153,20 @@
* Added API methods for plugins to set their own auth plugin hook
=> Added support for AdvancedLogin
######1.1
### 1.1
* Make the configuration options also work under BungeeCord (premiumUUID, forwardSkin)
* Catch configuration loading exception if it's not spigot build
* Fix config loading for older Spigot builds
######1.0
### 1.0
* Massive refactor to handle errors on force actions safely
* force Methods now runs async too
* force methods now returns a boolean to reflect if the method was successful
* isRegistered method should now throw an exception if the plugin was unable to query the requested data
######0.8
### 0.8
* Fixed BungeeCord support for the Bukkit module
* Added database storage to save the premium state
@@ -61,7 +174,7 @@
* Fixed issues with host lookup from hosts file (Thanks to @NorbiPeti)
* Remove handshake listener because it creates errors on some systems
######0.7
### 0.7
* Added BungeeAuth support
* Added /premium [player] command with optional player parameter
@@ -72,69 +185,69 @@
* Removes the need of an Bukkit auth plugin if you use a bungeecord one
* Optimize performance and thread-safety
* Fixed BungeeCord support
* Changed config option autologin to autoregister to clarify the usage
* Changed config option auto-login to auto-register to clarify the usage
######0.6
### 0.6
* Fixed 1.9 bugs
* Added UltraAuth support
######0.5
### 0.5
* Added unpremium command
* Added autologin - See config
* Added cracked command
* Added auto-login - See config
* Added config
* Added isRegistered API method
* Added forceRegister API method
* Fixed CrazyLogin player data restore -> Fixes memory leaks with this plugin
* Fixed premium name check to protocolsupport
* Fixed premium name check to ProtocolSupport
* Improved permissions management
######0.4
### 0.4
* Added forward premium skin
* Added plugin support for protocolsupport
* Added plugin support for ProtocolSupport
######0.3.2
### 0.3.2
* Run packet readers in a different thread (separated from the Netty I/O Thread)
-> Improves performance
* Fixed Plugin disable if the server is in online mode but have to be in offline mode
######0.3.1
### 0.3.1
* Improved BungeeCord security
#####0.3
### 0.3
* Added BungeeCord support
* Decrease timeout checks in order to fail faster on connection problems
* Code style improvements
######0.2.4
### 0.2.4
* Fixed NPE on invalid sessions
* Improved security by generating a randomized serverId
* Removed /premium [player] because it's safer for premium players who join without registration
######0.2.3
### 0.2.3
* Remove useless AuthMe forcelogin code
* Remove useless AuthMe force-login code
* Send a kick message to the client instead of just "Disconnect"
* Reformat source code
* Fix thread safety for fake start packets (Bukkit.getOfflinePlayer doesn't look like to be thread-safe)
* Added more documentation
######0.2.2
### 0.2.2
* Compile project with Java 7 :(
######0.2.1
### 0.2.1
* A couple of security fixes (premium players cannot longer steal the account of a cracked account)
* Added a /premium command to mark you as premium player
#####0.2
### 0.2
* Added support for CrazyLogin and LoginSecurity
* Now minecraft version independent
@@ -143,5 +256,5 @@
* More state validation
* Added better error handling
#####0.1
### 0.1
* First release

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015
Copyright (c) 2015-2018
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

215
README.md
View File

@@ -1,12 +1,9 @@
# FastLogin
[![Build Status](https://travis-ci.org/games647/FastLogin.svg?branch=master)](https://travis-ci.org/games647/FastLogin)
[![Donate Button](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8ZBULMAPN7MZC)
Checks if a Minecraft player has a paid account (premium). If so, they can skip offline authentication (auth plugins).
So they don't need to enter passwords. This is also called auto login (auto-login).
###Features:
## Features
* Detect paid accounts from others
* Automatically login paid accounts (premium)
@@ -14,166 +11,114 @@ So they don't need to enter passwords. This is also called auto login (auto-logi
* Cauldron support
* Forge/Sponge message support
* Premium UUID support
* Forwards Skins
* Forward skins
* Detect user name changed and will update the existing database record
* BungeeCord support
* Auto register new premium players
* Plugin: ProtocolSupport is supported and can be used as an alternative to ProtocolLib
* No client modifications needed
* Good performance by using async non blocking operations
* Good performance by using async operations
* Locale messages
* Free
* Open source
* Support for Bedrock players proxied through FloodGate
## Issues
Please use issues for bug reports, suggestions, questions and more. Please check for existing issues. Existing issues
can be voted up by adding up vote to the original post. Closing issues means that they are marked as resolved. Comments
are still allowed and it could be re-opened.
## Development builds
Development builds of this project can be acquired at the provided CI (continuous integration) server. It contains the
latest changes from the Source-Code in preparation for the following release. This means they could contain new
features, bug fixes and other changes since the last release.
They **could** contain new bugs and are likely to be less stable than released versions.
Specific builds can be grabbed by clicking on the build number on the left side or by clicking on status to retrieve the
latest build.
https://ci.codemc.org/job/Games647/job/FastLogin/changes
***
###Commands:
* /premium [player] Label the invoker or the argument as paid account
* /cracked [player] Label the invoker or the argument as cracked account
## Commands
###Permissions:
* fastlogin.bukkit.command.premium
* fastlogin.bukkit.command.cracked
/premium [player] Label the invoker or the argument as paid account
/cracked [player] Label the invoker or the argument as cracked account
###Requirements:
* Plugin: [ProtocolLib](http://www.spigotmc.org/resources/protocollib.1997/) or [ProtocolSupport](http://www.spigotmc.org/resources/protocolsupport.7201/)
* Tested Bukkit/[Spigot](https://www.spigotmc.org) 1.9 (could also work with other versions)
* Java 7+
* Run Spigot and/or BungeeCord/Waterfall in offline mode (see server.properties or config.yml)
* An auth plugin. Supported plugins
## Permissions
####Bukkit/Spigot/PaperSpigot
fastlogin.bukkit.command.premium
fastlogin.bukkit.command.cracked
fastlogin.command.premium.other
fastlogin.command.cracked.other
## Placeholder
This plugin supports `PlaceholderAPI` on `Spigot`. It exports the following variable
`%fastlogin_status%`. In BungeeCord environments, the status of a player will be delivered with a delay after the player
already successful joined the server. This takes about a couple of milliseconds. In this case the value
will be `Unknown`.
Possible values: `Premium`, `Cracked`, `Unknown`
## Requirements
* Plugin:
* [ProtocolLib](https://www.spigotmc.org/resources/protocollib.1997/) or
* [ProtocolSupport](https://www.spigotmc.org/resources/protocolsupport.7201/)
* [Spigot](https://www.spigotmc.org) 1.8.8+
* Java 8+
* Run Spigot (or a fork e.g. Paper) and/or BungeeCord (or a fork e.g. Waterfall) in offline mode
* An auth plugin.
### Supported auth plugins
#### Spigot/Paper
* [AuthMe (both 5.X and 3.X)](http://dev.bukkit.org/bukkit-plugins/authme-reloaded/)
* [xAuth](http://dev.bukkit.org/bukkit-plugins/xauth/)
* [LogIt](https://github.com/XziomekX/LogIt)
* [AdvancedLogin (Paid)](https://www.spigotmc.org/resources/advancedlogin.10510/)
* [CrazyLogin](http://dev.bukkit.org/bukkit-plugins/crazylogin/)
* [LoginSecurity](http://dev.bukkit.org/bukkit-plugins/loginsecurity/)
* [RoyalAuth](http://dev.bukkit.org/bukkit-plugins/royalauth/)
* [UltraAuth](http://dev.bukkit.org/bukkit-plugins/ultraauth-aa/)
* [AuthMe (5.X)](https://dev.bukkit.org/bukkit-plugins/authme-reloaded/)
* [CrazyLogin](https://dev.bukkit.org/bukkit-plugins/crazylogin/)
* [LoginSecurity](https://dev.bukkit.org/bukkit-plugins/loginsecurity/)
* [LogIt](https://github.com/games647/LogIt)
* [SodionAuth (2.0+)](https://github.com/Mohist-Community/SodionAuth)
* [UltraAuth](https://dev.bukkit.org/bukkit-plugins/ultraauth-aa/)
* [UserLogin](https://www.spigotmc.org/resources/userlogin.80669/)
* [xAuth](https://dev.bukkit.org/bukkit-plugins/xauth/)
####BungeeCord/Waterfall
#### BungeeCord/Waterfall
* [BungeeAuth](https://www.spigotmc.org/resources/bungeeauth.493/)
* [BungeeAuthenticator](https://www.spigotmc.org/resources/bungeecordauthenticator.87669/)
###Downloads
## Network requests
https://www.spigotmc.org/resources/fastlogin.14153/history
This plugin performs network requests to:
* https://api.mojang.com - retrieving uuid data to decide if we should activate premium login
* https://sessionserver.mojang.com - verify if the player is the owner of that account
***
###How to install
## How to install
####Bukkit/Spigot/PaperSpigot
### Spigot/Paper
1. Download and install ProtocolLib
2. Download and install FastLogin
1. Download and install ProtocolLib/ProtocolSupport
2. Download and install FastLogin (or FastLoginBukkit for newer versions)
3. Set your server in offline mode by setting the value onlinemode in your server.properties to false
####BungeeCord/Waterfall
### BungeeCord/Waterfall
1. Activate BungeeCord in the Spigot configuration
2. Restart your server
3. Now there is proxy-whitelist file in the FastLogin folder
3. Now there is `allowed-proxies.txt` file in the FastLogin folder
Put your stats id from the BungeeCord config into this file
4. Activate ipForward in your BungeeCord config
5. Download and Install FastLogin on BungeeCord AND Spigot
5. Download and Install FastLogin (or FastLoginBungee in newer versions) on BungeeCord AND Spigot
(on the servers where your login plugin is or where player should be able to execute the commands of FastLogin)
6. Check your database settings in the config of FastLogin on BungeeCord
7. Set your proxy (BungeeCord) in offline mode by setting the value onlinemode in your config.yml to false
8. (BungeeCord doesn't support SQLite per default, so you should change the configuration to MySQL or MariaDB)
***
###FAQ
####Index
1. [How does Minecraft logins work?](#how-does-minecraft-logins-work)
2. [How does this plugin work?](#how-does-this-plugin-work)
3. [Why does the plugin require offline mode?](#why-does-the-plugin-require-offline-mode)
4. [Can cracked player join with premium usernames?](#can-cracked-player-join-with-premium-usernames)
5. [Why do players have to invoke a command?](#why-do-players-have-to-invoke-a-command)
6. [What happens if a paid account joins with a used username?](#what-happens-if-a-paid-account-joins-with-a-used-username)
7. [Does the plugin have BungeeCord support?](#does-the-plugin-have-bungeecord-support)
8. [Could premium players have a premium UUID and Skin?](#could-premium-players-have-a-premium-uuid-and-skin)
9. [Is this plugin compatible with Cauldron?](#is-this-plugin-compatible-with-cauldron)
####How does minecraft logins work?
######Online Mode
1. Client -> Server: I want to login, here is my username
2. Server -> Client: Okay. I'm in online mode so here is my public key for encryption and my serverid
3. Client -> Mojang: I'm player "xyz". I want to join a server with that serverid
4. Mojang -> Client: Session data checked. You can continue
5. Client -> Server: I received a successful response from Mojang. Heres our shared secret key
6. Server -> Mojang: Does the player "xyz" with this shared secret key has a valid account to join me?
7. Mojang -> Server: Yes, the player has the following additionally properties (UUID, Skin)
8. Client and Server: encrypt all following communication packet
9. Server -> Client: Everything checked you can play now
######Offline Mode
In offline mode step 2-7 is skipped. So a login request is directly followed by 8.
######More details
http://wiki.vg/Protocol#Login
####How does this plugin work?
By using ProtocolLib, this plugin works as a proxy between the client and server. This plugin will fake that the server
runs in online mode. It does everything an online mode server would do. This will be for example, generating keys or
checking for valid sessions. Because everything is the same compared to an offline mode login after an encrypted
connection, we will intercept only **login** packets of **premium** players.
1. Player is connecting to the server.
2. Plugin checks if the username we received activated the fast login method (i.e. using command)
3. Run a check if the username is currently used by a paid account.
(We don't know yet if the client connecting is premium)
4. Request an Mojang Session Server authentication
5. On response check if all data is correct
6. Encrypt the connection
7. On success intercept all related login packets and fake a new login packet as a normal offline login
####Why does the plugin require offline mode?
1. As you can see in the question "how does minecraft login works", offline mode is equivalent to online mode except of
the encryption and session checks on login. So we can intercept and cancel the first packets for premium players and
enable an encrypted connection. Then we send a new fake packet in order to pretend that this a new login request from
a offline mode player. The server will handle the rest.
2. Some plugins check if the server is in online mode. If so, they could process the real offline (cracked) accounts
incorrectly. For example, a plugin tries to fetch the UUID from Mojang, but the name of the player is not associated to
a paid account.
3. Servers, who allow cracked players and just speed up logins for premium players, are **already** in offline mode.
####Can cracked player join with premium usernames?
Yes, indeed. Therefore the command for toggling the fast login method exists.
####Why do players have to invoke a command?
1. It's a secure way to make sure a person with a paid account cannot steal the account
of a cracked player that has the same username. The player have to proof first that it's his own account.
2. We only receive the username from the player on login. We could check if that username is associated
to a paid account but if we request a online mode login from a cracked player (who uses a username from
a paid account), the player will disconnect with the reason "bad login" or "Invalid session". There is no way to change
that message on the server side (without client modifications), because it's a connection between the Client and the
Sessionserver.
3. If a premium player would skip registration too, a player of a cracked account could later still register the
account and would claim and steal the account from the premium player. Because commands cannot be invoked unless the
player has a account or is logged in, protects this method also premium players
###What happens if a paid account joins with a used username?
The player on the server have to activate the feature of this plugin by command. If a person buys the username
of his own account, it's still secured. A normal offline mode login makes sure he's the owner of the server account
and Mojang account. Then the command can be executed. So someone different cannot steal the account of cracked player
by buying the username.
####Does the plugin have BungeeCord support?
Yes it has. See the how to install above.
####Could premium players have a premium UUID and Skin?
Since 0.7 both features are implemented. You can check the config.yml in order to activate it.
####Is this plugin compatible with Cauldron?
It's not tested yet, but all needed methods also exists in Cauldron so it could work together.
***
###Useful Links:
* [Login Protocol](http://wiki.vg/Protocol#Login)
* [Protocol Encryption](http://wiki.vg/Protocol_Encryption)
7. Set proxy and Spigot in offline mode by setting the value onlinemode in your config.yml to false
8. You should *always* firewall your Spigot server that it's only accessible through BungeeCord
* https://www.spigotmc.org/wiki/bungeecord-installation/#post-installation
* BungeeCord doesn't support SQLite per default, so you should change the configuration to MySQL or MariaDB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,11 +1,11 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.games647</groupId>
<artifactId>fastlogin</artifactId>
<version>1.5.1</version>
<version>1.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -15,80 +15,153 @@
<name>FastLoginBukkit</name>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<shadedArtifactAttached>false</shadedArtifactAttached>
<relocations>
<relocation>
<pattern>com.zaxxer.hikari</pattern>
<shadedPattern>fastlogin.hikari</shadedPattern>
</relocation>
<relocation>
<pattern>org.slf4j</pattern>
<shadedPattern>fastlogin.slf4j</shadedPattern>
</relocation>
<relocation>
<pattern>net.md_5.bungee.config</pattern>
<shadedPattern>fastlogin.config</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.gson</pattern>
<shadedPattern>fastlogin.gson</shadedPattern>
</relocation>
<relocation>
<pattern>io.papermc.lib</pattern>
<shadedPattern>fastlogin.paperlib</shadedPattern>
</relocation>
</relocations>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<!--Bukkit-Server-API -->
<!-- PaperSpigot API and PaperLib -->
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
<id>papermc</id>
<url>https://papermc.io/repo/repository/maven-public/</url>
</repository>
<!--ProtocolLib-->
<!-- ProtocolLib -->
<repository>
<id>dmulloy2-repo</id>
<url>http://repo.dmulloy2.net/content/groups/public/</url>
<url>https://repo.dmulloy2.net/nexus/repository/public/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<!--Authme Reloaded-->
<!-- AuthMe Reloaded, xAuth and LoginSecurity -->
<repository>
<id>xephi-repo</id>
<url>http://ci.xephi.fr/plugin/repository/everything/</url>
<id>codemc-releases</id>
<url>https://repo.codemc.io/repository/maven-public/</url>
</repository>
<!--xAuth-->
<repository>
<id>luricos.de-repo</id>
<url>http://repo.luricos.de/bukkit-plugins/</url>
</repository>
<repository>
<id>logit-only-repo</id>
<url>http://ci.ac3-servers.eu/job/LogIt-Classic/2/maven-repository/repository/</url>
</repository>
<!--Github automatic maven builds-->
<!-- GitHub automatic maven builds -->
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<!-- PlaceholderAPI -->
<repository>
<id>placeholderapi</id>
<url>https://repo.extendedclip.com/content/repositories/placeholderapi</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<!--Common plugin component-->
<dependency>
<groupId>com.github.games647</groupId>
<artifactId>fastlogin.core</artifactId>
<version>${project.version}</version>
</dependency>
<!-- PaperSpigot API for correcting usercache usage -->
<dependency>
<groupId>com.destroystokyo.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.15.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!--Server API-->
<!-- PaperLib for checking if server uses PaperSpigot -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.9-R0.1-SNAPSHOT</version>
<scope>provided</scope>
<groupId>io.papermc</groupId>
<artifactId>paperlib</artifactId>
<version>1.0.6</version>
<scope>compile</scope>
</dependency>
<!--Library for listening and sending Minecraft packets-->
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>4.0.1</version>
<optional>true</optional>
<version>4.6.0</version>
<scope>provided</scope>
</dependency>
<!--Changing onlinemode on login process-->
<dependency>
<groupId>protcolsupport</groupId>
<groupId>com.github.ProtocolSupport</groupId>
<artifactId>ProtocolSupport</artifactId>
<version>Build-337</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/ProtocolSupport b337.jar</systemPath>
<!--4.29.dev after commit about API improvements-->
<version>3a80c661fe</version>
<scope>provided</scope>
</dependency>
<!--Provide premium placeholders-->
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
<version>2.10.8</version>
<scope>provided</scope>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--Login Plugins-->
<dependency>
<groupId>fr.xephi</groupId>
<artifactId>authme</artifactId>
<version>5.2-SNAPSHOT</version>
<version>5.4.0</version>
<scope>provided</scope>
<optional>true</optional>
<exclusions>
<exclusion>
@@ -99,10 +172,11 @@
</dependency>
<dependency>
<groupId>com.github.lenis0012</groupId>
<artifactId>LoginSecurity-2</artifactId>
<!--Old version 2.0-->
<version>-9c09e73b7f-1</version>
<groupId>com.lenis0012.bukkit</groupId>
<artifactId>loginsecurity</artifactId>
<version>3.0.2</version>
<scope>provided</scope>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>*</groupId>
@@ -112,21 +186,11 @@
</dependency>
<dependency>
<groupId>io.github.lucaseasedup.logit</groupId>
<groupId>com.github.games647</groupId>
<artifactId>LogIt</artifactId>
<version>SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.RoyalDev</groupId>
<artifactId>RoyalAuth</artifactId>
<version>-e21354a9b7-1</version>
<version>9e3581db27</version>
<scope>provided</scope>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>*</groupId>
@@ -139,6 +203,7 @@
<groupId>de.luricos.bukkit</groupId>
<artifactId>xAuth</artifactId>
<version>2.6</version>
<scope>provided</scope>
<optional>true</optional>
<!--These artifacts produce conflicts on downloading-->
<exclusions>
@@ -174,7 +239,21 @@
<version>2.0.2</version>
<optional>true</optional>
<scope>system</scope>
<systemPath>${project.basedir}/lib/UltraAuth v2.0.2.jar</systemPath>
<systemPath>${project.basedir}/lib/UltraAuth v2.1.2.jar</systemPath>
</dependency>
<dependency>
<groupId>com.github.Mohist-Community.SodionAuth</groupId>
<artifactId>SodionAuth-Bukkit</artifactId>
<version>b74392aa34</version>
<exclusions>
<exclusion>
<groupId>com.github.Mohist-Community.SodionAuth</groupId>
<artifactId>SodionAuth-Libs</artifactId>
</exclusion>
</exclusions>
<optional>true</optional>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,59 +0,0 @@
package com.github.games647.fastlogin.bukkit;
import com.github.games647.fastlogin.core.FastLoginCore;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.File;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Logger;
import org.bukkit.ChatColor;
import org.bukkit.configuration.file.YamlConfiguration;
public class BukkitCore extends FastLoginCore {
private final FastLoginBukkit plugin;
public BukkitCore(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@Override
public File getDataFolder() {
return plugin.getDataFolder();
}
@Override
public Logger getLogger() {
return plugin.getLogger();
}
@Override
public ThreadFactory getThreadFactory() {
String pluginName = plugin.getName();
return new ThreadFactoryBuilder()
.setNameFormat(pluginName + " Database Pool Thread #%1$d")
//Hikari create daemons by default
.setDaemon(true)
.build();
}
@Override
public void loadMessages() {
plugin.saveResource("messages.yml", false);
File messageFile = new File(plugin.getDataFolder(), "messages.yml");
YamlConfiguration messageConfig = YamlConfiguration.loadConfiguration(messageFile);
for (String key : messageConfig.getKeys(false)) {
String message = ChatColor.translateAlternateColorCodes('&', messageConfig.getString(key));
if (!message.isEmpty()) {
localeMessages.put(key, message);
}
}
}
@Override
public void loadConfig() {
plugin.saveDefaultConfig();
}
}

View File

@@ -1,120 +0,0 @@
package com.github.games647.fastlogin.bukkit;
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
import com.github.games647.fastlogin.core.LoginSession;
import com.github.games647.fastlogin.core.PlayerProfile;
import java.util.UUID;
import org.apache.commons.lang.ArrayUtils;
/**
* Represents a client connecting to the server.
*
* This session is invalid if the player disconnects or the login was successful
*/
public class BukkitLoginSession extends LoginSession {
private final String serverId;
private final byte[] verifyToken;
private UUID uuid;
private WrappedSignedProperty skinProperty;
private boolean verified;
public BukkitLoginSession(String username, String serverId, byte[] verifyToken, boolean registered
, PlayerProfile profile) {
super(username, registered, profile);
this.serverId = serverId;
this.verifyToken = ArrayUtils.clone(verifyToken);
}
//available for bungeecord
public BukkitLoginSession(String username, boolean registered) {
this(username, "", ArrayUtils.EMPTY_BYTE_ARRAY, registered, null);
}
//cracked player
public BukkitLoginSession(String username, PlayerProfile profile) {
this(username, "", ArrayUtils.EMPTY_BYTE_ARRAY, false, profile);
}
/**
* Gets the random generated server id. This makes sure the request sent from the client is just for this server.
*
* See this for details http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
*
* Empty if it's a BungeeCord connection
*
* @return random generated server id
*/
public String getServerId() {
return serverId;
}
/**
* Gets the verify token the server sent to the client.
*
* Empty if it's a BungeeCord connection
*
* @return the verify token from the server
*/
public byte[] getVerifyToken() {
return ArrayUtils.clone(verifyToken);
}
/**
* Gets the premium skin of this player
*
* @return skin property or null if the player has no skin or is a cracked account
*/
public synchronized WrappedSignedProperty getSkin() {
return this.skinProperty;
}
/**
* Sets the premium skin property which was retrieved by the session server
*
* @param skinProperty premium skin property
*/
public synchronized void setSkin(WrappedSignedProperty skinProperty) {
this.skinProperty = skinProperty;
}
/**
* Sets whether the player has a premium (paid account) account and valid session
*
* @param verified whether the player has valid session
*/
public synchronized void setVerified(boolean verified) {
this.verified = verified;
}
/**
* Get the premium UUID of this player
*
* @return the premium UUID or null if not fetched
*/
public synchronized UUID getUuid() {
return uuid;
}
/**
* Set the online UUID if it's fetched
*
* @param uuid premium UUID
*/
public synchronized void setUuid(UUID uuid) {
this.uuid = uuid;
}
/**
* Get whether the player has a premium (paid account) account and valid session
*
* @return whether the player has a valid session
*/
public synchronized boolean isVerified() {
return verified;
}
}

View File

@@ -0,0 +1,27 @@
package com.github.games647.fastlogin.bukkit;
import com.github.games647.fastlogin.core.AsyncScheduler;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadFactory;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.slf4j.Logger;
public class BukkitScheduler extends AsyncScheduler {
private final Plugin plugin;
private final Executor syncExecutor;
public BukkitScheduler(Plugin plugin, Logger logger, ThreadFactory threadFactory) {
super(logger, threadFactory);
this.plugin = plugin;
syncExecutor = r -> Bukkit.getScheduler().runTask(plugin, r);
}
public Executor getSyncExecutor() {
return syncExecutor;
}
}

View File

@@ -0,0 +1,24 @@
package com.github.games647.fastlogin.bukkit;
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
import com.github.games647.fastlogin.core.SessionManager;
import java.net.InetSocketAddress;
import java.util.UUID;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
public class BukkitSessionManager extends SessionManager<PlayerQuitEvent, InetSocketAddress, BukkitLoginSession>
implements Listener {
@EventHandler
@Override
public void onPlayQuit(PlayerQuitEvent quitEvent) {
Player player = quitEvent.getPlayer();
UUID playerId = player.getUniqueId();
endPlaySession(playerId);
}
}

View File

@@ -1,12 +0,0 @@
package com.github.games647.fastlogin.bukkit;
import org.apache.commons.lang.RandomStringUtils;
import org.bukkit.entity.Player;
public class DefaultPasswordGenerator implements PasswordGenerator {
@Override
public String getRandomPassword(Player player) {
return RandomStringUtils.random(8, true, true);
}
}

View File

@@ -1,123 +0,0 @@
package com.github.games647.fastlogin.bukkit;
import com.google.common.base.Charsets;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* Encryption and decryption minecraft util for connection between servers
* and paid minecraft account clients.
*
* Source: https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/MinecraftEncryption.java
*
* Remapped by: https://github.com/Techcable/MinecraftMappings/tree/master/1.8
*/
public class EncryptionUtil {
public static KeyPair generateKeyPair() {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1_024);
return keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException nosuchalgorithmexception) {
//Should be existing in every vm
throw new ExceptionInInitializerError(nosuchalgorithmexception);
}
}
public static byte[] getServerIdHash(String serverId, PublicKey publicKey, SecretKey secretKey) {
return digestOperation("SHA-1"
, new byte[][]{serverId.getBytes(Charsets.ISO_8859_1), secretKey.getEncoded(), publicKey.getEncoded()});
}
private static byte[] digestOperation(String algo, byte[]... content) {
try {
MessageDigest messagedigest = MessageDigest.getInstance(algo);
for (byte[] data : content) {
messagedigest.update(data);
}
return messagedigest.digest();
} catch (NoSuchAlgorithmException nosuchalgorithmexception) {
nosuchalgorithmexception.printStackTrace();
return null;
}
}
// public static PublicKey decodePublicKey(byte[] encodedKey) {
// try {
// KeyFactory keyfactory = KeyFactory.getInstance("RSA");
//
// X509EncodedKeySpec x509encodedkeyspec = new X509EncodedKeySpec(encodedKey);
// return keyfactory.generatePublic(x509encodedkeyspec);
// } catch (NoSuchAlgorithmException | InvalidKeySpecException nosuchalgorithmexception) {
// //ignore
// }
//
// System.err.println("Public key reconstitute failed!");
// return null;
// }
public static SecretKey decryptSharedKey(PrivateKey privateKey, byte[] encryptedSharedKey) {
return new SecretKeySpec(decryptData(privateKey, encryptedSharedKey), "AES");
}
public static byte[] decryptData(Key key, byte[] data) {
return cipherOperation(Cipher.DECRYPT_MODE, key, data);
}
private static byte[] cipherOperation(int operationMode, Key key, byte[] data) {
try {
return createCipherInstance(operationMode, key.getAlgorithm(), key).doFinal(data);
} catch (IllegalBlockSizeException | BadPaddingException illegalblocksizeexception) {
illegalblocksizeexception.printStackTrace();
}
System.err.println("Cipher data failed!");
return null;
}
private static Cipher createCipherInstance(int operationMode, String cipherName, Key key) {
try {
Cipher cipher = Cipher.getInstance(cipherName);
cipher.init(operationMode, key);
return cipher;
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException invalidkeyexception) {
invalidkeyexception.printStackTrace();
}
System.err.println("Cipher creation failed!");
return null;
}
//
// public static Cipher createBufferedBlockCipher(int operationMode, Key key) {
// try {
// Cipher cipher = Cipher.getInstance("AES/CFB8/NoPadding");
//
// cipher.init(operationMode, key, new IvParameterSpec(key.getEncoded()));
// return cipher;
// } catch (GeneralSecurityException generalsecurityexception) {
// throw new RuntimeException(generalsecurityexception);
// }
// }
private EncryptionUtil() {
//utility
}
}

View File

@@ -1,217 +1,172 @@
package com.github.games647.fastlogin.bukkit;
import com.github.games647.fastlogin.bukkit.tasks.DelayedAuthHook;
import com.avaje.ebeaninternal.api.ClassUtil;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.utility.SafeCacheBuilder;
import com.github.games647.fastlogin.bukkit.commands.CrackedCommand;
import com.github.games647.fastlogin.bukkit.commands.PremiumCommand;
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
import com.github.games647.fastlogin.bukkit.listener.BukkitJoinListener;
import com.github.games647.fastlogin.bukkit.listener.BungeeCordListener;
import com.github.games647.fastlogin.bukkit.listener.ProtocolSupportListener;
import com.github.games647.fastlogin.bukkit.listener.packet.EncryptionPacketListener;
import com.github.games647.fastlogin.bukkit.listener.packet.StartPacketListener;
import com.github.games647.fastlogin.core.FastLoginCore;
import com.google.common.cache.CacheLoader;
import com.github.games647.fastlogin.bukkit.auth.proxy.ProxyManager;
import com.github.games647.fastlogin.bukkit.auth.protocollib.ProtocolLibListener;
import com.github.games647.fastlogin.bukkit.auth.protocolsupport.ProtocolSupportListener;
import com.github.games647.fastlogin.bukkit.command.CrackedCommand;
import com.github.games647.fastlogin.bukkit.command.PremiumCommand;
import com.github.games647.fastlogin.bukkit.hook.DelayedAuthHook;
import com.github.games647.fastlogin.bukkit.listener.ConnectionListener;
import com.github.games647.fastlogin.bukkit.listener.PaperPreLoginListener;
import com.github.games647.fastlogin.core.CommonUtil;
import com.github.games647.fastlogin.core.PremiumStatus;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.PlatformPlugin;
import java.security.KeyPair;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import io.papermc.lib.PaperLib;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.slf4j.Logger;
/**
* This plugin checks if a player has a paid account and if so tries to skip offline mode authentication.
*/
public class FastLoginBukkit extends JavaPlugin {
public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<CommandSender> {
private static final int WORKER_THREADS = 5;
private final BukkitSessionManager sessionManager = new BukkitSessionManager();
private final Map<UUID, PremiumStatus> premiumPlayers = new ConcurrentHashMap<>();
private final FastLoginCore<Player, CommandSender, FastLoginBukkit> core = new FastLoginCore<>(this);
//provide a immutable key pair to be thread safe | used for encrypting and decrypting traffic
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
private final Logger logger;
private final BukkitScheduler scheduler;
private boolean bungeeCord;
private final FastLoginCore core = new BukkitCore(this);
private boolean serverStarted;
private ProxyManager proxyManager;
//this map is thread-safe for async access (Packet Listener)
//SafeCacheBuilder is used in order to be version independent
private final ConcurrentMap<String, BukkitLoginSession> session = SafeCacheBuilder.<String, BukkitLoginSession>newBuilder()
//2 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang)
.expireAfterWrite(1, TimeUnit.MINUTES)
//mapped by ip:port -> PlayerSession
.build(new CacheLoader<String, BukkitLoginSession>() {
private PremiumPlaceholder premiumPlaceholder;
@Override
public BukkitLoginSession load(String key) throws Exception {
//A key should be inserted manually on start packet
throw new UnsupportedOperationException("Not supported");
}
});
public FastLoginBukkit() {
this.logger = CommonUtil.createLoggerFromJDK(getLogger());
this.scheduler = new BukkitScheduler(this, logger, getThreadFactory());
}
private BukkitAuthPlugin authPlugin;
private PasswordGenerator passwordGenerator = new DefaultPasswordGenerator();
@Override
public void onEnable() {
core.setMojangApiConnector(new MojangApiBukkit(core));
core.loadConfig();
core.loadMessages();
try {
if (ClassUtil.isPresent("org.spigotmc.SpigotConfig")) {
bungeeCord = Class.forName("org.spigotmc.SpigotConfig").getDeclaredField("bungee").getBoolean(null);
}
} catch (Exception | NoSuchMethodError ex) {
getLogger().warning("Cannot check bungeecord support. You use a non-spigot build");
ex.printStackTrace();
}
core.load();
if (getServer().getOnlineMode()) {
//we need to require offline to prevent a session request for a offline player
getLogger().severe("Server have to be in offline mode");
//we need to require offline to prevent a loginSession request for a offline player
logger.error("Server has to be in offline mode");
setEnabled(false);
return;
}
if (bungeeCord) {
setServerStarted();
//check for incoming messages from the bungeecord version of this plugin
getServer().getMessenger().registerIncomingPluginChannel(this, getName(), new BungeeCordListener(this));
getServer().getMessenger().registerOutgoingPluginChannel(this, getName());
//register listeners on success
} else {
String driver = getConfig().getString("driver");
String host = getConfig().getString("host", "");
int port = getConfig().getInt("port", 3306);
String database = getConfig().getString("database");
String username = getConfig().getString("username", "");
String password = getConfig().getString("password", "");
if (!core.setupDatabase(driver, host, port, database, username, password)) {
proxyManager = new ProxyManager(this);
proxyManager.initialize();
PluginManager pluginManager = getServer().getPluginManager();
if (!proxyManager.isEnabled()) {
if (!core.setupDatabase()) {
setEnabled(false);
return;
}
if (getServer().getPluginManager().isPluginEnabled("ProtocolSupport")) {
getServer().getPluginManager().registerEvents(new ProtocolSupportListener(this), this);
if (pluginManager.isPluginEnabled("ProtocolSupport")) {
pluginManager.registerEvents(new ProtocolSupportListener(this, core.getRateLimiter()), this);
} else if (pluginManager.isPluginEnabled("ProtocolLib")) {
ProtocolLibListener.register(this, core.getRateLimiter());
} else {
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
//we are performing HTTP request on these so run it async (seperate from the Netty IO threads)
AsynchronousManager asynchronousManager = protocolManager.getAsynchronousManager();
StartPacketListener startPacketListener = new StartPacketListener(this, protocolManager);
EncryptionPacketListener encryptionPacketListener = new EncryptionPacketListener(this, protocolManager);
asynchronousManager.registerAsyncHandler(startPacketListener).start(WORKER_THREADS);
asynchronousManager.registerAsyncHandler(encryptionPacketListener).start(WORKER_THREADS);
logger.warn("Either ProtocolLib or ProtocolSupport have to be installed if you don't use proxies");
}
}
//delay dependency setup because we load the plugin very early where plugins are initialized yet
getServer().getScheduler().runTask(this, new DelayedAuthHook(this));
getServer().getScheduler().runTaskLater(this, new DelayedAuthHook(this), 5L);
getServer().getPluginManager().registerEvents(new BukkitJoinListener(this), this);
pluginManager.registerEvents(new ConnectionListener(this), this);
//if server is using paper - we need to add one more listener to correct the usercache usage
if (PaperLib.isPaper()) {
pluginManager.registerEvents(new PaperPreLoginListener(this), this);
}
//register commands using a unique name
getCommand("premium").setExecutor(new PremiumCommand(this));
getCommand("cracked").setExecutor(new CrackedCommand(this));
if (pluginManager.isPluginEnabled("PlaceholderAPI")) {
premiumPlaceholder = new PremiumPlaceholder(this);
premiumPlaceholder.register();
}
}
@Override
public void onDisable() {
//clean up
session.clear();
premiumPlayers.clear();
core.close();
//remove old blacklists
for (Player player : getServer().getOnlinePlayers()) {
player.removeMetadata(getName(), this);
}
if (core != null) {
core.close();
proxyManager.cleanup();
if (getServer().getPluginManager().isPluginEnabled("PlaceholderAPI") && premiumPlaceholder != null) {
premiumPlaceholder.unregister();
}
}
public FastLoginCore getCore() {
public FastLoginCore<Player, CommandSender, FastLoginBukkit> getCore() {
return core;
}
public String generateStringPassword(Player player) {
return passwordGenerator.getRandomPassword(player);
}
public void setPasswordGenerator(PasswordGenerator passwordGenerator) {
this.passwordGenerator = passwordGenerator;
/**
* Fetches the premium status of an online player.
*
* @param onlinePlayer
* @return the online status or unknown if an error happened, the player isn't online or a proxy doesn't send
* us the status message yet (This means you cannot check the login status on the PlayerJoinEvent).
* @deprecated this method could be removed in future versions and exists only as a temporarily solution
*/
@Deprecated
public PremiumStatus getStatus(UUID onlinePlayer) {
StoredProfile playSession = sessionManager.getPlaySession(onlinePlayer);
return Optional.ofNullable(playSession).map(profile -> {
if (profile.isPremium())
return PremiumStatus.PREMIUM;
return PremiumStatus.CRACKED;
}).orElse(PremiumStatus.UNKNOWN);
}
/**
* Gets a thread-safe map about players which are connecting to the server are being checked to be premium (paid
* account)
*
* @return a thread-safe session map
* @return a thread-safe loginSession map
*/
public ConcurrentMap<String, BukkitLoginSession> getSessions() {
return session;
public BukkitSessionManager getSessionManager() {
return sessionManager;
}
/**
* Gets the server KeyPair. This is used to encrypt or decrypt traffic between the client and server
*
* @return the server KeyPair
*/
public KeyPair getServerKey() {
return keyPair;
public Map<UUID, PremiumStatus> getPremiumPlayers() {
return premiumPlayers;
}
/**
* Gets the auth plugin hook in order to interact with the plugins. This can be null if no supporting auth plugin
* was found.
*
* @return interface to any supported auth plugin
*/
public BukkitAuthPlugin getAuthPlugin() {
if (authPlugin == null) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
getLogger().log(Level.SEVERE, null, ex);
}
}
return authPlugin;
public ProxyManager getProxyManager() {
return proxyManager;
}
public void setAuthPluginHook(BukkitAuthPlugin authPlugin) {
this.authPlugin = authPlugin;
@Override
public Path getPluginFolder() {
return getDataFolder().toPath();
}
public boolean isBungeeCord() {
return bungeeCord;
@Override
public Logger getLog() {
return logger;
}
/**
* Wait before the server is fully started. This is workaround, because connections right on startup are not
* injected by ProtocolLib
*
* @return
*/
public boolean isServerFullyStarted() {
return serverStarted;
@Override
public BukkitScheduler getScheduler() {
return scheduler;
}
public void setServerStarted() {
if (!this.serverStarted) {
this.serverStarted = true;
}
@Override
public void sendMessage(CommandSender receiver, String message) {
receiver.sendMessage(message);
}
}

View File

@@ -0,0 +1,83 @@
package com.github.games647.fastlogin.bukkit;
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginAutoLoginEvent;
import com.github.games647.fastlogin.core.PremiumStatus;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.message.SuccessMessage;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.auth.ForceLoginManagement;
import com.github.games647.fastlogin.core.auth.LoginSession;
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
import java.util.concurrent.ExecutionException;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.metadata.FixedMetadataValue;
public class ForceLoginTask extends ForceLoginManagement<Player, CommandSender, BukkitLoginSession, FastLoginBukkit> {
public ForceLoginTask(FastLoginCore<Player, CommandSender, FastLoginBukkit> core, Player player,
BukkitLoginSession session) {
super(core, player, session);
}
@Override
public void run() {
// block this target player for proxy ID brute force attacks
FastLoginBukkit plugin = core.getPlugin();
player.setMetadata(core.getPlugin().getName(), new FixedMetadataValue(plugin, true));
if (session != null && !session.getUsername().equals(player.getName())) {
String playerName = player.getName();
plugin.getLog().warn("Player username {} is not matching session {}", playerName, session.getUsername());
return;
}
super.run();
PremiumStatus status = PremiumStatus.CRACKED;
if (isOnlineMode()) {
status = PremiumStatus.PREMIUM;
}
plugin.getPremiumPlayers().put(player.getUniqueId(), status);
}
@Override
public FastLoginAutoLoginEvent callFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
BukkitFastLoginAutoLoginEvent event = new BukkitFastLoginAutoLoginEvent(session, profile);
core.getPlugin().getServer().getPluginManager().callEvent(event);
return event;
}
@Override
public void onForceActionSuccess(LoginSession session) {
if (core.getPlugin().getProxyManager().isEnabled()) {
core.getPlugin().getProxyManager().sendPluginMessage(player, new SuccessMessage());
}
}
@Override
public String getName(Player player) {
return player.getName();
}
@Override
public boolean isOnline(Player player) {
try {
//the player-list isn't thread-safe
return Bukkit.getScheduler().callSyncMethod(core.getPlugin(), player::isOnline).get();
} catch (InterruptedException | ExecutionException ex) {
core.getPlugin().getLog().error("Failed to perform thread-safe online check for {}", player, ex);
return false;
}
}
@Override
public boolean isOnlineMode() {
return session.isVerified();
}
}

View File

@@ -1,73 +0,0 @@
package com.github.games647.fastlogin.bukkit;
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
import com.github.games647.fastlogin.core.FastLoginCore;
import com.github.games647.fastlogin.core.MojangApiConnector;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.util.UUID;
import java.util.logging.Level;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
public class MojangApiBukkit extends MojangApiConnector {
//mojang api check to prove a player is logged in minecraft and made a join server request
private static final String HAS_JOINED_URL = "https://sessionserver.mojang.com/session/minecraft/hasJoined?";
public MojangApiBukkit(FastLoginCore plugin) {
super(plugin);
}
@Override
public boolean hasJoinedServer(Object session, String serverId) {
if (!(session instanceof BukkitLoginSession)) {
return false;
}
BukkitLoginSession playerSession = (BukkitLoginSession) session;
try {
String url = HAS_JOINED_URL + "username=" + playerSession.getUsername() + "&serverId=" + serverId;
HttpURLConnection conn = getConnection(url);
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line = reader.readLine();
if (line != null && !line.equals("null")) {
//validate parsing
//http://wiki.vg/Protocol_Encryption#Server
JSONObject userData = (JSONObject) JSONValue.parseWithException(line);
String uuid = (String) userData.get("id");
playerSession.setUuid(FastLoginCore.parseId(uuid));
JSONArray properties = (JSONArray) userData.get("properties");
JSONObject skinProperty = (JSONObject) properties.get(0);
String propertyName = (String) skinProperty.get("name");
if (propertyName.equals("textures")) {
String skinValue = (String) skinProperty.get("value");
String signature = (String) skinProperty.get("signature");
playerSession.setSkin(WrappedSignedProperty.fromValues(propertyName, skinValue, signature));
}
return true;
}
} catch (Exception ex) {
//catch not only ioexceptions also parse and NPE on unexpected json format
plugin.getLogger().log(Level.WARNING, "Failed to verify session", ex);
}
//this connection doesn't need to be closed. So can make use of keep alive in java
return false;
}
@Override
protected UUID getUUIDFromJson(String json) {
JSONObject userData = (JSONObject) JSONValue.parse(json);
String uuid = (String) userData.get("id");
return FastLoginCore.parseId(uuid);
}
}

View File

@@ -1,8 +0,0 @@
package com.github.games647.fastlogin.bukkit;
import org.bukkit.entity.Player;
public interface PasswordGenerator {
String getRandomPassword(Player player);
}

View File

@@ -0,0 +1,46 @@
package com.github.games647.fastlogin.bukkit;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.OfflinePlayer;
public class PremiumPlaceholder extends PlaceholderExpansion {
private static final String PLACEHOLDER_VARIABLE = "status";
private final FastLoginBukkit plugin;
public PremiumPlaceholder(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@Override
public String onRequest(OfflinePlayer player, String identifier) {
// player is null if offline
if (player != null && PLACEHOLDER_VARIABLE.equals(identifier)) {
return plugin.getStatus(player.getUniqueId()).getReadableName();
}
return null;
}
@Override
public String getIdentifier() {
return plugin.getName();
}
@Override
public String getRequiredPlugin() {
return plugin.getName();
}
@Override
public String getAuthor() {
return String.join(", ", plugin.getDescription().getAuthors());
}
@Override
public String getVersion() {
return plugin.getDescription().getVersion();
}
}

View File

@@ -0,0 +1,91 @@
package com.github.games647.fastlogin.bukkit.auth;
import com.github.games647.craftapi.model.skin.SkinProperty;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.LoginSession;
import java.util.Optional;
/**
* Represents a client connecting to the server.
*
* This session is invalid if the player disconnects or the login was successful
*/
public class BukkitLoginSession extends LoginSession {
private static final byte[] EMPTY_ARRAY = {};
private final String serverId;
private final byte[] verifyToken;
private boolean verified;
private SkinProperty skinProperty;
public BukkitLoginSession(String username, String serverId, byte[] verifyToken, boolean registered
, StoredProfile profile) {
super(username, registered, profile);
this.serverId = serverId;
this.verifyToken = verifyToken.clone();
}
// available for proxies
public BukkitLoginSession(String username, boolean registered) {
this(username, "", EMPTY_ARRAY, registered, null);
}
//cracked player
public BukkitLoginSession(String username, StoredProfile profile) {
this(username, "", EMPTY_ARRAY, false, profile);
}
//ProtocolSupport
public BukkitLoginSession(String username, boolean registered, StoredProfile profile) {
this(username, "", EMPTY_ARRAY, registered, profile);
}
/**
* Gets the verify token the server sent to the client.
*
* Empty if it's a proxy connection
*
* @return the verify token from the server
*/
public synchronized byte[] getVerifyToken() {
return verifyToken.clone();
}
/**
* @return premium skin if available
*/
public synchronized Optional<SkinProperty> getSkin() {
return Optional.ofNullable(skinProperty);
}
/**
* Sets the premium skin property which was retrieved by the session server
* @param skinProperty premium skin
*/
public synchronized void setSkinProperty(SkinProperty skinProperty) {
this.skinProperty = skinProperty;
}
/**
* Sets whether the player has a premium (paid account) account and valid session
*
* @param verified whether the player has valid session
*/
public synchronized void setVerified(boolean verified) {
this.verified = verified;
}
/**
* Get whether the player has a premium (paid account) account and valid session
*
* @return whether the player has a valid session
*/
public synchronized boolean isVerified() {
return verified;
}
}

View File

@@ -0,0 +1,131 @@
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* Encryption and decryption minecraft util for connection between servers
* and paid Minecraft account clients.
*
* @see net.minecraft.server.MinecraftEncryption
*/
public class EncryptionUtil {
public static final int VERIFY_TOKEN_LENGTH = 4;
public static final String KEY_PAIR_ALGORITHM = "RSA";
private EncryptionUtil() {
// utility
}
/**
* Generate a RSA key pair
*
* @return The RSA key pair.
*/
public static KeyPair generateKeyPair() {
// KeyPair b()
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM);
keyPairGenerator.initialize(1_024);
return keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException nosuchalgorithmexception) {
// Should be existing in every vm
throw new ExceptionInInitializerError(nosuchalgorithmexception);
}
}
/**
* Generate a random token. This is used to verify that we are communicating with the same player
* in a login session.
*
* @param random random generator
* @return an error with 4 bytes long
*/
public static byte[] generateVerifyToken(Random random) {
// extracted from LoginListener
byte[] token = new byte[VERIFY_TOKEN_LENGTH];
random.nextBytes(token);
return token;
}
/**
* Generate the server id based on client and server data.
*
* @param sessionId session for the current login attempt
* @param sharedSecret shared secret between the client and the server
* @param publicKey public key of the server
* @return the server id formatted as a hexadecimal string.
*/
public static String getServerIdHashString(String sessionId, SecretKey sharedSecret, PublicKey publicKey) {
// found in LoginListener
try {
byte[] serverHash = getServerIdHash(sessionId, publicKey, sharedSecret);
return (new BigInteger(serverHash)).toString(16);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
/**
* Decrypts the content and extracts the key spec.
*
* @param privateKey private server key
* @param sharedKey the encrypted shared key
* @return shared secret key
* @throws GeneralSecurityException if it fails to decrypt the data
*/
public static SecretKey decryptSharedKey(PrivateKey privateKey, byte[] sharedKey) throws GeneralSecurityException {
// SecretKey a(PrivateKey var0, byte[] var1)
return new SecretKeySpec(decrypt(privateKey, sharedKey), "AES");
}
public static byte[] decrypt(PrivateKey key, byte[] data) throws GeneralSecurityException {
// b(Key var0, byte[] var1)
Cipher cipher = Cipher.getInstance(key.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, key);
return decrypt(cipher, data);
}
/**
* Decrypted the given data using the cipher.
*
* @param cipher decryption cypher initialized with the private key
* @param data the encrypted data
* @return clear text data
* @throws GeneralSecurityException if it fails to decrypt the data
*/
private static byte[] decrypt(Cipher cipher, byte[] data) throws GeneralSecurityException {
// inlined: byte[] a(int var0, Key var1, byte[] var2), Cipher a(int var0, String var1, Key
// var2)
return cipher.doFinal(data);
}
private static byte[] getServerIdHash(
String sessionId, PublicKey publicKey, SecretKey sharedSecret)
throws NoSuchAlgorithmException {
// byte[] a(String var0, PublicKey var1, SecretKey var2)
MessageDigest digest = MessageDigest.getInstance("SHA-1");
// inlined from byte[] a(String var0, byte[]... var1)
digest.update(sessionId.getBytes(StandardCharsets.ISO_8859_1));
digest.update(sharedSecret.getEncoded());
digest.update(publicKey.getEncoded());
return digest.digest();
}
}

View File

@@ -0,0 +1,23 @@
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent.Result;
public class InitializedListener implements Listener {
private final ProtocolLibListener module;
protected InitializedListener(ProtocolLibListener protocolLibModule) {
this.module = protocolLibModule;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
if (loginEvent.getResult() == Result.ALLOWED && !module.isReadyToInject()) {
loginEvent.disallow(Result.KICK_OTHER, module.getPlugin().getCore().getMessage("not-started"));
}
}
}

View File

@@ -0,0 +1,89 @@
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.PacketEvent;
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.JoinManagement;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import java.security.PublicKey;
import java.util.Random;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class NameCheckTask extends JoinManagement<Player, CommandSender, ProtocolLibLoginSource>
implements Runnable {
private final FastLoginBukkit plugin;
private final PacketEvent packetEvent;
private final PublicKey publicKey;
private final Random random;
private final Player player;
private final String username;
protected NameCheckTask(FastLoginBukkit plugin, PacketEvent packetEvent, Random random,
Player player, String username, PublicKey publicKey) {
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
this.plugin = plugin;
this.packetEvent = packetEvent;
this.publicKey = publicKey;
this.random = random;
this.player = player;
this.username = username;
}
@Override
public void run() {
try {
super.onLogin(username, new ProtocolLibLoginSource(packetEvent, player, random, publicKey));
} finally {
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
}
}
@Override
public FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, ProtocolLibLoginSource source, StoredProfile profile) {
BukkitFastLoginPreLoginEvent event = new BukkitFastLoginPreLoginEvent(username, source, profile);
plugin.getServer().getPluginManager().callEvent(event);
return event;
}
//Minecraft server implementation
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
@Override
public void requestPremiumLogin(ProtocolLibLoginSource source, StoredProfile profile
, String username, boolean registered) {
try {
source.setOnlineMode();
} catch (Exception ex) {
plugin.getLog().error("Cannot send encryption packet. Falling back to cracked login for: {}", profile, ex);
return;
}
String ip = player.getAddress().getAddress().getHostAddress();
core.getPendingLogin().put(ip + username, new Object());
String serverId = source.getServerId();
byte[] verify = source.getVerifyToken();
BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId, verify, registered, profile);
plugin.getSessionManager().startLoginSession(player.getAddress(), playerSession);
//cancel only if the player has a paid account otherwise login as normal offline player
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
packetEvent.setCancelled(true);
}
}
@Override
public void startCrackedSession(ProtocolLibLoginSource source, StoredProfile profile, String username) {
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
plugin.getSessionManager().startLoginSession(player.getAddress(), loginSession);
}
}

View File

@@ -0,0 +1,124 @@
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.auth.RateLimiter;
import io.papermc.lib.PaperLib;
import java.security.KeyPair;
import java.security.SecureRandom;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import static com.comphenix.protocol.PacketType.Login.Client.ENCRYPTION_BEGIN;
import static com.comphenix.protocol.PacketType.Login.Client.START;
public class ProtocolLibListener extends PacketAdapter {
private final FastLoginBukkit plugin;
//just create a new once on plugin enable. This used for verify token generation
private final SecureRandom random = new SecureRandom();
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
private final RateLimiter rateLimiter;
// Wait before the server is fully started. This is workaround, because connections right on startup are not
// injected by ProtocolLib
private boolean serverStarted;
protected ProtocolLibListener(FastLoginBukkit plugin, RateLimiter rateLimiter) {
//run async in order to not block the server, because we are making api calls to Mojang
super(params()
.plugin(plugin)
.types(START, ENCRYPTION_BEGIN)
.optionAsync());
this.plugin = plugin;
this.rateLimiter = rateLimiter;
}
public static void register(FastLoginBukkit plugin, RateLimiter rateLimiter) {
//they will be created with a static builder, because otherwise it will throw a NoClassDefFoundError
ProtocolLibListener packetListener = new ProtocolLibListener(plugin, rateLimiter);
ProtocolLibrary.getProtocolManager()
.getAsynchronousManager()
.registerAsyncHandler(packetListener)
.start();
PluginManager pluginManager = Bukkit.getServer().getPluginManager();
pluginManager.registerEvents(new InitializedListener(packetListener), plugin);
//if server is using paper - we need to set the skin at pre login anyway, so no need for this listener
if (!PaperLib.isPaper() && plugin.getConfig().getBoolean("forwardSkin")) {
pluginManager.registerEvents(new SkinApplyListener(plugin), plugin);
}
}
@Override
public void onPacketReceiving(PacketEvent packetEvent) {
if (packetEvent.isCancelled() || plugin.getCore().getAuthPluginHook() == null) {
return;
}
markReadyToInject();
Player sender = packetEvent.getPlayer();
PacketType packetType = packetEvent.getPacketType();
if (packetType == START) {
if (!rateLimiter.tryAcquire()) {
plugin.getLog().warn("Join limit hit - Ignoring player {}", sender);
return;
}
onLogin(packetEvent, sender);
} else {
onEncryptionBegin(packetEvent, sender);
}
}
private void onEncryptionBegin(PacketEvent packetEvent, Player sender) {
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
packetEvent.getAsyncMarker().incrementProcessingDelay();
Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, sharedSecret, keyPair);
plugin.getScheduler().runAsync(verifyTask);
}
private void onLogin(PacketEvent packetEvent, Player player) {
//this includes ip:port. Should be unique for an incoming login request with a timeout of 2 minutes
String sessionKey = player.getAddress().toString();
//remove old data every time on a new login in order to keep the session only for one person
plugin.getSessionManager().endLoginSession(player.getAddress());
//player.getName() won't work at this state
PacketContainer packet = packetEvent.getPacket();
String username = packet.getGameProfiles().read(0).getName();
plugin.getLog().trace("GameProfile {} with {} connecting", sessionKey, username);
packetEvent.getAsyncMarker().incrementProcessingDelay();
Runnable nameCheckTask = new NameCheckTask(plugin, packetEvent, random, player, username, keyPair.getPublic());
plugin.getScheduler().runAsync(nameCheckTask);
}
public void markReadyToInject() {
this.serverStarted = true;
}
public boolean isReadyToInject() {
return serverStarted;
}
@Override
public FastLoginBukkit getPlugin() {
return plugin;
}
}

View File

@@ -0,0 +1,108 @@
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.github.games647.fastlogin.core.auth.LoginSource;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Random;
import org.bukkit.entity.Player;
import static com.comphenix.protocol.PacketType.Login.Server.DISCONNECT;
import static com.comphenix.protocol.PacketType.Login.Server.ENCRYPTION_BEGIN;
class ProtocolLibLoginSource implements LoginSource {
private final PacketEvent packetEvent;
private final Player player;
private final Random random;
private final PublicKey publicKey;
private final String serverId = "";
private byte[] verifyToken;
protected ProtocolLibLoginSource(PacketEvent packetEvent, Player player, Random random, PublicKey publicKey) {
this.packetEvent = packetEvent;
this.player = player;
this.random = random;
this.publicKey = publicKey;
}
@Override
public void setOnlineMode() throws Exception {
verifyToken = EncryptionUtil.generateVerifyToken(random);
/*
* Packet Information: https://wiki.vg/Protocol#Encryption_Request
*
* ServerID="" (String) key=public server key verifyToken=random 4 byte array
*/
PacketContainer newPacket = new PacketContainer(ENCRYPTION_BEGIN);
newPacket.getStrings().write(0, serverId);
StructureModifier<PublicKey> keyModifier = newPacket.getSpecificModifier(PublicKey.class);
int verifyField = 0;
if (keyModifier.getFields().isEmpty()) {
// Since 1.16.4 this is now a byte field
newPacket.getByteArrays().write(0, publicKey.getEncoded());
verifyField++;
} else {
keyModifier.write(0, publicKey);
}
newPacket.getByteArrays().write(verifyField, verifyToken);
//serverId is a empty string
ProtocolLibrary.getProtocolManager().sendServerPacket(player, newPacket);
}
@Override
public void kick(String message) throws InvocationTargetException {
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
PacketContainer kickPacket = new PacketContainer(DISCONNECT);
kickPacket.getChatComponents().write(0, WrappedChatComponent.fromText(message));
try {
//send kick packet at login state
//the normal event.getPlayer.kickPlayer(String) method does only work at play state
protocolManager.sendServerPacket(player, kickPacket);
} finally {
//tell the server that we want to close the connection
player.kickPlayer("Disconnect");
}
}
@Override
public InetSocketAddress getAddress() {
return packetEvent.getPlayer().getAddress();
}
public String getServerId() {
return serverId;
}
public byte[] getVerifyToken() {
return verifyToken.clone();
}
@Override
public String toString() {
return this.getClass().getSimpleName() + '{' +
"packetEvent=" + packetEvent +
", player=" + player +
", random=" + random +
", serverId='" + serverId + '\'' +
", verifyToken=" + Arrays.toString(verifyToken) +
'}';
}
}

View File

@@ -0,0 +1,52 @@
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
import com.github.games647.craftapi.model.skin.Textures;
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent.Result;
public class SkinApplyListener implements Listener {
private static final Class<?> GAME_PROFILE = MinecraftReflection.getGameProfileClass();
private static final MethodAccessor GET_PROPERTIES = Accessors.getMethodAccessor(GAME_PROFILE, "getProperties");
private final FastLoginBukkit plugin;
protected SkinApplyListener(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.LOW)
//run this on the loginEvent to let skins plugins see the skin like in normal Minecraft behaviour
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
if (loginEvent.getResult() != Result.ALLOWED) {
return;
}
Player player = loginEvent.getPlayer();
//go through every session, because player.getAddress is null
//loginEvent.getAddress is just a InetAddress not InetSocketAddress, so not unique enough
BukkitLoginSession session = plugin.getSessionManager().getLoginSession(player.getAddress());
if (session.getUsername().equals(player.getName())) {
session.getSkin().ifPresent(skin -> applySkin(player, skin.getValue(), skin.getSignature()));
}
}
private void applySkin(Player player, String skinData, String signature) {
WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player);
WrappedSignedProperty skin = WrappedSignedProperty.fromValues(Textures.KEY, skinData, signature);
gameProfile.getProperties().put(Textures.KEY, skin);
}
}

View File

@@ -0,0 +1,264 @@
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.github.games647.craftapi.model.auth.Verification;
import com.github.games647.craftapi.model.skin.SkinProperty;
import com.github.games647.craftapi.resolver.MojangResolver;
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import org.bukkit.entity.Player;
import static com.comphenix.protocol.PacketType.Login.Client.START;
import static com.comphenix.protocol.PacketType.Login.Server.DISCONNECT;
public class VerifyResponseTask implements Runnable {
private final FastLoginBukkit plugin;
private final PacketEvent packetEvent;
private final KeyPair serverKey;
private final Player player;
private final byte[] sharedSecret;
private static Method encryptMethod;
private static Method cipherMethod;
protected VerifyResponseTask(FastLoginBukkit plugin, PacketEvent packetEvent, Player player,
byte[] sharedSecret, KeyPair keyPair) {
this.plugin = plugin;
this.packetEvent = packetEvent;
this.player = player;
this.sharedSecret = Arrays.copyOf(sharedSecret, sharedSecret.length);
this.serverKey = keyPair;
}
@Override
public void run() {
try {
BukkitLoginSession session = plugin.getSessionManager().getLoginSession(player.getAddress());
if (session == null) {
disconnect("invalid-request", true
, "GameProfile {0} tried to send encryption response at invalid state", player.getAddress());
} else {
verifyResponse(session);
}
} finally {
//this is a fake packet; it shouldn't be send to the server
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
packetEvent.setCancelled(true);
}
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
}
}
private void verifyResponse(BukkitLoginSession session) {
PrivateKey privateKey = serverKey.getPrivate();
SecretKey loginKey;
try {
loginKey = EncryptionUtil.decryptSharedKey(privateKey, sharedSecret);
} catch (GeneralSecurityException securityEx) {
disconnect("error-kick", false, "Cannot decrypt received contents", securityEx);
return;
}
try {
if (!checkVerifyToken(session) || !enableEncryption(loginKey)) {
return;
}
} catch (Exception ex) {
disconnect("error-kick", false, "Cannot decrypt received contents", ex);
return;
}
String serverId = EncryptionUtil.getServerIdHashString("", loginKey, serverKey.getPublic());
String requestedUsername = session.getRequestUsername();
InetSocketAddress socketAddress = player.getAddress();
try {
MojangResolver resolver = plugin.getCore().getResolver();
InetAddress address = socketAddress.getAddress();
Optional<Verification> response = resolver.hasJoined(requestedUsername, serverId, address);
if (response.isPresent()) {
Verification verification = response.get();
plugin.getLog().info("Profile {} has a verified premium account: {}", requestedUsername, verification);
String realUsername = verification.getName();
if (realUsername == null) {
disconnect("invalid-session", true, "Username field null for {}", requestedUsername);
return;
}
SkinProperty[] properties = verification.getProperties();
if (properties.length > 0) {
session.setSkinProperty(properties[0]);
}
session.setVerifiedUsername(realUsername);
session.setUuid(verification.getId());
session.setVerified(true);
setPremiumUUID(session.getUuid());
receiveFakeStartPacket(realUsername);
} else {
//user tried to fake a authentication
disconnect("invalid-session", true
, "GameProfile {0} ({1}) tried to log in with an invalid session ServerId: {2}"
, session.getRequestUsername(), socketAddress, serverId);
}
} catch (IOException ioEx) {
disconnect("error-kick", false, "Failed to connect to session server", ioEx);
}
}
private void setPremiumUUID(UUID premiumUUID) {
if (plugin.getConfig().getBoolean("premiumUuid") && premiumUUID != null) {
try {
Object networkManager = getNetworkManager();
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/NetworkManager.java#L69
FieldUtils.writeField(networkManager, "spoofedUUID", premiumUUID, true);
} catch (Exception exc) {
plugin.getLog().error("Error setting premium uuid of {}", player, exc);
}
}
}
private boolean checkVerifyToken(BukkitLoginSession session) throws GeneralSecurityException {
byte[] requestVerify = session.getVerifyToken();
//encrypted verify token
byte[] responseVerify = packetEvent.getPacket().getByteArrays().read(1);
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L182
if (!Arrays.equals(requestVerify, EncryptionUtil.decrypt(serverKey.getPrivate(), responseVerify))) {
//check if the verify token are equal to the server sent one
disconnect("invalid-verify-token", true
, "GameProfile {0} ({1}) tried to login with an invalid verify token. Server: {2} Client: {3}"
, session.getRequestUsername(), packetEvent.getPlayer().getAddress(), requestVerify, responseVerify);
return false;
}
return true;
}
//try to get the networkManager from ProtocolLib
private Object getNetworkManager() throws IllegalAccessException, ClassNotFoundException {
Object injectorContainer = TemporaryPlayerFactory.getInjectorFromPlayer(player);
//ChannelInjector
Class<?> injectorClass = Class.forName("com.comphenix.protocol.injector.netty.Injector");
Object rawInjector = FuzzyReflection.getFieldValue(injectorContainer, injectorClass, true);
return FieldUtils.readField(rawInjector, "networkManager", true);
}
private boolean enableEncryption(SecretKey loginKey) throws IllegalArgumentException {
// Initialize method reflections
if (encryptMethod == null) {
Class<?> networkManagerClass = MinecraftReflection.getNetworkManagerClass();
try {
// Try to get the old (pre MC 1.16.4) encryption method
encryptMethod = FuzzyReflection.fromClass(networkManagerClass)
.getMethodByParameters("a", SecretKey.class);
} catch (IllegalArgumentException exception) {
// Get the new encryption method
encryptMethod = FuzzyReflection.fromClass(networkManagerClass)
.getMethodByParameters("a", Cipher.class, Cipher.class);
// Get the needed Cipher helper method (used to generate ciphers from login key)
Class<?> encryptionClass = MinecraftReflection.getMinecraftClass("MinecraftEncryption");
cipherMethod = FuzzyReflection.fromClass(encryptionClass)
.getMethodByParameters("a", int.class, Key.class);
}
}
try {
Object networkManager = this.getNetworkManager();
// If cipherMethod is null - use old encryption (pre MC 1.16.4), otherwise use the new cipher one
if (cipherMethod == null) {
// Encrypt/decrypt packet flow, this behaviour is expected by the client
encryptMethod.invoke(networkManager, loginKey);
} else {
// Create ciphers from login key
Object decryptionCipher = cipherMethod.invoke(null, Cipher.DECRYPT_MODE, loginKey);
Object encryptionCipher = cipherMethod.invoke(null, Cipher.ENCRYPT_MODE, loginKey);
// Encrypt/decrypt packet flow, this behaviour is expected by the client
encryptMethod.invoke(networkManager, decryptionCipher, encryptionCipher);
}
} catch (Exception ex) {
disconnect("error-kick", false, "Couldn't enable encryption", ex);
return false;
}
return true;
}
private void disconnect(String reasonKey, boolean debug, String logMessage, Object... arguments) {
if (debug) {
plugin.getLog().debug(logMessage, arguments);
} else {
plugin.getLog().error(logMessage, arguments);
}
kickPlayer(plugin.getCore().getMessage(reasonKey));
}
private void kickPlayer(String reason) {
PacketContainer kickPacket = new PacketContainer(DISCONNECT);
kickPacket.getChatComponents().write(0, WrappedChatComponent.fromText(reason));
try {
//send kick packet at login state
//the normal event.getPlayer.kickPlayer(String) method does only work at play state
ProtocolLibrary.getProtocolManager().sendServerPacket(player, kickPacket);
//tell the server that we want to close the connection
player.kickPlayer("Disconnect");
} catch (InvocationTargetException ex) {
plugin.getLog().error("Error sending kick packet for: {}", player, ex);
}
}
//fake a new login packet in order to let the server handle all the other stuff
private void receiveFakeStartPacket(String username) {
//see StartPacketListener for packet information
PacketContainer startPacket = new PacketContainer(START);
//uuid is ignored by the packet definition
WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username);
startPacket.getGameProfiles().write(0, fakeProfile);
try {
//we don't want to handle our own packets so ignore filters
ProtocolLibrary.getProtocolManager().recieveClientPacket(player, startPacket, false);
} catch (InvocationTargetException | IllegalAccessException ex) {
plugin.getLog().warn("Failed to fake a new start packet for: {}", username, ex);
//cancel the event in order to prevent the server receiving an invalid packet
kickPlayer(plugin.getCore().getMessage("error-kick"));
}
}
}

View File

@@ -0,0 +1,42 @@
package com.github.games647.fastlogin.bukkit.auth.protocolsupport;
import com.github.games647.fastlogin.core.auth.LoginSource;
import java.net.InetSocketAddress;
import protocolsupport.api.events.PlayerLoginStartEvent;
public class ProtocolLoginSource implements LoginSource {
private final PlayerLoginStartEvent loginStartEvent;
protected ProtocolLoginSource(PlayerLoginStartEvent loginStartEvent) {
this.loginStartEvent = loginStartEvent;
}
@Override
public void setOnlineMode() {
loginStartEvent.setOnlineMode(true);
}
@Override
public void kick(String message) {
loginStartEvent.denyLogin(message);
}
@Override
public InetSocketAddress getAddress() {
return loginStartEvent.getAddress();
}
public PlayerLoginStartEvent getLoginStartEvent() {
return loginStartEvent;
}
@Override
public String toString() {
return this.getClass().getSimpleName() + '{' +
"loginStartEvent=" + loginStartEvent +
'}';
}
}

View File

@@ -0,0 +1,102 @@
package com.github.games647.fastlogin.bukkit.auth.protocolsupport;
import com.github.games647.craftapi.UUIDAdapter;
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
import com.github.games647.fastlogin.core.auth.RateLimiter;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.JoinManagement;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import java.net.InetSocketAddress;
import java.util.Optional;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import protocolsupport.api.events.ConnectionCloseEvent;
import protocolsupport.api.events.PlayerLoginStartEvent;
import protocolsupport.api.events.PlayerProfileCompleteEvent;
public class ProtocolSupportListener extends JoinManagement<Player, CommandSender, ProtocolLoginSource>
implements Listener {
private final FastLoginBukkit plugin;
private final RateLimiter rateLimiter;
public ProtocolSupportListener(FastLoginBukkit plugin, RateLimiter rateLimiter) {
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
this.plugin = plugin;
this.rateLimiter = rateLimiter;
}
@EventHandler
public void onLoginStart(PlayerLoginStartEvent loginStartEvent) {
if (loginStartEvent.isLoginDenied() || plugin.getCore().getAuthPluginHook() == null) {
return;
}
if (!rateLimiter.tryAcquire()) {
plugin.getLog().warn("Join limit hit - Ignoring player {}", loginStartEvent.getConnection());
return;
}
String username = loginStartEvent.getConnection().getProfile().getName();
InetSocketAddress address = loginStartEvent.getAddress();
//remove old data every time on a new login in order to keep the session only for one person
plugin.getSessionManager().endLoginSession(address);
super.onLogin(username, new ProtocolLoginSource(loginStartEvent));
}
@EventHandler
public void onConnectionClosed(ConnectionCloseEvent closeEvent) {
InetSocketAddress address = closeEvent.getConnection().getAddress();
plugin.getSessionManager().endLoginSession(address);
}
@EventHandler
public void onPropertiesResolve(PlayerProfileCompleteEvent profileCompleteEvent) {
InetSocketAddress address = profileCompleteEvent.getAddress();
BukkitLoginSession session = plugin.getSessionManager().getLoginSession(address);
if (session != null && profileCompleteEvent.getConnection().getProfile().isOnlineMode()) {
session.setVerified(true);
if (!plugin.getConfig().getBoolean("premiumUuid")) {
String username = Optional.ofNullable(profileCompleteEvent.getForcedName())
.orElse(profileCompleteEvent.getConnection().getProfile().getName());
profileCompleteEvent.setForcedUUID(UUIDAdapter.generateOfflineId(username));
}
}
}
@Override
public FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, ProtocolLoginSource source, StoredProfile profile) {
BukkitFastLoginPreLoginEvent event = new BukkitFastLoginPreLoginEvent(username, source, profile);
plugin.getServer().getPluginManager().callEvent(event);
return event;
}
@Override
public void requestPremiumLogin(ProtocolLoginSource source, StoredProfile profile, String username
, boolean registered) {
source.setOnlineMode();
String ip = source.getAddress().getAddress().getHostAddress();
plugin.getCore().getPendingLogin().put(ip + username, new Object());
BukkitLoginSession playerSession = new BukkitLoginSession(username, registered, profile);
plugin.getSessionManager().startLoginSession(source.getAddress(), playerSession);
}
@Override
public void startCrackedSession(ProtocolLoginSource source, StoredProfile profile, String username) {
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
plugin.getSessionManager().startLoginSession(source.getAddress(), loginSession);
}
}

View File

@@ -0,0 +1,169 @@
package com.github.games647.fastlogin.bukkit.auth.proxy;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.message.ChannelMessage;
import com.github.games647.fastlogin.core.message.LoginActionMessage;
import com.github.games647.fastlogin.core.message.NamespaceKey;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Stream;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageRecipient;
import static com.github.games647.fastlogin.core.message.ChangePremiumMessage.CHANGE_CHANNEL;
import static com.github.games647.fastlogin.core.message.SuccessMessage.SUCCESS_CHANNEL;
import static java.util.stream.Collectors.toSet;
public class ProxyManager {
private static final String LEGACY_FILE_NAME = "proxy-whitelist.txt";
private static final String FILE_NAME = "allowed-proxies.txt";
//null if proxies allowed list is empty so proxy support is disabled
private Set<UUID> proxyIds;
private final FastLoginBukkit plugin;
private boolean enabled;
private final Set<UUID> firedJoinEvents = new HashSet<>();
public ProxyManager(FastLoginBukkit plugin) {
this.plugin = plugin;
}
public void cleanup() {
//remove old blocked status
Bukkit.getOnlinePlayers().forEach(player -> player.removeMetadata(plugin.getName(), plugin));
}
public void sendPluginMessage(PluginMessageRecipient player, ChannelMessage message) {
if (player != null) {
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
message.writeTo(dataOutput);
NamespaceKey channel = new NamespaceKey(plugin.getName(), message.getChannelName());
player.sendPluginMessage(plugin, channel.getCombinedName(), dataOutput.toByteArray());
}
}
public boolean isEnabled() {
return enabled;
}
public void initialize() {
try {
enabled = detectProxy();
} catch (Exception ex) {
plugin.getLog().warn("Cannot check proxy support. Fallback to non-proxy mode", ex);
}
if (enabled) {
proxyIds = loadProxyIds();
registerPluginChannels();
}
}
private boolean detectProxy() throws Exception {
try {
enabled = Class.forName("org.spigotmc.SpigotConfig").getDeclaredField("bungee").getBoolean(null);
return enabled;
} catch (ClassNotFoundException notFoundEx) {
//ignore server has no proxy support
return false;
} catch (Exception ex) {
throw ex;
}
}
private void registerPluginChannels() {
Server server = Bukkit.getServer();
// check for incoming messages from the proxy version of this plugin
String groupId = plugin.getName();
String forceChannel = NamespaceKey.getCombined(groupId, LoginActionMessage.FORCE_CHANNEL);
server.getMessenger().registerIncomingPluginChannel(plugin, forceChannel, new ProxyMessagingListener(plugin));
// outgoing
String successChannel = new NamespaceKey(groupId, SUCCESS_CHANNEL).getCombinedName();
String changeChannel = new NamespaceKey(groupId, CHANGE_CHANNEL).getCombinedName();
server.getMessenger().registerOutgoingPluginChannel(plugin, successChannel);
server.getMessenger().registerOutgoingPluginChannel(plugin, changeChannel);
}
private Set<UUID> loadProxyIds() {
Path proxiesFile = plugin.getPluginFolder().resolve(FILE_NAME);
Path legacyFile = plugin.getPluginFolder().resolve(LEGACY_FILE_NAME);
try {
if (Files.notExists(proxiesFile)) {
if (Files.exists(legacyFile)) {
Files.move(legacyFile, proxiesFile);
}
if (Files.notExists(legacyFile)) {
Files.createFile(proxiesFile);
}
}
Files.deleteIfExists(legacyFile);
try (Stream<String> lines = Files.lines(proxiesFile)) {
return lines.map(String::trim)
.map(UUID::fromString)
.collect(toSet());
}
} catch (IOException ex) {
plugin.getLog().error("Failed to read proxies", ex);
} catch (Exception ex) {
plugin.getLog().error("Failed to retrieve proxy Id. Disabling proxy support", ex);
}
return Collections.emptySet();
}
public boolean isProxyAllowed(UUID proxyId) {
return proxyIds != null && proxyIds.contains(proxyId);
}
/**
* Mark the event to be fired including the task delay.
*
* @param player
*/
public synchronized void markJoinEventFired(Player player) {
firedJoinEvents.add(player.getUniqueId());
}
/**
* Check if the event fired including with the task delay. This necessary to restore the order of processing the
* proxy messages after the PlayerJoinEvent fires including the delay.
*
* If the join event fired, the delay exceeded, but it ran earlier and couldn't find the recently started login
* session. If not fired, we can start a new force login task. This will still match the requirement that we wait
* a certain time after the player join event fired.
*
* @param player
* @return event fired including delay
*/
public synchronized boolean didJoinEventFired(Player player) {
return firedJoinEvents.contains(player.getUniqueId());
}
/**
* Player quit clean up
*
* @param player
*/
public synchronized void cleanup(Player player) {
firedJoinEvents.remove(player.getUniqueId());
}
}

View File

@@ -0,0 +1,114 @@
package com.github.games647.fastlogin.bukkit.auth.proxy;
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.ForceLoginTask;
import com.github.games647.fastlogin.core.PremiumStatus;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import com.github.games647.fastlogin.core.message.LoginActionMessage;
import com.github.games647.fastlogin.core.message.LoginActionMessage.Type;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import java.net.InetSocketAddress;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener;
/**
* Responsible for receiving messages from a proxy instance.
*
* This class also receives the plugin message from the proxy version of this plugin in order to get notified if
* the connection is in online mode.
*/
public class ProxyMessagingListener implements PluginMessageListener {
private final FastLoginBukkit plugin;
protected ProxyMessagingListener(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
ByteArrayDataInput dataInput = ByteStreams.newDataInput(message);
LoginActionMessage loginMessage = new LoginActionMessage();
loginMessage.readFrom(dataInput);
plugin.getLog().debug("Received plugin message {}", loginMessage);
Player targetPlayer = player;
if (!loginMessage.getPlayerName().equals(player.getName())) {
targetPlayer = Bukkit.getPlayerExact(loginMessage.getPlayerName());;
}
if (targetPlayer == null) {
plugin.getLog().warn("Force action player {} not found", loginMessage.getPlayerName());
return;
}
// fail if target player is blocked because already authenticated or wrong proxy id
if (targetPlayer.hasMetadata(plugin.getName())) {
plugin.getLog().warn("Received message {} from a blocked player {}", loginMessage, targetPlayer);
} else {
UUID sourceId = loginMessage.getProxyId();
if (plugin.getProxyManager().isProxyAllowed(sourceId)) {
readMessage(targetPlayer, loginMessage);
} else {
plugin.getLog().warn("Received proxy id: {} that doesn't exist in the proxy file", sourceId);
}
}
}
private void readMessage(Player player, LoginActionMessage message) {
String playerName = message.getPlayerName();
Type type = message.getType();
InetSocketAddress address = player.getAddress();
plugin.getLog().info("Player info {} command for {} from proxy", type, playerName);
if (type == Type.LOGIN) {
onLoginMessage(player, playerName, address);
} else if (type == Type.REGISTER) {
onRegisterMessage(player, playerName, address);
} else if (type == Type.CRACKED) {
//we don't start a force login task here so update it manually
plugin.getPremiumPlayers().put(player.getUniqueId(), PremiumStatus.CRACKED);
}
}
private void onLoginMessage(Player player, String playerName, InetSocketAddress address) {
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, true);
startLoginTaskIfReady(player, playerSession);
}
private void onRegisterMessage(Player player, String playerName, InetSocketAddress address) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
AuthPlugin<Player> authPlugin = plugin.getCore().getAuthPluginHook();
try {
//we need to check if the player is registered on Bukkit too
if (authPlugin == null || !authPlugin.isRegistered(playerName)) {
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, false);
startLoginTaskIfReady(player, playerSession);
}
} catch (Exception ex) {
plugin.getLog().error("Failed to query isRegistered for player: {}", player, ex);
}
});
}
private void startLoginTaskIfReady(Player player, BukkitLoginSession session) {
session.setVerified(true);
plugin.getSessionManager().startLoginSession(player.getAddress(), session);
// only start a new login task if the join event fired earlier. This event then didn
boolean result = plugin.getProxyManager().didJoinEventFired(player);
plugin.getLog().info("Delaying force login until join event fired?: {}", result);
if (result) {
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player, session);
Bukkit.getScheduler().runTaskAsynchronously(plugin, forceLoginTask);
}
}
}

View File

@@ -0,0 +1,94 @@
package com.github.games647.fastlogin.bukkit.command;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPremiumToggleEvent;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import static com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent.*;
public class CrackedCommand extends ToggleCommand {
public CrackedCommand(FastLoginBukkit plugin) {
super(plugin);
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length == 0) {
onCrackedSelf(sender, command, args);
} else {
onCrackedOther(sender, command, args);
}
return true;
}
private void onCrackedSelf(CommandSender sender, Command cmd, String[] args) {
if (isConsole(sender)) {
return;
}
if (forwardCrackedCommand(sender, sender.getName())) {
return;
}
if (plugin.getProxyManager().isEnabled()) {
sendProxyActivateMessage(sender, sender.getName(), false);
plugin.getCore().sendLocaleMessage("wait-on-proxy", sender);
} else {
//todo: load async if
StoredProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
if (profile.isPremium()) {
plugin.getCore().sendLocaleMessage("remove-premium", sender);
profile.setPremium(false);
profile.setId(null);
plugin.getScheduler().runAsync(() -> {
plugin.getCore().getStorage().save(profile);
plugin.getServer().getPluginManager().callEvent(
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_OTHER));
});
} else {
plugin.getCore().sendLocaleMessage("not-premium", sender);
}
}
}
private void onCrackedOther(CommandSender sender, Command command, String[] args) {
if (!hasOtherPermission(sender, command)) {
return;
}
if (forwardCrackedCommand(sender, args[0])) {
return;
}
//todo: load async
StoredProfile profile = plugin.getCore().getStorage().loadProfile(args[0]);
if (profile == null) {
sender.sendMessage("Error occurred");
return;
}
//existing player is already cracked
if (profile.isSaved() && !profile.isPremium()) {
plugin.getCore().sendLocaleMessage("not-premium-other", sender);
} else {
plugin.getCore().sendLocaleMessage("remove-premium", sender);
profile.setPremium(false);
plugin.getScheduler().runAsync(() -> {
plugin.getCore().getStorage().save(profile);
plugin.getServer().getPluginManager().callEvent(
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_OTHER));
});
}
}
private boolean forwardCrackedCommand(CommandSender sender, String target) {
return forwardProxyCommand(sender, target, false);
}
}

View File

@@ -0,0 +1,104 @@
package com.github.games647.fastlogin.bukkit.command;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPremiumToggleEvent;
import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent.PremiumToggleReason;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import java.util.UUID;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
/**
* Let users activate fast login by command. This only be accessible if
* the user has access to it's account. So we can make sure that not another
* person with a paid account and the same username can steal their account.
*/
public class PremiumCommand extends ToggleCommand {
public PremiumCommand(FastLoginBukkit plugin) {
super(plugin);
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length == 0) {
onPremiumSelf(sender, command, args);
} else {
onPremiumOther(sender, command, args);
}
return true;
}
private void onPremiumSelf(CommandSender sender, Command cmd, String[] args) {
if (isConsole(sender)) {
return;
}
if (forwardPremiumCommand(sender, sender.getName())) {
return;
}
UUID id = ((Player) sender).getUniqueId();
if (plugin.getConfig().getBoolean("premium-warning") && !plugin.getCore().getPendingConfirms().contains(id)) {
sender.sendMessage(plugin.getCore().getMessage("premium-warning"));
plugin.getCore().getPendingConfirms().add(id);
return;
}
plugin.getCore().getPendingConfirms().remove(id);
//todo: load async
StoredProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
if (profile.isPremium()) {
plugin.getCore().sendLocaleMessage("already-exists", sender);
} else {
//todo: resolve uuid
profile.setPremium(true);
plugin.getScheduler().runAsync(() -> {
plugin.getCore().getStorage().save(profile);
plugin.getServer().getPluginManager().callEvent(
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_SELF));
});
plugin.getCore().sendLocaleMessage("add-premium", sender);
}
}
private void onPremiumOther(CommandSender sender, Command command, String[] args) {
if (!hasOtherPermission(sender, command)) {
return;
}
if (forwardPremiumCommand(sender, args[0])) {
return;
}
//todo: load async
StoredProfile profile = plugin.getCore().getStorage().loadProfile(args[0]);
if (profile == null) {
plugin.getCore().sendLocaleMessage("player-unknown", sender);
return;
}
if (profile.isPremium()) {
plugin.getCore().sendLocaleMessage("already-exists-other", sender);
} else {
//todo: resolve uuid
profile.setPremium(true);
plugin.getScheduler().runAsync(() -> {
plugin.getCore().getStorage().save(profile);
plugin.getServer().getPluginManager().callEvent(
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_OTHER));
});
plugin.getCore().sendLocaleMessage("add-premium-other", sender);
}
}
private boolean forwardPremiumCommand(CommandSender sender, String target) {
return forwardProxyCommand(sender, target, true);
}
}

View File

@@ -0,0 +1,69 @@
package com.github.games647.fastlogin.bukkit.command;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
import com.github.games647.fastlogin.core.message.ChannelMessage;
import java.util.Optional;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageRecipient;
public abstract class ToggleCommand implements CommandExecutor {
protected final FastLoginBukkit plugin;
public ToggleCommand(FastLoginBukkit plugin) {
this.plugin = plugin;
}
protected boolean hasOtherPermission(CommandSender sender, Command cmd) {
if (!sender.hasPermission(cmd.getPermission() + ".other")) {
plugin.getCore().sendLocaleMessage("no-permission", sender);
return false;
}
return true;
}
protected boolean forwardProxyCommand(CommandSender sender, String target, boolean activate) {
if (plugin.getProxyManager().isEnabled()) {
sendProxyActivateMessage(sender, target, activate);
plugin.getCore().sendLocaleMessage("wait-on-proxy", sender);
return true;
}
return false;
}
protected boolean isConsole(CommandSender sender) {
if (sender instanceof Player) {
return false;
}
//console or command block
sender.sendMessage(plugin.getCore().getMessage("no-console"));
return true;
}
protected void sendProxyActivateMessage(CommandSender invoker, String target, boolean activate) {
if (invoker instanceof PluginMessageRecipient) {
ChannelMessage message = new ChangePremiumMessage(target, activate, true);
plugin.getProxyManager().sendPluginMessage((PluginMessageRecipient) invoker, message);
} else {
Optional<? extends Player> optPlayer = Bukkit.getServer().getOnlinePlayers().stream().findFirst();
if (!optPlayer.isPresent()) {
plugin.getLog().info("No player online to send a plugin message to the proxy");
return;
}
Player sender = optPlayer.get();
ChannelMessage message = new ChangePremiumMessage(target, activate, false);
plugin.getProxyManager().sendPluginMessage(sender, message);
}
}
}

View File

@@ -1,115 +0,0 @@
package com.github.games647.fastlogin.bukkit.commands;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.PlayerProfile;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class CrackedCommand implements CommandExecutor {
protected final FastLoginBukkit plugin;
public CrackedCommand(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length == 0) {
if (!(sender instanceof Player)) {
//console or command block
sender.sendMessage(plugin.getCore().getMessage("no-console"));
return true;
}
if (plugin.isBungeeCord()) {
notifiyBungeeCord(sender, sender.getName());
String message = plugin.getCore().getMessage("wait-on-proxy");
if (message != null) {
sender.sendMessage(message);
}
} else {
//todo: load async if it's not in the cache anymore
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
if (profile.isPremium()) {
sender.sendMessage(ChatColor.DARK_GREEN + "Removed from the list of premium players");
profile.setPremium(false);
profile.setUuid(null);
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
@Override
public void run() {
plugin.getCore().getStorage().save(profile);
}
});
} else {
sender.sendMessage(ChatColor.DARK_RED + "You are not in the premium list");
}
}
return true;
} else {
if (!sender.hasPermission(command.getPermission() + ".other")) {
sender.sendMessage(plugin.getCore().getMessage("no-permission"));
return true;
}
if (plugin.isBungeeCord()) {
notifiyBungeeCord(sender, args[0]);
String message = plugin.getCore().getMessage("wait-on-proxy");
if (message != null) {
sender.sendMessage(message);
}
} else {
//todo: load async if it's not in the cache anymore
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(args[0]);
if (profile == null) {
sender.sendMessage(plugin.getCore().getMessage("player-unknown"));
return true;
}
if (profile.isPremium()) {
sender.sendMessage(ChatColor.DARK_GREEN + "Removed from the list of premium players");
profile.setPremium(false);
profile.setUuid(null);
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
@Override
public void run() {
plugin.getCore().getStorage().save(profile);
}
});
} else {
sender.sendMessage(ChatColor.DARK_RED + "Player is not in the premium list");
}
}
}
return true;
}
private void notifiyBungeeCord(CommandSender sender, String target) {
if (sender instanceof Player) {
notifiyBungeeCord((Player) sender, target);
} else {
//todo: add console support
// Player firstPlayer = Iterables.getFirst(Bukkit.getOnlinePlayers(), null);
// notifiyBungeeCord(firstPlayer, target);
}
}
private void notifiyBungeeCord(Player sender, String target) {
if (plugin.isBungeeCord()) {
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
dataOutput.writeUTF("OFF");
dataOutput.writeUTF(target);
plugin.getLogger().info("No player online to send a plugin message to the proxy");
}
}
}

View File

@@ -1,122 +0,0 @@
package com.github.games647.fastlogin.bukkit.commands;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.PlayerProfile;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
/**
* Let users activate fast login by command. This only be accessible if
* the user has access to it's account. So we can make sure that not another
* person with a paid account and the same username can steal his account.
*/
public class PremiumCommand implements CommandExecutor {
protected final FastLoginBukkit plugin;
public PremiumCommand(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length == 0) {
if (!(sender instanceof Player)) {
//console or command block
sender.sendMessage(plugin.getCore().getMessage("no-console"));
return true;
}
if (plugin.isBungeeCord()) {
notifiyBungeeCord(sender, sender.getName());
String message = plugin.getCore().getMessage("wait-on-proxy");
if (message != null) {
sender.sendMessage(message);
}
} else {
// //todo: load async if it's not in the cache anymore
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
if (profile.isPremium()) {
sender.sendMessage(ChatColor.DARK_RED + "You are already on the premium list");
} else {
//todo: resolve uuid
profile.setPremium(true);
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
@Override
public void run() {
plugin.getCore().getStorage().save(profile);
}
});
sender.sendMessage(ChatColor.DARK_GREEN + "Added to the list of premium players");
}
}
return true;
} else {
if (!sender.hasPermission(command.getPermission() + ".other")) {
sender.sendMessage(plugin.getCore().getMessage("no-permission"));
return true;
}
if (plugin.isBungeeCord()) {
notifiyBungeeCord(sender, args[0]);
String message = plugin.getCore().getMessage("wait-on-proxy");
if (message != null) {
sender.sendMessage(message);
}
} else {
//todo: load async if it's not in the cache anymore
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(args[0]);
if (profile == null) {
sender.sendMessage(plugin.getCore().getMessage("player-unknown"));
return true;
}
if (profile.isPremium()) {
sender.sendMessage(ChatColor.DARK_RED + "Player is already on the premium list");
} else {
//todo: resolve uuid
profile.setPremium(true);
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
@Override
public void run() {
plugin.getCore().getStorage().save(profile);
}
});
sender.sendMessage(ChatColor.DARK_GREEN + "Added to the list of premium players");
}
}
}
return true;
}
private void notifiyBungeeCord(CommandSender sender, String target) {
if (sender instanceof Player) {
notifiyBungeeCord((Player) sender, target);
} else {
//todo: add console support
// Player firstPlayer = Iterables.getFirst(Bukkit.getOnlinePlayers(), null);
// notifiyBungeeCord(firstPlayer, target);
}
}
private void notifiyBungeeCord(Player sender, String target) {
if (plugin.isBungeeCord()) {
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
dataOutput.writeUTF("ON");
dataOutput.writeUTF(target);
sender.sendPluginMessage(plugin, plugin.getName(), dataOutput.toByteArray());
}
}
}

View File

@@ -0,0 +1,52 @@
package com.github.games647.fastlogin.bukkit.event;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.LoginSession;
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class BukkitFastLoginAutoLoginEvent extends Event implements FastLoginAutoLoginEvent, Cancellable {
private static final HandlerList handlers = new HandlerList();
private final LoginSession session;
private final StoredProfile profile;
private boolean cancelled;
public BukkitFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
super(true);
this.session = session;
this.profile = profile;
}
@Override
public LoginSession getSession() {
return session;
}
@Override
public StoredProfile getProfile() {
return profile;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,47 @@
package com.github.games647.fastlogin.bukkit.event;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.LoginSource;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class BukkitFastLoginPreLoginEvent extends Event implements FastLoginPreLoginEvent {
private static final HandlerList handlers = new HandlerList();
private final String username;
private final LoginSource source;
private final StoredProfile profile;
public BukkitFastLoginPreLoginEvent(String username, LoginSource source, StoredProfile profile) {
super(true);
this.username = username;
this.source = source;
this.profile = profile;
}
@Override
public String getUsername() {
return username;
}
@Override
public LoginSource getSource() {
return source;
}
@Override
public StoredProfile getProfile() {
return profile;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,38 @@
package com.github.games647.fastlogin.bukkit.event;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class BukkitFastLoginPremiumToggleEvent extends Event implements FastLoginPremiumToggleEvent {
private static final HandlerList handlers = new HandlerList();
private final StoredProfile profile;
private final PremiumToggleReason reason;
public BukkitFastLoginPremiumToggleEvent(StoredProfile profile, PremiumToggleReason reason) {
super(true);
this.profile = profile;
this.reason = reason;
}
@Override
public StoredProfile getProfile() {
return profile;
}
@Override
public PremiumToggleReason getReason() {
return reason;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,91 @@
package com.github.games647.fastlogin.bukkit.hook;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import fr.xephi.authme.api.v3.AuthMeApi;
import fr.xephi.authme.events.RestoreSessionEvent;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.process.register.executors.ApiPasswordRegisterParams;
import fr.xephi.authme.process.register.executors.RegistrationMethod;
import java.lang.reflect.Field;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
/**
* GitHub: https://github.com/Xephi/AuthMeReloaded/
* <p>
* Project page:
* <p>
* Bukkit: https://dev.bukkit.org/bukkit-plugins/authme-reloaded/
* <p>
* Spigot: https://www.spigotmc.org/resources/authme-reloaded.6269/
*/
public class AuthMeHook implements AuthPlugin<Player>, Listener {
private final FastLoginBukkit plugin;
private final AuthMeApi authmeAPI;
private Management authmeManagement;
protected AuthMeHook(FastLoginBukkit plugin) {
this.plugin = plugin;
this.authmeAPI = AuthMeApi.getInstance();
if (plugin.getConfig().getBoolean("respectIpLimit", false)) {
try {
Field managementField = this.authmeAPI.getClass().getDeclaredField("management");
managementField.setAccessible(true);
this.authmeManagement = (Management) managementField.get(this.authmeAPI);
} catch (NoSuchFieldException | IllegalAccessException exception) {
this.authmeManagement = null;
}
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onSessionRestore(RestoreSessionEvent restoreSessionEvent) {
Player player = restoreSessionEvent.getPlayer();
StoredProfile session = plugin.getSessionManager().getPlaySession(player.getUniqueId());
if (session != null && session.isPremium()) {
restoreSessionEvent.setCancelled(true);
}
}
@Override
public boolean forceLogin(Player player) {
if (authmeAPI.isAuthenticated(player)) {
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
return false;
}
//skips registration and login
authmeAPI.forceLogin(player);
return true;
}
@Override
public boolean isRegistered(String playerName) {
return authmeAPI.isRegistered(playerName);
}
@Override
//this automatically login the player too
public boolean forceRegister(Player player, String password) {
//if we have the management - we can trigger register with IP limit checks
if (authmeManagement != null) {
authmeManagement.performRegister(RegistrationMethod.PASSWORD_REGISTRATION,
ApiPasswordRegisterParams.of(player, password, true));
} else {
authmeAPI.forceRegister(player, password);
}
return true;
}
}

View File

@@ -0,0 +1,123 @@
package com.github.games647.fastlogin.bukkit.hook;
import com.comphenix.protocol.reflect.FieldUtils;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import de.st_ddt.crazylogin.CrazyLogin;
import de.st_ddt.crazylogin.data.LoginPlayerData;
import de.st_ddt.crazylogin.databases.CrazyLoginDataDatabase;
import de.st_ddt.crazylogin.listener.PlayerListener;
import de.st_ddt.crazylogin.metadata.Authenticated;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
/**
* GitHub: https://github.com/ST-DDT/CrazyLogin
* <p>
* Project page:
* <p>
* Bukkit: https://dev.bukkit.org/server-mods/crazylogin/
*/
public class CrazyLoginHook implements AuthPlugin<Player> {
private final FastLoginBukkit plugin;
private final CrazyLogin crazyLoginPlugin;
private final PlayerListener playerListener;
protected CrazyLoginHook(FastLoginBukkit plugin) {
this.plugin = plugin;
crazyLoginPlugin = CrazyLogin.getPlugin();
playerListener = getListener();
}
@Override
public boolean forceLogin(Player player) {
//not thread-safe operation
Future<Optional<LoginPlayerData>> future = Bukkit.getScheduler().callSyncMethod(plugin, () -> {
LoginPlayerData playerData = crazyLoginPlugin.getPlayerData(player);
if (playerData != null) {
//mark the account as logged in
playerData.setLoggedIn(true);
String ip = player.getAddress().getAddress().getHostAddress();
//this should be done after login to restore the inventory, show players, prevent potential memory leaks...
//from: https://github.com/ST-DDT/CrazyLogin/blob/master/src/main/java/de/st_ddt/crazylogin/CrazyLogin.java#L1948
playerData.resetLoginFails();
player.setFireTicks(0);
if (playerListener != null) {
playerListener.removeMovementBlocker(player);
playerListener.disableHidenInventory(player);
playerListener.disableSaveLogin(player);
playerListener.unhidePlayer(player);
}
//loginFailuresPerIP.remove(IP);
//illegalCommandUsesPerIP.remove(IP);
//tempBans.remove(IP);
playerData.addIP(ip);
player.setMetadata("Authenticated", new Authenticated(crazyLoginPlugin, player));
crazyLoginPlugin.unregisterDynamicHooks();
return Optional.of(playerData);
}
return Optional.empty();
});
try {
Optional<LoginPlayerData> result = future.get().filter(LoginPlayerData::isLoggedIn);
if (result.isPresent()) {
//SQL-Queries should run async
crazyLoginPlugin.getCrazyDatabase().saveWithoutPassword(result.get());
return true;
}
} catch (InterruptedException | ExecutionException ex) {
plugin.getLog().error("Failed to forceLogin player: {}", player, ex);
return false;
}
return false;
}
@Override
public boolean isRegistered(String playerName) {
return crazyLoginPlugin.getPlayerData(playerName) != null;
}
@Override
public boolean forceRegister(Player player, String password) {
CrazyLoginDataDatabase crazyDatabase = crazyLoginPlugin.getCrazyDatabase();
//this executes a sql query and accesses only thread safe collections so we can run it async
LoginPlayerData playerData = crazyLoginPlugin.getPlayerData(player.getName());
if (playerData == null) {
//create a fake account - this will be saved to the database with the password=FAILEDLOADING
//user cannot login with that password unless the admin uses plain text
//this automatically marks the player as logged in
crazyDatabase.save(new LoginPlayerData(player));
return forceLogin(player);
}
return false;
}
private PlayerListener getListener() {
PlayerListener listener;
try {
listener = (PlayerListener) FieldUtils.readField(crazyLoginPlugin, "playerListener", true);
} catch (IllegalAccessException ex) {
plugin.getLog().error("Failed to get the listener instance for auto login", ex);
listener = null;
}
return listener;
}
}

View File

@@ -0,0 +1,90 @@
package com.github.games647.fastlogin.bukkit.hook;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
public class DelayedAuthHook implements Runnable {
private final FastLoginBukkit plugin;
public DelayedAuthHook(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@Override
public void run() {
boolean hookFound = isHookFound();
if (plugin.getProxyManager().isEnabled()) {
plugin.getLog().info("Proxy setting detected. No auth plugin is required");
} else if (!hookFound) {
plugin.getLog().warn("No auth plugin were found by this plugin "
+ "(other plugins could hook into this after the initialization of this plugin)"
+ "and BungeeCord (or similar proxies) is deactivated. "
+ "Either one or both of the checks have to pass in order to use this plugin");
}
}
private boolean isHookFound() {
return plugin.getCore().getAuthPluginHook() != null || registerHooks();
}
private boolean registerHooks() {
AuthPlugin<Player> authPluginHook = getAuthHook();
if (authPluginHook == null) {
//run this check for exceptions (errors) and not found plugins
plugin.getLog().warn("No support offline Auth plugin found. ");
return false;
}
if (authPluginHook instanceof Listener) {
Bukkit.getPluginManager().registerEvents((Listener) authPluginHook, plugin);
}
if (plugin.getCore().getAuthPluginHook() == null) {
plugin.getLog().info("Hooking into auth plugin: {}", authPluginHook.getClass().getSimpleName());
plugin.getCore().setAuthPluginHook(authPluginHook);
}
return true;
}
private AuthPlugin<Player> getAuthHook() {
try {
@SuppressWarnings("unchecked")
List<Class<? extends AuthPlugin<Player>>> hooks = Arrays.asList(AuthMeHook.class,
CrazyLoginHook.class, LogItHook.class, LoginSecurityHook.class,
SodionAuthHook.class, UltraAuthHook.class, xAuthHook.class);
for (Class<? extends AuthPlugin<Player>> clazz : hooks) {
String pluginName = clazz.getSimpleName().replace("Hook", "");
//uses only member classes which uses AuthPlugin interface (skip interfaces)
if (Bukkit.getPluginManager().isPluginEnabled(pluginName)) {
//check only for enabled plugins. A single plugin could be disabled by plugin managers
return newInstance(clazz);
}
}
} catch (ReflectiveOperationException ex) {
plugin.getLog().error("Couldn't load the auth hook class", ex);
}
return null;
}
private AuthPlugin<Player> newInstance(Class<? extends AuthPlugin<Player>> clazz)
throws ReflectiveOperationException {
try {
Constructor<? extends AuthPlugin<Player>> cons = clazz.getDeclaredConstructor(FastLoginBukkit.class);
return cons.newInstance(plugin);
} catch (NoSuchMethodException noMethodEx) {
return clazz.getDeclaredConstructor().newInstance();
}
}
}

View File

@@ -0,0 +1,58 @@
package com.github.games647.fastlogin.bukkit.hook;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import io.github.lucaseasedup.logit.CancelledState;
import io.github.lucaseasedup.logit.LogItCore;
import io.github.lucaseasedup.logit.account.Account;
import io.github.lucaseasedup.logit.session.SessionManager;
import java.time.Instant;
import org.bukkit.entity.Player;
/**
* GitHub: https://github.com/XziomekX/LogIt
* <p>
* Project page:
* <p>
* Bukkit: Unknown
* <p>
* Spigot: Unknown
*/
public class LogItHook implements AuthPlugin<Player> {
private final FastLoginBukkit plugin;
protected LogItHook(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@Override
public boolean forceLogin(Player player) {
SessionManager sessionManager = LogItCore.getInstance().getSessionManager();
if (sessionManager.isSessionAlive(player)) {
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
return false;
}
return sessionManager.startSession(player) == CancelledState.NOT_CANCELLED;
}
@Override
public boolean isRegistered(String playerName) {
return LogItCore.getInstance().getAccountManager().isRegistered(playerName);
}
@Override
public boolean forceRegister(Player player, String password) {
Account account = new Account(player.getName());
account.changePassword(password);
Instant now = Instant.now();
account.setLastActiveDate(now.getEpochSecond());
account.setRegistrationDate(now.getEpochSecond());
return LogItCore.getInstance().getAccountManager().insertAccount(account) == CancelledState.NOT_CANCELLED;
}
}

View File

@@ -0,0 +1,53 @@
package com.github.games647.fastlogin.bukkit.hook;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import com.lenis0012.bukkit.loginsecurity.LoginSecurity;
import com.lenis0012.bukkit.loginsecurity.session.AuthService;
import com.lenis0012.bukkit.loginsecurity.session.PlayerSession;
import com.lenis0012.bukkit.loginsecurity.session.action.LoginAction;
import com.lenis0012.bukkit.loginsecurity.session.action.RegisterAction;
import org.bukkit.entity.Player;
/**
* GitHub: https://github.com/lenis0012/LoginSecurity-2
* <p>
* Project page:
* <p>
* Bukkit: https://dev.bukkit.org/bukkit-plugins/loginsecurity/
* <p>
* Spigot: https://www.spigotmc.org/resources/loginsecurity.19362/
*/
public class LoginSecurityHook implements AuthPlugin<Player> {
private final FastLoginBukkit plugin;
protected LoginSecurityHook(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@Override
public boolean forceLogin(Player player) {
PlayerSession session = LoginSecurity.getSessionManager().getPlayerSession(player);
if (session.isAuthorized()) {
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
return true;
}
return session.isAuthorized()
|| session.performAction(new LoginAction(AuthService.PLUGIN, plugin)).isSuccess();
}
@Override
public boolean isRegistered(String playerName) {
PlayerSession session = LoginSecurity.getSessionManager().getOfflineSession(playerName);
return session.isRegistered();
}
@Override
public boolean forceRegister(Player player, String password) {
PlayerSession session = LoginSecurity.getSessionManager().getPlayerSession(player);
return session.performAction(new RegisterAction(AuthService.PLUGIN, plugin, password)).isSuccess();
}
}

View File

@@ -0,0 +1,53 @@
package com.github.games647.fastlogin.bukkit.hook;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import org.bukkit.entity.Player;
import red.mohist.sodionauth.bukkit.implementation.BukkitPlayer;
import red.mohist.sodionauth.core.SodionAuthApi;
import red.mohist.sodionauth.core.exception.AuthenticatedException;
/**
* GitHub: https://github.com/Mohist-Community/SodionAuth
* <p>
* Project page:
* <p>
* Bukkit: Unknown
* <p>
* Spigot: Unknown
*/
public class SodionAuthHook implements AuthPlugin<Player> {
private final FastLoginBukkit plugin;
protected SodionAuthHook(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@Override
public boolean forceLogin(Player player) {
try {
SodionAuthApi.login(new BukkitPlayer(player));
} catch (AuthenticatedException e) {
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
return false;
}
return true;
}
@Override
public boolean forceRegister(Player player, String password) {
try{
return SodionAuthApi.register(new BukkitPlayer(player), password);
} catch (UnsupportedOperationException e){
plugin.getLog().warn("Currently SodionAuth is not accepting forceRegister, " +
"It may be caused by unsupported AuthBackend");
return false;
}
}
@Override
public boolean isRegistered(String playerName) {
return SodionAuthApi.isRegistered(playerName);
}
}

View File

@@ -0,0 +1,64 @@
package com.github.games647.fastlogin.bukkit.hook;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import ultraauth.api.UltraAuthAPI;
import ultraauth.main.Main;
import ultraauth.managers.PlayerManager;
/**
* Project page:
* <p>
* Bukkit: https://dev.bukkit.org/bukkit-plugins/ultraauth-aa/
* <p>
* Spigot: https://www.spigotmc.org/resources/ultraauth.17044/
*/
public class UltraAuthHook implements AuthPlugin<Player> {
private final Plugin ultraAuthPlugin = Main.main;
private final FastLoginBukkit plugin;
protected UltraAuthHook(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@Override
public boolean forceLogin(Player player) {
//not thread-safe
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(plugin, () -> {
if (UltraAuthAPI.isAuthenticated(player)) {
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
return false;
}
UltraAuthAPI.authenticatedPlayer(player);
return UltraAuthAPI.isAuthenticated(player);
});
try {
return future.get();
} catch (InterruptedException | ExecutionException ex) {
plugin.getLog().error("Failed to forceLogin player: {}", player, ex);
return false;
}
}
@Override
public boolean isRegistered(String playerName) {
return UltraAuthAPI.isRegisterd(playerName);
}
@Override
public boolean forceRegister(Player player, String password) {
UltraAuthAPI.setPlayerPasswordOnline(player, password);
//the register method silents any exception so check if our entry was saved
return PlayerManager.getInstance().checkPlayerPassword(player, password) && forceLogin(player);
}
}

View File

@@ -0,0 +1,87 @@
package com.github.games647.fastlogin.bukkit.hook;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import de.luricos.bukkit.xAuth.xAuth;
import de.luricos.bukkit.xAuth.xAuthPlayer;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
/**
* GitHub: https://github.com/LycanDevelopment/xAuth/
* <p>
* Project page:
* <p>
* Bukkit: https://dev.bukkit.org/bukkit-plugins/xauth/
*/
public class xAuthHook implements AuthPlugin<Player> {
private final xAuth xAuthPlugin = xAuth.getPlugin();
private final FastLoginBukkit plugin;
protected xAuthHook(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@Override
public boolean forceLogin(Player player) {
//not thread-safe
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(plugin, () -> {
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(player);
if (xAuthPlayer != null) {
if (xAuthPlayer.isAuthenticated()) {
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
return false;
}
//we checked that the player is premium (paid account)
xAuthPlayer.setPremium(true);
//unprotect the inventory, op status...
return xAuthPlugin.getPlayerManager().doLogin(xAuthPlayer);
}
return false;
});
try {
return future.get();
} catch (InterruptedException | ExecutionException ex) {
plugin.getLog().error("Failed to forceLogin player: {}", player, ex);
return false;
}
}
@Override
public boolean isRegistered(String playerName) {
//this will load the player if it's not in the cache
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(playerName);
return xAuthPlayer != null && xAuthPlayer.isRegistered();
}
@Override
public boolean forceRegister(Player player, final String password) {
//not thread-safe
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(xAuthPlugin, () -> {
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(player);
//this should run async because the plugin executes a sql query, but the method
//accesses non thread-safe collections :(
return xAuthPlayer != null
&& xAuthPlugin.getAuthClass(xAuthPlayer).adminRegister(player.getName(), password, null);
});
try {
//login in the player after registration
return future.get() && forceLogin(player);
} catch (InterruptedException | ExecutionException ex) {
plugin.getLog().error("Failed to forceRegister player: {}", player, ex);
return false;
}
}
}

View File

@@ -1,57 +0,0 @@
package com.github.games647.fastlogin.bukkit.hooks;
import com.avaje.ebeaninternal.api.ClassUtil;
import fr.xephi.authme.api.API;
import fr.xephi.authme.api.NewAPI;
import org.bukkit.entity.Player;
/**
* Github: https://github.com/Xephi/AuthMeReloaded/
* Project page:
*
* Bukkit: http://dev.bukkit.org/bukkit-plugins/authme-reloaded/
* Spigot: https://www.spigotmc.org/resources/authme-reloaded.6269/
*/
public class AuthMeHook implements BukkitAuthPlugin {
private final boolean isNewAPIAvailable;
public AuthMeHook() {
this.isNewAPIAvailable = ClassUtil.isPresent("fr.xephi.authme.api.NewAPI");
}
@Override
public boolean forceLogin(Player player) {
//skips registration and login
if (isNewAPIAvailable) {
NewAPI.getInstance().forceLogin(player);
} else {
API.forceLogin(player);
}
return true;
}
@Override
public boolean isRegistered(String playerName) throws Exception {
if (isNewAPIAvailable) {
return NewAPI.getInstance().isRegistered(playerName);
} else {
return API.isRegistered(playerName);
}
}
@Override
public boolean forceRegister(Player player, String password) {
if (isNewAPIAvailable) {
NewAPI.getInstance().forceRegister(player, password);
} else {
API.registerPlayer(player.getName(), password);
forceLogin(player);
}
return true;
}
}

View File

@@ -1,119 +0,0 @@
package com.github.games647.fastlogin.bukkit.hooks;
import com.comphenix.protocol.reflect.FuzzyReflection;
import de.st_ddt.crazylogin.CrazyLogin;
import de.st_ddt.crazylogin.data.LoginPlayerData;
import de.st_ddt.crazylogin.databases.CrazyLoginDataDatabase;
import de.st_ddt.crazylogin.listener.PlayerListener;
import de.st_ddt.crazylogin.metadata.Authenticated;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
/**
* Github: https://github.com/ST-DDT/CrazyLogin
*
* Project page:
*
* Bukkit: http://dev.bukkit.org/server-mods/crazylogin/
*/
public class CrazyLoginHook implements BukkitAuthPlugin {
protected final CrazyLogin crazyLoginPlugin = CrazyLogin.getPlugin();
private final PlayerListener playerListener = getListener();
@Override
public boolean forceLogin(final Player player) {
//not thread-safe operation
Future<LoginPlayerData> future = Bukkit.getScheduler().callSyncMethod(crazyLoginPlugin
, new Callable<LoginPlayerData>() {
@Override
public LoginPlayerData call() throws Exception {
LoginPlayerData playerData = crazyLoginPlugin.getPlayerData(player.getName());
if (playerData != null) {
//mark the account as logged in
playerData.setLoggedIn(true);
String ip = player.getAddress().getAddress().getHostAddress();
//this should be done after login to restore the inventory, unhide players, prevent potential memory leaks...
//from: https://github.com/ST-DDT/CrazyLogin/blob/master/src/main/java/de/st_ddt/crazylogin/CrazyLogin.java#L1948
playerData.resetLoginFails();
player.setFireTicks(0);
if (playerListener != null) {
playerListener.removeMovementBlocker(player);
playerListener.disableHidenInventory(player);
playerListener.disableSaveLogin(player);
playerListener.unhidePlayer(player);
}
//loginFailuresPerIP.remove(IP);
//illegalCommandUsesPerIP.remove(IP);
//tempBans.remove(IP);
playerData.addIP(ip);
player.setMetadata("Authenticated", new Authenticated(crazyLoginPlugin, player));
crazyLoginPlugin.unregisterDynamicHooks();
return playerData;
}
return null;
}
});
try {
LoginPlayerData result = future.get();
if (result != null && result.isLoggedIn()) {
//SQL-Queries should run async
crazyLoginPlugin.getCrazyDatabase().saveWithoutPassword(result);
return true;
}
} catch (InterruptedException | ExecutionException ex) {
crazyLoginPlugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
return false;
}
return false;
}
@Override
public boolean isRegistered(String playerName) throws Exception {
return crazyLoginPlugin.getPlayerData(playerName) != null;
}
@Override
public boolean forceRegister(final Player player, String password) {
CrazyLoginDataDatabase crazyDatabase = crazyLoginPlugin.getCrazyDatabase();
//this executes a sql query and accesses only thread safe collections so we can run it async
LoginPlayerData playerData = crazyLoginPlugin.getPlayerData(player.getName());
if (playerData == null) {
//create a fake account - this will be saved to the database with the password=FAILEDLOADING
//user cannot login with that password unless the admin uses plain text
//this automatically marks the player as logged in
playerData = new LoginPlayerData(player);
crazyDatabase.save(playerData);
return forceLogin(player);
}
return false;
}
private PlayerListener getListener() {
PlayerListener listener;
try {
listener = FuzzyReflection.getFieldValue(crazyLoginPlugin, PlayerListener.class, true);
} catch (Exception ex) {
crazyLoginPlugin.getLogger().log(Level.SEVERE, "Failed to get the listener instance for auto login", ex);
listener = null;
}
return listener;
}
}

View File

@@ -1,36 +0,0 @@
package com.github.games647.fastlogin.bukkit.hooks;
import io.github.lucaseasedup.logit.CancelledState;
import io.github.lucaseasedup.logit.LogItCore;
import io.github.lucaseasedup.logit.account.Account;
import org.bukkit.entity.Player;
/**
* Github: https://github.com/XziomekX/LogIt
* Project page:
*
* Bukkit: Unknown
* Spigot: Unknown
*/
public class LogItHook implements BukkitAuthPlugin {
@Override
public boolean forceLogin(Player player) {
return LogItCore.getInstance().getSessionManager().startSession(player) == CancelledState.NOT_CANCELLED;
}
@Override
public boolean isRegistered(String playerName) throws Exception {
return LogItCore.getInstance().getAccountManager().isRegistered(playerName);
}
@Override
public boolean forceRegister(Player player, String password) {
Account account = new Account(player.getName());
account.changePassword(password);
account.setLastActiveDate(System.currentTimeMillis() / 1000);
account.setRegistrationDate(System.currentTimeMillis() / 1000);
return LogItCore.getInstance().getAccountManager().insertAccount(account) == CancelledState.NOT_CANCELLED;
}
}

View File

@@ -1,96 +0,0 @@
package com.github.games647.fastlogin.bukkit.hooks;
import com.google.common.base.Charsets;
import com.lenis0012.bukkit.ls.LoginSecurity;
import com.lenis0012.bukkit.ls.data.DataManager;
import java.net.InetAddress;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
/**
* Github: https://github.com/lenis0012/LoginSecurity-2 Project page:
*
* Bukkit: http://dev.bukkit.org/bukkit-plugins/loginsecurity/ Spigot:
* https://www.spigotmc.org/resources/loginsecurity.19362/
*
* on join:
* https://github.com/lenis0012/LoginSecurity-2/blob/master/src/main/java/com/lenis0012/bukkit/ls/LoginSecurity.java#L282
*/
public class LoginSecurityHook implements BukkitAuthPlugin {
protected final LoginSecurity securityPlugin = LoginSecurity.instance;
// protected final boolean newVersion;
public LoginSecurityHook() {
// this.newVersion = ClassUtil.isPresent("com.lenis0012.bukkit.loginsecurity.session.action.LoginAction");
}
@Override
public boolean forceLogin(final Player player) {
//Login command of this plugin: (How the plugin logs the player in)
//https://github.com/lenis0012/LoginSecurity-2/blob/master/src/main/java/com/lenis0012/bukkit/ls/commands/LoginCommand.java#L39
//not thread-safe operation
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(securityPlugin, new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
String name = player.getName().toLowerCase();
//mark the user as logged in
securityPlugin.authList.remove(name);
//cancel timeout timer
securityPlugin.thread.timeout.remove(name);
//remove effects and restore location
securityPlugin.rehabPlayer(player, name);
return true;
}
});
try {
return future.get();
} catch (InterruptedException | ExecutionException ex) {
securityPlugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
return false;
}
}
@Override
public boolean isRegistered(String playerName) throws Exception {
//https://github.com/lenis0012/LoginSecurity-2/blob/master/src/main/java/com/lenis0012/bukkit/ls/LoginSecurity.java#L296
DataManager dataManager = securityPlugin.data;
//https://github.com/lenis0012/LoginSecurity-2/blob/master/src/main/java/com/lenis0012/bukkit/ls/LoginSecurity.java#L283
UUID offlineUuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + playerName).getBytes(Charsets.UTF_8));
return dataManager.isRegistered(offlineUuid.toString().replace("-", ""));
//check for loginsecurity sessions in order to prevent a sql query?
//sesUse && thread.getSession().containsKey(uuid) && checkLastIp(player)) {
}
@Override
public boolean forceRegister(Player player, String password) {
DataManager dataManager = securityPlugin.data;
UUID playerUUID = player.getUniqueId();
String uuidString = playerUUID.toString().replace("-", "");
InetAddress ipAddress = player.getAddress().getAddress();
String passwordHash = securityPlugin.hasher.hash(password);
//this executes a sql query without interacting with other parts so we can run it async.
dataManager.register(uuidString, passwordHash, securityPlugin.hasher.getTypeId(), ipAddress.toString());
String storedPassword = dataManager.getPassword(uuidString);
if (storedPassword != null && storedPassword.equals(passwordHash)) {
//the register method silents any excpetion so check if our entry was saved
return forceLogin(player);
}
return false;
}
}

View File

@@ -1,68 +0,0 @@
package com.github.games647.fastlogin.bukkit.hooks;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.royaldev.royalauth.AuthPlayer;
import org.royaldev.royalauth.Config;
import org.royaldev.royalauth.RoyalAuth;
/**
* Github: https://github.com/RoyalDev/RoyalAuth
*
* Project page:
*
* Bukkit: http://dev.bukkit.org/bukkit-plugins/royalauth/
*/
public class RoyalAuthHook implements BukkitAuthPlugin {
private final RoyalAuth royalAuthPlugin = (RoyalAuth) Bukkit.getPluginManager().getPlugin("RoyalAuth");
@Override
public boolean forceLogin(final Player player) {
//not thread-safe
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(royalAuthPlugin, new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
AuthPlayer authPlayer = AuthPlayer.getAuthPlayer(player);
//https://github.com/RoyalDev/RoyalAuth/blob/master/src/main/java/org/royaldev/royalauth/commands/CmdLogin.java#L62
//not thread-safe
authPlayer.login();
return authPlayer.isLoggedIn();
}
});
try {
return future.get();
} catch (InterruptedException | ExecutionException ex) {
royalAuthPlugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
return false;
}
}
@Override
public boolean isRegistered(String playerName) throws Exception {
AuthPlayer authPlayer = AuthPlayer.getAuthPlayer(playerName);
return authPlayer.isRegistered();
}
@Override
public boolean forceRegister(Player player, String password) {
//https://github.com/RoyalDev/RoyalAuth/blob/master/src/main/java/org/royaldev/royalauth/commands/CmdRegister.java#L50
AuthPlayer authPlayer = AuthPlayer.getAuthPlayer(player);
boolean registerSuccess = authPlayer.setPassword(password, Config.passwordHashType);
if (registerSuccess) {
//login in the player after registration
return forceLogin(player);
}
return false;
}
}

View File

@@ -1,60 +0,0 @@
package com.github.games647.fastlogin.bukkit.hooks;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import ultraauth.api.UltraAuthAPI;
import ultraauth.main.Main;
import ultraauth.managers.PlayerManager;
/**
* Project page:
*
* Bukkit: http://dev.bukkit.org/bukkit-plugins/ultraauth-aa/
* Spigot: https://www.spigotmc.org/resources/ultraauth.17044/
*/
public class UltraAuthHook implements BukkitAuthPlugin {
protected final Plugin ultraAuthPlugin = Main.main;
@Override
public boolean forceLogin(final Player player) {
//not thread-safe
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(ultraAuthPlugin, new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
UltraAuthAPI.authenticatedPlayer(player);
return UltraAuthAPI.isAuthenticated(player);
}
});
try {
return future.get();
} catch (InterruptedException | ExecutionException ex) {
ultraAuthPlugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
return false;
}
}
@Override
public boolean isRegistered(String playerName) throws Exception {
return UltraAuthAPI.isRegisterd(new FakePlayer(playerName));
}
@Override
public boolean forceRegister(Player player, String password) {
UltraAuthAPI.setPlayerPasswordOnline(player, password);
if (PlayerManager.getInstance().checkPlayerPassword(player, password)) {
//the register method silents any excpetion so check if our entry was saved
return forceLogin(player);
}
return false;
}
}

View File

@@ -1,93 +0,0 @@
package com.github.games647.fastlogin.bukkit.hooks;
import de.luricos.bukkit.xAuth.xAuth;
import de.luricos.bukkit.xAuth.xAuthPlayer;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
/**
* Github: https://github.com/LycanDevelopment/xAuth/
*
* Project page:
*
* Bukkit: http://dev.bukkit.org/bukkit-plugins/xauth/
*/
public class xAuthHook implements BukkitAuthPlugin {
protected final xAuth xAuthPlugin = xAuth.getPlugin();
@Override
public boolean forceLogin(final Player player) {
//not thread-safe
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(xAuthPlugin, new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(player);
if (xAuthPlayer != null) {
//we checked that the player is premium (paid account)
xAuthPlayer.setPremium(true);
//unprotect the inventory, op status...
return xAuthPlugin.getPlayerManager().doLogin(xAuthPlayer);
}
return false;
}
});
try {
return future.get();
} catch (InterruptedException | ExecutionException ex) {
xAuthPlugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
return false;
}
}
@Override
public boolean isRegistered(String playerName) throws Exception {
//this will load the player if it's not in the cache
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(playerName);
return xAuthPlayer != null && xAuthPlayer.isRegistered();
}
@Override
public boolean forceRegister(final Player player, final String password) {
//not thread-safe
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(xAuthPlugin, new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(player);
if (xAuthPlayer != null) {
//this should run async because the plugin executes a sql query, but the method
//accesses non thread-safe collections :(
boolean registerSuccess = xAuthPlugin.getAuthClass(xAuthPlayer)
.adminRegister(player.getName(), password, null);
return registerSuccess;
}
return false;
}
});
try {
boolean success = future.get();
if (success) {
//login in the player after registration
return forceLogin(player);
}
return false;
} catch (InterruptedException | ExecutionException ex) {
xAuthPlugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
return false;
}
}
}

View File

@@ -1,57 +0,0 @@
package com.github.games647.fastlogin.bukkit.listener;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.tasks.ForceLoginTask;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent.Result;
/**
* This listener tells authentication plugins if the player has a premium account and we checked it successfully. So the
* plugin can skip authentication.
*/
public class BukkitJoinListener implements Listener {
private static final long DELAY_LOGIN = 20L / 2;
protected final FastLoginBukkit plugin;
public BukkitJoinListener(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
if (loginEvent.getResult() == Result.ALLOWED && !plugin.isServerFullyStarted()) {
loginEvent.disallow(Result.KICK_OTHER, "§cServer is not fully started yet. Please retry");
}
}
@EventHandler(ignoreCancelled = true)
public void onPlayerJoin(PlayerJoinEvent joinEvent) {
Player player = joinEvent.getPlayer();
BukkitLoginSession session = plugin.getSessions().get(player.getAddress().toString());
if (session != null && plugin.getConfig().getBoolean("forwardSkin")) {
WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player);
WrappedSignedProperty skin = session.getSkin();
if (skin != null) {
gameProfile.getProperties().put("textures", skin);
}
}
if (!plugin.isBungeeCord()) {
//Wait before auth plugin and we received a message from BungeeCord initializes the player
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, new ForceLoginTask(plugin, player), DELAY_LOGIN);
}
}
}

View File

@@ -1,132 +0,0 @@
package com.github.games647.fastlogin.bukkit.listener;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.tasks.ForceLoginTask;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
import com.google.common.base.Charsets;
import com.google.common.collect.Sets;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.plugin.messaging.PluginMessageListener;
/**
* Responsible for receiving messages from a BungeeCord instance.
*
* This class also receives the plugin message from the bungeecord version of this plugin in order to get notified if
* the connection is in online mode.
*/
public class BungeeCordListener implements PluginMessageListener {
private static final String FILE_NAME = "proxy-whitelist.txt";
protected final FastLoginBukkit plugin;
//null if whitelist is empty so bungeecord support is disabled
private final Set<UUID> proxyIds;
public BungeeCordListener(FastLoginBukkit plugin) {
this.plugin = plugin;
this.proxyIds = loadBungeeCordIds();
}
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (!channel.equals(plugin.getName())) {
return;
}
ByteArrayDataInput dataInput = ByteStreams.newDataInput(message);
String subchannel = dataInput.readUTF();
plugin.getLogger().log(Level.FINEST, "Received plugin message for subchannel {0} from {1}"
, new Object[]{subchannel, player});
final String playerName = dataInput.readUTF();
//check if the player is still online or disconnected
final Player checkedPlayer = plugin.getServer().getPlayerExact(playerName);
//fail if target player is blacklisted because already authed or wrong bungeecord id
if (checkedPlayer != null && !checkedPlayer.hasMetadata(plugin.getName())) {
//blacklist this target player for BungeeCord Id brute force attacks
player.setMetadata(plugin.getName(), new FixedMetadataValue(plugin, true));
//bungeecord UUID
long mostSignificantBits = dataInput.readLong();
long leastSignificantBits = dataInput.readLong();
UUID sourceId = new UUID(mostSignificantBits, leastSignificantBits);
plugin.getLogger().log(Level.FINEST, "Received proxy id {0} from {1}", new Object[]{sourceId, player});
//fail if BungeeCord support is disabled (id = null)
if (proxyIds.contains(sourceId)) {
final String id = '/' + checkedPlayer.getAddress().getAddress().getHostAddress() + ':'
+ checkedPlayer.getAddress().getPort();
if ("AUTO_LOGIN".equalsIgnoreCase(subchannel)) {
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, true);
playerSession.setVerified(true);
plugin.getSessions().put(id, playerSession);
} else if ("AUTO_REGISTER".equalsIgnoreCase(subchannel)) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
@Override
public void run() {
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
try {
//we need to check if the player is registered on Bukkit too
if (authPlugin != null && !authPlugin.isRegistered(playerName)) {
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, false);
playerSession.setVerified(true);
plugin.getSessions().put(id, playerSession);
}
} catch (Exception ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to query isRegistered", ex);
}
}
});
}
Bukkit.getScheduler().runTaskAsynchronously(plugin, new ForceLoginTask(plugin, player));
}
}
}
public Set<UUID> loadBungeeCordIds() {
File whitelistFile = new File(plugin.getDataFolder(), FILE_NAME);
//create a new folder if it doesn't exist. Fail silently otherwise
whitelistFile.getParentFile().mkdir();
try {
if (!whitelistFile.exists()) {
whitelistFile.createNewFile();
}
Set<UUID> ids = Sets.newHashSet();
List<String> lines = Files.readLines(whitelistFile, Charsets.UTF_8);
for (String line : lines) {
if (line == null || line.trim().isEmpty()) {
continue;
}
UUID uuid = UUID.fromString(line.trim());
ids.add(uuid);
}
return ids;
} catch (IOException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to create file for Proxy whitelist", ex);
} catch (Exception ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to retrieve proxy Id. Disabling BungeeCord support", ex);
}
return null;
}
}

View File

@@ -0,0 +1,68 @@
package com.github.games647.fastlogin.bukkit.listener;
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.ForceLoginTask;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
/**
* This listener tells authentication plugins if the player has a premium account and we checked it successfully. So the
* plugin can skip authentication.
*/
public class ConnectionListener implements Listener {
private static final long DELAY_LOGIN = 20L / 2;
private final FastLoginBukkit plugin;
public ConnectionListener(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
removeBlockedStatus(loginEvent.getPlayer());
}
@EventHandler(ignoreCancelled = true)
public void onPlayerJoin(PlayerJoinEvent joinEvent) {
Player player = joinEvent.getPlayer();
Bukkit.getScheduler().runTaskLater(plugin, () -> {
// session exists so the player is ready for force login
// cases: Paper (firing proxy message before PlayerJoinEvent) or not running proxy and already
// having the login session from the login process
BukkitLoginSession session = plugin.getSessionManager().getLoginSession(player.getAddress());
if (session != null) {
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player, session);
Bukkit.getScheduler().runTaskAsynchronously(plugin, forceLoginTask);
}
plugin.getProxyManager().markJoinEventFired(player);
// delay the login process to let auth plugins initialize the player
// Magic number however as there is no direct event from those plugins
}, DELAY_LOGIN);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent quitEvent) {
Player player = quitEvent.getPlayer();
removeBlockedStatus(player);
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
plugin.getPremiumPlayers().remove(player.getUniqueId());
plugin.getProxyManager().cleanup(player);
}
private void removeBlockedStatus(Player player) {
player.removeMetadata(plugin.getName(), plugin);
}
}

View File

@@ -0,0 +1,42 @@
package com.github.games647.fastlogin.bukkit.listener;
import com.destroystokyo.paper.profile.ProfileProperty;
import com.github.games647.craftapi.model.skin.Textures;
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
import org.bukkit.event.player.AsyncPlayerPreLoginEvent.Result;
public class PaperPreLoginListener implements Listener {
private final FastLoginBukkit plugin;
public PaperPreLoginListener(final FastLoginBukkit plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.HIGHEST)
//if paper is used - player skin must be set at pre login, otherwise usercache is used
//using usercache makes premium name change basically impossible
public void onAsyncPlayerPreLogin(AsyncPlayerPreLoginEvent event) {
if (event.getLoginResult() != Result.ALLOWED) {
return;
}
// event gives us only IP, not the port, so we need to loop through all the sessions
for (BukkitLoginSession session : plugin.getSessionManager().getLoginSessions().values()) {
if (!event.getName().equals(session.getUsername())) {
continue;
}
session.getSkin().ifPresent(skin -> event.getPlayerProfile().setProperty(new ProfileProperty(Textures.KEY,
skin.getValue(), skin.getSignature())));
break;
}
}
}

View File

@@ -1,107 +0,0 @@
package com.github.games647.fastlogin.bukkit.listener;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
import com.github.games647.fastlogin.core.PlayerProfile;
import java.net.InetSocketAddress;
import java.util.UUID;
import java.util.logging.Level;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import protocolsupport.api.events.PlayerLoginStartEvent;
import protocolsupport.api.events.PlayerPropertiesResolveEvent;
public class ProtocolSupportListener implements Listener {
protected final FastLoginBukkit plugin;
public ProtocolSupportListener(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@EventHandler(ignoreCancelled = true)
public void onLoginStart(PlayerLoginStartEvent loginStartEvent) {
plugin.setServerStarted();
if (loginStartEvent.isLoginDenied()) {
return;
}
String username = loginStartEvent.getName();
//remove old data every time on a new login in order to keep the session only for one person
plugin.getSessions().remove(loginStartEvent.getAddress().toString());
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
if (authPlugin == null) {
return;
}
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(username);
if (profile != null) {
if (profile.getUserId() == -1) {
UUID premiumUUID = null;
if (plugin.getConfig().getBoolean("nameChangeCheck") || plugin.getConfig().getBoolean("autoRegister")) {
premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username);
}
//user not exists in the db
try {
if (premiumUUID != null && plugin.getConfig().getBoolean("nameChangeCheck")) {
profile = plugin.getCore().getStorage().loadProfile(premiumUUID);
if (profile != null) {
plugin.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID);
startPremiumSession(username, loginStartEvent, false, profile);
return;
}
}
if (premiumUUID != null
&& plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) {
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
startPremiumSession(username, loginStartEvent, false, profile);
return;
}
//no premium check passed so we save it as a cracked player
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
plugin.getSessions().put(loginStartEvent.getAddress().toString(), loginSession);
} catch (Exception ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to query isRegistered", ex);
}
} else if (profile.isPremium()) {
startPremiumSession(username, loginStartEvent, true, profile);
} else {
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
plugin.getSessions().put(loginStartEvent.getAddress().toString(), loginSession);
}
}
}
@EventHandler(ignoreCancelled = true)
public void onPropertiesResolve(PlayerPropertiesResolveEvent propertiesResolveEvent) {
//skin was resolved -> premium player
if (propertiesResolveEvent.hasProperty("textures")) {
InetSocketAddress address = propertiesResolveEvent.getAddress();
BukkitLoginSession session = plugin.getSessions().get(address.toString());
if (session != null) {
session.setVerified(true);
}
}
}
private void startPremiumSession(String playerName, PlayerLoginStartEvent loginStartEvent, boolean registered
, PlayerProfile playerProfile) {
loginStartEvent.setOnlineMode(true);
InetSocketAddress address = loginStartEvent.getAddress();
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, null, null, registered, playerProfile);
plugin.getSessions().put(address.toString(), playerSession);
if (plugin.getConfig().getBoolean("premiumUuid")) {
loginStartEvent.setUseOnlineModeUUID(true);
}
}
}

View File

@@ -1,229 +0,0 @@
package com.github.games647.fastlogin.bukkit.listener.packet;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.EncryptionUtil;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.util.Arrays;
import java.util.UUID;
import java.util.logging.Level;
import javax.crypto.SecretKey;
import org.bukkit.entity.Player;
/**
* Handles incoming encryption responses from connecting clients.
* It prevents them from reaching the server because that cannot handle
* it in offline mode.
*
* Moreover this manages a started premium check from
* this plugin. So check if all data is correct and we can prove him as a
* owner of a paid minecraft account.
*
* Receiving packet information:
* http://wiki.vg/Protocol#Encryption_Response
*
* sharedSecret=encrypted byte array
* verify token=encrypted byte array
*/
public class EncryptionPacketListener extends PacketAdapter {
private final ProtocolManager protocolManager;
//hides the inherit Plugin plugin field, but we need this type
private final FastLoginBukkit plugin;
public EncryptionPacketListener(FastLoginBukkit plugin, ProtocolManager protocolManger) {
//run async in order to not block the server, because we make api calls to Mojang
super(params(plugin, PacketType.Login.Client.ENCRYPTION_BEGIN).optionAsync());
this.plugin = plugin;
this.protocolManager = protocolManger;
}
/**
* C->S : Handshake State=2
* C->S : Login Start
* S->C : Encryption Key Request
* (Client Auth)
* C->S : Encryption Key Response
* (Server Auth, Both enable encryption)
* S->C : Login Success (*)
*
* On offline logins is Login Start followed by Login Success
*
* Minecraft Server implementation
* https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L180
*/
@Override
public void onPacketReceiving(PacketEvent packetEvent) {
Player player = packetEvent.getPlayer();
BukkitLoginSession session = plugin.getSessions().get(player.getAddress().toString());
if (session == null) {
disconnect(packetEvent, plugin.getCore().getMessage("invalid-requst"), true
, "Player {0} tried to send encryption response at invalid state", player.getAddress());
return;
}
PrivateKey privateKey = plugin.getServerKey().getPrivate();
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
SecretKey loginKey = EncryptionUtil.decryptSharedKey(privateKey, sharedSecret);
if (!checkVerifyToken(session, privateKey, packetEvent) || !encryptConnection(player, loginKey, packetEvent)) {
return;
}
//this makes sure the request from the client is for us
//this might be relevant http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
String generatedId = session.getServerId();
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L193
//generate the server id based on client and server data
byte[] serverIdHash = EncryptionUtil.getServerIdHash(generatedId, plugin.getServerKey().getPublic(), loginKey);
String serverId = (new BigInteger(serverIdHash)).toString(16);
String username = session.getUsername();
if (plugin.getCore().getMojangApiConnector().hasJoinedServer(session, serverId)) {
plugin.getLogger().log(Level.FINE, "Player {0} has a verified premium account", username);
session.setVerified(true);
setPremiumUUID(session, player);
receiveFakeStartPacket(username, player);
} else {
//user tried to fake a authentication
disconnect(packetEvent, plugin.getCore().getMessage("invalid-session"), true
, "Player {0} ({1}) tried to log in with an invalid session ServerId: {2}"
, session.getUsername(), player.getAddress(), serverId);
}
//this is a fake packet; it shouldn't be send to the server
packetEvent.setCancelled(true);
}
private void setPremiumUUID(BukkitLoginSession session, Player player) {
UUID uuid = session.getUuid();
if (plugin.getConfig().getBoolean("premiumUuid") && uuid != null) {
try {
Object networkManager = getNetworkManager(player);
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/NetworkManager.java#L69
Field spoofField = FuzzyReflection.fromObject(networkManager).getFieldByType("spoofedUUID", UUID.class);
spoofField.set(networkManager, uuid);
} catch (ReflectiveOperationException reflectiveOperationException) {
plugin.getLogger().log(Level.SEVERE, "Error setting premium uuid", reflectiveOperationException);
}
}
}
private boolean checkVerifyToken(BukkitLoginSession session, PrivateKey privateKey, PacketEvent packetEvent) {
byte[] requestVerify = session.getVerifyToken();
//encrypted verify token
byte[] responseVerify = packetEvent.getPacket().getByteArrays().read(1);
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L182
if (!Arrays.equals(requestVerify, EncryptionUtil.decryptData(privateKey, responseVerify))) {
//check if the verify token are equal to the server sent one
disconnect(packetEvent, plugin.getCore().getMessage("invalid-verify-token"), true
, "Player {0} ({1}) tried to login with an invalid verify token. Server: {2} Client: {3}"
, session.getUsername(), packetEvent.getPlayer().getAddress(), requestVerify, responseVerify);
return false;
}
return true;
}
//try to get the networkManager from ProtocolLib
private Object getNetworkManager(Player player)
throws IllegalAccessException, NoSuchFieldException {
Object socketInjector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
Field injectorField = socketInjector.getClass().getDeclaredField("injector");
injectorField.setAccessible(true);
Object rawInjector = injectorField.get(socketInjector);
injectorField = rawInjector.getClass().getDeclaredField("networkManager");
injectorField.setAccessible(true);
return injectorField.get(rawInjector);
}
private boolean encryptConnection(Player player, SecretKey loginKey, PacketEvent packetEvent)
throws IllegalArgumentException {
try {
//get the NMS connection handle of this player
Object networkManager = getNetworkManager(player);
//try to detect the method by parameters
Method encryptConnectionMethod = FuzzyReflection
.fromObject(networkManager).getMethodByParameters("a", SecretKey.class);
//encrypt/decrypt following packets
//the client expects this behaviour
encryptConnectionMethod.invoke(networkManager, loginKey);
} catch (ReflectiveOperationException ex) {
disconnect(packetEvent, plugin.getCore().getMessage("error-kick"), false, "Couldn't enable encryption", ex);
return false;
}
return true;
}
private void disconnect(PacketEvent packetEvent, String kickReason, boolean debugLevel, String logMessage
, Object... arguments) {
if (debugLevel) {
plugin.getLogger().log(Level.FINE, logMessage, arguments);
} else {
plugin.getLogger().log(Level.SEVERE, logMessage, arguments);
}
kickPlayer(packetEvent.getPlayer(), kickReason);
//cancel the event in order to prevent the server receiving an invalid packet
packetEvent.setCancelled(true);
}
private void kickPlayer(Player player, String reason) {
PacketContainer kickPacket = protocolManager.createPacket(PacketType.Login.Server.DISCONNECT);
kickPacket.getChatComponents().write(0, WrappedChatComponent.fromText(reason));
try {
//send kick packet at login state
//the normal event.getPlayer.kickPlayer(String) method does only work at play state
protocolManager.sendServerPacket(player, kickPacket);
//tell the server that we want to close the connection
player.kickPlayer("Disconnect");
} catch (InvocationTargetException ex) {
plugin.getLogger().log(Level.SEVERE, "Error sending kickpacket", ex);
}
}
//fake a new login packet in order to let the server handle all the other stuff
private void receiveFakeStartPacket(String username, Player from) {
//see StartPacketListener for packet information
PacketContainer startPacket = protocolManager.createPacket(PacketType.Login.Client.START);
//uuid is ignored by the packet definition
WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username);
startPacket.getGameProfiles().write(0, fakeProfile);
try {
//we don't want to handle our own packets so ignore filters
protocolManager.recieveClientPacket(from, startPacket, false);
} catch (InvocationTargetException | IllegalAccessException ex) {
plugin.getLogger().log(Level.WARNING, "Failed to fake a new start packet", ex);
//cancel the event in order to prevent the server receiving an invalid packet
kickPlayer(from, plugin.getCore().getMessage("error-kick"));
}
}
}

View File

@@ -1,174 +0,0 @@
package com.github.games647.fastlogin.bukkit.listener.packet;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
import com.github.games647.fastlogin.core.PlayerProfile;
import java.lang.reflect.InvocationTargetException;
import java.security.PublicKey;
import java.util.Random;
import java.util.UUID;
import java.util.logging.Level;
import org.bukkit.entity.Player;
/**
* Handles incoming start packets from connecting clients. It
* checks if we can start checking if the player is premium and
* start a request to the client that it should start online mode
* login.
*
* Receiving packet information:
* http://wiki.vg/Protocol#Login_Start
*
* String=Username
*/
public class StartPacketListener extends PacketAdapter {
private static final int VERIFY_TOKEN_LENGTH = 4;
private final ProtocolManager protocolManager;
//hides the inherit Plugin plugin field, but we need a more detailed type than just Plugin
private final FastLoginBukkit plugin;
//just create a new once on plugin enable. This used for verify token generation
private final Random random = new Random();
public StartPacketListener(FastLoginBukkit plugin, ProtocolManager protocolManger) {
//run async in order to not block the server, because we are making api calls to Mojang
super(params(plugin, PacketType.Login.Client.START).optionAsync());
this.plugin = plugin;
this.protocolManager = protocolManger;
}
/**
* C->S : Handshake State=2
* C->S : Login Start
* S->C : Encryption Key Request
* (Client Auth)
* C->S : Encryption Key Response
* (Server Auth, Both enable encryption)
* S->C : Login Success (*)
*
* On offline logins is Login Start followed by Login Success
*/
@Override
public void onPacketReceiving(PacketEvent packetEvent) {
plugin.setServerStarted();
Player player = packetEvent.getPlayer();
//this includes ip:port. Should be unique for an incoming login request with a timeout of 2 minutes
String sessionKey = player.getAddress().toString();
//remove old data every time on a new login in order to keep the session only for one person
plugin.getSessions().remove(sessionKey);
//player.getName() won't work at this state
PacketContainer packet = packetEvent.getPacket();
String username = packet.getGameProfiles().read(0).getName();
plugin.getLogger().log(Level.FINER, "Player {0} with {1} connecting to the server"
, new Object[]{sessionKey, username});
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
if (authPlugin == null) {
return;
}
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(username);
if (profile != null) {
if (profile.getUserId() == -1) {
UUID premiumUUID = null;
if (plugin.getConfig().getBoolean("nameChangeCheck") || plugin.getConfig().getBoolean("autoRegister")) {
premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username);
}
//user not exists in the db
try {
if (premiumUUID != null && plugin.getConfig().getBoolean("nameChangeCheck")) {
profile = plugin.getCore().getStorage().loadProfile(premiumUUID);
if (profile != null) {
plugin.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID);
enablePremiumLogin(username, profile, sessionKey, player, packetEvent, false);
return;
}
}
if (premiumUUID != null
&& plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) {
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
enablePremiumLogin(username, profile, sessionKey, player, packetEvent, false);
return;
}
//no premium check passed so we save it as a cracked player
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
plugin.getSessions().put(sessionKey, loginSession);
} catch (Exception ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to query isRegistered", ex);
}
} else if (profile.isPremium()) {
enablePremiumLogin(username, profile, sessionKey, player, packetEvent, true);
} else {
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
plugin.getSessions().put(sessionKey, loginSession);
}
}
}
//minecraft server implementation
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
private void enablePremiumLogin(String username, PlayerProfile profile, String sessionKey, Player player
, PacketEvent packetEvent, boolean registered) {
//randomized server id to make sure the request is for our server
//this could be relevant http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
String serverId = Long.toString(random.nextLong(), 16);
//generate a random token which should be the same when we receive it from the client
byte[] verifyToken = new byte[VERIFY_TOKEN_LENGTH];
random.nextBytes(verifyToken);
boolean success = sentEncryptionRequest(player, serverId, verifyToken);
if (success) {
BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId
, verifyToken, registered, profile);
plugin.getSessions().put(sessionKey, playerSession);
//cancel only if the player has a paid account otherwise login as normal offline player
packetEvent.setCancelled(true);
}
}
private boolean sentEncryptionRequest(Player player, String serverId, byte[] verifyToken) {
try {
/**
* Packet Information: http://wiki.vg/Protocol#Encryption_Request
*
* ServerID="" (String)
* key=public server key
* verifyToken=random 4 byte array
*/
PacketContainer newPacket = protocolManager.createPacket(PacketType.Login.Server.ENCRYPTION_BEGIN);
newPacket.getStrings().write(0, serverId);
newPacket.getSpecificModifier(PublicKey.class).write(0, plugin.getServerKey().getPublic());
newPacket.getByteArrays().write(0, verifyToken);
//serverId is a empty string
protocolManager.sendServerPacket(player, newPacket);
return true;
} catch (InvocationTargetException ex) {
plugin.getLogger().log(Level.SEVERE, "Cannot send encryption packet. Falling back to normal login", ex);
}
return false;
}
}

View File

@@ -1,71 +0,0 @@
package com.github.games647.fastlogin.bukkit.tasks;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.hooks.AuthMeHook;
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
import com.github.games647.fastlogin.bukkit.hooks.CrazyLoginHook;
import com.github.games647.fastlogin.bukkit.hooks.LogItHook;
import com.github.games647.fastlogin.bukkit.hooks.LoginSecurityHook;
import com.github.games647.fastlogin.bukkit.hooks.UltraAuthHook;
import com.github.games647.fastlogin.bukkit.hooks.xAuthHook;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.logging.Level;
import org.bukkit.Bukkit;
public class DelayedAuthHook implements Runnable {
private final FastLoginBukkit plugin;
public DelayedAuthHook(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@Override
public void run() {
boolean hookFound = registerHooks();
if (plugin.isBungeeCord()) {
plugin.getLogger().info("BungeeCord setting detected. No auth plugin is required");
} else if (!hookFound) {
plugin.getLogger().warning("No auth plugin were found by this plugin "
+ "(other plugins could hook into this after the intialization of this plugin)"
+ "and bungeecord is deactivated. "
+ "Either one or both of the checks have to pass in order to use this plugin");
}
}
private boolean registerHooks() {
BukkitAuthPlugin authPluginHook = null;
try {
List<Class<? extends BukkitAuthPlugin>> supportedHooks = Lists.newArrayList(AuthMeHook.class
, CrazyLoginHook.class, LogItHook.class, LoginSecurityHook.class, UltraAuthHook.class
, xAuthHook.class);
for (Class<? extends BukkitAuthPlugin> clazz : supportedHooks) {
String pluginName = clazz.getSimpleName().replace("Hook", "");
//uses only member classes which uses AuthPlugin interface (skip interfaces)
if (Bukkit.getServer().getPluginManager().getPlugin(pluginName) != null) {
//check only for enabled plugins. A single plugin could be disabled by plugin managers
authPluginHook = clazz.newInstance();
plugin.getLogger().log(Level.INFO, "Hooking into auth plugin: {0}", pluginName);
break;
}
}
} catch (InstantiationException | IllegalAccessException ex) {
plugin.getLogger().log(Level.SEVERE, "Couldn't load the integration class", ex);
}
if (authPluginHook == null) {
//run this check for exceptions (errors) and not found plugins
plugin.getLogger().warning("No support offline Auth plugin found. ");
return false;
}
if (plugin.getAuthPlugin() == null) {
plugin.setAuthPluginHook(authPluginHook);
}
return true;
}
}

View File

@@ -1,135 +0,0 @@
package com.github.games647.fastlogin.bukkit.tasks;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
import com.github.games647.fastlogin.core.PlayerProfile;
import com.github.games647.fastlogin.core.Storage;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
public class ForceLoginTask implements Runnable {
private final FastLoginBukkit plugin;
protected final Player player;
public ForceLoginTask(FastLoginBukkit plugin, Player player) {
this.plugin = plugin;
this.player = player;
}
@Override
public void run() {
if (!isOnlineThreadSafe()) {
return;
}
//remove the bungeecord identifier if there is ones
String id = '/' + player.getAddress().getAddress().getHostAddress() + ':' + player.getAddress().getPort();
BukkitLoginSession session = plugin.getSessions().remove(id);
if (session == null) {
return;
}
Storage storage = plugin.getCore().getStorage();
PlayerProfile playerProfile = session.getProfile();
//check if it's the same player as we checked before
if (session.isVerified() && player.getName().equals(session.getUsername())) {
//premium player
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
if (authPlugin == null) {
//maybe only bungeecord plugin
sendSuccessNotification();
} else {
boolean success = false;
if (isOnlineThreadSafe()) {
if (session.needsRegistration()) {
success = forceRegister(authPlugin, player);
} else {
success = forceLogin(authPlugin, player);
}
}
if (success) {
//update only on success to prevent corrupt data
if (playerProfile != null) {
playerProfile.setUuid(session.getUuid());
//save cracked players too
playerProfile.setPremium(session.isVerified());
storage.save(playerProfile);
}
sendSuccessNotification();
}
}
} else {
//cracked player
if (playerProfile != null) {
playerProfile.setUuid(null);
playerProfile.setPremium(false);
storage.save(playerProfile);
}
}
}
private boolean forceRegister(BukkitAuthPlugin authPlugin, Player player) {
plugin.getLogger().log(Level.FINE, "Register player {0}", player.getName());
String generatedPassword = plugin.generateStringPassword(player);
boolean success = authPlugin.forceRegister(player, generatedPassword);
String message = plugin.getCore().getMessage("auto-register");
if (message != null) {
message = message.replace("%password", generatedPassword);
player.sendMessage(message);
}
return success;
}
private boolean forceLogin(BukkitAuthPlugin authPlugin, Player player) {
plugin.getLogger().log(Level.FINE, "Logging player {0} in", player.getName());
boolean success = authPlugin.forceLogin(player);
String message = plugin.getCore().getMessage("auto-login");
if (message != null) {
player.sendMessage(message);
}
return success;
}
private void sendSuccessNotification() {
if (plugin.isBungeeCord()) {
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
dataOutput.writeUTF("SUCCESS");
player.sendPluginMessage(plugin, plugin.getName(), dataOutput.toByteArray());
}
}
private boolean isOnlineThreadSafe() {
//the playerlist isn't thread-safe
Future<Boolean> onlineFuture = Bukkit.getScheduler().callSyncMethod(plugin, new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return player.isOnline();
}
});
try {
return onlineFuture.get();
} catch (InterruptedException | ExecutionException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to perform thread-safe online check", ex);
return false;
}
}
}

View File

@@ -1,32 +1,32 @@
# project informations for Bukkit in order to register our plugin with all it components
# project data for Bukkit in order to register our plugin with all it components
# ${-} are variables from Maven (pom.xml) which will be replaced after the build
name: ${project.parent.name}
version: ${project.version}
version: ${project.version}-${git.commit.id.abbrev}
main: ${project.groupId}.${project.artifactId}.${project.name}
# meta informations for plugin managers
# meta data for plugin managers
authors: [games647, 'https://github.com/games647/FastLogin/graphs/contributors']
description: |
${project.description}
website: ${project.url}
dev-url: ${project.url}
# Load the plugin as early as possible to inject it for all players
load: STARTUP
# Without Protocollib the plugin does not work at all
depend: [ProtocolLib]
# This plugin don't have to be transformed for compatibility with Minecraft >= 1.13
api-version: '1.13'
softdepend:
# We depend either ProtocolLib or ProtocolSupport
- ProtocolSupport
- ProtocolLib
# Premium variable
- PlaceholderAPI
# Auth plugins
# - xAuth
# - AuthMe
# - LogIt
# - CrazyLogin
# - LoginSecurity
# - RoyalAuth
# - UltraAuth
- AuthMe
- LoginSecurity
- xAuth
- LogIt
- UltraAuth
- CrazyLogin
commands:
${project.parent.name}:
@@ -36,10 +36,10 @@ commands:
permission: ${project.artifactId}.command.premium
cracked:
description: 'Label the invoker or the player specified as cracked if he was marked premium before'
description: 'Label the invoker or the player specified as cracked if marked premium before'
aliases: [unpremium]
usage: /<command> [player]
permission: ${project.artifactId}.command.unpremium
permission: ${project.artifactId}.command.cracked
permissions:
${project.artifactId}.command.premium:
@@ -55,7 +55,7 @@ permissions:
description: 'Label themselves as cracked'
default: true
${project.artifactId}.command..cracked.other:
${project.artifactId}.command.cracked.other:
description: 'Label others as cracked'
children:
${project.artifactId}.command.cracked: true
${project.artifactId}.command.cracked: true

View File

@@ -0,0 +1,43 @@
package com.github.games647.fastlogin.bukkit.listener.protocollib;
import com.github.games647.fastlogin.bukkit.auth.protocollib.EncryptionUtil;
import java.security.SecureRandom;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
public class EncryptionUtilTest {
@Test
public void testVerifyToken() throws Exception {
SecureRandom random = new SecureRandom();
byte[] token = EncryptionUtil.generateVerifyToken(random);
assertThat(token, notNullValue());
assertThat(token.length, is(4));
}
// @Test
// public void testDecryptSharedSecret() throws Exception {
//
// }
//
// @Test
// public void testDecryptData() throws Exception {
//
// }
// private static SecretKey createNewSharedKey() {
// try {
// KeyGenerator keygenerator = KeyGenerator.getInstance("AES");
// keygenerator.init(128);
// return keygenerator.generateKey();
// } catch (NoSuchAlgorithmException nosuchalgorithmexception) {
// throw new Error(nosuchalgorithmexception);
// }
// }
}

Binary file not shown.

View File

@@ -1,11 +1,11 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.games647</groupId>
<artifactId>fastlogin</artifactId>
<version>1.5.1</version>
<version>1.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -16,52 +16,100 @@
<!--Represents the main plugin-->
<name>FastLoginBungee</name>
<repositories>
<!--Waterfall-->
<!-- <repository>
<id>ellune-releases</id>
<url>https://repo.ellune.net/content/repositories/snapshots/</url>
</repository>-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<shadedArtifactAttached>false</shadedArtifactAttached>
<artifactSet>
<excludes>
<!--Those classes are already present in BungeeCord version-->
<exclude>net.md-5:bungeecord-config</exclude>
<exclude>com.google.code.gson:gson</exclude>
</excludes>
</artifactSet>
<relocations>
<relocation>
<pattern>com.zaxxer.hikari</pattern>
<shadedPattern>fastlogin.hikari</shadedPattern>
</relocation>
<relocation>
<pattern>org.slf4j</pattern>
<shadedPattern>fastlogin.slf4j</shadedPattern>
</relocation>
</relocations>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!--BungeeCord with also the part outside the API-->
<repositories>
<repository>
<id>RYRED-REPO</id>
<url>http://mvn.ryred.co/repository/snapshots/</url>
<id>codemc-repo</id>
<url>https://repo.codemc.io/repository/maven-public/</url>
</repository>
<!--Github automatic maven builds-->
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
<id>nukkitx-repo</id>
<url>https://repo.nukkitx.com/maven-snapshots/</url>
</repository>
<repository>
<id>spigotplugins-repo</id>
<url>https://maven.gamestrike.de/mvn/</url>
</repository>
</repositories>
<dependencies>
<!--Common plugin component-->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>fastlogin.core</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<!--BungeeCord with also the part outside the API-->
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-proxy</artifactId>
<version>1.9-SNAPSHOT</version>
<version>1.16-R0.5-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- <dependency>
<groupId>io.github.waterfallmc</groupId>
<artifactId>waterfall-api</artifactId>
<version>1.9-SNAPSHOT</version>
<type>jar</type>
<!-- Bedrock player bridge -->
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>floodgate-bungee</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>-->
</dependency>
<!--Login plugin-->
<dependency>
<groupId>me.vik1395</groupId>
<artifactId>BungeeAuth</artifactId>
<version>1.4</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/BungeeAuth-1.4.jar</systemPath>
</dependency>
<dependency>
<groupId>com.github.MatteCarra</groupId>
<artifactId>BungeeAuth</artifactId>
<version>-1.2.1-gc367d92-8</version>
<groupId>de.xxschrandxx.bca</groupId>
<artifactId>BungeeCordAuthenticator</artifactId>
<version>0.0.2-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,85 +0,0 @@
package com.github.games647.fastlogin.bungee;
import com.github.games647.fastlogin.core.FastLoginCore;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.scheduler.GroupedThreadFactory;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
public class BungeeCore extends FastLoginCore {
private final FastLoginBungee plugin;
public BungeeCore(FastLoginBungee plugin) {
this.plugin = plugin;
}
@Override
public File getDataFolder() {
return plugin.getDataFolder();
}
@Override
public Logger getLogger() {
return plugin.getLogger();
}
@Override
public ThreadFactory getThreadFactory() {
String pluginName = plugin.getDescription().getName();
return new ThreadFactoryBuilder()
.setNameFormat(pluginName + " Database Pool Thread #%1$d")
//Hikari create daemons by default
.setDaemon(true)
.setThreadFactory(new GroupedThreadFactory(plugin, pluginName)).build();
}
@Override
public void loadMessages() {
try {
saveDefaultFile("messages.yml");
File messageFile = new File(getDataFolder(), "messages.yml");
Configuration messageConfig = ConfigurationProvider.getProvider(YamlConfiguration.class).load(messageFile);
for (String key : messageConfig.getKeys()) {
String message = ChatColor.translateAlternateColorCodes('&', messageConfig.getString(key));
if (!message.isEmpty()) {
localeMessages.put(key, message);
}
}
} catch (IOException ex) {
getLogger().log(Level.SEVERE, "Failed to load messages", ex);
}
}
@Override
public void loadConfig() {
if (!getDataFolder().exists()) {
getDataFolder().mkdir();
}
saveDefaultFile("config.yml");
}
private void saveDefaultFile(String fileName) {
File configFile = new File(getDataFolder(), fileName);
if (!configFile.exists()) {
try (InputStream in = plugin.getResourceAsStream(fileName)) {
Files.copy(in, configFile.toPath());
} catch (IOException ioExc) {
getLogger().log(Level.SEVERE, "Error saving default " + fileName, ioExc);
}
}
}
}

View File

@@ -0,0 +1,43 @@
package com.github.games647.fastlogin.bungee;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.LoginSession;
public class BungeeLoginSession extends LoginSession {
private boolean alreadySaved;
private boolean alreadyLogged;
public BungeeLoginSession(String username, boolean registered, StoredProfile profile) {
super(username, registered, profile);
}
public synchronized void setRegistered(boolean registered) {
this.registered = registered;
}
public synchronized boolean isAlreadySaved() {
return alreadySaved;
}
public synchronized void setAlreadySaved(boolean alreadySaved) {
this.alreadySaved = alreadySaved;
}
public synchronized boolean isAlreadyLogged() {
return alreadyLogged;
}
public synchronized void setAlreadyLogged(boolean alreadyLogged) {
this.alreadyLogged = alreadyLogged;
}
@Override
public synchronized String toString() {
return this.getClass().getSimpleName() + '{' +
"alreadySaved=" + alreadySaved +
", alreadyLogged=" + alreadyLogged +
", registered=" + registered +
"} " + super.toString();
}
}

View File

@@ -0,0 +1,54 @@
package com.github.games647.fastlogin.bungee;
import com.github.games647.fastlogin.core.auth.LoginSource;
import java.net.InetSocketAddress;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.event.PreLoginEvent;
public class BungeeLoginSource implements LoginSource {
private final PendingConnection connection;
private final PreLoginEvent preLoginEvent;
public BungeeLoginSource(PendingConnection connection, PreLoginEvent preLoginEvent) {
this.connection = connection;
this.preLoginEvent = preLoginEvent;
}
@Override
public void setOnlineMode() {
connection.setOnlineMode(true);
}
@Override
public void kick(String message) {
preLoginEvent.setCancelled(true);
if (message == null) {
preLoginEvent.setCancelReason(new ComponentBuilder("Kicked").color(ChatColor.WHITE).create());
} else {
preLoginEvent.setCancelReason(TextComponent.fromLegacyText(message));
}
}
@Override
public InetSocketAddress getAddress() {
return connection.getAddress();
}
public PendingConnection getConnection() {
return connection;
}
@Override
public String toString() {
return this.getClass().getSimpleName() + '{' +
"connection=" + connection +
'}';
}
}

View File

@@ -0,0 +1,23 @@
package com.github.games647.fastlogin.bungee;
import com.github.games647.fastlogin.core.SessionManager;
import java.util.UUID;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
public class BungeeSessionManager extends SessionManager<PlayerDisconnectEvent, PendingConnection, BungeeLoginSession>
implements Listener {
//todo: memory leak on cancelled login event
@EventHandler
public void onPlayQuit(PlayerDisconnectEvent disconnectEvent) {
ProxiedPlayer player = disconnectEvent.getPlayer();
UUID playerId = player.getUniqueId();
endPlaySession(playerId);
}
}

View File

@@ -1,125 +1,139 @@
package com.github.games647.fastlogin.bungee;
import com.github.games647.fastlogin.bungee.hooks.BungeeAuthHook;
import com.github.games647.fastlogin.bungee.hooks.BungeeAuthPlugin;
import com.github.games647.fastlogin.bungee.listener.PlayerConnectionListener;
import com.github.games647.fastlogin.bungee.hook.BungeeAuthHook;
import com.github.games647.fastlogin.bungee.hook.BungeeCordAuthenticatorHook;
import com.github.games647.fastlogin.bungee.listener.ConnectListener;
import com.github.games647.fastlogin.bungee.listener.PluginMessageListener;
import com.github.games647.fastlogin.core.FastLoginCore;
import com.github.games647.fastlogin.core.LoginSession;
import com.google.common.cache.CacheBuilder;
import com.github.games647.fastlogin.core.AsyncScheduler;
import com.github.games647.fastlogin.core.CommonUtil;
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
import com.github.games647.fastlogin.core.message.ChannelMessage;
import com.github.games647.fastlogin.core.message.NamespaceKey;
import com.github.games647.fastlogin.core.message.SuccessMessage;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.PlatformPlugin;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.nio.file.Path;
import java.util.concurrent.ThreadFactory;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.api.scheduler.GroupedThreadFactory;
import org.slf4j.Logger;
/**
* BungeeCord version of FastLogin. This plugin keeps track on online mode connections.
*/
public class FastLoginBungee extends Plugin {
public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSender> {
private static final char[] PASSWORD_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
.toCharArray();
private final BungeeSessionManager sessionManager = new BungeeSessionManager();
private final FastLoginCore loginCore = new BungeeCore(this);
private BungeeAuthPlugin bungeeAuthPlugin;
private Configuration configuration;
private final Random random = new Random();
private final ConcurrentMap<PendingConnection, LoginSession> session = CacheBuilder
.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES)
.<PendingConnection, LoginSession>build().asMap();
private FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core;
private AsyncScheduler scheduler;
private Logger logger;
@Override
public void onEnable() {
loginCore.setMojangApiConnector(new MojangApiBungee(loginCore));
logger = CommonUtil.createLoggerFromJDK(getLogger());
scheduler = new AsyncScheduler(logger, getThreadFactory());
loginCore.loadConfig();
loginCore.loadMessages();
try {
File configFile = new File(getDataFolder(), "config.yml");
configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile);
String driver = configuration.getString("driver");
String host = configuration.getString("host", "");
int port = configuration.getInt("port", 3306);
String database = configuration.getString("database");
String username = configuration.getString("username", "");
String password = configuration.getString("password", "");
if (!loginCore.setupDatabase(driver, host, port, database, username, password)) {
return;
}
} catch (IOException ioExc) {
getLogger().log(Level.SEVERE, "Error loading config. Disabling plugin...", ioExc);
core = new FastLoginCore<>(this);
core.load();
if (!core.setupDatabase()) {
return;
}
//events
getProxy().getPluginManager().registerListener(this, new PlayerConnectionListener(this));
getProxy().getPluginManager().registerListener(this, new PluginMessageListener(this));
PluginManager pluginManager = getProxy().getPluginManager();
boolean floodgateAvail = pluginManager.getPlugin("floodgate") != null;
ConnectListener connectListener = new ConnectListener(this, core.getRateLimiter(), floodgateAvail);
//this is required to listen to messages from the server
getProxy().registerChannel(getDescription().getName());
pluginManager.registerListener(this, connectListener);
pluginManager.registerListener(this, new PluginMessageListener(this));
//this is required to listen to incoming messages from the server
getProxy().registerChannel(NamespaceKey.getCombined(getName(), ChangePremiumMessage.CHANGE_CHANNEL));
getProxy().registerChannel(NamespaceKey.getCombined(getName(), SuccessMessage.SUCCESS_CHANNEL));
registerHook();
}
public String generateStringPassword() {
StringBuilder generatedPassword = new StringBuilder(8);
for (int i = 1; i <= 8; i++) {
generatedPassword.append(PASSWORD_CHARACTERS[random.nextInt(PASSWORD_CHARACTERS.length - 1)]);
}
return generatedPassword.toString();
}
@Override
public void onDisable() {
loginCore.close();
if (core != null) {
core.close();
}
}
public FastLoginCore getCore() {
return loginCore;
}
public void setAuthPluginHook(BungeeAuthPlugin authPlugin) {
this.bungeeAuthPlugin = authPlugin;
}
public Configuration getConfig() {
return configuration;
}
public ConcurrentMap<PendingConnection, LoginSession> getSession() {
return session;
}
/**
* Get the auth plugin hook for BungeeCord
*
* @return the auth hook for BungeeCord. null if none found
*/
public BungeeAuthPlugin getBungeeAuthPlugin() {
return bungeeAuthPlugin;
public FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> getCore() {
return core;
}
private void registerHook() {
Plugin plugin = getProxy().getPluginManager().getPlugin("BungeeAuth");
if (plugin != null) {
bungeeAuthPlugin = new BungeeAuthHook();
getLogger().info("Hooked into BungeeAuth");
Plugin BungeeAuth = getProxy().getPluginManager().getPlugin("BungeeAuth");
if (BungeeAuth != null) {
core.setAuthPluginHook(new BungeeAuthHook());
logger.info("Hooked into BungeeAuth");
}
Plugin BungeeCordAuthenticatorBungee = getProxy().getPluginManager().getPlugin("BungeeCordAuthenticatorBungee");
if (BungeeCordAuthenticatorBungee != null) {
logger.info("Try to hook into BungeeCordAuthenticatorBungee...");
BungeeCordAuthenticatorHook hook = new BungeeCordAuthenticatorHook(BungeeCordAuthenticatorBungee, logger);
core.setAuthPluginHook(hook);
}
}
public void sendPluginMessage(Server server, ChannelMessage message) {
if (server != null) {
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
message.writeTo(dataOutput);
NamespaceKey channel = new NamespaceKey(getName(), message.getChannelName());
server.sendData(channel.getCombinedName(), dataOutput.toByteArray());
}
}
@Override
public String getName() {
return getDescription().getName();
}
@Override
public Path getPluginFolder() {
return getDataFolder().toPath();
}
@Override
public Logger getLog() {
return logger;
}
@Override
public void sendMessage(CommandSender receiver, String message) {
receiver.sendMessage(TextComponent.fromLegacyText(message));
}
@Override
@SuppressWarnings("deprecation")
public ThreadFactory getThreadFactory() {
return new ThreadFactoryBuilder()
.setNameFormat(getName() + " Pool Thread #%1$d")
//Hikari create daemons by default
.setDaemon(true)
.setThreadFactory(new GroupedThreadFactory(this, getName()))
.build();
}
@Override
public AsyncScheduler getScheduler() {
return scheduler;
}
}

View File

@@ -1,27 +0,0 @@
package com.github.games647.fastlogin.bungee;
import com.github.games647.fastlogin.core.FastLoginCore;
import com.github.games647.fastlogin.core.MojangApiConnector;
import java.util.UUID;
import net.md_5.bungee.BungeeCord;
public class MojangApiBungee extends MojangApiConnector {
public MojangApiBungee(FastLoginCore plugin) {
super(plugin);
}
@Override
protected UUID getUUIDFromJson(String json) {
MojangPlayer mojangPlayer = BungeeCord.getInstance().gson.fromJson(json, MojangPlayer.class);
return FastLoginCore.parseId(mojangPlayer.getId());
}
@Override
public boolean hasJoinedServer(Object session, String serverId) {
//this is not needed in Bungee
throw new UnsupportedOperationException("Not supported");
}
}

View File

@@ -1,15 +0,0 @@
package com.github.games647.fastlogin.bungee;
public class MojangPlayer {
private String id;
private String name;
public String getId() {
return id;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,39 @@
package com.github.games647.fastlogin.bungee.event;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.LoginSession;
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
import net.md_5.bungee.api.plugin.Cancellable;
import net.md_5.bungee.api.plugin.Event;
public class BungeeFastLoginAutoLoginEvent extends Event implements FastLoginAutoLoginEvent, Cancellable {
private final LoginSession session;
private final StoredProfile profile;
private boolean cancelled;
public BungeeFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
this.session = session;
this.profile = profile;
}
@Override
public LoginSession getSession() {
return session;
}
@Override
public StoredProfile getProfile() {
return profile;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}

View File

@@ -0,0 +1,34 @@
package com.github.games647.fastlogin.bungee.event;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.LoginSource;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import net.md_5.bungee.api.plugin.Event;
public class BungeeFastLoginPreLoginEvent extends Event implements FastLoginPreLoginEvent {
private final String username;
private final LoginSource source;
private final StoredProfile profile;
public BungeeFastLoginPreLoginEvent(String username, LoginSource source, StoredProfile profile) {
this.username = username;
this.source = source;
this.profile = profile;
}
@Override
public String getUsername() {
return username;
}
@Override
public LoginSource getSource() {
return source;
}
@Override
public StoredProfile getProfile() {
return profile;
}
}

View File

@@ -0,0 +1,26 @@
package com.github.games647.fastlogin.bungee.event;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent;
import net.md_5.bungee.api.plugin.Event;
public class BungeeFastLoginPremiumToggleEvent extends Event implements FastLoginPremiumToggleEvent {
private final StoredProfile profile;
private final PremiumToggleReason reason;
public BungeeFastLoginPremiumToggleEvent(StoredProfile profile, PremiumToggleReason reason) {
this.profile = profile;
this.reason = reason;
}
@Override
public StoredProfile getProfile() {
return profile;
}
@Override
public FastLoginPremiumToggleEvent.PremiumToggleReason getReason() {
return reason;
}
}

View File

@@ -0,0 +1,36 @@
package com.github.games647.fastlogin.bungee.hook;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import me.vik1395.BungeeAuth.Main;
import me.vik1395.BungeeAuthAPI.RequestHandler;
import net.md_5.bungee.api.connection.ProxiedPlayer;
/**
* GitHub: https://github.com/vik1395/BungeeAuth-Minecraft
*
* Project page:
*
* Spigot: https://www.spigotmc.org/resources/bungeeauth.493/
*/
public class BungeeAuthHook implements AuthPlugin<ProxiedPlayer> {
private final RequestHandler requestHandler = new RequestHandler();
@Override
public boolean forceLogin(ProxiedPlayer player) {
String playerName = player.getName();
return Main.plonline.contains(playerName) || requestHandler.forceLogin(playerName);
}
@Override
public boolean isRegistered(String playerName) {
return requestHandler.isRegistered(playerName);
}
@Override
public boolean forceRegister(ProxiedPlayer player, String password) {
return requestHandler.forceRegister(player, password);
}
}

View File

@@ -0,0 +1,70 @@
package com.github.games647.fastlogin.bungee.hook;
import java.sql.SQLException;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import org.slf4j.Logger;
import de.xxschrandxx.bca.bungee.BungeeCordAuthenticatorBungee;
import de.xxschrandxx.bca.bungee.api.BungeeCordAuthenticatorBungeeAPI;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;
/**
* GitHub:
* https://github.com/xXSchrandXx/SpigotPlugins/tree/master/BungeeCordAuthenticator
*
* Project page:
*
* Spigot: https://www.spigotmc.org/resources/bungeecordauthenticator.87669/
*/
public class BungeeCordAuthenticatorHook implements AuthPlugin<ProxiedPlayer> {
public final BungeeCordAuthenticatorBungeeAPI api;
public BungeeCordAuthenticatorHook(Plugin plugin, Logger logger) {
BungeeCordAuthenticatorBungee bcab = (BungeeCordAuthenticatorBungee) plugin;
api = bcab.getAPI();
logger.info("BungeeCordAuthenticatorHook | Hooked successful!");
}
@Override
public boolean forceLogin(ProxiedPlayer player) {
if (api.isAuthenticated(player)) {
return true;
} else {
try {
api.setAuthenticated(player);
}
catch (SQLException e) {
e.printStackTrace();
return false;
}
return true;
}
}
@Override
public boolean isRegistered(String playerName) {
try {
return api.getSQL().checkPlayerEntry(playerName);
}
catch (SQLException e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean forceRegister(ProxiedPlayer player, String password) {
try {
return api.createPlayerEntry(player, password);
}
catch (SQLException e) {
e.printStackTrace();
return false;
}
}
}

View File

@@ -1,104 +0,0 @@
package com.github.games647.fastlogin.bungee.hooks;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import me.vik1395.BungeeAuth.ListenerClass;
import me.vik1395.BungeeAuth.Main;
import me.vik1395.BungeeAuth.Password.PasswordHandler;
import me.vik1395.BungeeAuth.Tables;
import net.md_5.bungee.api.connection.ProxiedPlayer;
/**
* Github: https://github.com/MatteCarra/BungeeAuth
*
* Project page:
*
* Spigot: https://www.spigotmc.org/resources/bungeeauth.493/
*/
public class BungeeAuthHook implements BungeeAuthPlugin {
//https://github.com/MatteCarra/BungeeAuth/blob/master/src/me/vik1395/BungeeAuth/Login.java#L32
private final Tables databaseConnection = new Tables();
@Override
public boolean forceLogin(ProxiedPlayer player) {
//https://github.com/MatteCarra/BungeeAuth/blob/master/src/me/vik1395/BungeeAuth/Login.java#L92-95
Main.plonline.add(player.getName());
//renamed from ct to databaseConnection
// databaseConnection.setStatus(player.getName(), "online");
Class<?>[] parameterTypes = new Class<?>[]{String.class, String.class};
Object[] arguments = new Object[]{player.getName(), "online"};
try {
callProtected("setStatus", parameterTypes, arguments);
ListenerClass.movePlayer(player, false);
//proparly not thread-safe
ListenerClass.prelogin.get(player.getName()).cancel();
} catch (Exception ex) {
Main.plugin.getLogger().severe("[BungeeAuth] Error force loging in player");
return false;
}
return true;
}
@Override
public boolean isRegistered(String playerName) throws Exception {
//https://github.com/MatteCarra/BungeeAuth/blob/master/src/me/vik1395/BungeeAuth/Register.java#L46
//renamed t to databaseConnection
return databaseConnection.checkPlayerEntry(playerName);
}
@Override
public boolean forceRegister(ProxiedPlayer player, String password) {
//https://github.com/MatteCarra/BungeeAuth/blob/master/src/me/vik1395/BungeeAuth/Register.java#L102
PasswordHandler ph = new PasswordHandler();
Random rand = new Random();
int maxp = 7; //Total Password Hashing methods.
Date dNow = new Date();
SimpleDateFormat ft = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
String Pw = password;
String pType = "" + rand.nextInt(maxp + 1);
String regdate = ft.format(dNow);
//https://github.com/MatteCarra/BungeeAuth/blob/master/src/me/vik1395/BungeeAuth/Register.java#L60
String lastip = player.getAddress().getAddress().getHostAddress();
String lastseen = regdate;
String hash = ph.newHash(Pw, pType);
//creates a new SQL entry with the player's details.
//renamed t to databaseConnection
// databaseConnection.newPlayerEntry(player.getName(), hash, pType, "", lastip, regdate, lastip, lastseen);
Class<?>[] parameterTypes = new Class<?>[] {String.class, String.class, String.class, String.class
, String.class, String.class, String.class, String.class};
Object[] arguments = new Object[] {player.getName(), hash, pType, "", lastip, regdate, lastip, lastseen};
try {
callProtected("newPlayerEntry", parameterTypes, arguments);
//proparly not thread-safe
forceLogin(player);
} catch (Exception ex) {
Main.plugin.getLogger().severe("[BungeeAuth] Error when creating a new player in the Database");
return false;
}
return true;
}
//pail ;(
private void callProtected(String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Exception {
Class<? extends Tables> tableClass = databaseConnection.getClass();
Method method = tableClass.getDeclaredMethod(methodName, parameterTypes);
method.setAccessible(true);
method.invoke(databaseConnection, arguments);
}
}

View File

@@ -1,55 +0,0 @@
package com.github.games647.fastlogin.bungee.hooks;
import net.md_5.bungee.api.connection.ProxiedPlayer;
/**
* Represents a supporting authentication plugin in BungeeCord/Waterfall/... servers
*/
public interface BungeeAuthPlugin {
/**
* Login the premium (paid account) player after
* the player joined successfully a server.
*
* @param player the player that needs to be logged in
* @return if the operation was successful
*/
boolean forceLogin(ProxiedPlayer player);
/**
* Checks whether an account exists for this player name.
*
* This check should check if a cracked player account exists
* so we can be sure the premium player doesn't steal the account
* of that player.
*
* This operation will be performed async while the player is
* connecting
*
* @param playerName player name
* @return if the player has an account
* @throws Exception if an error occurred
*/
boolean isRegistered(String playerName) throws Exception;
/**
* Forces a register in order to protect the paid account.
* The method will be invoked after the player joined a server.
*
* After a successful registration the player should be logged
* in too.
*
* The method will be called only for premium accounts.
* So it's recommended to set additionally premium property
* if possible.
*
* If we don't register an account, cracked players
* could steal the unregistered account from the paid
* player account
*
* @param player the premium account
* @param password a strong random generated password
* @return if the operation was successful
*/
boolean forceRegister(ProxiedPlayer player, String password);
}

View File

@@ -0,0 +1,191 @@
package com.github.games647.fastlogin.bungee.listener;
import com.github.games647.craftapi.UUIDAdapter;
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.task.AsyncPremiumCheck;
import com.github.games647.fastlogin.bungee.task.ForceLoginTask;
import com.github.games647.fastlogin.core.auth.RateLimiter;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.LoginSession;
import com.google.common.base.Throwables;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Field;
import java.util.UUID;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PreLoginEvent;
import net.md_5.bungee.api.event.ServerConnectedEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.connection.LoginResult.Property;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import org.geysermc.floodgate.FloodgateAPI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Enables online mode logins for specified users and sends plugin message to the Bukkit version of this plugin in
* order to clear that the connection is online mode.
*/
public class ConnectListener implements Listener {
private static final String UUID_FIELD_NAME = "uniqueId";
private static final boolean initialHandlerClazzFound;
private static final MethodHandle uniqueIdSetter;
static {
MethodHandle setHandle = null;
boolean handlerFound = false;
try {
Lookup lookup = MethodHandles.lookup();
Class.forName("net.md_5.bungee.connection.InitialHandler");
handlerFound = true;
Field uuidField = InitialHandler.class.getDeclaredField(UUID_FIELD_NAME);
uuidField.setAccessible(true);
setHandle = lookup.unreflectSetter(uuidField);
} catch (ClassNotFoundException classNotFoundException) {
Logger logger = LoggerFactory.getLogger(ConnectListener.class);
logger.error(
"Cannot find Bungee initial handler; Disabling premium UUID and skin won't work.",
classNotFoundException
);
} catch (ReflectiveOperationException reflectiveOperationException) {
reflectiveOperationException.printStackTrace();
}
initialHandlerClazzFound = handlerFound;
uniqueIdSetter = setHandle;
}
private final FastLoginBungee plugin;
private final RateLimiter rateLimiter;
private final Property[] emptyProperties = {};
private final boolean floodGateAvailable;
public ConnectListener(FastLoginBungee plugin, RateLimiter rateLimiter, boolean floodgateAvailable) {
this.plugin = plugin;
this.rateLimiter = rateLimiter;
this.floodGateAvailable = floodgateAvailable;
}
@EventHandler
public void onPreLogin(PreLoginEvent preLoginEvent) {
PendingConnection connection = preLoginEvent.getConnection();
if (preLoginEvent.isCancelled() || isBedrockPlayer(connection.getUniqueId())) {
return;
}
if (!rateLimiter.tryAcquire()) {
plugin.getLog().warn("Join limit hit - Ignoring player {}", connection);
return;
}
String username = connection.getName();
plugin.getLog().info("Incoming login request for {} from {}", username, connection.getSocketAddress());
preLoginEvent.registerIntent(plugin);
Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, preLoginEvent, connection, username);
plugin.getScheduler().runAsync(asyncPremiumCheck);
}
@EventHandler(priority = EventPriority.LOWEST)
public void onLogin(LoginEvent loginEvent) {
if (loginEvent.isCancelled()) {
return;
}
//use the login event instead of the post login event in order to send the login success packet to the client
//with the offline uuid this makes it possible to set the skin then
PendingConnection connection = loginEvent.getConnection();
if (connection.isOnlineMode()) {
LoginSession session = plugin.getSession().get(connection);
UUID verifiedUUID = connection.getUniqueId();
String verifiedUsername = connection.getName();
session.setUuid(verifiedUUID);
session.setVerifiedUsername(verifiedUsername);
StoredProfile playerProfile = session.getProfile();
playerProfile.setId(verifiedUUID);
// bungeecord will do this automatically so override it on disabled option
if (uniqueIdSetter != null) {
InitialHandler initialHandler = (InitialHandler) connection;
if (!plugin.getCore().getConfig().get("premiumUuid", true)) {
setOfflineId(initialHandler, verifiedUsername);
}
if (!plugin.getCore().getConfig().get("forwardSkin", true)) {
// this is null on offline mode
LoginResult loginProfile = initialHandler.getLoginProfile();
loginProfile.setProperties(emptyProperties);
}
}
}
}
private void setOfflineId(InitialHandler connection, String username) {
try {
final UUID oldPremiumId = connection.getUniqueId();
final UUID offlineUUID = UUIDAdapter.generateOfflineId(username);
// BungeeCord only allows setting the UUID in PreLogin events and before requesting online mode
// However if online mode is requested, it will override previous values
// So we have to do it with reflection
uniqueIdSetter.invokeExact(connection, offlineUUID);
String format = "Overridden UUID from {} to {} (based of {}) on {}";
plugin.getLog().info(format, oldPremiumId, offlineUUID, username, connection);
} catch (Exception ex) {
plugin.getLog().error("Failed to set offline uuid of {}", username, ex);
} catch (Throwable throwable) {
// throw remaining exceptions like outofmemory that we shouldn't handle ourself
Throwables.throwIfUnchecked(throwable);
}
}
@EventHandler
public void onServerConnected(ServerConnectedEvent serverConnectedEvent) {
ProxiedPlayer player = serverConnectedEvent.getPlayer();
Server server = serverConnectedEvent.getServer();
BungeeLoginSession session = plugin.getSession().get(player.getPendingConnection());
if (session == null) {
return;
}
// delay sending force command, because Paper will process the login event asynchronously
// In this case it means that the force command (plugin message) is already received and processed while
// player is still in the login phase and reported to be offline.
Runnable loginTask = new ForceLoginTask(plugin.getCore(), player, server, session);
plugin.getScheduler().runAsync(loginTask);
}
@EventHandler
public void onDisconnect(PlayerDisconnectEvent disconnectEvent) {
ProxiedPlayer player = disconnectEvent.getPlayer();
plugin.getSession().remove(player.getPendingConnection());
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
}
private boolean isBedrockPlayer(UUID correctedUUID) {
// Floodgate will set a correct UUID at the beginning of the PreLoginEvent
// and will cancel the online mode login for those players
// Therefore we just ignore those
return floodGateAvailable && FloodgateAPI.isBedrockPlayer(correctedUUID);
}
}

View File

@@ -1,91 +0,0 @@
package com.github.games647.fastlogin.bungee.listener;
import com.github.games647.fastlogin.bungee.tasks.AsyncPremiumCheck;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.tasks.ForceLoginTask;
import com.github.games647.fastlogin.core.LoginSession;
import com.github.games647.fastlogin.core.PlayerProfile;
import com.google.common.base.Charsets;
import java.lang.reflect.Field;
import java.util.UUID;
import java.util.logging.Level;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.event.PreLoginEvent;
import net.md_5.bungee.api.event.ServerConnectedEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.connection.LoginResult.Property;
import net.md_5.bungee.event.EventHandler;
/**
* Enables online mode logins for specified users and sends
* plugin message to the Bukkit version of this plugin in
* order to clear that the connection is online mode.
*/
public class PlayerConnectionListener implements Listener {
protected final FastLoginBungee plugin;
public PlayerConnectionListener(FastLoginBungee plugin) {
this.plugin = plugin;
}
@EventHandler
public void onPreLogin(PreLoginEvent preLoginEvent) {
if (preLoginEvent.isCancelled()) {
return;
}
preLoginEvent.registerIntent(plugin);
ProxyServer.getInstance().getScheduler().runAsync(plugin, new AsyncPremiumCheck(plugin, preLoginEvent));
}
@EventHandler
public void onLogin(PostLoginEvent loginEvent) {
ProxiedPlayer player = loginEvent.getPlayer();
PendingConnection connection = player.getPendingConnection();
String username = connection.getName();
if (connection.isOnlineMode()) {
LoginSession session = plugin.getSession().get(connection);
PlayerProfile playerProfile = session.getProfile();
playerProfile.setUuid(player.getUniqueId());
//bungeecord will do this automatically so override it on disabled option
InitialHandler initialHandler = (InitialHandler) connection;
if (!plugin.getConfig().getBoolean("premiumUuid")) {
try {
UUID offlineUUID = UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(Charsets.UTF_8));
//bungeecord doesn't support overriding the premium uuid
//so we have to do it with reflection
Field idField = initialHandler.getClass().getDeclaredField("uniqueId");
idField.setAccessible(true);
idField.set(connection, offlineUUID);
} catch (NoSuchFieldException | IllegalAccessException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to set offline uuid", ex);
}
}
if (!plugin.getConfig().getBoolean("forwardSkin")) {
//this is null on offline mode
LoginResult loginProfile = initialHandler.getLoginProfile();
if (loginProfile != null) {
loginProfile.setProperties(new Property[]{});
}
}
}
}
@EventHandler
public void onServerConnected(ServerConnectedEvent serverConnectedEvent) {
ProxiedPlayer player = serverConnectedEvent.getPlayer();
ForceLoginTask loginTask = new ForceLoginTask(plugin, player, serverConnectedEvent.getServer());
ProxyServer.getInstance().getScheduler().runAsync(plugin, loginTask);
}
}

View File

@@ -1,12 +1,20 @@
package com.github.games647.fastlogin.bungee.listener;
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.tasks.AsyncToggleMessage;
import com.github.games647.fastlogin.core.PlayerProfile;
import com.github.games647.fastlogin.bungee.task.AsyncToggleMessage;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
import com.github.games647.fastlogin.core.message.NamespaceKey;
import com.github.games647.fastlogin.core.message.SuccessMessage;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import net.md_5.bungee.api.ProxyServer;
import java.util.Arrays;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.PluginMessageEvent;
@@ -15,16 +23,22 @@ import net.md_5.bungee.event.EventHandler;
public class PluginMessageListener implements Listener {
protected final FastLoginBungee plugin;
private final FastLoginBungee plugin;
private final String successChannel;
private final String changeChannel;
public PluginMessageListener(FastLoginBungee plugin) {
this.plugin = plugin;
this.successChannel = new NamespaceKey(plugin.getName(), SuccessMessage.SUCCESS_CHANNEL).getCombinedName();
this.changeChannel = new NamespaceKey(plugin.getName(), ChangePremiumMessage.CHANGE_CHANNEL).getCombinedName();
}
@EventHandler
public void onPluginMessage(PluginMessageEvent pluginMessageEvent) {
String channel = pluginMessageEvent.getTag();
if (pluginMessageEvent.isCancelled() || !plugin.getDescription().getName().equals(channel)) {
if (pluginMessageEvent.isCancelled() || !channel.startsWith(plugin.getName().toLowerCase())) {
return;
}
@@ -32,36 +46,61 @@ public class PluginMessageListener implements Listener {
//moreover the client shouldn't be able fake a running premium check by sending the result message
pluginMessageEvent.setCancelled(true);
//check if the message is sent from the server
if (Server.class.isAssignableFrom(pluginMessageEvent.getSender().getClass())) {
readMessage(pluginMessageEvent);
if (!(pluginMessageEvent.getSender() instanceof Server)) {
//check if the message is sent from the server
return;
}
//so that we can safely process this in the background
byte[] data = Arrays.copyOf(pluginMessageEvent.getData(), pluginMessageEvent.getData().length);
ProxiedPlayer forPlayer = (ProxiedPlayer) pluginMessageEvent.getReceiver();
plugin.getScheduler().runAsync(() -> readMessage(forPlayer, channel, data));
}
private void readMessage(ProxiedPlayer forPlayer, String channel, byte[] data) {
FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core = plugin.getCore();
ByteArrayDataInput dataInput = ByteStreams.newDataInput(data);
if (successChannel.equals(channel)) {
onSuccessMessage(forPlayer);
} else if (changeChannel.equals(channel)) {
ChangePremiumMessage changeMessage = new ChangePremiumMessage();
changeMessage.readFrom(dataInput);
String playerName = changeMessage.getPlayerName();
boolean isSourceInvoker = changeMessage.isSourceInvoker();
if (changeMessage.shouldEnable()) {
if (playerName.equals(forPlayer.getName()) && plugin.getCore().getConfig().get("premium-warning", true)
&& !core.getPendingConfirms().contains(forPlayer.getUniqueId())) {
String message = core.getMessage("premium-warning");
forPlayer.sendMessage(TextComponent.fromLegacyText(message));
core.getPendingConfirms().add(forPlayer.getUniqueId());
return;
}
core.getPendingConfirms().remove(forPlayer.getUniqueId());
Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, true, isSourceInvoker);
plugin.getScheduler().runAsync(task);
} else {
Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, false, isSourceInvoker);
plugin.getScheduler().runAsync(task);
}
}
}
private void readMessage(PluginMessageEvent pluginMessageEvent) {
byte[] data = pluginMessageEvent.getData();
ByteArrayDataInput dataInput = ByteStreams.newDataInput(data);
String subchannel = dataInput.readUTF();
private void onSuccessMessage(ProxiedPlayer forPlayer) {
if (forPlayer.getPendingConnection().isOnlineMode()) {
//bukkit module successfully received and force logged in the user
//update only on success to prevent corrupt data
BungeeLoginSession loginSession = plugin.getSession().get(forPlayer.getPendingConnection());
StoredProfile playerProfile = loginSession.getProfile();
loginSession.setRegistered(true);
ProxiedPlayer fromPlayer = (ProxiedPlayer) pluginMessageEvent.getReceiver();
if ("ON".equals(subchannel)) {
String playerName = dataInput.readUTF();
AsyncToggleMessage task = new AsyncToggleMessage(plugin, fromPlayer, playerName, true);
ProxyServer.getInstance().getScheduler().runAsync(plugin, task);
} else if ("OFF".equals(subchannel)) {
String playerName = dataInput.readUTF();
AsyncToggleMessage task = new AsyncToggleMessage(plugin, fromPlayer, playerName, false);
ProxyServer.getInstance().getScheduler().runAsync(plugin, task);
} else if ("SUCCESS".equals(subchannel)) {
if (fromPlayer.getPendingConnection().isOnlineMode()) {
//bukkit module successfully received and force logged in the user
//update only on success to prevent corrupt data
PlayerProfile playerProfile = plugin.getCore().getStorage().loadProfile(fromPlayer.getName());
if (!loginSession.isAlreadySaved()) {
playerProfile.setPremium(true);
//we override this in the loginevent
plugin.getCore().getStorage().save(playerProfile);
loginSession.setAlreadySaved(true);
}
}
}

View File

@@ -0,0 +1,67 @@
package com.github.games647.fastlogin.bungee.task;
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
import com.github.games647.fastlogin.bungee.BungeeLoginSource;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.event.BungeeFastLoginPreLoginEvent;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.auth.JoinManagement;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PreLoginEvent;
public class AsyncPremiumCheck extends JoinManagement<ProxiedPlayer, CommandSender, BungeeLoginSource>
implements Runnable {
private final FastLoginBungee plugin;
private final PreLoginEvent preLoginEvent;
private final String username;
private final PendingConnection connection;
public AsyncPremiumCheck(FastLoginBungee plugin, PreLoginEvent preLoginEvent, PendingConnection connection,
String username) {
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
this.plugin = plugin;
this.preLoginEvent = preLoginEvent;
this.connection = connection;
this.username = username;
}
@Override
public void run() {
plugin.getSession().remove(connection);
try {
super.onLogin(username, new BungeeLoginSource(connection, preLoginEvent));
} finally {
preLoginEvent.completeIntent(plugin);
}
}
@Override
public FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, BungeeLoginSource source,
StoredProfile profile) {
return plugin.getProxy().getPluginManager()
.callEvent(new BungeeFastLoginPreLoginEvent(username, source, profile));
}
@Override
public void requestPremiumLogin(BungeeLoginSource source, StoredProfile profile,
String username, boolean registered) {
source.setOnlineMode();
plugin.getSession().put(source.getConnection(), new BungeeLoginSession(username, registered, profile));
String ip = source.getAddress().getAddress().getHostAddress();
plugin.getCore().getPendingLogin().put(ip + username, new Object());
}
@Override
public void startCrackedSession(BungeeLoginSource source, StoredProfile profile, String username) {
plugin.getSession().put(source.getConnection(), new BungeeLoginSession(username, false, profile));
}
}

View File

@@ -0,0 +1,83 @@
package com.github.games647.fastlogin.bungee.task;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.event.BungeeFastLoginPremiumToggleEvent;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent.PremiumToggleReason;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
public class AsyncToggleMessage implements Runnable {
private final FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core;
private final ProxiedPlayer sender;
private final String targetPlayer;
private final boolean toPremium;
private final boolean isPlayerSender;
public AsyncToggleMessage(FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core,
ProxiedPlayer sender, String playerName, boolean toPremium, boolean playerSender) {
this.core = core;
this.sender = sender;
this.targetPlayer = playerName;
this.toPremium = toPremium;
this.isPlayerSender = playerSender;
}
@Override
public void run() {
if (toPremium) {
activatePremium();
} else {
turnOffPremium();
}
}
private void turnOffPremium() {
StoredProfile playerProfile = core.getStorage().loadProfile(targetPlayer);
//existing player is already cracked
if (playerProfile.isSaved() && !playerProfile.isPremium()) {
sendMessage("not-premium");
return;
}
playerProfile.setPremium(false);
playerProfile.setId(null);
core.getStorage().save(playerProfile);
PremiumToggleReason reason = (!isPlayerSender || !sender.getName().equalsIgnoreCase(playerProfile.getName())) ?
PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF;
core.getPlugin().getProxy().getPluginManager().callEvent(
new BungeeFastLoginPremiumToggleEvent(playerProfile, reason));
sendMessage("remove-premium");
}
private void activatePremium() {
StoredProfile playerProfile = core.getStorage().loadProfile(targetPlayer);
if (playerProfile.isPremium()) {
sendMessage("already-exists");
return;
}
playerProfile.setPremium(true);
core.getStorage().save(playerProfile);
PremiumToggleReason reason = (!isPlayerSender || !sender.getName().equalsIgnoreCase(playerProfile.getName())) ?
PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF;
core.getPlugin().getProxy().getPluginManager().callEvent(
new BungeeFastLoginPremiumToggleEvent(playerProfile, reason));
sendMessage("add-premium");
}
private void sendMessage(String localeId) {
String message = core.getMessage(localeId);
if (isPlayerSender) {
sender.sendMessage(TextComponent.fromLegacyText(message));
} else {
CommandSender console = ProxyServer.getInstance().getConsole();
console.sendMessage(TextComponent.fromLegacyText(message));
}
}
}

View File

@@ -0,0 +1,96 @@
package com.github.games647.fastlogin.bungee.task;
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.event.BungeeFastLoginAutoLoginEvent;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.github.games647.fastlogin.core.message.ChannelMessage;
import com.github.games647.fastlogin.core.message.LoginActionMessage;
import com.github.games647.fastlogin.core.message.LoginActionMessage.Type;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.auth.ForceLoginManagement;
import com.github.games647.fastlogin.core.auth.LoginSession;
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
import java.util.UUID;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
public class ForceLoginTask
extends ForceLoginManagement<ProxiedPlayer, CommandSender, BungeeLoginSession, FastLoginBungee> {
private final Server server;
public ForceLoginTask(FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core,
ProxiedPlayer player, Server server, BungeeLoginSession session) {
super(core, player, session);
this.server = server;
}
@Override
public void run() {
if (session == null) {
return;
}
super.run();
if (!isOnlineMode()) {
session.setAlreadySaved(true);
}
}
@Override
public boolean forceLogin(ProxiedPlayer player) {
if (session.isAlreadyLogged()) {
return true;
}
session.setAlreadyLogged(true);
return super.forceLogin(player);
}
@Override
public FastLoginAutoLoginEvent callFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
return core.getPlugin().getProxy().getPluginManager()
.callEvent(new BungeeFastLoginAutoLoginEvent(session, profile));
}
@Override
public boolean forceRegister(ProxiedPlayer player) {
return session.isAlreadyLogged() || super.forceRegister(player);
}
@Override
public void onForceActionSuccess(LoginSession session) {
//sub channel name
Type type = Type.LOGIN;
if (session.needsRegistration()) {
type = Type.REGISTER;
}
UUID proxyId = UUID.fromString(ProxyServer.getInstance().getConfig().getUuid());
ChannelMessage loginMessage = new LoginActionMessage(type, player.getName(), proxyId);
core.getPlugin().sendPluginMessage(server, loginMessage);
}
@Override
public String getName(ProxiedPlayer player) {
return player.getName();
}
@Override
public boolean isOnline(ProxiedPlayer player) {
return player.isConnected();
}
@Override
public boolean isOnlineMode() {
return player.getPendingConnection().isOnlineMode();
}
}

View File

@@ -1,92 +0,0 @@
package com.github.games647.fastlogin.bungee.tasks;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.hooks.BungeeAuthPlugin;
import com.github.games647.fastlogin.core.LoginSession;
import com.github.games647.fastlogin.core.PlayerProfile;
import java.util.UUID;
import java.util.logging.Level;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.event.PreLoginEvent;
public class AsyncPremiumCheck implements Runnable {
private final FastLoginBungee plugin;
private final PreLoginEvent preLoginEvent;
public AsyncPremiumCheck(FastLoginBungee plugin, PreLoginEvent preLoginEvent) {
this.plugin = plugin;
this.preLoginEvent = preLoginEvent;
}
@Override
public void run() {
PendingConnection connection = preLoginEvent.getConnection();
plugin.getSession().remove(connection);
String username = connection.getName();
try {
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(username);
if (profile == null) {
return;
}
if (profile.getUserId() == -1) {
UUID premiumUUID = null;
if (plugin.getConfig().getBoolean("nameChangeCheck") || plugin.getConfig().getBoolean("autoRegister")) {
premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username);
}
if (premiumUUID == null
|| checkNameChange(premiumUUID, connection, username)
|| checkPremiumName(username, connection, profile)) {
//nothing detected the player as premium -> start a cracked session
plugin.getSession().put(connection, new LoginSession(username, false, profile));
}
} else if (profile.isPremium()) {
requestPremiumLogin(connection, profile, username, true);
} else {
plugin.getSession().put(connection, new LoginSession(username, false, profile));
}
} catch (Exception ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to check premium state", ex);
} finally {
preLoginEvent.completeIntent(plugin);
}
}
private boolean checkPremiumName(String username, PendingConnection connection, PlayerProfile profile)
throws Exception {
BungeeAuthPlugin authPlugin = plugin.getBungeeAuthPlugin();
if (plugin.getConfig().getBoolean("autoRegister")
&& (authPlugin == null || !authPlugin.isRegistered(username))) {
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
requestPremiumLogin(connection, profile, username, false);
return true;
}
return false;
}
private boolean checkNameChange(UUID premiumUUID, PendingConnection connection, String username) {
//user not exists in the db
if (plugin.getConfig().getBoolean("nameChangeCheck")) {
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(premiumUUID);
if (profile != null) {
//uuid exists in the database
plugin.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID);
requestPremiumLogin(connection, profile, username, false);
return true;
}
}
return false;
}
private void requestPremiumLogin(PendingConnection con, PlayerProfile profile, String username, boolean register) {
con.setOnlineMode(true);
plugin.getSession().put(con, new LoginSession(username, register, profile));
}
}

View File

@@ -1,57 +0,0 @@
package com.github.games647.fastlogin.bungee.tasks;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.core.PlayerProfile;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
public class AsyncToggleMessage implements Runnable {
private final FastLoginBungee plugin;
private final ProxiedPlayer fromPlayer;
private final String targetPlayer;
private final boolean toPremium;
public AsyncToggleMessage(FastLoginBungee plugin, ProxiedPlayer fromPlayer, String targetPlayer
, boolean toPremium) {
this.plugin = plugin;
this.fromPlayer = fromPlayer;
this.targetPlayer = targetPlayer;
this.toPremium = toPremium;
}
@Override
public void run() {
if (toPremium) {
activatePremium();
} else {
turnOffPremium();
}
}
private void turnOffPremium() {
PlayerProfile playerProfile = plugin.getCore().getStorage().loadProfile(targetPlayer);
if (!playerProfile.isPremium()) {
fromPlayer.sendMessage(TextComponent.fromLegacyText(plugin.getCore().getMessage("not-premium")));
return;
}
playerProfile.setPremium(false);
playerProfile.setUuid(null);
plugin.getCore().getStorage().save(playerProfile);
fromPlayer.sendMessage(TextComponent.fromLegacyText(plugin.getCore().getMessage("remove-premium")));
}
private void activatePremium() {
PlayerProfile playerProfile = plugin.getCore().getStorage().loadProfile(targetPlayer);
if (playerProfile.isPremium()) {
fromPlayer.sendMessage(TextComponent.fromLegacyText(plugin.getCore().getMessage("already-exists")));
return;
}
playerProfile.setPremium(true);
plugin.getCore().getStorage().save(playerProfile);
fromPlayer.sendMessage(TextComponent.fromLegacyText(plugin.getCore().getMessage("add-premium")));
}
}

View File

@@ -1,79 +0,0 @@
package com.github.games647.fastlogin.bungee.tasks;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.hooks.BungeeAuthPlugin;
import com.github.games647.fastlogin.core.LoginSession;
import com.github.games647.fastlogin.core.PlayerProfile;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import java.util.UUID;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
public class ForceLoginTask implements Runnable {
private final FastLoginBungee plugin;
private final ProxiedPlayer player;
private final Server server;
public ForceLoginTask(FastLoginBungee plugin, ProxiedPlayer player, Server server) {
this.plugin = plugin;
this.player = player;
this.server = server;
}
@Override
public void run() {
PendingConnection pendingConnection = player.getPendingConnection();
LoginSession session = plugin.getSession().remove(pendingConnection);
PlayerProfile playerProfile = session.getProfile();
//force login only on success
if (pendingConnection.isOnlineMode()) {
boolean autoRegister = session.needsRegistration();
BungeeAuthPlugin authPlugin = plugin.getBungeeAuthPlugin();
if (authPlugin == null) {
sendBukkitLoginNotification(autoRegister);
} else if (player.isConnected()) {
if (session.needsRegistration()) {
String password = plugin.generateStringPassword();
if (authPlugin.forceRegister(player, password)) {
sendBukkitLoginNotification(autoRegister);
}
} else if (authPlugin.forceLogin(player)) {
sendBukkitLoginNotification(autoRegister);
}
}
} else {
//cracked player
playerProfile.setPremium(false);
plugin.getCore().getStorage().save(playerProfile);
}
}
private void sendBukkitLoginNotification(boolean autoRegister) {
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
//subchannel name
if (autoRegister) {
dataOutput.writeUTF("AUTO_REGISTER");
} else {
dataOutput.writeUTF("AUTO_LOGIN");
}
//Data is sent through a random player. We have to tell the Bukkit version of this plugin the target
dataOutput.writeUTF(player.getName());
//proxy identifier to check if it's a acceptable proxy
UUID proxyId = UUID.fromString(plugin.getProxy().getConfig().getUuid());
dataOutput.writeLong(proxyId.getMostSignificantBits());
dataOutput.writeLong(proxyId.getLeastSignificantBits());
if (server != null) {
server.sendData(plugin.getDescription().getName(), dataOutput.toByteArray());
}
}
}

View File

@@ -1,16 +1,17 @@
# project informations for BungeeCord
# project data for BungeeCord
# This file will be prioritised over plugin.yml which can be also used for Bungee
# This make it easy to combine BungeeCord and Bukkit support in one plugin
name: ${project.parent.name}
# ${-} will be automatically replaced by Maven
main: ${project.groupId}.${project.artifactId}.${project.name}
version: ${project.version}
author: games647, http://github.com/games647/FastLogin/graphs/contributors
version: ${project.version}-${git.commit.id.abbrev}
author: games647, https://github.com/games647/FastLogin/graphs/contributors
softdepends:
softDepends:
# BungeeCord auth plugins
- BungeeAuth
- BungeeCordAuthenticatorBungee
description: |
${project.description}
${project.description}

View File

@@ -1,11 +1,11 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.games647</groupId>
<artifactId>fastlogin</artifactId>
<version>1.5.1</version>
<version>1.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -14,19 +14,69 @@
<name>FastLoginCore</name>
<repositories>
<repository>
<id>luck-repo</id>
<url>https://ci.lucko.me/plugin/repository/everything</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>codemc-repo</id>
<url>https://repo.codemc.io/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<!-- Libraries that we shade into the project -->
<!--Database pooling-->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.4.6</version>
<version>4.0.1</version>
</dependency>
<!--Logging framework implements slf4j which is required by hikari-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.21</version>
<version>1.7.30</version>
</dependency>
<!-- snakeyaml is present in Bungee, Spigot, Cauldron and so we could use this independent implementation -->
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-config</artifactId>
<version>1.12-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--Common component for contacting the Mojang API-->
<dependency>
<groupId>com.github.games647</groupId>
<artifactId>craftapi</artifactId>
<version>0.4</version>
</dependency>
<!-- APIs we can use because they are available in all platforms (Spigot, Bungee) -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>17.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.4</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,69 @@
package com.github.games647.fastlogin.core;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
/**
* This limits the number of threads that are used at maximum. Thread creation can be very heavy for the CPU and
* context switching between threads too. However we need many threads for blocking HTTP and database calls.
* Nevertheless this number can be further limited, because the number of actually working database threads
* is limited by the size of our database pool. The goal is to separate concerns into processing and blocking only
* threads.
*/
public class AsyncScheduler {
private static final int MAX_CAPACITY = 1024;
//todo: single thread for delaying and scheduling tasks
private final Logger logger;
// 30 threads are still too many - the optimal solution is to separate into processing and blocking threads
// where processing threads could only be max number of cores while blocking threads could be minimized using
// non-blocking I/O and a single event executor
private final ExecutorService processingPool;
/*
private final ExecutorService databaseExecutor = new ThreadPoolExecutor(1, 10,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(MAX_CAPACITY));
*/
public AsyncScheduler(Logger logger, ThreadFactory threadFactory) {
this.logger = logger;
processingPool = new ThreadPoolExecutor(6, 32,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(MAX_CAPACITY), threadFactory);
}
/*
public <R> CompletableFuture<R> runDatabaseTask(Supplier<R> databaseTask) {
return CompletableFuture.supplyAsync(databaseTask, databaseExecutor)
.exceptionally(error -> {
logger.warn("Error occurred on thread pool", error);
return null;
})
// change context to the processing pool
.thenApplyAsync(r -> r, processingPool);
}
*/
public CompletableFuture<Void> runAsync(Runnable task) {
return CompletableFuture.runAsync(task, processingPool).exceptionally(error -> {
logger.warn("Error occurred on thread pool", error);
return null;
});
}
public void shutdown() {
MoreExecutors.shutdownAndAwaitTermination(processingPool, 1, TimeUnit.MINUTES);
//MoreExecutors.shutdownAndAwaitTermination(databaseExecutor, 1, TimeUnit.MINUTES);
}
}

Some files were not shown because too many files have changed in this diff Show More