mirror of
https://github.com/TuxCoding/FastLogin.git
synced 2025-07-29 10:17:38 +02:00
Compare commits
377 Commits
Author | SHA1 | Date | |
---|---|---|---|
5676e99dec | |||
65469ed579 | |||
8ecb5657d3 | |||
b0cf6e39c7 | |||
88a526b5bf | |||
57b84509da | |||
e0bc7d914c | |||
f1933f735a | |||
f69154418e | |||
74b13231f8 | |||
79627e3b60 | |||
1722ab3267 | |||
fdc0485f05 | |||
1068ddbadd | |||
b0d5bd606c | |||
1e59101ada | |||
46ac3c74e4 | |||
d22af0f42e | |||
2cc078773b | |||
c1b8b60bf7 | |||
c54d6eecfc | |||
0cf6c4c188 | |||
282467d21b | |||
e8a5dc7433 | |||
4b45932d6a | |||
1375cf3997 | |||
5a5cf016d9 | |||
2c7e569653 | |||
94c5fe302e | |||
c80012ddb1 | |||
17361b1b54 | |||
426b458a58 | |||
62a8b939cc | |||
327c8c4c9d | |||
fbbe7a735a | |||
4110ce2fa2 | |||
28743d23bf | |||
145bd95679 | |||
3fe17cf8d9 | |||
3446d4c443 | |||
c528433079 | |||
25858ea11f | |||
204ffbb2ee | |||
9c1ba81cbe | |||
15c5857c4f | |||
4b0ad3b186 | |||
101f7207a9 | |||
64175fe9e8 | |||
f7a10d86eb | |||
542aabad73 | |||
260b93a565 | |||
6604cca8bd | |||
c172b1ec84 | |||
9b0b8f5fcb | |||
20104b2b00 | |||
fd76d2448e | |||
53a1821a9d | |||
1c6f4e82e0 | |||
084afef899 | |||
8a9eed3a74 | |||
1ea6d929b1 | |||
ddc3aa9279 | |||
2a79a9511b | |||
791df26702 | |||
cdf1988f2f | |||
f476c091bb | |||
352c72df64 | |||
2cd0b194aa | |||
f2e42019d6 | |||
82ec71e8d0 | |||
6d207d62ba | |||
889dab3152 | |||
71c1f4f12e | |||
3651c4873c | |||
8b613a48cc | |||
78f8fa1f05 | |||
ac5820bb75 | |||
c1cb28c996 | |||
b534765ff8 | |||
5bcfdfeb32 | |||
b7c0fd549c | |||
61c1364506 | |||
a29dd849f9 | |||
3f9eba69ba | |||
f250f8071f | |||
8272aeac69 | |||
4d470be712 | |||
e2c04f2c26 | |||
86694982c7 | |||
04b00f4f22 | |||
48c2355745 | |||
cff25c958d | |||
06bb4b80dd | |||
2bdd051a41 | |||
526a8a9d51 | |||
8cbdb66625 | |||
e5e815a885 | |||
d0d5bd300b | |||
0c550edb05 | |||
181ea71222 | |||
c38692e237 | |||
dcef62fa57 | |||
856613a8c7 | |||
3beb8beaeb | |||
f3ea7ecbbe | |||
25c725f237 | |||
ffe4eb7364 | |||
82a258097d | |||
57eff4b3ec | |||
4858049c2a | |||
bb2cc1b42a | |||
2512c5cf67 | |||
c7c0782071 | |||
df945146b8 | |||
e32b0232e9 | |||
6daa654af8 | |||
0f01002564 | |||
28a20a46fa | |||
105e00b7e8 | |||
dce44295d0 | |||
1f917f3a8d | |||
e6c23a4bb5 | |||
66b808c999 | |||
2932de5588 | |||
16f7461568 | |||
2f0eb81735 | |||
bb80521ab6 | |||
109508dae6 | |||
5bf9b05d30 | |||
7839804a4c | |||
ca58c55eca | |||
10453fd637 | |||
d18b734550 | |||
7f51659cc7 | |||
bb240d3aa0 | |||
484855724b | |||
4ea7968366 | |||
44a47bc97f | |||
82cb25f809 | |||
551441cdc4 | |||
22a56862b0 | |||
edf5933e07 | |||
c6da04de70 | |||
0459b0a5a1 | |||
033333e35c | |||
6595dc6ac0 | |||
ea44002e91 | |||
131de8404c | |||
fbdd8ffc35 | |||
7db8c78975 | |||
b102f06f8e | |||
a79e18445a | |||
cf1a0c1bef | |||
059c3f346e | |||
47db2c7858 | |||
5bb8640d78 | |||
881b2ec7bc | |||
194c67cd6f | |||
863607c9a4 | |||
f37cc0a0db | |||
70a81bfcdf | |||
b8d029d6da | |||
c47dd1df80 | |||
4d5b1787b1 | |||
8c764220bd | |||
9af076b4c4 | |||
22aa9287e9 | |||
f08daa9b72 | |||
bc53743c6b | |||
a430a079c9 | |||
f3ac6090f1 | |||
5ca9b9c59a | |||
b886d1501f | |||
0082cc6536 | |||
7f96d55084 | |||
3851d539f8 | |||
a25d97879f | |||
41abffdb08 | |||
e69eb70377 | |||
e924b7a2fa | |||
157ca04691 | |||
ae3e03405d | |||
bebb04bdea | |||
91f41c55de | |||
1acc825f81 | |||
87ca00d75d | |||
62ffb1a904 | |||
5075a71843 | |||
da266c7e91 | |||
acab4766b1 | |||
bef90d11cd | |||
a02acd2d63 | |||
ca42a7c19e | |||
b533197f05 | |||
c94711f315 | |||
ee7af80bf0 | |||
17c2099bf1 | |||
31d6b67381 | |||
4b423c9ccb | |||
4292e9aaa0 | |||
07d0aededa | |||
218bc50c96 | |||
a3b2e33aad | |||
76f5ba7ed1 | |||
2cd50d23ad | |||
9f5f61f1c2 | |||
3e9c8e3a7e | |||
8e5da01be0 | |||
5022c9aa7b | |||
ad1ab22586 | |||
99ef5ce726 | |||
9b7634a9f3 | |||
115fc2e7ba | |||
b660951e1e | |||
e495f70ccd | |||
b35d67b5c0 | |||
58ac73a5a9 | |||
ebe768f7a2 | |||
d20db79f46 | |||
c28d889c1b | |||
ad60397851 | |||
88fdeff3f1 | |||
558ee1c92c | |||
3e84ebd787 | |||
36d7564c3a | |||
596caa0573 | |||
fe4331298f | |||
a67d84ef3f | |||
71362dfd7d | |||
fcd98fce43 | |||
6c1c4e7286 | |||
164fb735d6 | |||
fa1b0970a5 | |||
974bf498fc | |||
27c04ff08f | |||
fb357424e6 | |||
c73bb70256 | |||
dc395cdc3f | |||
f7626ab969 | |||
5f9802d589 | |||
642c1621ad | |||
eb965d5a48 | |||
457bc9cf47 | |||
2ab3c6b77c | |||
f27bad02d3 | |||
9334296beb | |||
fd9940e6f0 | |||
0745957e79 | |||
bb2e60f6e1 | |||
d15861b8e5 | |||
b84b340a77 | |||
c50249edea | |||
757ddb905a | |||
9914b7f358 | |||
bba4eb4eec | |||
2b16f3341f | |||
167ce66057 | |||
8d1021e44c | |||
a811a741f5 | |||
a6348766b3 | |||
22dcc50950 | |||
bd3494eed0 | |||
1aba9a0f3b | |||
6faf00e1bf | |||
0d89614f3c | |||
b009658eea | |||
2881689f09 | |||
6d1a97fd32 | |||
b74faa2fd5 | |||
4800a88886 | |||
92c9ab5b76 | |||
d90e3fdb44 | |||
8abbb8f07c | |||
f04a44b1d2 | |||
1a66121977 | |||
413a0325f8 | |||
9fc7e0bf43 | |||
ac8bcb1758 | |||
bebcb3e9de | |||
0b899f61a8 | |||
7733135ce4 | |||
be89eec23b | |||
679060d4e9 | |||
f6aa064835 | |||
de4b73c3bd | |||
ac15829dcc | |||
0b709997a4 | |||
8809875ca4 | |||
aa30c070b9 | |||
51d0aefbf3 | |||
cb876a52bd | |||
3e844be65d | |||
dce95cf0d0 | |||
81eeaeae83 | |||
6b1542de88 | |||
99b7367366 | |||
961b144efb | |||
dcd06ad613 | |||
c4c043e1c5 | |||
87aa9dd668 | |||
2838c06ab3 | |||
ae58e0539a | |||
624745728f | |||
d0287ec2b4 | |||
e6a4af92cc | |||
8f3920fa99 | |||
a723b2ddd3 | |||
5cf67127c7 | |||
e5309b9fa1 | |||
e439126294 | |||
59703bac4e | |||
bfaf390463 | |||
9e06fd7735 | |||
d56a0f9ff1 | |||
96fe190cac | |||
d4f5b547d4 | |||
67a4f41056 | |||
5174a84a17 | |||
a7b164b513 | |||
ffa5059c67 | |||
dfe37dfc1b | |||
6edd40742d | |||
b697dc6655 | |||
36974450ce | |||
57a59045ce | |||
11cc4eabc0 | |||
53e02d5457 | |||
0f85674ec1 | |||
378ab09bc8 | |||
740b11b434 | |||
77f0184899 | |||
2885daf8b9 | |||
1e128d12f5 | |||
f2a8446c8d | |||
cc8c49e25b | |||
25e182148f | |||
f00608c321 | |||
b86bdf5f23 | |||
9a30a0b299 | |||
fd3b1ed8b6 | |||
f3e675e547 | |||
0967f31b9a | |||
8cb4621055 | |||
f610001c9b | |||
dd386408d1 | |||
10bfd279d6 | |||
5608821fe3 | |||
9c0ad7d70c | |||
099b8e5d0a | |||
b4ade882be | |||
4a3cb42152 | |||
8fc5050e8e | |||
015739fe4c | |||
b2ae46a90a | |||
353cd17823 | |||
157b8499a9 | |||
bd46dae086 | |||
eacbb1ed76 | |||
e389433138 | |||
d1b2fe8865 | |||
3b4c4a1c79 | |||
b22df62f90 | |||
d118de8649 | |||
f8c10d6890 | |||
c3f8e59a9a | |||
834818bb7a | |||
fa46dc690b | |||
fdc2772f38 | |||
53af09ae34 | |||
f6f6aaf1de | |||
c0ef95e808 | |||
cb129547f5 | |||
eb394b5f60 | |||
aebbc84621 | |||
0eee6ba2be | |||
7e2057a7a2 | |||
800f077be0 |
41
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
41
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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
|
22
.github/ISSUE_TEMPLATE/enhancement_request.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/enhancement_request.md
vendored
Normal 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
10
.github/ISSUE_TEMPLATE/question.md
vendored
Normal 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
8
.github/pull_request_template.md
vendored
Normal 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 #...)
|
42
.github/workflows/maven.yml
vendored
Normal file
42
.github/workflows/maven.yml
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
# Human readable name
|
||||
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
|
||||
# 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@v1
|
||||
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.3.0
|
||||
with:
|
||||
# Use Java 8, because it's minimum required version
|
||||
java-version: 8
|
||||
# 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 posssible errors in depdendencies
|
||||
run: mvn --batch-mode package --no-snapshot-updates --strict-checksums --file pom.xml
|
54
.gitignore
vendored
54
.gitignore
vendored
@ -1,37 +1,22 @@
|
||||
# Eclipse stuff
|
||||
/.classpath
|
||||
/.project
|
||||
/.settings
|
||||
# Eclipse
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
|
||||
# netbeans
|
||||
/nbproject
|
||||
# NetBeans
|
||||
nbproject/
|
||||
nb-configuration.xml
|
||||
|
||||
# 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
|
||||
|
||||
@ -40,3 +25,20 @@ gradle-app.setting
|
||||
|
||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||
!gradle-wrapper.jar
|
||||
|
||||
# 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
|
||||
|
||||
|
258
CHANGELOG.md
Normal file
258
CHANGELOG.md
Normal file
@ -0,0 +1,258 @@
|
||||
### 1.11
|
||||
|
||||
* 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
|
||||
|
||||
* 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
|
||||
|
||||
* Added Bungee setAuthPlugin method
|
||||
* Added nameChangeCheck
|
||||
* Multiple BungeeCord support
|
||||
|
||||
### 1.3.1
|
||||
|
||||
* Prevent thread create violation in BungeeCord
|
||||
|
||||
### 1.3
|
||||
|
||||
* Added support for AuthMe 3.X
|
||||
* Fixed premium logins if the server is not fully started
|
||||
* Added other command argument to /premium and /cracked
|
||||
* Added support for LogIt
|
||||
* Fixed 1.7 Minecraft support by removing guava 11+ only features -> Cauldron support
|
||||
* Fixed BungeeCord support in Cauldron
|
||||
|
||||
### 1.2.1
|
||||
|
||||
* Fix premium status change notification message on BungeeCord
|
||||
|
||||
### 1.2
|
||||
|
||||
* Fix race condition in BungeeCord
|
||||
* Fix dead lock in xAuth
|
||||
* Added API methods for plugins to set their own password generator
|
||||
* Added API methods for plugins to set their own auth plugin hook
|
||||
=> Added support for AdvancedLogin
|
||||
|
||||
### 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
|
||||
|
||||
* 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
|
||||
|
||||
* Fixed BungeeCord support for the Bukkit module
|
||||
* Added database storage to save the premium state
|
||||
* Fix logical error on /premium (Thanks to @NorbiPeti)
|
||||
* Fixed issues with host lookup from hosts file (Thanks to @NorbiPeti)
|
||||
* Remove handshake listener because it creates errors on some systems
|
||||
|
||||
### 0.7
|
||||
|
||||
* Added BungeeAuth support
|
||||
* Added /premium [player] command with optional player parameter
|
||||
* Added a check if the player is already on the premium list
|
||||
* Added a forwardSkin config option
|
||||
* Added premium UUID support
|
||||
* Updated to the newest changes of Spigot
|
||||
* 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 auto-login to auto-register to clarify the usage
|
||||
|
||||
### 0.6
|
||||
|
||||
* Fixed 1.9 bugs
|
||||
* Added UltraAuth support
|
||||
|
||||
### 0.5
|
||||
|
||||
* 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
|
||||
* Improved permissions management
|
||||
|
||||
### 0.4
|
||||
|
||||
* Added forward premium skin
|
||||
* Added plugin support for ProtocolSupport
|
||||
|
||||
### 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
|
||||
|
||||
* Improved BungeeCord security
|
||||
|
||||
### 0.3
|
||||
|
||||
* Added BungeeCord support
|
||||
* Decrease timeout checks in order to fail faster on connection problems
|
||||
* Code style improvements
|
||||
|
||||
### 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
|
||||
|
||||
* 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
|
||||
|
||||
* Compile project with Java 7 :(
|
||||
|
||||
### 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
|
||||
|
||||
* Added support for CrazyLogin and LoginSecurity
|
||||
* Now minecraft version independent
|
||||
* Added debug logging
|
||||
* Code clean up
|
||||
* More state validation
|
||||
* Added better error handling
|
||||
|
||||
### 0.1
|
||||
* First release
|
2
LICENSE
2
LICENSE
@ -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
|
||||
|
106
README.md
106
README.md
@ -1,7 +1,103 @@
|
||||
# mcMMOExtras
|
||||
# FastLogin
|
||||
|
||||
A visual boss bar Bukkit plugin for mcMMO that keeps people entertained and encourages them to want to level up.
|
||||
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).
|
||||
|
||||
See
|
||||
* http://dev.bukkit.org/bukkit-plugins/mcmmoextras/
|
||||
* http://www.curse.com/bukkit-plugins/minecraft/mcmmoextras
|
||||
## Features
|
||||
|
||||
* Detect paid accounts from others
|
||||
* Automatically login paid accounts (premium)
|
||||
* Support various of auth plugins
|
||||
* Cauldron support
|
||||
* Forge/Sponge message support
|
||||
* Premium UUID support
|
||||
* Forwards 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
|
||||
* Locale messages
|
||||
* Import the database from similar plugins
|
||||
|
||||
## 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.
|
||||
|
||||
Nevertheless builds are only tested using a small set of automated and a few manual tests. Therefore they **could**
|
||||
contain new bugs and are likely to be less stable than released versions.
|
||||
|
||||
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
|
||||
|
||||
## Permissions
|
||||
|
||||
fastlogin.bukkit.command.premium
|
||||
fastlogin.bukkit.command.cracked
|
||||
fastlogin.command.premium.other
|
||||
fastlogin.command.cracked.other
|
||||
|
||||
## 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 and/or BungeeCord/Waterfall in offline mode (see server.properties or config.yml)
|
||||
* An auth plugin. Supported plugins
|
||||
|
||||
### Bukkit/Spigot/Paper
|
||||
|
||||
* [AuthMe (5.X)](https://dev.bukkit.org/bukkit-plugins/authme-reloaded/)
|
||||
* [xAuth](https://dev.bukkit.org/bukkit-plugins/xauth/)
|
||||
* [LogIt](https://github.com/games647/LogIt)
|
||||
* [AdvancedLogin (Paid)](https://www.spigotmc.org/resources/advancedlogin.10510/)
|
||||
* [CrazyLogin](https://dev.bukkit.org/bukkit-plugins/crazylogin/)
|
||||
* [LoginSecurity](https://dev.bukkit.org/bukkit-plugins/loginsecurity/)
|
||||
* [UltraAuth](https://dev.bukkit.org/bukkit-plugins/ultraauth-aa/)
|
||||
|
||||
### BungeeCord/Waterfall
|
||||
|
||||
* [BungeeAuth](https://www.spigotmc.org/resources/bungeeauth.493/)
|
||||
|
||||
## Network requests
|
||||
|
||||
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
|
||||
|
||||
### Bukkit/Spigot/Paper
|
||||
|
||||
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
|
||||
|
||||
1. Activate BungeeCord in the Spigot configuration
|
||||
2. Restart your server
|
||||
3. Now there is proxy-whitelist 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 (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. 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
|
||||
|
BIN
bukkit/lib/CrazyCore v10.7.7.jar
Normal file
BIN
bukkit/lib/CrazyCore v10.7.7.jar
Normal file
Binary file not shown.
BIN
bukkit/lib/CrazyLogin v7.23.2.jar
Normal file
BIN
bukkit/lib/CrazyLogin v7.23.2.jar
Normal file
Binary file not shown.
BIN
bukkit/lib/UltraAuth v2.1.2.jar
Normal file
BIN
bukkit/lib/UltraAuth v2.1.2.jar
Normal file
Binary file not shown.
237
bukkit/pom.xml
Normal file
237
bukkit/pom.xml
Normal file
@ -0,0 +1,237 @@
|
||||
<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 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.11-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<!--This have to be in lowercase because it's used by plugin.yml-->
|
||||
<artifactId>fastlogin.bukkit</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>FastLoginBukkit</name>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.2</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>
|
||||
</relocations>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<!-- Bukkit-Server-API -->
|
||||
<repository>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||
<!-- Disable snapshot release policy to speed up, when finding a artifact -->
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
|
||||
<!-- ProtocolLib -->
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>https://repo.dmulloy2.net/nexus/repository/public/</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
|
||||
<!-- AuthMe Reloaded, xAuth and LoginSecurity -->
|
||||
<repository>
|
||||
<id>codemc-releases</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>
|
||||
<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>
|
||||
|
||||
<!--Server API-->
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.12.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!--Library for listening and sending Minecraft packets-->
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>4.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!--Changing onlinemode on login process-->
|
||||
<dependency>
|
||||
<groupId>com.github.ProtocolSupport</groupId>
|
||||
<artifactId>ProtocolSupport</artifactId>
|
||||
<!--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.4</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.4.0</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.lenis0012.bukkit</groupId>
|
||||
<artifactId>loginsecurity</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>LogIt</artifactId>
|
||||
<version>9e3581db27</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<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>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!--No maven repository :(-->
|
||||
<dependency>
|
||||
<groupId>de.st_ddt.crazy</groupId>
|
||||
<artifactId>CrazyCore</artifactId>
|
||||
<version>10.7.7</version>
|
||||
<optional>true</optional>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/lib/CrazyCore v10.7.7.jar</systemPath>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.st_ddt.crazy</groupId>
|
||||
<artifactId>CrazyLogin</artifactId>
|
||||
<version>7.23</version>
|
||||
<optional>true</optional>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/lib/CrazyLogin v7.23.2.jar</systemPath>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ultraauth</groupId>
|
||||
<artifactId>ultraauth</artifactId>
|
||||
<version>2.0.2</version>
|
||||
<optional>true</optional>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/lib/UltraAuth v2.1.2.jar</systemPath>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,99 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.craftapi.model.skin.SkinProperty;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.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();
|
||||
}
|
||||
|
||||
public BukkitLoginSession(String username, String serverId, byte[] verifyToken, StoredProfile profile) {
|
||||
// confirmation login
|
||||
super(username, profile);
|
||||
|
||||
this.serverId = serverId;
|
||||
this.verifyToken = verifyToken.clone();
|
||||
}
|
||||
|
||||
//available for BungeeCord
|
||||
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 BungeeCord 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
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() {
|
||||
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) {
|
||||
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, Key sharedSecret, PublicKey publicKey) {
|
||||
try {
|
||||
byte[] serverHash = getServerIdHash(sessionId, sharedSecret, publicKey);
|
||||
return (new BigInteger(serverHash)).toString(16);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts the content and extracts the key spec.
|
||||
*
|
||||
* @param cipher decryption cipher initialized with the private key
|
||||
* @param sharedKey the encrypted shared key
|
||||
* @return shared secret key
|
||||
* @throws GeneralSecurityException if it fails to decrypt the data
|
||||
*/
|
||||
public static SecretKey decryptSharedKey(Cipher cipher, byte[] sharedKey) throws GeneralSecurityException {
|
||||
return new SecretKeySpec(decrypt(cipher, sharedKey), "AES");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static byte[] decrypt(Cipher cipher, byte[] data) throws GeneralSecurityException {
|
||||
return cipher.doFinal(data);
|
||||
}
|
||||
|
||||
private static byte[] getServerIdHash(String sessionId, Key sharedSecret, PublicKey publicKey)
|
||||
throws NoSuchAlgorithmException {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
|
||||
digest.update(sessionId.getBytes(StandardCharsets.ISO_8859_1));
|
||||
digest.update(sharedSecret.getEncoded());
|
||||
digest.update(publicKey.getEncoded());
|
||||
|
||||
return digest.digest();
|
||||
}
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.command.CrackedCommand;
|
||||
import com.github.games647.fastlogin.bukkit.command.PremiumCommand;
|
||||
import com.github.games647.fastlogin.bukkit.listener.BungeeListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.ConnectionListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.ProtocolLibListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.SkinApplyListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocolsupport.ProtocolSupportListener;
|
||||
import com.github.games647.fastlogin.bukkit.task.DelayedAuthHook;
|
||||
import com.github.games647.fastlogin.core.CommonUtil;
|
||||
import com.github.games647.fastlogin.core.PremiumStatus;
|
||||
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.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 java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.plugin.messaging.PluginMessageRecipient;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import static com.github.games647.fastlogin.core.message.ChangePremiumMessage.CHANGE_CHANNEL;
|
||||
import static com.github.games647.fastlogin.core.message.SuccessMessage.SUCCESS_CHANNEL;
|
||||
|
||||
/**
|
||||
* This plugin checks if a player has a paid account and if so tries to skip offline mode authentication.
|
||||
*/
|
||||
public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<CommandSender> {
|
||||
|
||||
//1 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang)
|
||||
private final ConcurrentMap<String, BukkitLoginSession> loginSession = CommonUtil.buildCache(1, -1);
|
||||
private final Map<UUID, PremiumStatus> premiumPlayers = new ConcurrentHashMap<>();
|
||||
private final Logger logger;
|
||||
|
||||
private boolean serverStarted;
|
||||
private boolean bungeeCord;
|
||||
private final BukkitScheduler scheduler;
|
||||
private FastLoginCore<Player, CommandSender, FastLoginBukkit> core;
|
||||
|
||||
public FastLoginBukkit() {
|
||||
this.logger = CommonUtil.createLoggerFromJDK(getLogger());
|
||||
this.scheduler = new BukkitScheduler(this, logger, getThreadFactory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
core = new FastLoginCore<>(this);
|
||||
core.load();
|
||||
|
||||
try {
|
||||
bungeeCord = Class.forName("org.spigotmc.SpigotConfig").getDeclaredField("bungee").getBoolean(null);
|
||||
} catch (ClassNotFoundException notFoundEx) {
|
||||
//ignore server has no bungee support
|
||||
} catch (Exception ex) {
|
||||
logger.warn("Cannot check bungeecord support. You use a non-Spigot build", ex);
|
||||
}
|
||||
|
||||
if (getServer().getOnlineMode()) {
|
||||
//we need to require offline to prevent a loginSession request for a offline player
|
||||
logger.error("Server have to be in offline mode");
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
PluginManager pluginManager = getServer().getPluginManager();
|
||||
if (bungeeCord) {
|
||||
setServerStarted();
|
||||
|
||||
// check for incoming messages from the bungeecord version of this plugin
|
||||
String forceChannel = NamespaceKey.getCombined(getName(), LoginActionMessage.FORCE_CHANNEL);
|
||||
getServer().getMessenger().registerIncomingPluginChannel(this, forceChannel, new BungeeListener(this));
|
||||
|
||||
// outgoing
|
||||
String successChannel = new NamespaceKey(getName(), SUCCESS_CHANNEL).getCombinedName();
|
||||
String changeChannel = new NamespaceKey(getName(), CHANGE_CHANNEL).getCombinedName();
|
||||
getServer().getMessenger().registerOutgoingPluginChannel(this, successChannel);
|
||||
getServer().getMessenger().registerOutgoingPluginChannel(this, changeChannel);
|
||||
} else {
|
||||
if (!core.setupDatabase()) {
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pluginManager.isPluginEnabled("ProtocolSupport")) {
|
||||
pluginManager.registerEvents(new ProtocolSupportListener(this), this);
|
||||
} else if (pluginManager.isPluginEnabled("ProtocolLib")) {
|
||||
ProtocolLibListener.register(this);
|
||||
pluginManager.registerEvents(new SkinApplyListener(this), this);
|
||||
} else {
|
||||
logger.warn("Either ProtocolLib or ProtocolSupport have to be installed if you don't use BungeeCord");
|
||||
}
|
||||
}
|
||||
|
||||
//delay dependency setup because we load the plugin very early where plugins are initialized yet
|
||||
getServer().getScheduler().runTaskLater(this, new DelayedAuthHook(this), 5L);
|
||||
|
||||
pluginManager.registerEvents(new ConnectionListener(this), this);
|
||||
|
||||
//register commands using a unique name
|
||||
getCommand("premium").setExecutor(new PremiumCommand(this));
|
||||
getCommand("cracked").setExecutor(new CrackedCommand(this));
|
||||
|
||||
if (pluginManager.isPluginEnabled("PlaceholderAPI")) {
|
||||
//prevents NoClassDef errors if it's not available
|
||||
PremiumPlaceholder.register(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
loginSession.clear();
|
||||
premiumPlayers.clear();
|
||||
|
||||
if (core != null) {
|
||||
core.close();
|
||||
}
|
||||
|
||||
//remove old blacklists
|
||||
getServer().getOnlinePlayers().forEach(player -> player.removeMetadata(getName(), this));
|
||||
}
|
||||
|
||||
public FastLoginCore<Player, CommandSender, FastLoginBukkit> getCore() {
|
||||
return core;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 loginSession map
|
||||
*/
|
||||
public ConcurrentMap<String, BukkitLoginSession> getLoginSessions() {
|
||||
return loginSession;
|
||||
}
|
||||
|
||||
public Map<UUID, PremiumStatus> getPremiumPlayers() {
|
||||
return premiumPlayers;
|
||||
}
|
||||
|
||||
public boolean isBungeeEnabled() {
|
||||
return bungeeCord;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 BungeeCord 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) {
|
||||
return premiumPlayers.getOrDefault(onlinePlayer, PremiumStatus.UNKNOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait before the server is fully started. This is workaround, because connections right on startup are not
|
||||
* injected by ProtocolLib
|
||||
*
|
||||
* @return true if ProtocolLib can now intercept packets
|
||||
*/
|
||||
public boolean isServerFullyStarted() {
|
||||
return serverStarted;
|
||||
}
|
||||
|
||||
public void setServerStarted() {
|
||||
if (!this.serverStarted) {
|
||||
this.serverStarted = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void sendPluginMessage(PluginMessageRecipient player, ChannelMessage message) {
|
||||
if (player != null) {
|
||||
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
|
||||
message.writeTo(dataOutput);
|
||||
|
||||
NamespaceKey channel = new NamespaceKey(getName(), message.getChannelName());
|
||||
player.sendPluginMessage(this, channel.getCombinedName(), dataOutput.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getPluginFolder() {
|
||||
return getDataFolder().toPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLog() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BukkitScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(CommandSender receiver, String message) {
|
||||
receiver.sendMessage(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class PremiumPlaceholder extends PlaceholderExpansion {
|
||||
|
||||
private static final String PLACEHOLDER_VARIABLE = "fastlogin_status";
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public PremiumPlaceholder(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public static void register(FastLoginBukkit plugin) {
|
||||
PremiumPlaceholder placeholderHook = new PremiumPlaceholder(plugin);
|
||||
PlaceholderAPI.registerPlaceholderHook(PLACEHOLDER_VARIABLE, placeholderHook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onPlaceholderRequest(Player player, String variable) {
|
||||
if (player != null && PLACEHOLDER_VARIABLE.equals(variable)) {
|
||||
return plugin.getStatus(player.getUniqueId()).name();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return PLACEHOLDER_VARIABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPlugin() {
|
||||
return plugin.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthor() {
|
||||
return plugin.getDescription().getAuthors().stream().collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return plugin.getName();
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package com.github.games647.fastlogin.bukkit.command;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
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.isBungeeEnabled()) {
|
||||
sendBungeeActivateMessage(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);
|
||||
});
|
||||
} 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean forwardCrackedCommand(CommandSender sender, String target) {
|
||||
return forwardBungeeCommand(sender, target, false);
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
package com.github.games647.fastlogin.bukkit.command;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.ConfirmationState;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
|
||||
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 his 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;
|
||||
}
|
||||
|
||||
Player player = (Player) sender;
|
||||
String playerName = sender.getName();
|
||||
if (forwardPremiumCommand(sender, playerName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// non-bungee mode
|
||||
if (plugin.getConfig().getBoolean("premium-confirm")) {
|
||||
ConfirmationState state = plugin.getCore().getPendingConfirms().get(playerName);
|
||||
if (state == null) {
|
||||
// no pending confirmation
|
||||
plugin.getCore().getPendingConfirms().put(playerName, ConfirmationState.REQUIRE_RELOGIN);
|
||||
player.kickPlayer(plugin.getCore().getMessage("premium-confirm"));
|
||||
} else if (state == ConfirmationState.REQUIRE_AUTH_PLUGIN_LOGIN) {
|
||||
// player logged in successful using premium authentication
|
||||
activate(sender, playerName);
|
||||
}
|
||||
} else {
|
||||
activate(sender, playerName);
|
||||
}
|
||||
}
|
||||
|
||||
private void activate(CommandSender sender, String playerName) {
|
||||
plugin.getCore().getPendingConfirms().remove(playerName);
|
||||
|
||||
//todo: load async
|
||||
StoredProfile profile = plugin.getCore().getStorage().loadProfile(playerName);
|
||||
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.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.getCore().sendLocaleMessage("add-premium-other", sender);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean forwardPremiumCommand(CommandSender sender, String target) {
|
||||
return forwardBungeeCommand(sender, target, true);
|
||||
}
|
||||
}
|
@ -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 forwardBungeeCommand(CommandSender sender, String target, boolean activate) {
|
||||
if (plugin.isBungeeEnabled()) {
|
||||
sendBungeeActivateMessage(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 sendBungeeActivateMessage(CommandSender invoker, String target, boolean activate) {
|
||||
if (invoker instanceof PluginMessageRecipient) {
|
||||
ChannelMessage message = new ChangePremiumMessage(target, activate, true);
|
||||
plugin.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.sendPluginMessage(sender, message);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.github.games647.fastlogin.bukkit.event;
|
||||
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.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;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.github.games647.fastlogin.bukkit.event;
|
||||
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.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;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package com.github.games647.fastlogin.bukkit.hook;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import fr.xephi.authme.api.v3.AuthMeApi;
|
||||
import fr.xephi.authme.events.RestoreSessionEvent;
|
||||
|
||||
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;
|
||||
|
||||
public AuthMeHook(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onSessionRestore(RestoreSessionEvent restoreSessionEvent) {
|
||||
Player player = restoreSessionEvent.getPlayer();
|
||||
|
||||
String id = '/' + player.getAddress().getAddress().getHostAddress() + ':' + player.getAddress().getPort();
|
||||
BukkitLoginSession session = plugin.getLoginSessions().get(id);
|
||||
if (session != null && session.isVerified()) {
|
||||
restoreSessionEvent.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
if (AuthMeApi.getInstance().isAuthenticated(player)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//skips registration and login
|
||||
AuthMeApi.getInstance().forceLogin(player);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) {
|
||||
return AuthMeApi.getInstance().isRegistered(playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
//this automatically login the player too
|
||||
AuthMeApi.getInstance().forceRegister(player, password);
|
||||
return true;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
public 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.github.games647.fastlogin.bukkit.hook;
|
||||
|
||||
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> {
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
SessionManager sessionManager = LogItCore.getInstance().getSessionManager();
|
||||
return sessionManager.isSessionAlive(player)
|
||||
|| 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
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;
|
||||
|
||||
public LoginSecurityHook(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
PlayerSession session = LoginSecurity.getSessionManager().getPlayerSession(player);
|
||||
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();
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
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;
|
||||
|
||||
public 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)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
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;
|
||||
|
||||
public 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()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
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.task.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.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
/**
|
||||
* 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 BungeeListener implements PluginMessageListener {
|
||||
|
||||
private static final String FILE_NAME = "proxy-whitelist.txt";
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
//null if whitelist is empty so bungeecord support is disabled
|
||||
private final Set<UUID> proxyIds;
|
||||
|
||||
public BungeeListener(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
this.proxyIds = loadBungeeCordIds();
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
//check if the player is still online or disconnected
|
||||
Player checkedPlayer = Bukkit.getPlayerExact(loginMessage.getPlayerName());
|
||||
if (checkedPlayer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
//fail if target player is blacklisted because already authenticated or wrong bungeecord id
|
||||
if (checkedPlayer.hasMetadata(plugin.getName())) {
|
||||
plugin.getLog().warn("Received message {} from a blacklisted player {}", loginMessage, checkedPlayer);
|
||||
} else {
|
||||
//fail if BungeeCord support is disabled (id = null)
|
||||
UUID sourceId = loginMessage.getProxyId();
|
||||
if (proxyIds.contains(sourceId)) {
|
||||
readMessage(checkedPlayer, loginMessage);
|
||||
} else {
|
||||
plugin.getLog().warn("Received proxy id: {} that doesn't exist in the proxy whitelist file", sourceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readMessage(Player player, LoginActionMessage message) {
|
||||
String playerName = message.getPlayerName();
|
||||
Type type = message.getType();
|
||||
|
||||
InetSocketAddress address = player.getAddress();
|
||||
String id = '/' + address.getAddress().getHostAddress() + ':' + address.getPort();
|
||||
if (type == Type.LOGIN) {
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, true);
|
||||
playerSession.setVerified(true);
|
||||
plugin.getLoginSessions().put(id, playerSession);
|
||||
|
||||
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, new ForceLoginTask(plugin.getCore(), player), 10L);
|
||||
} else if (type == Type.REGISTER) {
|
||||
Bukkit.getScheduler().runTaskLaterAsynchronously(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);
|
||||
playerSession.setVerified(true);
|
||||
plugin.getLoginSessions().put(id, playerSession);
|
||||
new ForceLoginTask(plugin.getCore(), player).run();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
plugin.getLog().error("Failed to query isRegistered for player: {}", player, ex);
|
||||
}
|
||||
}, 10L);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<UUID> loadBungeeCordIds() {
|
||||
Path whitelistFile = plugin.getPluginFolder().resolve(FILE_NAME);
|
||||
try {
|
||||
if (Files.notExists(whitelistFile)) {
|
||||
Files.createFile(whitelistFile);
|
||||
}
|
||||
|
||||
try (Stream<String> lines = Files.lines(whitelistFile)) {
|
||||
return lines.map(String::trim)
|
||||
.map(UUID::fromString)
|
||||
.collect(toSet());
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
plugin.getLog().error("Failed to create file for Proxy whitelist", ex);
|
||||
} catch (Exception ex) {
|
||||
plugin.getLog().error("Failed to retrieve proxy Id. Disabling BungeeCord support", ex);
|
||||
}
|
||||
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.task.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;
|
||||
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) {
|
||||
if (loginEvent.getResult() == Result.ALLOWED && !plugin.isServerFullyStarted()) {
|
||||
loginEvent.disallow(Result.KICK_OTHER, plugin.getCore().getMessage("not-started"));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerJoin(PlayerJoinEvent joinEvent) {
|
||||
Player player = joinEvent.getPlayer();
|
||||
|
||||
removeBlacklistStatus(player);
|
||||
if (!plugin.isBungeeEnabled()) {
|
||||
//Wait before auth plugin and we received a message from BungeeCord initializes the player
|
||||
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player);
|
||||
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, forceLoginTask, DELAY_LOGIN);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent quitEvent) {
|
||||
Player player = quitEvent.getPlayer();
|
||||
removeBlacklistStatus(player);
|
||||
|
||||
plugin.getPremiumPlayers().remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
private void removeBlacklistStatus(Player player) {
|
||||
player.removeMetadata(plugin.getName(), plugin);
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
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.event.BukkitFastLoginPreLoginEvent;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.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;
|
||||
|
||||
public 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.getLoginSessions().put(player.getAddress().toString(), 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 requestConfirmationLogin(ProtocolLibLoginSource source, StoredProfile profile, String username) {
|
||||
try {
|
||||
source.setOnlineMode();
|
||||
} catch (Exception ex) {
|
||||
plugin.getLog().error("Cannot send encryption packet. Falling back to cracked login for: {}", profile, ex);
|
||||
return;
|
||||
}
|
||||
|
||||
String serverId = source.getServerId();
|
||||
byte[] verify = source.getVerifyToken();
|
||||
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId, verify, profile);
|
||||
plugin.getLoginSessions().put(player.getAddress().toString(), 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.getLoginSessions().put(player.getAddress().toString(), loginSession);
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.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.EncryptionUtil;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
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 static final int WORKER_THREADS = 3;
|
||||
|
||||
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();
|
||||
|
||||
public ProtocolLibListener(FastLoginBukkit plugin) {
|
||||
//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;
|
||||
}
|
||||
|
||||
public static void register(FastLoginBukkit plugin) {
|
||||
//they will be created with a static builder, because otherwise it will throw a NoClassDefFoundError
|
||||
ProtocolLibrary.getProtocolManager()
|
||||
.getAsynchronousManager()
|
||||
.registerAsyncHandler(new ProtocolLibListener(plugin))
|
||||
.start(WORKER_THREADS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||
if (packetEvent.isCancelled()
|
||||
|| plugin.getCore().getAuthPluginHook()== null
|
||||
|| !plugin.isServerFullyStarted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player sender = packetEvent.getPlayer();
|
||||
PacketType packetType = packetEvent.getPacketType();
|
||||
if (packetType == START) {
|
||||
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.getLoginSessions().remove(sessionKey);
|
||||
|
||||
//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);
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.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.wrappers.WrappedChatComponent;
|
||||
import com.github.games647.fastlogin.bukkit.EncryptionUtil;
|
||||
import com.github.games647.fastlogin.core.shared.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;
|
||||
|
||||
public 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;
|
||||
|
||||
public 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);
|
||||
newPacket.getSpecificModifier(PublicKey.class).write(0, publicKey);
|
||||
|
||||
newPacket.getByteArrays().write(0, 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) +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.comphenix.protocol.reflect.MethodUtils;
|
||||
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.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
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;
|
||||
|
||||
public 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();
|
||||
|
||||
if (plugin.getConfig().getBoolean("forwardSkin")) {
|
||||
//go through every session, because player.getAddress is null
|
||||
//loginEvent.getAddress is just a InetAddress not InetSocketAddress, so not unique enough
|
||||
for (BukkitLoginSession session : plugin.getLoginSessions().values()) {
|
||||
if (session.getUsername().equals(player.getName())) {
|
||||
session.getSkin().ifPresent(skin -> applySkin(player, skin.getValue(), skin.getSignature()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applySkin(Player player, String skinData, String signature) {
|
||||
WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player);
|
||||
|
||||
WrappedSignedProperty skin = WrappedSignedProperty.fromValues(Textures.KEY, skinData, signature);
|
||||
try {
|
||||
gameProfile.getProperties().put(Textures.KEY, skin);
|
||||
} catch (ClassCastException castException) {
|
||||
//Cauldron, MCPC, Thermos, ...
|
||||
Object map = GET_PROPERTIES.invoke(gameProfile.getHandle());
|
||||
try {
|
||||
MethodUtils.invokeMethod(map, "put", new Object[]{Textures.KEY, skin.getHandle()});
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
|
||||
plugin.getLog().error("Error setting premium skin of: {}", player, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,234 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.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.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.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.EncryptionUtil;
|
||||
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.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;
|
||||
|
||||
public 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.getLoginSessions().get(player.getAddress().toString());
|
||||
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();
|
||||
|
||||
Cipher cipher;
|
||||
SecretKey loginKey;
|
||||
try {
|
||||
cipher = Cipher.getInstance(privateKey.getAlgorithm());
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
|
||||
loginKey = EncryptionUtil.decryptSharedKey(cipher, sharedSecret);
|
||||
} catch (GeneralSecurityException securityEx) {
|
||||
disconnect("error-kick", false, "Cannot decrypt received contents", securityEx);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!checkVerifyToken(session, cipher, privateKey) || !encryptConnection(loginKey)) {
|
||||
return;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
disconnect("error-kick", false, "Cannot decrypt received contents", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
String serverId = EncryptionUtil.getServerIdHashString("", loginKey, serverKey.getPublic());
|
||||
|
||||
String username = session.getUsername();
|
||||
InetSocketAddress socketAddress = player.getAddress();
|
||||
try {
|
||||
MojangResolver resolver = plugin.getCore().getResolver();
|
||||
InetAddress address = socketAddress.getAddress();
|
||||
Optional<Verification> response = resolver.hasJoined(username, serverId, address);
|
||||
if (response.isPresent()) {
|
||||
plugin.getLog().info("GameProfile {} has a verified premium account", username);
|
||||
|
||||
SkinProperty[] properties = response.get().getProperties();
|
||||
if (properties.length > 0) {
|
||||
session.setSkinProperty(properties[0]);
|
||||
}
|
||||
|
||||
session.setUuid(response.get().getId());
|
||||
session.setVerified(true);
|
||||
|
||||
setPremiumUUID(session.getUuid());
|
||||
receiveFakeStartPacket(username);
|
||||
} 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.getUsername(), 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, Cipher cipher, PrivateKey privateKey)
|
||||
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(cipher, 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.getUsername(), 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 encryptConnection(SecretKey loginKey) throws IllegalArgumentException {
|
||||
try {
|
||||
//get the NMS connection handle of this player
|
||||
Object networkManager = getNetworkManager();
|
||||
|
||||
//try to detect the method by parameters
|
||||
Method encryptMethod = FuzzyReflection
|
||||
.fromObject(networkManager).getMethodByParameters("a", SecretKey.class);
|
||||
|
||||
//encrypt/decrypt following packets
|
||||
//the client expects this behaviour
|
||||
encryptMethod.invoke(networkManager, loginKey);
|
||||
} 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"));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocolsupport;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.LoginSource;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import protocolsupport.api.events.PlayerLoginStartEvent;
|
||||
|
||||
public class ProtocolLoginSource implements LoginSource {
|
||||
|
||||
private final PlayerLoginStartEvent loginStartEvent;
|
||||
|
||||
public 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"loginStartEvent=" + loginStartEvent +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocolsupport;
|
||||
|
||||
import com.github.games647.craftapi.UUIDAdapter;
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.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;
|
||||
|
||||
public ProtocolSupportListener(FastLoginBukkit plugin) {
|
||||
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
|
||||
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onLoginStart(PlayerLoginStartEvent loginStartEvent) {
|
||||
if (loginStartEvent.isLoginDenied() || plugin.getCore().getAuthPluginHook() == null) {
|
||||
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.getLoginSessions().remove(address.toString());
|
||||
|
||||
super.onLogin(username, new ProtocolLoginSource(loginStartEvent));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onConnectionClosed(ConnectionCloseEvent closeEvent) {
|
||||
InetSocketAddress address = closeEvent.getConnection().getAddress();
|
||||
plugin.getLoginSessions().remove(address.toString());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPropertiesResolve(PlayerProfileCompleteEvent profileCompleteEvent) {
|
||||
InetSocketAddress address = profileCompleteEvent.getAddress();
|
||||
BukkitLoginSession session = plugin.getLoginSessions().get(address.toString());
|
||||
|
||||
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.getLoginSessions().put(source.getAddress().toString(), playerSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestConfirmationLogin(ProtocolLoginSource source, StoredProfile profile, String username) {
|
||||
source.setOnlineMode();
|
||||
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, profile);
|
||||
plugin.getLoginSessions().put(source.getAddress().toString(), playerSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startCrackedSession(ProtocolLoginSource source, StoredProfile profile, String username) {
|
||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||
plugin.getLoginSessions().put(source.getAddress().toString(), loginSession);
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package com.github.games647.fastlogin.bukkit.task;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.hook.AuthMeHook;
|
||||
import com.github.games647.fastlogin.bukkit.hook.CrazyLoginHook;
|
||||
import com.github.games647.fastlogin.bukkit.hook.LogItHook;
|
||||
import com.github.games647.fastlogin.bukkit.hook.LoginSecurityHook;
|
||||
import com.github.games647.fastlogin.bukkit.hook.UltraAuthHook;
|
||||
import com.github.games647.fastlogin.bukkit.hook.xAuthHook;
|
||||
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.isBungeeEnabled()) {
|
||||
plugin.getLog().info("BungeeCord 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 is deactivated. "
|
||||
+ "Either one or both of the checks have to pass in order to use this plugin");
|
||||
}
|
||||
|
||||
if (hookFound) {
|
||||
plugin.setServerStarted();
|
||||
}
|
||||
}
|
||||
|
||||
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, 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package com.github.games647.fastlogin.bukkit.task;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginAutoLoginEvent;
|
||||
import com.github.games647.fastlogin.core.PremiumStatus;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.message.SuccessMessage;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.ForceLoginManagement;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
|
||||
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) {
|
||||
super(core, player, getSession(core.getPlugin(), player));
|
||||
}
|
||||
|
||||
private static BukkitLoginSession getSession(FastLoginBukkit plugin, Player player) {
|
||||
//remove the bungeecord identifier if there is ones
|
||||
String id = '/' + player.getAddress().getAddress().getHostAddress() + ':' + player.getAddress().getPort();
|
||||
return plugin.getLoginSessions().remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
//blacklist this target player for BungeeCord ID brute force attacks
|
||||
FastLoginBukkit plugin = core.getPlugin();
|
||||
player.setMetadata(core.getPlugin().getName(), new FixedMetadataValue(plugin, true));
|
||||
|
||||
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().isBungeeEnabled()) {
|
||||
core.getPlugin().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() {
|
||||
if (session == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return session.isVerified() && player.getName().equals(session.getUsername());
|
||||
}
|
||||
}
|
60
bukkit/src/main/resources/plugin.yml
Normal file
60
bukkit/src/main/resources/plugin.yml
Normal file
@ -0,0 +1,60 @@
|
||||
# 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}-${git.commit.id.abbrev}
|
||||
main: ${project.groupId}.${project.artifactId}.${project.name}
|
||||
|
||||
# meta data for plugin managers
|
||||
authors: [games647, 'https://github.com/games647/FastLogin/graphs/contributors']
|
||||
description: |
|
||||
${project.description}
|
||||
website: ${project.url}
|
||||
dev-url: ${project.url}
|
||||
|
||||
# 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
|
||||
- PlaceholderAPI
|
||||
# Auth plugins
|
||||
- AuthMe
|
||||
- LoginSecurity
|
||||
- xAuth
|
||||
- LogIt
|
||||
- UltraAuth
|
||||
- CrazyLogin
|
||||
|
||||
commands:
|
||||
${project.parent.name}:
|
||||
description: 'Label the invoker as premium'
|
||||
aliases: [prem, premium, loginfast]
|
||||
usage: /<command> [player]
|
||||
permission: ${project.artifactId}.command.premium
|
||||
|
||||
cracked:
|
||||
description: 'Label the invoker or the player specified as cracked if he was marked premium before'
|
||||
aliases: [unpremium]
|
||||
usage: /<command> [player]
|
||||
permission: ${project.artifactId}.command.cracked
|
||||
|
||||
permissions:
|
||||
${project.artifactId}.command.premium:
|
||||
description: 'Label themselves as premium'
|
||||
default: true
|
||||
|
||||
${project.artifactId}.command.premium.other:
|
||||
description: 'Label others as premium'
|
||||
children:
|
||||
${project.artifactId}.command.premium: true
|
||||
|
||||
${project.artifactId}.command.cracked:
|
||||
description: 'Label themselves as cracked'
|
||||
default: true
|
||||
|
||||
${project.artifactId}.command.cracked.other:
|
||||
description: 'Label others as cracked'
|
||||
children:
|
||||
${project.artifactId}.command.cracked: true
|
@ -0,0 +1,42 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
|
||||
import static org.junit.Assert.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);
|
||||
// }
|
||||
// }
|
||||
}
|
BIN
bungee/lib/BungeeAuth-1.4.jar
Normal file
BIN
bungee/lib/BungeeAuth-1.4.jar
Normal file
Binary file not shown.
90
bungee/pom.xml
Normal file
90
bungee/pom.xml
Normal file
@ -0,0 +1,90 @@
|
||||
<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 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.11-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<!--This have to be in lowercase because it's used by plugin.yml-->
|
||||
<artifactId>fastlogin.bungee</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<!--Represents the main plugin-->
|
||||
<name>FastLoginBungee</name>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.2</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>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>codemc-repo</id>
|
||||
<url>https://repo.codemc.io/repository/maven-public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<!--Common plugin component-->
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>fastlogin.core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--BungeeCord with also the part outside the API-->
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-proxy</artifactId>
|
||||
<version>1.14-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</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>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,48 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.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 BungeeLoginSession(String username, StoredProfile profile) {
|
||||
super(username, 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;
|
||||
this.confirmationLogin = false;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.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(TextComponent.fromLegacyText(message));
|
||||
else
|
||||
preLoginEvent.setCancelReason(new ComponentBuilder("Kicked").color(ChatColor.WHITE).create());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getAddress() {
|
||||
return connection.getAddress();
|
||||
}
|
||||
|
||||
public PendingConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"connection=" + connection +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.hook.BungeeAuthHook;
|
||||
import com.github.games647.fastlogin.bungee.listener.ConnectListener;
|
||||
import com.github.games647.fastlogin.bungee.listener.PluginMessageListener;
|
||||
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.collect.MapMaker;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
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.plugin.Plugin;
|
||||
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 implements PlatformPlugin<CommandSender> {
|
||||
|
||||
private final ConcurrentMap<PendingConnection, BungeeLoginSession> session = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
private FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core;
|
||||
private AsyncScheduler scheduler;
|
||||
private Logger logger;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
logger = CommonUtil.createLoggerFromJDK(getLogger());
|
||||
scheduler = new AsyncScheduler(logger, getThreadFactory());
|
||||
|
||||
core = new FastLoginCore<>(this);
|
||||
core.load();
|
||||
if (!core.setupDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//events
|
||||
getProxy().getPluginManager().registerListener(this, new ConnectListener(this));
|
||||
getProxy().getPluginManager().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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (core != null) {
|
||||
core.close();
|
||||
}
|
||||
}
|
||||
|
||||
public FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> getCore() {
|
||||
return core;
|
||||
}
|
||||
|
||||
public ConcurrentMap<PendingConnection, BungeeLoginSession> getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
private void registerHook() {
|
||||
Plugin plugin = getProxy().getPluginManager().getPlugin("BungeeAuth");
|
||||
if (plugin != null) {
|
||||
core.setAuthPluginHook(new BungeeAuthHook());
|
||||
logger.info("Hooked into BungeeAuth");
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.github.games647.fastlogin.bungee.event;
|
||||
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.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;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.github.games647.fastlogin.bungee.event;
|
||||
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
package com.github.games647.fastlogin.bungee.listener;
|
||||
|
||||
import com.github.games647.craftapi.UUIDAdapter;
|
||||
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.ConfirmationState;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 final FastLoginBungee plugin;
|
||||
private final Property[] emptyProperties = {};
|
||||
|
||||
public ConnectListener(FastLoginBungee plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPreLogin(PreLoginEvent preLoginEvent) {
|
||||
if (preLoginEvent.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
preLoginEvent.registerIntent(plugin);
|
||||
|
||||
PendingConnection connection = preLoginEvent.getConnection();
|
||||
Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, preLoginEvent, connection);
|
||||
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();
|
||||
InitialHandler initialHandler = (InitialHandler) connection;
|
||||
|
||||
String username = initialHandler.getLoginRequest().getData();
|
||||
if (connection.isOnlineMode()) {
|
||||
LoginSession session = plugin.getSession().get(connection);
|
||||
session.setUuid(connection.getUniqueId());
|
||||
|
||||
StoredProfile playerProfile = session.getProfile();
|
||||
playerProfile.setId(connection.getUniqueId());
|
||||
|
||||
//bungeecord will do this automatically so override it on disabled option
|
||||
if (!plugin.getCore().getConfig().get("premiumUuid", true)) {
|
||||
try {
|
||||
UUID offlineUUID = UUIDAdapter.generateOfflineId(username);
|
||||
|
||||
//bungeecord doesn't support overriding the premium uuid
|
||||
//so we have to do it with reflection
|
||||
Field idField = InitialHandler.class.getDeclaredField("uniqueId");
|
||||
idField.setAccessible(true);
|
||||
idField.set(connection, offlineUUID);
|
||||
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
||||
plugin.getLog().error("Failed to set offline uuid of {}", username, ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (!plugin.getCore().getConfig().get("forwardSkin", true)) {
|
||||
//this is null on offline mode
|
||||
LoginResult loginProfile = initialHandler.getLoginProfile();
|
||||
if (loginProfile != null) {
|
||||
loginProfile.setProperties(emptyProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(ServerConnectedEvent serverConnectedEvent) {
|
||||
ProxiedPlayer player = serverConnectedEvent.getPlayer();
|
||||
Server server = serverConnectedEvent.getServer();
|
||||
|
||||
Runnable loginTask = new ForceLoginTask(plugin.getCore(), player, server);
|
||||
plugin.getScheduler().runAsync(loginTask);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onDisconnect(PlayerDisconnectEvent disconnectEvent) {
|
||||
ProxiedPlayer player = disconnectEvent.getPlayer();
|
||||
plugin.getSession().remove(player.getPendingConnection());
|
||||
plugin.getCore().getPendingConfirms().remove(player.getName(), ConfirmationState.REQUIRE_AUTH_PLUGIN_LOGIN);
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
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.task.AsyncToggleMessage;
|
||||
import com.github.games647.fastlogin.core.ConfirmationState;
|
||||
import com.github.games647.fastlogin.core.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 java.util.Arrays;
|
||||
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
public class PluginMessageListener implements Listener {
|
||||
|
||||
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() || !channel.startsWith(plugin.getName().toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
//the client shouldn't be able to read the messages in order to know something about server internal states
|
||||
//moreover the client shouldn't be able fake a running premium check by sending the result message
|
||||
pluginMessageEvent.setCancelled(true);
|
||||
|
||||
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 fromPlayer, String channel, byte[] data) {
|
||||
ByteArrayDataInput dataInput = ByteStreams.newDataInput(data);
|
||||
if (successChannel.equals(channel)) {
|
||||
onSuccessMessage(fromPlayer);
|
||||
} else if (changeChannel.equals(channel)) {
|
||||
ChangePremiumMessage changeMessage = new ChangePremiumMessage();
|
||||
changeMessage.readFrom(dataInput);
|
||||
|
||||
String playerName = changeMessage.getPlayerName();
|
||||
boolean isSourceInvoker = changeMessage.isSourceInvoker();
|
||||
onChangeMessage(fromPlayer, changeMessage.shouldEnable(), playerName, isSourceInvoker);
|
||||
}
|
||||
}
|
||||
|
||||
private void onChangeMessage(ProxiedPlayer fromPlayer, boolean shouldEnable, String playerName, boolean isSourceInvoker) {
|
||||
FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core = plugin.getCore();
|
||||
if (shouldEnable) {
|
||||
if (!isSourceInvoker) {
|
||||
// fromPlayer is not the target player
|
||||
activePremiumLogin(fromPlayer, playerName, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (plugin.getCore().getConfig().getBoolean("premium-confirm", true)) {
|
||||
ConfirmationState state = plugin.getCore().getPendingConfirms().get(playerName);
|
||||
if (state == null) {
|
||||
// no pending confirmation
|
||||
core.sendLocaleMessage("premium-confirm", fromPlayer);
|
||||
core.getPendingConfirms().put(playerName, ConfirmationState.REQUIRE_RELOGIN);
|
||||
} else if (state == ConfirmationState.REQUIRE_AUTH_PLUGIN_LOGIN) {
|
||||
// player logged in successful using premium authentication
|
||||
activePremiumLogin(fromPlayer, playerName, true);
|
||||
}
|
||||
} else {
|
||||
activePremiumLogin(fromPlayer, playerName, true);
|
||||
}
|
||||
} else {
|
||||
Runnable task = new AsyncToggleMessage(core, fromPlayer, playerName, false, isSourceInvoker);
|
||||
plugin.getScheduler().runAsync(task);
|
||||
}
|
||||
}
|
||||
|
||||
private void activePremiumLogin(ProxiedPlayer fromPlayer, String playerName, boolean isSourceInvoker) {
|
||||
plugin.getCore().getPendingConfirms().remove(playerName);
|
||||
Runnable task = new AsyncToggleMessage(plugin.getCore(), fromPlayer, playerName, true, isSourceInvoker);
|
||||
plugin.getScheduler().runAsync(task);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (!loginSession.isAlreadySaved()) {
|
||||
loginSession.setAlreadySaved(true);
|
||||
|
||||
playerProfile.setId(loginSession.getUuid());
|
||||
playerProfile.setPremium(true);
|
||||
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
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.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.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;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
|
||||
public class AsyncPremiumCheck extends JoinManagement<ProxiedPlayer, CommandSender, BungeeLoginSource>
|
||||
implements Runnable {
|
||||
|
||||
private final FastLoginBungee plugin;
|
||||
private final PreLoginEvent preLoginEvent;
|
||||
|
||||
private final PendingConnection connection;
|
||||
|
||||
public AsyncPremiumCheck(FastLoginBungee plugin, PreLoginEvent preLoginEvent, PendingConnection connection) {
|
||||
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
|
||||
|
||||
this.plugin = plugin;
|
||||
this.preLoginEvent = preLoginEvent;
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.getSession().remove(connection);
|
||||
|
||||
InitialHandler initialHandler = (InitialHandler) connection;
|
||||
String username = initialHandler.getLoginRequest().getData();
|
||||
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 requestConfirmationLogin(BungeeLoginSource source, StoredProfile profile, String username) {
|
||||
source.setOnlineMode();
|
||||
plugin.getSession().put(source.getConnection(), new BungeeLoginSession(username, profile));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startCrackedSession(BungeeLoginSource source, StoredProfile profile, String username) {
|
||||
plugin.getSession().put(source.getConnection(), new BungeeLoginSession(username, false, profile));
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package com.github.games647.fastlogin.bungee.task;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
|
||||
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);
|
||||
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);
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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.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.shared.ForceLoginManagement;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
|
||||
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) {
|
||||
super(core, player, core.getPlugin().getSession().get(player.getPendingConnection()));
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
16
bungee/src/main/resources/bungee.yml
Normal file
16
bungee/src/main/resources/bungee.yml
Normal file
@ -0,0 +1,16 @@
|
||||
# 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}-${git.commit.id.abbrev}
|
||||
author: games647, https://github.com/games647/FastLogin/graphs/contributors
|
||||
|
||||
softDepends:
|
||||
# BungeeCord auth plugins
|
||||
- BungeeAuth
|
||||
|
||||
description: |
|
||||
${project.description}
|
@ -1,198 +0,0 @@
|
||||
Options:
|
||||
ConfigurationVersion_DO_NOT_CHANGE_THIS: 6
|
||||
|
||||
# 0 -> unlimited/as needed.
|
||||
NettyThreads: 0
|
||||
|
||||
# DON'T USE THAT IF YOU CAN USE SOCKETS, Enable when you can't use sockets but you want use that on bungee server, it isn't good as sockets, but it should not explode. NOTE: You NEED MySQL for that, and this is slower and may by VERY buggy on first join.
|
||||
UseChannels: false
|
||||
|
||||
# Time in ms between trying connect LISTENER to HANLDER socket, on linux using smaller times can cause some weird problems.
|
||||
ReTryConnectSocketTime: 250
|
||||
|
||||
# where AutoIn should store exceptions, enabled login, whitelists, cache and other player settings.
|
||||
SaveData:
|
||||
Type: MySQL
|
||||
MySQL:
|
||||
Host: localhost
|
||||
Port: 3306
|
||||
User: minecraft
|
||||
Pass:
|
||||
Database: minecraft
|
||||
Prefix: AutoIn_
|
||||
SQLite:
|
||||
File: AutoIn_PlayerOptions.db
|
||||
|
||||
# Both
|
||||
|
||||
Both:
|
||||
|
||||
Dependencies:
|
||||
|
||||
# If you have one of that plugin, you can force AutoIn to don't use them even if they can be used. (change to true)
|
||||
ForceDisable:
|
||||
ProtocolLib: false
|
||||
Skript: false
|
||||
AuthMe: false
|
||||
LogIt: false
|
||||
XAuth: false
|
||||
LoginSecurity: false
|
||||
|
||||
Listeners:
|
||||
|
||||
# you can try disable that and manualy configure priority below if auto-login will stop work after update of auth pluhin. PS: tell me about that problem!
|
||||
GetFromModule: true
|
||||
PlayerLoginEvent: LOWEST
|
||||
PlayerJoinEvent: LOWEST
|
||||
PlayerQuitEvent: MONITOR
|
||||
|
||||
Players:
|
||||
|
||||
# no more /login commands for premium users! Enable only on servers where you have auth plugin!
|
||||
AutoLogin: true
|
||||
|
||||
# if true autoin will try fix skins, you can disable that if you want use other plugin. (some plugins may still not work)
|
||||
FixSkins: true
|
||||
|
||||
# if true, then all players are exception by default, they can use /ai IAmNowPremium to remove exception flag (NOTE: they will lose all data after login as premium if you have fixedUUIDs set to false)
|
||||
NegateExceptions: false
|
||||
|
||||
# if true, then even premium players needs to register. PS: You can make registration optional, see wiki: https://github.com/GotoFinal/AutoIn/wiki/Registration
|
||||
Registration: true
|
||||
|
||||
# A.K.A. SwitchMode, If you disable that, only old cracked players will be able to join. New cracked players will be kicked from server. Good if you want switch from offline-mode to online-mode without losing players!
|
||||
AllowNewCrackedPlayers: true
|
||||
|
||||
# if true, then everyone have UUID generated from nickname
|
||||
FixedUUID: false
|
||||
|
||||
# WhiteList that works only on cracked players, black-list works even if this is disabled.
|
||||
CrackedWhiteListEnabled: false
|
||||
|
||||
ForceLogin:
|
||||
|
||||
# Allow use RegEx in nicknames list, like 'Test\\d{1,3}'
|
||||
UseRegEx: false
|
||||
|
||||
# Nicknames from this list don't need use login or register command, use to supprot mods like buildcraft and others
|
||||
Nicknames:
|
||||
- SomeNicknameThatDoNotNeedUseLoginOrRegisterCommandEvenIfHeDoNotHavePremium
|
||||
|
||||
Sessions:
|
||||
|
||||
# If enabled, plugin will remember premium players with their IP number, when servers will be down, premium players can be still auto-logged if IP will be valid.
|
||||
Enabled: true
|
||||
|
||||
# If true, then players with valid session can join even if mojang server are down and without using password. PS: You can enable this and ServerProtect, then registered players OR players with valid session can join.
|
||||
AsServerProtect: true
|
||||
|
||||
# time in easy format, 1w = 1 week, 1d = 1 day, 1d5h12s -> 1 day, 5 hours, 12 seconds
|
||||
Expires: 1d
|
||||
|
||||
# If true sessions aren't saved on reload.
|
||||
ExpiresOnRestart: true
|
||||
|
||||
Protections:
|
||||
|
||||
# If enabled, when new cracked player join to game he will get special protection "flag", that work like exception-player. so even if someone buy premium account with this same nickname, he will be still tract as cracked player.
|
||||
# If player will buy premium, he must disable protection using "IamNowPremium" command, or admin must disable it manually, using API or /setProtection command.
|
||||
CrackedPlayersNicknames:
|
||||
Enabled: true
|
||||
|
||||
# If disabled, player must use "ProtectMe" command, or protection must be enabled manually, using API or /setProtection command
|
||||
AutoProtect: true
|
||||
|
||||
|
||||
|
||||
ServerProtect:
|
||||
|
||||
# If enabled and mojang server don't work, only registered players can join (So you must set "Registration" to true). If disabled, all players will be kicked.
|
||||
Enabled: false
|
||||
|
||||
|
||||
# If enabled plugin will remember if username was premium. (It's saved to database/file) NOTE: Enable that if you have bigger server, like 100/200+ players online.
|
||||
Cache: true
|
||||
|
||||
# Server
|
||||
|
||||
|
||||
# ignored by proxy plugin
|
||||
Server:
|
||||
|
||||
|
||||
# It will try check if cache is updated before player join.
|
||||
UpdateCacheOnPreJoin: false
|
||||
|
||||
# It will try check if cache is updated after player join.
|
||||
UpdateCacheOnJoin: true
|
||||
|
||||
|
||||
# Proxy
|
||||
|
||||
|
||||
# ignored by server
|
||||
Proxy:
|
||||
|
||||
# Players needs to be logged in to use commands
|
||||
NeedLoginToUseCommands: true
|
||||
|
||||
# Player can use this commands
|
||||
NotBlocked:
|
||||
- /login
|
||||
- /register
|
||||
- /l
|
||||
|
||||
# players from selected group can be send to other servers. This override Proxy/BungeeCord setting!
|
||||
ForcedServers:
|
||||
PREMIUM:
|
||||
Enabled: false
|
||||
|
||||
# works like "default_server" from bungee
|
||||
Server: PremiumServer
|
||||
|
||||
# works like "force_default_server" from bungee
|
||||
Force: false
|
||||
|
||||
# works like "forced_hosts" from bungee -> this override "Force" option
|
||||
ForcedHosts:
|
||||
eg: nul.goto. ...\\.. .final..IIlIlIIlIl@71c66aab
|
||||
|
||||
# like "fallback_server" from bungee
|
||||
Fallback: FallbackServer
|
||||
CRACKED:
|
||||
Enabled: true
|
||||
|
||||
# works like "default_server" from bungee
|
||||
Server: CrackedServer
|
||||
|
||||
# works like "force_default_server" from bungee
|
||||
Force: true
|
||||
|
||||
# like "fallback_server" from bungee
|
||||
Fallback: FallbackServer
|
||||
EXCEPTION:
|
||||
Enabled: false
|
||||
|
||||
# works like "default_server" from bungee
|
||||
Server: PremiumServer
|
||||
|
||||
# works like "force_default_server" from bungee
|
||||
Force: true
|
||||
|
||||
# like "fallback_server" from bungee
|
||||
Fallback: FallbackServer
|
||||
|
||||
# This status will use this same settings as parent status, but you can override that settings by adding them here.
|
||||
Parent: CRACKED
|
||||
ERROR:
|
||||
Enabled: false
|
||||
|
||||
# works like "default_server" from bungee
|
||||
Server: ErrorServer
|
||||
|
||||
# works like "force_default_server" from bungee
|
||||
Force: false
|
||||
|
||||
# like "fallback_server" from bungee
|
||||
Fallback: FallbackServer
|
||||
|
82
core/pom.xml
Normal file
82
core/pom.xml
Normal file
@ -0,0 +1,82 @@
|
||||
<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 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.11-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>fastlogin.core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<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>3.4.2</version>
|
||||
</dependency>
|
||||
|
||||
<!--Logging framework implements slf4j which is required by hikari-->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-jdk14</artifactId>
|
||||
<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>
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import com.github.games647.craftapi.UUIDAdapter;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import static java.sql.Statement.RETURN_GENERATED_KEYS;
|
||||
|
||||
public class AuthStorage {
|
||||
|
||||
private static final String PREMIUM_TABLE = "premium";
|
||||
|
||||
private static final String LOAD_BY_NAME = "SELECT * FROM `" + PREMIUM_TABLE + "` WHERE `Name`=? LIMIT 1";
|
||||
private static final String LOAD_BY_UUID = "SELECT * FROM `" + PREMIUM_TABLE + "` WHERE `UUID`=? LIMIT 1";
|
||||
private static final String INSERT_PROFILE = "INSERT INTO `" + PREMIUM_TABLE
|
||||
+ "` (`UUID`, `Name`, `Premium`, `LastIp`) " + "VALUES (?, ?, ?, ?) ";
|
||||
// limit not necessary here, because it's unique
|
||||
private static final String UPDATE_PROFILE = "UPDATE `" + PREMIUM_TABLE
|
||||
+ "` SET `UUID`=?, `Name`=?, `Premium`=?, `LastIp`=?, `LastLogin`=CURRENT_TIMESTAMP WHERE `UserID`=?";
|
||||
|
||||
private final FastLoginCore<?, ?, ?> core;
|
||||
private final HikariDataSource dataSource;
|
||||
|
||||
public AuthStorage(FastLoginCore<?, ?, ?> core, String host, int port, String databasePath,
|
||||
HikariConfig config, boolean useSSL) {
|
||||
this.core = core;
|
||||
config.setPoolName(core.getPlugin().getName());
|
||||
|
||||
//a try to fix https://www.spigotmc.org/threads/fastlogin.101192/page-26#post-1874647
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("date_string_format", "yyyy-MM-dd HH:mm:ss");
|
||||
properties.setProperty("useSSL", String.valueOf(useSSL));
|
||||
config.setDataSourceProperties(properties);
|
||||
ThreadFactory platformThreadFactory = core.getPlugin().getThreadFactory();
|
||||
if (platformThreadFactory != null) {
|
||||
config.setThreadFactory(platformThreadFactory);
|
||||
}
|
||||
|
||||
String jdbcUrl = "jdbc:";
|
||||
if (config.getDriverClassName().contains("sqlite")) {
|
||||
String pluginFolder = core.getPlugin().getPluginFolder().toAbsolutePath().toString();
|
||||
databasePath = databasePath.replace("{pluginDir}", pluginFolder);
|
||||
|
||||
jdbcUrl += "sqlite://" + databasePath;
|
||||
config.setConnectionTestQuery("SELECT 1");
|
||||
config.setMaximumPoolSize(1);
|
||||
} else {
|
||||
jdbcUrl += "mysql://" + host + ':' + port + '/' + databasePath;
|
||||
// enable MySQL specific optimizations
|
||||
// default prepStmtCacheSize 25 - amount of cached statements - enough for us
|
||||
// default prepStmtCacheSqlLimit 256 - length of SQL - our queries are not longer
|
||||
// disabled by default - will return the same prepared statement instance
|
||||
config.addDataSourceProperty("cachePrepStmts", true);
|
||||
// default false - available in newer versions caches the statements server-side
|
||||
config.addDataSourceProperty("useServerPrepStmts", true);
|
||||
}
|
||||
|
||||
config.setJdbcUrl(jdbcUrl);
|
||||
this.dataSource = new HikariDataSource(config);
|
||||
}
|
||||
|
||||
public void createTables() throws SQLException {
|
||||
// choose surrogate PK(ID), because UUID can be null for offline players
|
||||
// if UUID is always Premium UUID we would have to update offline player entries on insert
|
||||
// name cannot be PK, because it can be changed for premium players
|
||||
String createDataStmt = "CREATE TABLE IF NOT EXISTS `" + PREMIUM_TABLE + "` ("
|
||||
+ "`UserID` INTEGER PRIMARY KEY AUTO_INCREMENT, "
|
||||
+ "`UUID` CHAR(36), "
|
||||
+ "`Name` VARCHAR(16) NOT NULL, "
|
||||
+ "`Premium` BOOLEAN NOT NULL, "
|
||||
+ "`LastIp` VARCHAR(255) NOT NULL, "
|
||||
+ "`LastLogin` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "
|
||||
//the premium shouldn't steal the cracked account by changing the name
|
||||
+ "UNIQUE (`Name`) "
|
||||
+ ')';
|
||||
|
||||
if (dataSource.getJdbcUrl().contains("sqlite")) {
|
||||
createDataStmt = createDataStmt.replace("AUTO_INCREMENT", "AUTOINCREMENT");
|
||||
}
|
||||
|
||||
//todo: add unique uuid index usage
|
||||
try (Connection con = dataSource.getConnection();
|
||||
Statement createStmt = con.createStatement()) {
|
||||
createStmt.executeUpdate(createDataStmt);
|
||||
}
|
||||
}
|
||||
|
||||
public StoredProfile loadProfile(String name) {
|
||||
try (Connection con = dataSource.getConnection();
|
||||
PreparedStatement loadStmt = con.prepareStatement(LOAD_BY_NAME)
|
||||
) {
|
||||
loadStmt.setString(1, name);
|
||||
|
||||
try (ResultSet resultSet = loadStmt.executeQuery()) {
|
||||
return parseResult(resultSet).orElseGet(() -> new StoredProfile(null, name, false, ""));
|
||||
}
|
||||
} catch (SQLException sqlEx) {
|
||||
core.getPlugin().getLog().error("Failed to query profile: {}", name, sqlEx);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public StoredProfile loadProfile(UUID uuid) {
|
||||
try (Connection con = dataSource.getConnection();
|
||||
PreparedStatement loadStmt = con.prepareStatement(LOAD_BY_UUID)) {
|
||||
loadStmt.setString(1, UUIDAdapter.toMojangId(uuid));
|
||||
|
||||
try (ResultSet resultSet = loadStmt.executeQuery()) {
|
||||
return parseResult(resultSet).orElse(null);
|
||||
}
|
||||
} catch (SQLException sqlEx) {
|
||||
core.getPlugin().getLog().error("Failed to query profile: {}", uuid, sqlEx);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Optional<StoredProfile> parseResult(ResultSet resultSet) throws SQLException {
|
||||
if (resultSet.next()) {
|
||||
long userId = resultSet.getInt(1);
|
||||
|
||||
UUID uuid = Optional.ofNullable(resultSet.getString(2)).map(UUIDAdapter::parseId).orElse(null);
|
||||
|
||||
String name = resultSet.getString(3);
|
||||
boolean premium = resultSet.getBoolean(4);
|
||||
String lastIp = resultSet.getString(5);
|
||||
Instant lastLogin = resultSet.getTimestamp(6).toInstant();
|
||||
return Optional.of(new StoredProfile(userId, uuid, name, premium, lastIp, lastLogin));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public void save(StoredProfile playerProfile) {
|
||||
try (Connection con = dataSource.getConnection()) {
|
||||
String uuid = playerProfile.getOptId().map(UUIDAdapter::toMojangId).orElse(null);
|
||||
|
||||
if (playerProfile.isSaved()) {
|
||||
try (PreparedStatement saveStmt = con.prepareStatement(UPDATE_PROFILE)) {
|
||||
saveStmt.setString(1, uuid);
|
||||
saveStmt.setString(2, playerProfile.getName());
|
||||
saveStmt.setBoolean(3, playerProfile.isPremium());
|
||||
saveStmt.setString(4, playerProfile.getLastIp());
|
||||
|
||||
saveStmt.setLong(5, playerProfile.getRowId());
|
||||
saveStmt.execute();
|
||||
}
|
||||
} else {
|
||||
try (PreparedStatement saveStmt = con.prepareStatement(INSERT_PROFILE, RETURN_GENERATED_KEYS)) {
|
||||
saveStmt.setString(1, uuid);
|
||||
|
||||
saveStmt.setString(2, playerProfile.getName());
|
||||
saveStmt.setBoolean(3, playerProfile.isPremium());
|
||||
saveStmt.setString(4, playerProfile.getLastIp());
|
||||
|
||||
saveStmt.execute();
|
||||
try (ResultSet generatedKeys = saveStmt.getGeneratedKeys()) {
|
||||
if (generatedKeys != null && generatedKeys.next()) {
|
||||
playerProfile.setRowId(generatedKeys.getInt(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
core.getPlugin().getLog().error("Failed to save playerProfile {}", playerProfile, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
dataSource.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import com.github.games647.craftapi.cache.SafeCacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.impl.JDK14LoggerAdapter;
|
||||
|
||||
public class CommonUtil {
|
||||
|
||||
private static final char COLOR_CHAR = '&';
|
||||
private static final char TRANSLATED_CHAR = '§';
|
||||
|
||||
public static <K, V> ConcurrentMap<K, V> buildCache(int expireAfterWrite, int maxSize) {
|
||||
SafeCacheBuilder<Object, Object> builder = SafeCacheBuilder.newBuilder();
|
||||
|
||||
if (expireAfterWrite > 0) {
|
||||
builder.expireAfterWrite(expireAfterWrite, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
if (maxSize > 0) {
|
||||
builder.maximumSize(maxSize);
|
||||
}
|
||||
|
||||
return builder.build(CacheLoader.from(() -> {
|
||||
throw new UnsupportedOperationException();
|
||||
}));
|
||||
}
|
||||
|
||||
public static String translateColorCodes(String rawMessage) {
|
||||
char[] chars = rawMessage.toCharArray();
|
||||
for (int i = 0; i < chars.length - 1; i++) {
|
||||
if (chars[i] == COLOR_CHAR && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(chars[i + 1]) > -1) {
|
||||
chars[i] = TRANSLATED_CHAR;
|
||||
chars[i + 1] = Character.toLowerCase(chars[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
return new String(chars);
|
||||
}
|
||||
|
||||
public static Logger createLoggerFromJDK(java.util.logging.Logger parent) {
|
||||
try {
|
||||
parent.setLevel(Level.ALL);
|
||||
|
||||
Class<JDK14LoggerAdapter> adapterClass = JDK14LoggerAdapter.class;
|
||||
Constructor<JDK14LoggerAdapter> cons = adapterClass.getDeclaredConstructor(java.util.logging.Logger.class);
|
||||
cons.setAccessible(true);
|
||||
return cons.newInstance(parent);
|
||||
} catch (ReflectiveOperationException reflectEx) {
|
||||
parent.log(Level.WARNING, "Cannot create slf4j logging adapter", reflectEx);
|
||||
parent.log(Level.WARNING, "Creating logger instance manually...");
|
||||
return LoggerFactory.getLogger(parent.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private CommonUtil() {
|
||||
//Utility class
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
public enum ConfirmationState {
|
||||
|
||||
/**
|
||||
* Require server login where we request onlinemode authentication
|
||||
*/
|
||||
REQUIRE_RELOGIN,
|
||||
|
||||
/**
|
||||
* The command have to be invoked again to confirm that the player who joined through onlinemode knows
|
||||
* the password of the cracked account
|
||||
*/
|
||||
REQUIRE_AUTH_PLUGIN_LOGIN
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
public enum PremiumStatus {
|
||||
|
||||
PREMIUM,
|
||||
|
||||
CRACKED,
|
||||
|
||||
UNKNOWN
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import com.github.games647.craftapi.model.Profile;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class StoredProfile extends Profile {
|
||||
|
||||
private long rowId;
|
||||
|
||||
private boolean premium;
|
||||
private String lastIp;
|
||||
private Instant lastLogin;
|
||||
|
||||
public StoredProfile(long rowId, UUID uuid, String playerName, boolean premium, String lastIp, Instant lastLogin) {
|
||||
super(uuid, playerName);
|
||||
|
||||
this.rowId = rowId;
|
||||
this.premium = premium;
|
||||
this.lastIp = lastIp;
|
||||
this.lastLogin = lastLogin;
|
||||
}
|
||||
|
||||
public StoredProfile(UUID uuid, String playerName, boolean premium, String lastIp) {
|
||||
this(-1, uuid, playerName, premium, lastIp, Instant.now());
|
||||
}
|
||||
|
||||
public synchronized boolean isSaved() {
|
||||
return rowId >= 0;
|
||||
}
|
||||
|
||||
public synchronized void setPlayerName(String playerName) {
|
||||
this.name = playerName;
|
||||
}
|
||||
|
||||
public synchronized long getRowId() {
|
||||
return rowId;
|
||||
}
|
||||
|
||||
public synchronized void setRowId(long generatedId) {
|
||||
this.rowId = generatedId;
|
||||
}
|
||||
|
||||
// can be null
|
||||
public synchronized UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public synchronized Optional<UUID> getOptId() {
|
||||
return Optional.ofNullable(id);
|
||||
}
|
||||
|
||||
public synchronized void setId(UUID uniqueId) {
|
||||
this.id = uniqueId;
|
||||
}
|
||||
|
||||
public synchronized boolean isPremium() {
|
||||
return premium;
|
||||
}
|
||||
|
||||
public synchronized void setPremium(boolean premium) {
|
||||
this.premium = premium;
|
||||
}
|
||||
|
||||
public synchronized String getLastIp() {
|
||||
return lastIp;
|
||||
}
|
||||
|
||||
public synchronized void setLastIp(String lastIp) {
|
||||
this.lastIp = lastIp;
|
||||
}
|
||||
|
||||
public synchronized Instant getLastLogin() {
|
||||
return lastLogin;
|
||||
}
|
||||
|
||||
public synchronized void setLastLogin(Instant lastLogin) {
|
||||
this.lastLogin = lastLogin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String toString() {
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"rowId=" + rowId +
|
||||
", premium=" + premium +
|
||||
", lastIp='" + lastIp + '\'' +
|
||||
", lastLogin=" + lastLogin +
|
||||
"} " + super.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package com.github.games647.fastlogin.core.hooks;
|
||||
|
||||
/**
|
||||
* Represents a supporting authentication plugin in BungeeCord and Bukkit/Spigot/... servers
|
||||
*
|
||||
* @param <P> either {@link org.bukkit.entity.Player} for Bukkit or {@link net.md_5.bungee.api.connection.ProxiedPlayer}
|
||||
* for BungeeCord
|
||||
*/
|
||||
public interface AuthPlugin<P> {
|
||||
|
||||
/**
|
||||
* Login the premium (paid account) player after the player joined successfully the server.
|
||||
*
|
||||
* <strong>This operation will be performed async while the player successfully
|
||||
* joined the server.</strong>
|
||||
*
|
||||
* @param player the player that needs to be logged in
|
||||
* @return if the operation was successful
|
||||
*/
|
||||
boolean forceLogin(P player);
|
||||
|
||||
/**
|
||||
* Forces a register in order to protect the paid account.
|
||||
*
|
||||
* <strong>This operation will be performed async while the player successfully
|
||||
* joined the server.</strong>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Background: 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(P player, String password);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.github.games647.fastlogin.core.hooks;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Random;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class DefaultPasswordGenerator<P> implements PasswordGenerator<P> {
|
||||
|
||||
private static final int PASSWORD_LENGTH = 8;
|
||||
private static final char[] PASSWORD_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
.toCharArray();
|
||||
|
||||
private final Random random = new SecureRandom();
|
||||
|
||||
@Override
|
||||
public String getRandomPassword(P player) {
|
||||
StringBuilder generatedPassword = new StringBuilder(8);
|
||||
IntStream.rangeClosed(1, PASSWORD_LENGTH)
|
||||
.map(i -> random.nextInt(PASSWORD_CHARACTERS.length - 1))
|
||||
.mapToObj(pos -> PASSWORD_CHARACTERS[pos])
|
||||
.forEach(generatedPassword::append);
|
||||
|
||||
return generatedPassword.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.github.games647.fastlogin.core.hooks;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface PasswordGenerator<P> {
|
||||
|
||||
String getRandomPassword(P player);
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package com.github.games647.fastlogin.core.message;
|
||||
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
|
||||
public class ChangePremiumMessage implements ChannelMessage {
|
||||
|
||||
public static final String CHANGE_CHANNEL = "ch-st";
|
||||
|
||||
private String playerName;
|
||||
private boolean willEnable;
|
||||
private boolean isSourceInvoker;
|
||||
|
||||
public ChangePremiumMessage(String playerName, boolean willEnable, boolean isSourceInvoker) {
|
||||
this.playerName = playerName;
|
||||
this.willEnable = willEnable;
|
||||
this.isSourceInvoker = isSourceInvoker;
|
||||
}
|
||||
|
||||
public ChangePremiumMessage() {
|
||||
//reading from
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public boolean shouldEnable() {
|
||||
return willEnable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the player invoker = target
|
||||
*/
|
||||
public boolean isSourceInvoker() {
|
||||
return isSourceInvoker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelName() {
|
||||
return CHANGE_CHANNEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(ByteArrayDataInput input) {
|
||||
willEnable = input.readBoolean();
|
||||
playerName = input.readUTF();
|
||||
isSourceInvoker = input.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(ByteArrayDataOutput output) {
|
||||
output.writeBoolean(willEnable);
|
||||
output.writeUTF(playerName);
|
||||
output.writeBoolean(isSourceInvoker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"playerName='" + playerName + '\'' +
|
||||
", shouldEnable=" + willEnable +
|
||||
", isSourceInvoker=" + isSourceInvoker +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.github.games647.fastlogin.core.message;
|
||||
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
|
||||
public interface ChannelMessage {
|
||||
|
||||
String getChannelName();
|
||||
|
||||
void readFrom(ByteArrayDataInput input);
|
||||
|
||||
void writeTo(ByteArrayDataOutput output);
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package com.github.games647.fastlogin.core.message;
|
||||
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class LoginActionMessage implements ChannelMessage {
|
||||
|
||||
public static final String FORCE_CHANNEL = "force";
|
||||
|
||||
private Type type;
|
||||
|
||||
private String playerName;
|
||||
private UUID proxyId;
|
||||
|
||||
public LoginActionMessage(Type type, String playerName, UUID proxyId) {
|
||||
this.type = type;
|
||||
this.playerName = playerName;
|
||||
this.proxyId = proxyId;
|
||||
}
|
||||
|
||||
public LoginActionMessage() {
|
||||
//reading mode
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public UUID getProxyId() {
|
||||
return proxyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(ByteArrayDataInput input) {
|
||||
this.type = Type.values()[input.readInt()];
|
||||
|
||||
this.playerName = input.readUTF();
|
||||
|
||||
//bungeecord UUID
|
||||
long mostSignificantBits = input.readLong();
|
||||
long leastSignificantBits = input.readLong();
|
||||
this.proxyId = new UUID(mostSignificantBits, leastSignificantBits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(ByteArrayDataOutput output) {
|
||||
output.writeInt(type.ordinal());
|
||||
|
||||
//Data is sent through a random player. We have to tell the Bukkit version of this plugin the target
|
||||
output.writeUTF(playerName);
|
||||
|
||||
//proxy identifier to check if it's a acceptable proxy
|
||||
output.writeLong(proxyId.getMostSignificantBits());
|
||||
output.writeLong(proxyId.getLeastSignificantBits());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelName() {
|
||||
return FORCE_CHANNEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"type='" + type + '\'' +
|
||||
", playerName='" + playerName + '\'' +
|
||||
", proxyId=" + proxyId +
|
||||
'}';
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
|
||||
LOGIN,
|
||||
|
||||
REGISTER,
|
||||
|
||||
CRACKED
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.github.games647.fastlogin.core.message;
|
||||
|
||||
public class NamespaceKey {
|
||||
|
||||
private static final char SEPARATOR_CHAR = ':';
|
||||
|
||||
private final String namespace;
|
||||
private final String key;
|
||||
|
||||
private final String combined;
|
||||
|
||||
public NamespaceKey(String namespace, String key) {
|
||||
this.namespace = namespace.toLowerCase();
|
||||
this.key = key.toLowerCase();
|
||||
|
||||
this.combined = this.namespace + SEPARATOR_CHAR + this.key;
|
||||
}
|
||||
|
||||
public String getCombinedName() {
|
||||
return combined;
|
||||
}
|
||||
|
||||
public static String getCombined(String namespace, String key) {
|
||||
return new NamespaceKey(namespace, key).combined;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.github.games647.fastlogin.core.message;
|
||||
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
|
||||
public class SuccessMessage implements ChannelMessage {
|
||||
|
||||
public static final String SUCCESS_CHANNEL = "succ";
|
||||
|
||||
@Override
|
||||
public String getChannelName() {
|
||||
return SUCCESS_CHANNEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(ByteArrayDataInput input) {
|
||||
//empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(ByteArrayDataOutput output) {
|
||||
//empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + "{}";
|
||||
}
|
||||
}
|
@ -0,0 +1,257 @@
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import com.github.games647.craftapi.resolver.MojangResolver;
|
||||
import com.github.games647.craftapi.resolver.http.RotatingProxySelector;
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.CommonUtil;
|
||||
import com.github.games647.fastlogin.core.ConfirmationState;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.github.games647.fastlogin.core.hooks.DefaultPasswordGenerator;
|
||||
import com.github.games647.fastlogin.core.hooks.PasswordGenerator;
|
||||
import com.google.common.net.HostAndPort;
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.Proxy.Type;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import net.md_5.bungee.config.ConfigurationProvider;
|
||||
import net.md_5.bungee.config.YamlConfiguration;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import static java.util.function.Function.identity;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
/**
|
||||
* @param <P> GameProfile class
|
||||
* @param <C> CommandSender
|
||||
* @param <T> Plugin class
|
||||
*/
|
||||
public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
|
||||
private final Map<String, String> localeMessages = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<String, Object> pendingLogin = CommonUtil.buildCache(5, -1);
|
||||
private final ConcurrentMap<String, ConfirmationState> pendingConfirms = CommonUtil.buildCache(1, 1024);
|
||||
private final T plugin;
|
||||
|
||||
private final MojangResolver resolver = new MojangResolver();
|
||||
|
||||
private Configuration config;
|
||||
private AuthStorage storage;
|
||||
private PasswordGenerator<P> passwordGenerator = new DefaultPasswordGenerator<>();
|
||||
private AuthPlugin<P> authPlugin;
|
||||
|
||||
public FastLoginCore(T plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void load() {
|
||||
saveDefaultFile("messages.yml");
|
||||
saveDefaultFile("config.yml");
|
||||
|
||||
try {
|
||||
config = loadFile("config.yml");
|
||||
Configuration messages = loadFile("messages.yml");
|
||||
|
||||
messages.getKeys()
|
||||
.stream()
|
||||
.filter(key -> messages.get(key) != null)
|
||||
.collect(toMap(identity(), messages::get))
|
||||
.forEach((key, message) -> {
|
||||
String colored = CommonUtil.translateColorCodes((String) message);
|
||||
if (!colored.isEmpty()) {
|
||||
localeMessages.put(key, colored.replace("/newline", "\n"));
|
||||
}
|
||||
});
|
||||
} catch (IOException ioEx) {
|
||||
plugin.getLog().error("Failed to load yaml files", ioEx);
|
||||
}
|
||||
|
||||
Set<Proxy> proxies = config.getStringList("proxies")
|
||||
.stream()
|
||||
.map(HostAndPort::fromString)
|
||||
.map(proxy -> new InetSocketAddress(proxy.getHostText(), proxy.getPort()))
|
||||
.map(sa -> new Proxy(Type.HTTP, sa))
|
||||
.collect(toSet());
|
||||
|
||||
Collection<InetAddress> addresses = new HashSet<>();
|
||||
for (String localAddress : config.getStringList("ip-addresses")) {
|
||||
try {
|
||||
addresses.add(InetAddress.getByName(localAddress.replace('-', '.')));
|
||||
} catch (UnknownHostException ex) {
|
||||
plugin.getLog().error("IP-Address is unknown to us", ex);
|
||||
}
|
||||
}
|
||||
|
||||
resolver.setMaxNameRequests(config.getInt("mojang-request-limit"));
|
||||
resolver.setProxySelector(new RotatingProxySelector(proxies));
|
||||
resolver.setOutgoingAddresses(addresses);
|
||||
}
|
||||
|
||||
private Configuration loadFile(String fileName) throws IOException {
|
||||
ConfigurationProvider configProvider = ConfigurationProvider.getProvider(YamlConfiguration.class);
|
||||
|
||||
Configuration defaults;
|
||||
try (InputStream defaultStream = getClass().getClassLoader().getResourceAsStream(fileName)) {
|
||||
defaults = configProvider.load(defaultStream);
|
||||
}
|
||||
|
||||
Path file = plugin.getPluginFolder().resolve(fileName);
|
||||
|
||||
Configuration config;
|
||||
try (Reader reader = Files.newBufferedReader(file)) {
|
||||
config = configProvider.load(reader, defaults);
|
||||
}
|
||||
|
||||
//explicitly add keys here, because Configuration.getKeys doesn't return the keys from the default configuration
|
||||
for (String key : defaults.getKeys()) {
|
||||
config.set(key, config.get(key));
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public MojangResolver getResolver() {
|
||||
return resolver;
|
||||
}
|
||||
|
||||
public AuthStorage getStorage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
public T getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
public void sendLocaleMessage(String key, C receiver) {
|
||||
String message = localeMessages.get(key);
|
||||
if (message != null) {
|
||||
plugin.sendMultiLineMessage(receiver, message);
|
||||
}
|
||||
}
|
||||
|
||||
public String getMessage(String key) {
|
||||
return localeMessages.get(key);
|
||||
}
|
||||
|
||||
public boolean setupDatabase() {
|
||||
HikariConfig databaseConfig = new HikariConfig();
|
||||
databaseConfig.setDriverClassName(config.getString("driver"));
|
||||
if (!checkDriver(databaseConfig.getDriverClassName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String host = config.get("host", "");
|
||||
int port = config.get("port", 3306);
|
||||
String database = config.getString("database");
|
||||
|
||||
boolean useSSL = config.get("useSSL", false);
|
||||
|
||||
databaseConfig.setUsername(config.get("username", ""));
|
||||
databaseConfig.setPassword(config.getString("password"));
|
||||
|
||||
databaseConfig.setConnectionTimeout(config.getInt("timeout", 30) * 1_000L);
|
||||
databaseConfig.setMaxLifetime(config.getInt("lifetime", 30) * 1_000L);
|
||||
|
||||
storage = new AuthStorage(this, host, port, database, databaseConfig, useSSL);
|
||||
try {
|
||||
storage.createTables();
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
plugin.getLog().warn("Failed to setup database. Disabling plugin...", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkDriver(String className) {
|
||||
try {
|
||||
Class.forName(className);
|
||||
return true;
|
||||
} catch (ClassNotFoundException notFoundEx) {
|
||||
Logger log = plugin.getLog();
|
||||
log.warn("This driver {} is not supported on this platform", className);
|
||||
log.warn("Please choose MySQL (Spigot+BungeeCord), SQLite (Spigot+Sponge) or MariaDB (Sponge)", notFoundEx);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Configuration getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public PasswordGenerator<P> getPasswordGenerator() {
|
||||
return passwordGenerator;
|
||||
}
|
||||
|
||||
public void setPasswordGenerator(PasswordGenerator<P> passwordGenerator) {
|
||||
this.passwordGenerator = passwordGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of player names that are currently during the login process and might fail and so could be used
|
||||
* for second attempt logins
|
||||
*/
|
||||
public ConcurrentMap<String, Object> getPendingLogin() {
|
||||
return pendingLogin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of player names that request onlinemode authentication but are not yet approved
|
||||
*/
|
||||
public ConcurrentMap<String, ConfirmationState> getPendingConfirms() {
|
||||
return pendingConfirms;
|
||||
}
|
||||
|
||||
public AuthPlugin<P> getAuthPluginHook() {
|
||||
return authPlugin;
|
||||
}
|
||||
|
||||
public void setAuthPluginHook(AuthPlugin<P> authPlugin) {
|
||||
this.authPlugin = authPlugin;
|
||||
}
|
||||
|
||||
public void saveDefaultFile(String fileName) {
|
||||
Path dataFolder = plugin.getPluginFolder();
|
||||
|
||||
try {
|
||||
if (Files.notExists(dataFolder)) {
|
||||
Files.createDirectories(dataFolder);
|
||||
}
|
||||
|
||||
Path configFile = dataFolder.resolve(fileName);
|
||||
if (Files.notExists(configFile)) {
|
||||
try (InputStream defaultStream = getClass().getClassLoader().getResourceAsStream(fileName)) {
|
||||
Files.copy(defaultStream, configFile);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioExc) {
|
||||
plugin.getLog().error("Cannot create plugin folder {}", dataFolder, ioExc);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
plugin.getLog().info("Safely shutting down scheduler. This could take up to one minute.");
|
||||
plugin.getScheduler().shutdown();
|
||||
|
||||
if (storage != null) {
|
||||
storage.close();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
|
||||
|
||||
public abstract class ForceLoginManagement<P extends C, C, L extends LoginSession, T extends PlatformPlugin<C>>
|
||||
implements Runnable {
|
||||
|
||||
protected final FastLoginCore<P, C, T> core;
|
||||
protected final P player;
|
||||
protected final L session;
|
||||
|
||||
public ForceLoginManagement(FastLoginCore<P, C, T> core, P player, L session) {
|
||||
this.core = core;
|
||||
this.player = player;
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isOnline(player) || session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
AuthStorage storage = core.getStorage();
|
||||
StoredProfile playerProfile = session.getProfile();
|
||||
try {
|
||||
if (isOnlineMode()) {
|
||||
if (session.isConfirmationPending()) {
|
||||
// do not perform force actions, because this confirmation login we have to verify that it's the
|
||||
// owner of the account
|
||||
return;
|
||||
}
|
||||
|
||||
//premium player
|
||||
AuthPlugin<P> authPlugin = core.getAuthPluginHook();
|
||||
if (authPlugin == null) {
|
||||
//maybe only bungeecord plugin without any auth plugins on Bungee
|
||||
onForceActionSuccess(session);
|
||||
} else {
|
||||
boolean success = true;
|
||||
String playerName = getName(player);
|
||||
if (core.getConfig().get("autoLogin", true)) {
|
||||
if (session.needsRegistration()
|
||||
|| (core.getConfig().get("auto-register-unknown", false)
|
||||
&& !authPlugin.isRegistered(playerName))) {
|
||||
success = forceRegister(player);
|
||||
} else if (!callFastLoginAutoLoginEvent(session, playerProfile).isCancelled()) {
|
||||
success = forceLogin(player);
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
//update only on success to prevent corrupt data
|
||||
if (playerProfile != null) {
|
||||
playerProfile.setId(session.getUuid());
|
||||
playerProfile.setPremium(true);
|
||||
storage.save(playerProfile);
|
||||
}
|
||||
|
||||
onForceActionSuccess(session);
|
||||
}
|
||||
}
|
||||
} else if (playerProfile != null) {
|
||||
//cracked player
|
||||
playerProfile.setId(null);
|
||||
playerProfile.setPremium(false);
|
||||
storage.save(playerProfile);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
core.getPlugin().getLog().warn("ERROR ON FORCE LOGIN of {}", getName(player), ex);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean forceRegister(P player) {
|
||||
core.getPlugin().getLog().info("Register player {}", getName(player));
|
||||
|
||||
String generatedPassword = core.getPasswordGenerator().getRandomPassword(player);
|
||||
boolean success = core.getAuthPluginHook().forceRegister(player, generatedPassword);
|
||||
|
||||
String message = core.getMessage("auto-register");
|
||||
if (success && message != null) {
|
||||
message = message.replace("%password", generatedPassword);
|
||||
core.getPlugin().sendMessage(player, message);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public boolean forceLogin(P player) {
|
||||
core.getPlugin().getLog().info("Logging player {} in", getName(player));
|
||||
|
||||
boolean success = core.getAuthPluginHook().forceLogin(player);
|
||||
if (success) {
|
||||
core.sendLocaleMessage("auto-login", player);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public abstract FastLoginAutoLoginEvent callFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile);
|
||||
|
||||
public abstract void onForceActionSuccess(LoginSession session);
|
||||
|
||||
public abstract String getName(P player);
|
||||
|
||||
public abstract boolean isOnline(P player);
|
||||
|
||||
public abstract boolean isOnlineMode();
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import com.github.games647.craftapi.model.Profile;
|
||||
import com.github.games647.craftapi.resolver.RateLimitException;
|
||||
import com.github.games647.fastlogin.core.ConfirmationState;
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
|
||||
public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
|
||||
|
||||
protected final FastLoginCore<P, C, ?> core;
|
||||
protected final AuthPlugin<P> authHook;
|
||||
|
||||
public JoinManagement(FastLoginCore<P, C, ?> core, AuthPlugin<P> authHook) {
|
||||
this.core = core;
|
||||
this.authHook = authHook;
|
||||
}
|
||||
|
||||
public void onLogin(String username, S source) {
|
||||
StoredProfile profile = core.getStorage().loadProfile(username);
|
||||
if (profile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
callFastLoginPreLoginEvent(username, source, profile);
|
||||
|
||||
Configuration config = core.getConfig();
|
||||
|
||||
String ip = source.getAddress().getAddress().getHostAddress();
|
||||
profile.setLastIp(ip);
|
||||
try {
|
||||
if (profile.isSaved()) {
|
||||
if (profile.isPremium()) {
|
||||
requestPremiumLogin(source, profile, username, true);
|
||||
} else {
|
||||
ConfirmationState confirmationState = core.getPendingConfirms().get(username);
|
||||
if (confirmationState == ConfirmationState.REQUIRE_RELOGIN) {
|
||||
core.getPendingConfirms().put(username, ConfirmationState.REQUIRE_AUTH_PLUGIN_LOGIN);
|
||||
requestPremiumLogin(source, profile, username, true);
|
||||
} else {
|
||||
// cracked player, but wants to change to premium
|
||||
startCrackedSession(source, profile, username);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (core.getPendingLogin().remove(ip + username) != null && config.get("secondAttemptCracked", false)) {
|
||||
core.getPlugin().getLog().info("Second attempt login -> cracked {}", username);
|
||||
|
||||
//first login request failed so make a cracked session
|
||||
startCrackedSession(source, profile, username);
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Profile> premiumUUID = Optional.empty();
|
||||
if (config.get("nameChangeCheck", false) || config.get("autoRegister", false)) {
|
||||
premiumUUID = core.getResolver().findProfile(username);
|
||||
}
|
||||
|
||||
if (!premiumUUID.isPresent()
|
||||
|| (!checkNameChange(source, username, premiumUUID.get())
|
||||
&& !checkPremiumName(source, username, profile))) {
|
||||
//nothing detected the player as premium -> start a cracked session
|
||||
if (core.getConfig().get("switchMode", false)) {
|
||||
source.kick(core.getMessage("switch-kick-message"));
|
||||
return;
|
||||
}
|
||||
|
||||
startCrackedSession(source, profile, username);
|
||||
}
|
||||
}
|
||||
} catch (RateLimitException rateLimitEx) {
|
||||
core.getPlugin().getLog().error("Mojang's rate limit reached for {}. The public IPv4 address of this" +
|
||||
" server issued more than 600 Name -> UUID requests within 10 minutes. After those 10" +
|
||||
" minutes we can make requests again.", username);
|
||||
} catch (Exception ex) {
|
||||
core.getPlugin().getLog().error("Failed to check premium state for {}", username, ex);
|
||||
core.getPlugin().getLog().error("Failed to check premium state of {}", username, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkPremiumName(S source, String username, StoredProfile profile) throws Exception {
|
||||
core.getPlugin().getLog().info("GameProfile {} uses a premium username", username);
|
||||
if (core.getConfig().get("autoRegister", false) && (authHook == null || !authHook.isRegistered(username))) {
|
||||
requestPremiumLogin(source, profile, username, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean checkNameChange(S source, String username, Profile profile) {
|
||||
//user not exists in the db
|
||||
if (core.getConfig().get("nameChangeCheck", false)) {
|
||||
StoredProfile storedProfile = core.getStorage().loadProfile(profile.getId());
|
||||
if (storedProfile != null) {
|
||||
//uuid exists in the database
|
||||
core.getPlugin().getLog().info("GameProfile {} changed it's username", profile);
|
||||
|
||||
//update the username to the new one in the database
|
||||
storedProfile.setPlayerName(username);
|
||||
|
||||
requestPremiumLogin(source, storedProfile, username, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, S source, StoredProfile profile);
|
||||
|
||||
public abstract void requestPremiumLogin(S source, StoredProfile profile, String username, boolean registered);
|
||||
|
||||
public abstract void requestConfirmationLogin(S source, StoredProfile profile, String username);
|
||||
|
||||
public abstract void startCrackedSession(S source, StoredProfile profile, String username);
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class LoginSession {
|
||||
|
||||
private final String username;
|
||||
private final StoredProfile profile;
|
||||
|
||||
private UUID uuid;
|
||||
|
||||
protected boolean registered;
|
||||
|
||||
protected boolean confirmationLogin;
|
||||
|
||||
public LoginSession(String username, boolean registered, StoredProfile profile) {
|
||||
this.username = username;
|
||||
this.registered = registered;
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
public LoginSession(String username, StoredProfile profile) {
|
||||
this(username, true, profile);
|
||||
this.confirmationLogin = true;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* This value is always false if we authenticate the player with a cracked authentication
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean needsRegistration() {
|
||||
return !registered;
|
||||
}
|
||||
|
||||
public StoredProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
public synchronized boolean isConfirmationPending() {
|
||||
return confirmationLogin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String toString() {
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"username='" + username + '\'' +
|
||||
", profile=" + profile +
|
||||
", uuid=" + uuid +
|
||||
", registered=" + registered +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public interface LoginSource {
|
||||
|
||||
void setOnlineMode() throws Exception;
|
||||
|
||||
void kick(String message) throws Exception;
|
||||
|
||||
InetSocketAddress getAddress();
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import com.github.games647.fastlogin.core.AsyncScheduler;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public interface PlatformPlugin<C> {
|
||||
|
||||
String getName();
|
||||
|
||||
Path getPluginFolder();
|
||||
|
||||
Logger getLog();
|
||||
|
||||
void sendMessage(C receiver, String message);
|
||||
|
||||
AsyncScheduler getScheduler();
|
||||
|
||||
default void sendMultiLineMessage(C receiver, String message) {
|
||||
for (String line : message.split("%nl%")) {
|
||||
sendMessage(receiver, line);
|
||||
}
|
||||
}
|
||||
|
||||
default ThreadFactory getThreadFactory() {
|
||||
return new ThreadFactoryBuilder()
|
||||
.setNameFormat(getName() + " Pool Thread #%1$d")
|
||||
// Hikari create daemons by default and we could daemon threads for our own scheduler too
|
||||
// because we safely shutdown
|
||||
.setDaemon(true)
|
||||
.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.github.games647.fastlogin.core.shared.event;
|
||||
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
|
||||
public interface FastLoginAutoLoginEvent extends FastLoginCancellableEvent {
|
||||
LoginSession getSession();
|
||||
StoredProfile getProfile();
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.github.games647.fastlogin.core.shared.event;
|
||||
|
||||
public interface FastLoginCancellableEvent {
|
||||
|
||||
boolean isCancelled();
|
||||
void setCancelled(boolean cancelled);
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.github.games647.fastlogin.core.shared.event;
|
||||
|
||||
import com.github.games647.fastlogin.core.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSource;
|
||||
|
||||
public interface FastLoginPreLoginEvent {
|
||||
|
||||
String getUsername();
|
||||
LoginSource getSource();
|
||||
StoredProfile getProfile();
|
||||
}
|
192
core/src/main/resources/config.yml
Normal file
192
core/src/main/resources/config.yml
Normal file
@ -0,0 +1,192 @@
|
||||
# FastLogin config
|
||||
# Project site: https://www.spigotmc.org/resources/fastlogin.14153
|
||||
# Source code: https://github.com/games647/FastLogin
|
||||
#
|
||||
# You can access the newest config here:
|
||||
# https://github.com/games647/FastLogin/blob/master/core/src/main/resources/config.yml
|
||||
|
||||
# Request a premium login without forcing the player to type a command
|
||||
#
|
||||
# If you activate autoRegister, this plugin will check/do these points on login:
|
||||
# 1. An existing cracked account shouldn't exist
|
||||
# -> paid accounts cannot steal the existing account of cracked players
|
||||
# - (Already registered players could still use the /premium command to activate premium checks)
|
||||
# 2. Automatically registers an account with a strong random generated password
|
||||
# -> cracked player cannot register an account for the premium player and so cannot the steal the account
|
||||
#
|
||||
# Furthermore the premium player check have to be made based on the player name
|
||||
# This means if a cracked player connects to the server and we request a paid account login from this player
|
||||
# the player just disconnect and sees the message: 'bad login' or 'invalid session'
|
||||
# There is no way to change this message
|
||||
# For more information: https://github.com/games647/FastLogin#why-do-players-have-to-invoke-a-command
|
||||
autoRegister: false
|
||||
|
||||
# This is extra configuration option to the feature above. If we request a premium authentication from a player who
|
||||
# isn't actual premium but used a premium username, the player will disconnect with the reason "invalid session" or
|
||||
# "bad login".
|
||||
#
|
||||
# If you activate this, we are remembering this player and do not force another premium authentication if the player
|
||||
# tries to join again, so the player could join as cracked player.
|
||||
secondAttemptCracked: false
|
||||
|
||||
# New cracked players will be kicked from server. Good if you want switch from offline-mode to online-mode without
|
||||
# losing players!
|
||||
#
|
||||
# Existing cracked and premium players could still join your server. Moreover you could add playernames to a whitelist.
|
||||
# So that these cracked players could join too although they are new players.
|
||||
switchMode: false
|
||||
|
||||
# If this plugin detected that a player has a premium, it can also set the associated
|
||||
# uuid from that account. So if the player changes the username, they will still have
|
||||
# the same player data (inventory, permissions, ...)
|
||||
#
|
||||
# Warning: This also means that the UUID will be different if the player is connecting
|
||||
# through a offline mode connection. This **could** cause plugin compatibility issues.
|
||||
#
|
||||
# This is a example and doesn't apply for every plugin.
|
||||
# Example: If you want to ban players who aren't online at the moment, the ban plugin will look
|
||||
# after a offline uuid associated to the player, because the server is in offline mode. Then the premium
|
||||
# players could still join the server, because they have different UUID.
|
||||
#
|
||||
# Moreover you may want to convert the offline UUID to a premium UUID. This will ensure that the player
|
||||
# will have the same inventory, permissions, ... if they switched to premium authentication from offline/cracked
|
||||
# authentication.
|
||||
#
|
||||
# This feature requires Cauldron, Spigot or a fork of Spigot (Paper)
|
||||
premiumUuid: false
|
||||
|
||||
# This will make an additional check (only for player names which are not in the database) against the mojang servers
|
||||
# in order to get the premium UUID. If that premium UUID is in the database, we can assume on successful login that the
|
||||
# player changed it's username and we just update the name in the database.
|
||||
# Examples:
|
||||
# #### Case 1
|
||||
# nameChangeCheck = false ----- autoRegister = false
|
||||
#
|
||||
# GameProfile logins as cracked until the player invoked the command /premium. Then we could override the existing
|
||||
# database record.
|
||||
#
|
||||
# #### Case 2
|
||||
#
|
||||
# nameChangeCheck = true ----- autoRegister = false
|
||||
#
|
||||
# Connect the Mojang API and check what UUID the player has (UUID exists => Paid Minecraft account). If that UUID is in
|
||||
# the database it's an **existing player** and FastLogin can **assume** the player is premium and changed the username.
|
||||
# If it's not in the database, it's a new player and **could be a cracked player**. So we just use a offline mode
|
||||
# authentication for this player.
|
||||
#
|
||||
# **Limitation**: Cracked players who uses the new username of a paid account cannot join the server if the database
|
||||
# contains the old name. (Example: The owner of the paid account no longer plays on the server, but changed the username
|
||||
# in the meanwhile).
|
||||
#
|
||||
# #### Case 3
|
||||
#
|
||||
# nameChangeCheck = false ----- autoRegister = true
|
||||
#
|
||||
# We will always request a premium authentication if the username is unknown to us, but is in use by a paid Minecraft
|
||||
# account. This means it's kind of a more aggressive check like nameChangeCheck = true and autoRegister = false, because
|
||||
# it request a premium authentication which are completely new to us, that even the premium UUID is not in our database.
|
||||
#
|
||||
# **Limitation**: see below
|
||||
#
|
||||
# #### Case 4
|
||||
#
|
||||
# nameChangeCheck = true ----- autoRegister = true
|
||||
#
|
||||
# Based on autoRegister it checks if the player name is premium and login using a premium authentication. After that
|
||||
# fastlogin receives the premium UUID and can update the database record.
|
||||
#
|
||||
# **Limitation from autoRegister**: New offline players who uses the username of an existing Minecraft cannot join the
|
||||
# server.
|
||||
nameChangeCheck: false
|
||||
|
||||
# If your players have a premium account and a skin associated to their account, this plugin
|
||||
# can download the data and set it to the online player.
|
||||
#
|
||||
# Keep in mind that this will only works if the player:
|
||||
# * is the owner of the premium account
|
||||
# * the server connection is established through a premium connection (paid account authentication)
|
||||
# * has a skin
|
||||
#
|
||||
# This means this plugin doesn't need to create a new connection to the Mojang servers, because
|
||||
# the skin data is included in the Auth-Verification-Response sent by Mojang. If you want to use for other
|
||||
# players like cracked player, you have to use other plugins.
|
||||
#
|
||||
# If you want to use skins for your cracked player, you need an additional plugin like
|
||||
# ChangeSkin, SkinRestorer, ...
|
||||
forwardSkin: true
|
||||
|
||||
# Players have to rejoin to verify that they can join through onlinemode authentication.
|
||||
# After that they have to confirm again using the /premium command that they are also the owner of the auth plugin
|
||||
# account.
|
||||
# If the onlinemode authentication fails or the player waits to long, it will fallback to offlinemode authentication.
|
||||
premium-confirm: true
|
||||
|
||||
# If you have autoRegister or nameChangeCheck enabled, you could be rate-limited by Mojang.
|
||||
# The requests of the both options will be only made by FastLogin if the username is unknown to the server
|
||||
# You are allowed to make 600 requests per 10-minutes (60 per minute)
|
||||
# If you own a big server this value could be too low
|
||||
# Once the limit is reached, new players are always logged in as cracked until the rate-limit is expired.
|
||||
# (to the next ten minutes)
|
||||
#
|
||||
# The limit is IP-wide. If you have multiple IPv4-addresses you specify them here. FastLogin will then use it in
|
||||
# rotating order --> 5 different IP-addresses 5 * 600 per 10 minutes
|
||||
# If this list is empty only the default one will be used
|
||||
#
|
||||
# Lists are created like this:
|
||||
#ip-addresses:
|
||||
# - 192-168-0-2
|
||||
ip-addresses: []
|
||||
|
||||
# How many requests should be established to the Mojang API for Name -> UUID requests. Some other plugins as well
|
||||
# as the head Minecraft block make such requests as well. Using this option you can limit the amount requests this
|
||||
# plugin should make.
|
||||
#
|
||||
# If you lower this value, other plugins could still make requests while FastLogin cannot.
|
||||
# Mojang limits the amount of request to 600 per 10 minutes per IPv4-address.
|
||||
mojang-request-limit: 600
|
||||
|
||||
# This option automatically registers players which are in the FastLogin database, but not in the auth plugin database.
|
||||
# This can happen if you switch your auth plugin or cleared the database of the auth plugin.
|
||||
# https://github.com/games647/FastLogin/issues/85
|
||||
auto-register-unknown: false
|
||||
|
||||
# This disables the auto login from fastlogin. So a premium (like a paid account) authentication is requested, but
|
||||
# the player won't be auto logged into the account.
|
||||
#
|
||||
# This can be used as 2Factor authentication for better security of your accounts. A hacker then needs both passwords.
|
||||
# The password of your Minecraft and the password to login in with your auth plugin
|
||||
autoLogin: true
|
||||
|
||||
# Database configuration
|
||||
# Recommended is the use of MariaDB (a better version of MySQL)
|
||||
|
||||
# Single file SQLite database
|
||||
driver: 'org.sqlite.JDBC'
|
||||
# File location
|
||||
database: '{pluginDir}/FastLogin.db'
|
||||
|
||||
# MySQL/MariaDB
|
||||
# If you want to enable it uncomment only the lines below this not this line.
|
||||
#driver: 'com.mysql.jdbc.Driver'
|
||||
#host: '127.0.0.1'
|
||||
#port: 3306
|
||||
#database: 'fastlogin'
|
||||
#username: 'myUser'
|
||||
#password: 'myPassword'
|
||||
|
||||
# Advanced Connection Pool settings in seconds
|
||||
#timeout: 30
|
||||
#lifetime: 30
|
||||
|
||||
# It's strongly recommended to enable SSL and setup a SSL certificate if the MySQL server isn't running on the same
|
||||
# machine
|
||||
#useSSL: false
|
||||
|
||||
# HTTP proxies for connecting to the Mojang servers in order to check if the username of a player is premium.
|
||||
# This is a workaround to prevent rate-limiting by Mojang. These proxies will only be used once your server hit
|
||||
# the rate-limit or the custom value above.
|
||||
# Please make sure you use reliable proxies.
|
||||
proxies:
|
||||
# 'IP:Port' or 'Domain:Port'
|
||||
# - 'xyz.com:1337'
|
||||
# - 'test.com:5131'
|
98
core/src/main/resources/messages.yml
Normal file
98
core/src/main/resources/messages.yml
Normal file
@ -0,0 +1,98 @@
|
||||
# FastLogin localization
|
||||
# Project site: https://www.spigotmc.org/resources/fastlogin.14153
|
||||
# Source code: https://github.com/games647/FastLogin
|
||||
#
|
||||
# You can access the newest locale here:
|
||||
# https://github.com/games647/FastLogin/blob/master/core/src/main/resources/messages.yml
|
||||
#
|
||||
# You want to have language template? Visit the GitHub Wiki here:
|
||||
# https://github.com/games647/FastLogin/wiki/English
|
||||
|
||||
# In order to split a message into separate lines you could just make a new line, but keep the '
|
||||
# Example:
|
||||
# bla: '&aFirst line
|
||||
# Second line
|
||||
# Third line'
|
||||
|
||||
# If you want to disable a message, you can just set it to a empty value.
|
||||
# In this case no message will be sent
|
||||
# Example:
|
||||
# bla: ''
|
||||
|
||||
# ========= Shared (BungeeCord and Bukkit) ============
|
||||
|
||||
# Switch mode is activated and a new (non-whitelist) cracked player tries to join
|
||||
switch-kick-message: '&4Only paid Minecraft whitelisted accounts are allowed to join this server'
|
||||
|
||||
# GameProfile activated premium login in order to skip offline authentication
|
||||
add-premium: '&2Added to the list of premium players'
|
||||
|
||||
# GameProfile activated premium login in order to skip offline authentication
|
||||
add-premium-other: '&2Player has been added to the premium list'
|
||||
|
||||
# GameProfile is already set be a paid account
|
||||
already-exists: '&4You are already on the premium list'
|
||||
|
||||
# GameProfile is already set be a paid account
|
||||
already-exists-other: '&4Player is already on the premium list'
|
||||
|
||||
# GameProfile was changed to be cracked
|
||||
remove-premium: '&2Removed from the list of premium players'
|
||||
|
||||
# GameProfile is already set to be cracked
|
||||
not-premium: '&4You are not in the premium list'
|
||||
|
||||
# GameProfile is already set to be cracked
|
||||
not-premium-other: '&4Player is not in the premium list'
|
||||
|
||||
# Admin wanted to change the premium of a user that isn't known to the plugin
|
||||
player-unknown: '&4Player not in the database'
|
||||
|
||||
# ========= Bukkit/Spigot ================
|
||||
|
||||
# The user skipped the authentication, because it was a premium player
|
||||
auto-login: '&2Auto logged in'
|
||||
|
||||
# The user was auto registered on the first join. The user account will be registered to protect it from cracked players
|
||||
# The password can be used if the mojang servers are down and you still want your premium users to login (PLANNED)
|
||||
auto-register: '&2Auto registered with password: %password
|
||||
You may want change it?'
|
||||
|
||||
# GameProfile is not able to toggle the premium state of other players
|
||||
no-permission: '&4Not enough permissions'
|
||||
|
||||
# Although the console can toggle the premium state, it's not possible for the console itself.
|
||||
# Because the console is not a user. (obviously, isn't it?)
|
||||
no-console: '&4You are not a player. You cannot toggle the premium state for YOURSELF as a console'
|
||||
|
||||
# The user wants to toggle premium state, but BungeeCord support is enabled. This means the server have to communicate
|
||||
# with the BungeeCord first which will establish a connection with the database server.
|
||||
wait-on-proxy: '&6Sending request... (Do not forget to follow the BungeeCord setup guide)'
|
||||
|
||||
# When ProtocolLib is enabled and the plugin is unable to continue handling a login request after a requested premium
|
||||
# authentication. In this state the client expects a success packet with a encrypted connection or disconnect packet.
|
||||
# So we kick the player, if we cannot encrypt the connection. In other situation (example: premium name check),
|
||||
# the player will be just authenticated as cracked
|
||||
error-kick: '&4Error occurred'
|
||||
|
||||
# The server sends a verify token within the premium authentication request. If this doesn't match on response,
|
||||
# it could be another client sending malicious packets
|
||||
invalid-verify-token: '&4Invalid token'
|
||||
|
||||
# The client sent no request join server request to the mojang servers which would proof that it's owner of that
|
||||
# account. Only modified clients would do this.
|
||||
invalid-session: '&4Invalid session'
|
||||
|
||||
# The client sent a malicious packet without a login request packet
|
||||
invalid-requst: '&4Invalid request'
|
||||
|
||||
# Message if the Bukkit isn't fully started to inject the packets
|
||||
not-started: '&cServer is not fully started yet. Please retry'
|
||||
|
||||
# Premium confirmation message if the player tries to activate the command
|
||||
premium-confirm: '&6Please relogin and type the command again to apply the changes.
|
||||
If the request fails or you wait to long, it will fallback to offlinemode.'
|
||||
|
||||
# ========= Bungee/Waterfall only ================================
|
||||
|
||||
|
153
pom.xml
153
pom.xml
@ -1,65 +1,69 @@
|
||||
<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>
|
||||
|
||||
<groupId>com.github.games647</groupId>
|
||||
<!--This have to be in lowercase because it's used by plugin.yml-->
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>FastLogin</name>
|
||||
<version>0.1</version>
|
||||
<inceptionYear>2015</inceptionYear>
|
||||
<url>http://dev.bukkit.org/bukkit-plugins/fastlogin</url>
|
||||
<version>1.11-SNAPSHOT</version>
|
||||
|
||||
<url>https://www.spigotmc.org/resources/fastlogin.14153/</url>
|
||||
<description>
|
||||
Automatically logins premium player on a offline mode server
|
||||
Automatically login premium (paid accounts) player on a offline mode server
|
||||
</description>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<!--Possibility to deploy directly to the plugins folder-->
|
||||
<outputDir>${basedir}/target</outputDir>
|
||||
|
||||
<!-- Set default for non-git clones -->
|
||||
<git.commit.id>Unknown</git.commit.id>
|
||||
|
||||
<java.version>1.8</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<issueManagement>
|
||||
<system>GitHub</system>
|
||||
<url>https://github.com/games647/FastLogin/issues</url>
|
||||
</issueManagement>
|
||||
<modules>
|
||||
<module>core</module>
|
||||
<module>bukkit</module>
|
||||
<module>bungee</module>
|
||||
</modules>
|
||||
|
||||
<scm>
|
||||
<url>https://github.com/games647/FastLogin</url>
|
||||
<connection>scm:git:git://github.com/games647/FastLogin.git</connection>
|
||||
<developerConnection>scm:git:ssh://git@github.com:games647/FastLogin.git</developerConnection>
|
||||
</scm>
|
||||
<!--Deployment configuration for the Maven repository-->
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>codemc-snapshots</id>
|
||||
<url>https://repo.codemc.io/repository/maven-snapshots/</url>
|
||||
</snapshotRepository>
|
||||
<repository>
|
||||
<id>codemc-releases</id>
|
||||
<url>https://repo.codemc.io/repository/maven-releases/</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
|
||||
<build>
|
||||
<defaultGoal>install</defaultGoal>
|
||||
<!--Just use the project name to replace an old version of the plugin if the user does only copy-paste-->
|
||||
<finalName>${project.name}</finalName>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.2</version>
|
||||
<groupId>pl.project13.maven</groupId>
|
||||
<artifactId>git-commit-id-plugin</artifactId>
|
||||
<version>4.0.0</version>
|
||||
<configuration>
|
||||
<!--So many people still use Java 6 ;( http://mcstats.org/global/#Java+Version-->
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<showWarnings>true</showWarnings>
|
||||
<showDeprecation>true</showDeprecation>
|
||||
<!--false means actual true http://jira.codehaus.org/browse/MCOMPILER-209-->
|
||||
<useIncrementalCompilation>false</useIncrementalCompilation>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<configuration>
|
||||
<outputDirectory>${outputDir}</outputDirectory>
|
||||
<failOnNoGitDirectory>false</failOnNoGitDirectory>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>get-the-git-infos</id>
|
||||
<goals>
|
||||
<goal>revision</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
@ -69,81 +73,6 @@
|
||||
<!--Replace variables-->
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
|
||||
<!--Add the license to jar in order to see it in the final jar-->
|
||||
<resource>
|
||||
<directory>${basedir}</directory>
|
||||
<includes>
|
||||
<include>LICENSE</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<!--Bukkit-Server-API -->
|
||||
<repository>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
|
||||
<!--ProtocolLib-->
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>http://repo.dmulloy2.net/content/groups/public/</url>
|
||||
</repository>
|
||||
|
||||
<!--Authme Reloaded-->
|
||||
<repository>
|
||||
<id>xephi-repo</id>
|
||||
<url>http://ci.xephi.fr/plugin/repository/everything/</url>
|
||||
</repository>
|
||||
|
||||
<!--xAuth-->
|
||||
<repository>
|
||||
<id>luricos.de-repo</id>
|
||||
<url>http://repo.luricos.de/bukkit-plugins/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<!--Server API-->
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot</artifactId>
|
||||
<version>1.8.8-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!--Library for listening and sending Minecraft packets-->
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>3.6.3-SNAPSHOT</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!--Login Plugins-->
|
||||
<dependency>
|
||||
<groupId>fr.xephi</groupId>
|
||||
<artifactId>authme</artifactId>
|
||||
<version>5.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.luricos.bukkit</groupId>
|
||||
<artifactId>xAuth</artifactId>
|
||||
<version>2.6</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>net.gravitydevelopment.updater</groupId>
|
||||
<artifactId>updater</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>net.ess3</groupId>
|
||||
<artifactId>EssentialsGroupManager</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -1,93 +0,0 @@
|
||||
package com.github.games647.fastlogin;
|
||||
|
||||
import com.github.games647.fastlogin.listener.PlayerListener;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.github.games647.fastlogin.listener.EncryptionPacketListener;
|
||||
import com.github.games647.fastlogin.listener.StartPacketListener;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public class FastLogin extends JavaPlugin {
|
||||
|
||||
private final KeyPair keyPair = generateKey();
|
||||
private final Cache<String, PlayerData> session = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(2, TimeUnit.MINUTES)
|
||||
.build();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!getServer().getPluginManager().isPluginEnabled("AuthMe")
|
||||
&& !getServer().getPluginManager().isPluginEnabled("xAuth")) {
|
||||
getLogger().warning("No support offline Auth plugin found. ");
|
||||
getLogger().warning("Disabling this plugin...");
|
||||
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
protocolManager.addPacketListener(new EncryptionPacketListener(this, protocolManager));
|
||||
protocolManager.addPacketListener(new StartPacketListener(this, protocolManager));
|
||||
|
||||
getServer().getPluginManager().registerEvents(new PlayerListener(this), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
//online mode is only changeable aftter a restart
|
||||
if (getServer().getOnlineMode()) {
|
||||
getLogger().severe("Server have to be in offline mode");
|
||||
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
generateKey();
|
||||
}
|
||||
|
||||
private KeyPair generateKey() {
|
||||
try {
|
||||
KeyPairGenerator keypairgenerator = KeyPairGenerator.getInstance("RSA");
|
||||
|
||||
keypairgenerator.initialize(1024);
|
||||
return keypairgenerator.generateKeyPair();
|
||||
} catch (NoSuchAlgorithmException noSuchAlgorithmException) {
|
||||
//Should be default existing in every vm
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Cache<String, PlayerData> getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public KeyPair getKeyPair() {
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
public HttpURLConnection getConnection(String url) throws IOException {
|
||||
final HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
connection.setConnectTimeout(15000);
|
||||
connection.setReadTimeout(15000);
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setRequestProperty("User-Agent", "Premium-Checker");
|
||||
|
||||
return connection;
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package com.github.games647.fastlogin;
|
||||
|
||||
public class PlayerData {
|
||||
|
||||
private final byte[] verifyToken;
|
||||
private final String username;
|
||||
|
||||
public PlayerData(byte[] verifyToken, String username) {
|
||||
this.username = username;
|
||||
this.verifyToken = verifyToken;
|
||||
}
|
||||
|
||||
public byte[] getVerifyToken() {
|
||||
return verifyToken;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
package com.github.games647.fastlogin.listener;
|
||||
|
||||
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.SocketInjector;
|
||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.github.games647.fastlogin.FastLogin;
|
||||
import com.github.games647.fastlogin.PlayerData;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.security.PrivateKey;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import net.minecraft.server.v1_8_R3.MinecraftEncryption;
|
||||
import net.minecraft.server.v1_8_R3.NetworkManager;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.JSONValue;
|
||||
|
||||
public class EncryptionPacketListener extends PacketAdapter {
|
||||
|
||||
private static final String HAS_JOINED_URL = "https://sessionserver.mojang.com/session/minecraft/hasJoined?";
|
||||
|
||||
private final ProtocolManager protocolManager;
|
||||
private final FastLogin fastLogin;
|
||||
|
||||
public EncryptionPacketListener(FastLogin plugin, ProtocolManager protocolManger) {
|
||||
super(params(plugin, PacketType.Login.Client.ENCRYPTION_BEGIN).optionAsync());
|
||||
|
||||
this.fastLogin = 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 (*)
|
||||
*/
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
PacketContainer packet = event.getPacket();
|
||||
Player player = event.getPlayer();
|
||||
|
||||
final byte[] sharedSecret = packet.getByteArrays().read(0);
|
||||
byte[] clientVerify = packet.getByteArrays().read(1);
|
||||
|
||||
PrivateKey privateKey = fastLogin.getKeyPair().getPrivate();
|
||||
|
||||
String addressString = player.getAddress().toString();
|
||||
PlayerData cachedEntry = fastLogin.getSession().asMap().get(addressString);
|
||||
byte[] serverVerify = cachedEntry.getVerifyToken();
|
||||
if (!Arrays.equals(serverVerify, MinecraftEncryption.b(privateKey, clientVerify))) {
|
||||
player.kickPlayer("Invalid token");
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
//encrypt all following packets
|
||||
NetworkManager networkManager = getNetworkManager(event);
|
||||
SecretKey loginKey = MinecraftEncryption.a(privateKey, sharedSecret);
|
||||
networkManager.a(loginKey);
|
||||
String serverId = (new BigInteger(MinecraftEncryption.a("", fastLogin.getKeyPair().getPublic(), loginKey)))
|
||||
.toString(16);
|
||||
|
||||
String username = cachedEntry.getUsername();
|
||||
if (!hasJoinedServer(username, serverId)) {
|
||||
//user tried to fake a authentification
|
||||
player.kickPlayer("Invalid session");
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
//fake a new login packet
|
||||
PacketContainer startPacket = protocolManager.createPacket(PacketType.Login.Client.START, true);
|
||||
WrappedGameProfile fakeProfile = WrappedGameProfile.fromOfflinePlayer(Bukkit.getOfflinePlayer(username));
|
||||
startPacket.getGameProfiles().write(0, fakeProfile);
|
||||
try {
|
||||
protocolManager.recieveClientPacket(event.getPlayer(), startPacket, false);
|
||||
} catch (InvocationTargetException | IllegalAccessException ex) {
|
||||
plugin.getLogger().log(Level.WARNING, null, ex);
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
private NetworkManager getNetworkManager(PacketEvent event) throws IllegalArgumentException {
|
||||
SocketInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(event.getPlayer());
|
||||
NetworkManager networkManager = null;
|
||||
try {
|
||||
Field declaredField = injector.getClass().getDeclaredField("injector");
|
||||
declaredField.setAccessible(true);
|
||||
|
||||
Object rawInjector = declaredField.get(injector);
|
||||
|
||||
declaredField = rawInjector.getClass().getDeclaredField("networkManager");
|
||||
declaredField.setAccessible(true);
|
||||
networkManager = (NetworkManager) declaredField.get(rawInjector);
|
||||
} catch (IllegalAccessException | NoSuchFieldException ex) {
|
||||
plugin.getLogger().log(Level.WARNING, null, ex);
|
||||
}
|
||||
|
||||
return networkManager;
|
||||
}
|
||||
|
||||
private boolean hasJoinedServer(String username, String serverId) {
|
||||
try {
|
||||
String url = HAS_JOINED_URL + "username=" + username + "&serverId=" + serverId;
|
||||
|
||||
HttpURLConnection conn = fastLogin.getConnection(url);
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
if (!line.equals("null")) {
|
||||
JSONObject object = (JSONObject) JSONValue.parse(line);
|
||||
String uuid = (String) object.get("id");
|
||||
String name = (String) object.get("name");
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
plugin.getLogger().log(Level.WARNING, null, ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
package com.github.games647.fastlogin.listener;
|
||||
|
||||
import com.github.games647.fastlogin.FastLogin;
|
||||
|
||||
import de.luricos.bukkit.xAuth.xAuth;
|
||||
import de.luricos.bukkit.xAuth.xAuthPlayer;
|
||||
import de.luricos.bukkit.xAuth.xAuthPlayer.Status;
|
||||
|
||||
import fr.xephi.authme.api.NewAPI;
|
||||
import fr.xephi.authme.cache.limbo.LimboCache;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
|
||||
public class PlayerListener implements Listener {
|
||||
|
||||
private final FastLogin plugin;
|
||||
|
||||
public PlayerListener(FastLogin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onJoin(PlayerJoinEvent joinEvent) {
|
||||
final Player player = joinEvent.getPlayer();
|
||||
String address = player.getAddress().toString();
|
||||
if (plugin.getSession().asMap().containsKey(address)) {
|
||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
doLogin(player);
|
||||
}, 1 * 20L);
|
||||
}
|
||||
}
|
||||
|
||||
private void doLogin(Player player) {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("AuthMe")) {
|
||||
//add cache entry - otherwise loggin wouldn't work
|
||||
LimboCache.getInstance().addLimboPlayer(player);
|
||||
|
||||
//skips registration and login
|
||||
NewAPI.getInstance().forceLogin(player);
|
||||
} else if (Bukkit.getPluginManager().isPluginEnabled("xAuth")) {
|
||||
xAuth xAuthPlugin = xAuth.getPlugin();
|
||||
|
||||
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(player);
|
||||
xAuthPlayer.setPremium(true);
|
||||
xAuthPlugin.getAuthClass(xAuthPlayer).online(xAuthPlayer.getName());
|
||||
xAuthPlayer.setLoginTime(new Timestamp(System.currentTimeMillis()));
|
||||
|
||||
xAuthPlayer.setStatus(Status.AUTHENTICATED);
|
||||
|
||||
xAuthPlugin.getPlayerManager().unprotect(xAuthPlayer);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package com.github.games647.fastlogin.listener;
|
||||
|
||||
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.FastLogin;
|
||||
import com.github.games647.fastlogin.PlayerData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.security.PublicKey;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class StartPacketListener extends PacketAdapter {
|
||||
|
||||
//only premium members have a uuid from there
|
||||
private static final String UUID_LINK = "https://api.mojang.com/users/profiles/minecraft/";
|
||||
|
||||
private final ProtocolManager protocolManager;
|
||||
private final FastLogin fastLogin;
|
||||
|
||||
private final Random random = new Random();
|
||||
|
||||
public StartPacketListener(FastLogin plugin, ProtocolManager protocolManger) {
|
||||
super(params(plugin, PacketType.Login.Client.START).optionAsync());
|
||||
|
||||
this.fastLogin = 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 (*)
|
||||
*/
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||
PacketContainer packet = packetEvent.getPacket();
|
||||
Player player = packetEvent.getPlayer();
|
||||
|
||||
String username = packet.getGameProfiles().read(0).getName();
|
||||
if (isPremium(username)) {
|
||||
//do premium login process
|
||||
try {
|
||||
PacketContainer newPacket = protocolManager.createPacket(PacketType.Login.Server.ENCRYPTION_BEGIN, true);
|
||||
|
||||
//constr ServerID=""
|
||||
//public key=plugin.getPublic
|
||||
newPacket.getSpecificModifier(PublicKey.class).write(0, fastLogin.getKeyPair().getPublic());
|
||||
byte[] verifyToken = new byte[4];
|
||||
random.nextBytes(verifyToken);
|
||||
newPacket.getByteArrays().write(0, verifyToken);
|
||||
|
||||
String addressString = player.getAddress().toString();
|
||||
fastLogin.getSession().asMap().put(addressString, new PlayerData(verifyToken, username));
|
||||
|
||||
protocolManager.sendServerPacket(player, newPacket, false);
|
||||
} catch (InvocationTargetException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, null, ex);
|
||||
}
|
||||
|
||||
//cancel only if the player is premium
|
||||
packetEvent.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPremium(String playerName) {
|
||||
try {
|
||||
final HttpURLConnection connection = fastLogin.getConnection(UUID_LINK + playerName);
|
||||
final int responseCode = connection.getResponseCode();
|
||||
|
||||
return responseCode == HttpURLConnection.HTTP_OK;
|
||||
} catch (IOException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, null, ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
# project informations for Bukkit in order to register our plugin with all it components
|
||||
# ${project.name} are variables from Maven (pom.xml) which will be replaced after the build
|
||||
name: ${project.name}
|
||||
version: ${project.version}
|
||||
main: ${project.groupId}.${project.artifactId}.${project.name}
|
||||
|
||||
# meta informations for plugin managers
|
||||
authors: [Xeroun, games647, 'https://github.com/games647/FastLogin/graphs/contributors']
|
||||
description: |
|
||||
${project.description}
|
||||
website: ${project.url}
|
||||
dev-url: ${project.url}
|
||||
|
||||
depend: [ProtocolLib]
|
Reference in New Issue
Block a user