mirror of
https://github.com/TuxCoding/FastLogin.git
synced 2025-12-23 07:12:39 +01:00
Compare commits
464 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c21c7eaeec | ||
|
|
804a0aec4b | ||
|
|
13bc92f6c3 | ||
|
|
e88f2e7f8e | ||
|
|
337e01e537 | ||
|
|
d9f254fbff | ||
|
|
09ab7288da | ||
|
|
ff2e5c0435 | ||
|
|
30c233c953 | ||
|
|
6fd81e8e29 | ||
|
|
4765372b0a | ||
|
|
303c064416 | ||
|
|
a15de80c7d | ||
|
|
25356db175 | ||
|
|
465519eece | ||
|
|
16f131ee2d | ||
|
|
43046bea97 | ||
|
|
aa734853c6 | ||
|
|
28fb40d063 | ||
|
|
29c633f68a | ||
|
|
c079653d09 | ||
|
|
7fbfa8240c | ||
|
|
1ab346d067 | ||
|
|
50a8fd2f79 | ||
|
|
9ff404a424 | ||
|
|
5029701f2a | ||
|
|
1f63f91f17 | ||
|
|
71d84d1748 | ||
|
|
64072d84d8 | ||
|
|
f2441ecc15 | ||
|
|
fe1499f1fb | ||
|
|
e74288e676 | ||
|
|
d034e71331 | ||
|
|
7e43b99f91 | ||
|
|
496a036fb4 | ||
|
|
fa1f85698a | ||
|
|
25b6ca7653 | ||
|
|
b30eb0de29 | ||
|
|
c96d64597a | ||
|
|
0ef2000f2a | ||
|
|
a36ac7f9e3 | ||
|
|
2e38550db0 | ||
|
|
4e2b6c7810 | ||
|
|
4acbcbfd19 | ||
|
|
cae2f8f58d | ||
|
|
de1487b6aa | ||
|
|
96cdac1332 | ||
|
|
2601fea84f | ||
|
|
2bd339b0bf | ||
|
|
286b755ee3 | ||
|
|
5c5b7384a4 | ||
|
|
7c125dc0b6 | ||
|
|
103a8320ec | ||
|
|
49df461643 | ||
|
|
3b69904257 | ||
|
|
b41d56f835 | ||
|
|
b1797c84d9 | ||
|
|
9941a69c6d | ||
|
|
0ee269785a | ||
|
|
c74c8f5fee | ||
|
|
0ad3a853b5 | ||
|
|
42637813e8 | ||
|
|
feee64309e | ||
|
|
b9cf8f0498 | ||
|
|
30a763dbc4 | ||
|
|
42790b27f8 | ||
|
|
c58eda983a | ||
|
|
477d3df1dd | ||
|
|
c1dc69845e | ||
|
|
c3f6881e4b | ||
|
|
25b380f74a | ||
|
|
a5777869c8 | ||
|
|
cf3e8434a2 | ||
|
|
5881be9b78 | ||
|
|
58c42f9f7f | ||
|
|
f40e787b55 | ||
|
|
b6a95bb153 | ||
|
|
9bae5c3f79 | ||
|
|
b5ccc1df2e | ||
|
|
d829c71438 | ||
|
|
9cf5431d1a | ||
|
|
e0a7f207c5 | ||
|
|
efb4c34b50 | ||
|
|
68d35c75c2 | ||
|
|
0d93a2c371 | ||
|
|
6091a228ab | ||
|
|
6f99b6e9b4 | ||
|
|
109e19e6da | ||
|
|
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 #...)
|
||||
49
.github/workflows/maven.yml
vendored
Normal file
49
.github/workflows/maven.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
# Automatically build, run unit and integration tests to detect errors early (CI provided by GitHub)
|
||||
# including making pull requests review easier
|
||||
|
||||
# Human readable name in the actions tab
|
||||
name: Java CI
|
||||
|
||||
# Build on every push and pull request regardless of the branch
|
||||
# Wiki: https://help.github.com/en/actions/reference/events-that-trigger-workflows
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
# job id
|
||||
build_and_test:
|
||||
|
||||
# Environment image - always newest OS
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Run steps
|
||||
steps:
|
||||
# Pull changes
|
||||
- uses: actions/checkout@v2.3.4
|
||||
|
||||
# Cache artifacts - however this has the downside that we don't get notified of
|
||||
# artifact resolution failures like invalid repository
|
||||
# Nevertheless the repositories should be more stable and it makes no sense to pull
|
||||
# a same version every time
|
||||
# A dry run would make more sense
|
||||
- uses: actions/cache@v2.1.4
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
|
||||
# Setup Java
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1.4.3
|
||||
with:
|
||||
# Use Java 11, because it's minimum required version
|
||||
java-version: 11
|
||||
|
||||
# Build and test (included in package)
|
||||
- name: Build with Maven and test
|
||||
# Run non-interactive, package (with compile+test),
|
||||
# ignore snapshot updates, because they are likely to have breaking changes, enforce checksums to validate
|
||||
# possible errors in dependencies
|
||||
run: mvn package test --batch-mode --no-snapshot-updates --strict-checksums --file pom.xml
|
||||
50
.gitignore
vendored
50
.gitignore
vendored
@@ -1,8 +1,46 @@
|
||||
# Eclipse
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
|
||||
# NetBeans
|
||||
nbproject/
|
||||
nb-configuration.xml
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# Maven
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
|
||||
# Gradle
|
||||
.gradle
|
||||
|
||||
# Ignore Gradle GUI config
|
||||
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
|
||||
|
||||
# Factorypath from Visual Studio Code
|
||||
.factorypath
|
||||
260
CHANGELOG.md
Normal file
260
CHANGELOG.md
Normal file
@@ -0,0 +1,260 @@
|
||||
### 1.11
|
||||
|
||||
* TODO: Replace reflection with methodhandles
|
||||
|
||||
* Use direct proxies instead of ssl factories for multiple IP-addresses
|
||||
* Remove local address check for multiple IP-addresses
|
||||
* Fix parsing of local IP-addresses
|
||||
* Fix address rotating for contacting the Mojang API
|
||||
* Optimize issue template
|
||||
* Use Instant for timestamps
|
||||
* Migrate SLF4J logging (Fixes #177)
|
||||
* Use Gson's TypeAdapter for more type safety
|
||||
* Add support for IPv6 proxies
|
||||
* Shared configuration implementation for easier maintained code
|
||||
* Use Gson for json parsing, because it's supported on all platforms and removes code duplicates
|
||||
* Clean up project code
|
||||
* Drop support for deprecated AuthMe API
|
||||
* Remove legacy database migration code
|
||||
* Drop support for RoyalAuth, because it doesn't seem to be supported anymore
|
||||
* Clean up client-server encryption -> use only one cipher per connection, simplify code
|
||||
|
||||
### 1.10
|
||||
|
||||
* Prevent authentication proxies
|
||||
* Drop database importer
|
||||
* More logging by default
|
||||
* Add support for HTTP proxies
|
||||
* Set the fake offline UUID on lowest priority (-> as soon as possible)
|
||||
* Remove bungee chatcolor for Bukkit to support KCauldron
|
||||
* Minor cleanup using inspections + Https
|
||||
* Increase hook delay to let ProtocolLib inject the listener
|
||||
* Drop support for old AuthMe API + Add support for new AuthMe API
|
||||
* Remove eBean util usage to make it compatible with 1.12
|
||||
* Do not try to hook into a plugin if auth plugin hook is already set using the FastLogin API
|
||||
* Automatically register accounts if they are not in the auth plugin database but in the FastLogin database
|
||||
* Update BungeeAuth dependency and use the new API. Please update your plugin if you still use the old one.
|
||||
* Remove deprecated API methods from the last version
|
||||
* Finally update the IP column on every login
|
||||
* No duplicate session login
|
||||
* Fix timestamp parsing in newer versions of SQLite
|
||||
* Fix Spigot console command invocation sends result to in game players
|
||||
|
||||
### 1.9
|
||||
|
||||
* Added second attempt login -> cracked login
|
||||
* Added cracked whitelist (switch-mode -> switching to online-mode from offlinemode)
|
||||
* Added configuration to disable auto logins for 2Factor authentication
|
||||
* Added missing add-premium-other message
|
||||
* Upgrade to Java 8 -> Minimize file size
|
||||
* Refactored/Cleaned up a lot of code
|
||||
* [API] Deprecated platform specific auth-plugin. Please use AuthPlugin< platform specific player type >
|
||||
* [API] Deprecated bukkit's password generator. Please use PasswordGenerator< platform specific player type >
|
||||
* Fix ProtocolSupport autoRegister
|
||||
* Fix update username in FastLogin database after nameChange
|
||||
* Fix logging exceptions on encryption enabling
|
||||
* Fix compatibility with older ProtocolLib versions (for 1.7) because of the missing getMethodAcccessorOrNull method
|
||||
* Fix correct cracked permission for bukkit
|
||||
* A try to fix SQLite timestamp parsing
|
||||
* Drop support for LoginSecurity 1.X since 2.X seems to be stable
|
||||
* Remove the nasty UltraAuth fakeplayer workaround by using a new api method. You should UltraAuth if you have it
|
||||
|
||||
### 1.8
|
||||
|
||||
* Added autoIn importer
|
||||
* Added BFA importer
|
||||
* Added ElDziAuth importer
|
||||
* Fix third-party not premium player detection
|
||||
* Fix ProtocolSupport BungeeCord
|
||||
* Fix duplicate logins for BungeeAuth users
|
||||
|
||||
### 1.7.1
|
||||
|
||||
* Fix BungeeCord autoRegister (Fixes #46)
|
||||
* Fix ProtocolSupport auto-register
|
||||
|
||||
### 1.7
|
||||
|
||||
* Added support for making requests to Mojang from different IPv4 addresses
|
||||
* Added us.mcapi.com as third-party APIs to workaround rate-limits
|
||||
* Fixed NPE in BungeeCord on cracked session
|
||||
* Fixed skin applies if premium uuid is deactivated
|
||||
* Fix player entry is not saved if namechangecheck is enabled
|
||||
* Fix skin applies for third-party plugins
|
||||
* Switch to mcapi.ca for uuid lookups
|
||||
* Fix BungeeCord not setting an premium uuid
|
||||
* Fix setting skin on Cauldron
|
||||
* Fix saving on name change
|
||||
|
||||
### 1.6.2
|
||||
|
||||
* Fixed support for new LoginSecurity version
|
||||
|
||||
### 1.6.1
|
||||
|
||||
* Fix message typo in BungeeCord which created a NPE if premium-warning is activated
|
||||
|
||||
### 1.6
|
||||
|
||||
* Add a warning message if the user tries to invoke the premium command
|
||||
* Added missing translation if the server isn't fully started
|
||||
* Removed ProtocolLib as required dependency. You can use ProtocolSupport or BungeeCord as alternative
|
||||
* Reduce the number of worker threads from 5 to 3 in ProtocolLib
|
||||
* Process packets in ProtocolLib async/non-blocking -> better performance
|
||||
* Fixed missing translation in commands
|
||||
* Fixed cracked command not working on BungeeCord
|
||||
* Fix error if forward skins is disabled
|
||||
|
||||
### 1.5.2
|
||||
|
||||
* Fixed BungeeCord force logins if there is a lobby server
|
||||
* Removed cache expire in BungeeCord
|
||||
* Applies skin earlier to make it visible for other plugins listening on login events
|
||||
|
||||
### 1.5.1
|
||||
|
||||
* Fixed BungeeCord support by correctly saving the proxy ids
|
||||
|
||||
### 1.5
|
||||
|
||||
* 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
|
||||
|
||||
124
README.md
124
README.md
@@ -1,2 +1,124 @@
|
||||
# FastLogin
|
||||
Checks if a minecraft player has a valid paid account. If so, they can skip offline authentification.
|
||||
|
||||
Checks if a Minecraft player has a paid account (premium). If so, they can skip offline authentication (auth plugins).
|
||||
So they don't need to enter passwords. This is also called auto login (auto-login).
|
||||
|
||||
## Features
|
||||
|
||||
* Detect paid accounts from others
|
||||
* Automatically login paid accounts (premium)
|
||||
* Support various of auth plugins
|
||||
* Cauldron support
|
||||
* Forge/Sponge message support
|
||||
* Premium UUID support
|
||||
* Forward skins
|
||||
* Detect user name changed and will update the existing database record
|
||||
* BungeeCord support
|
||||
* Auto register new premium players
|
||||
* Plugin: ProtocolSupport is supported and can be used as an alternative to ProtocolLib
|
||||
* No client modifications needed
|
||||
* Good performance by using async operations
|
||||
* Locale messages
|
||||
* Support for Bedrock players proxied through FloodGate
|
||||
|
||||
## Issues
|
||||
|
||||
Please use issues for bug reports, suggestions, questions and more. Please check for existing issues. Existing issues
|
||||
can be voted up by adding up vote to the original post. Closing issues means that they are marked as resolved. Comments
|
||||
are still allowed and it could be re-opened.
|
||||
|
||||
## Development builds
|
||||
|
||||
Development builds of this project can be acquired at the provided CI (continuous integration) server. It contains the
|
||||
latest changes from the Source-Code in preparation for the following release. This means they could contain new
|
||||
features, bug fixes and other changes since the last release.
|
||||
|
||||
They **could** contain new bugs and are likely to be less stable than released versions.
|
||||
|
||||
Specific builds can be grabbed by clicking on the build number on the left side or by clicking on status to retrieve the
|
||||
latest build.
|
||||
https://ci.codemc.org/job/Games647/job/FastLogin/changes
|
||||
|
||||
***
|
||||
|
||||
## Commands
|
||||
|
||||
/premium [player] Label the invoker or the argument as paid account
|
||||
/cracked [player] Label the invoker or the argument as cracked account
|
||||
|
||||
## Permissions
|
||||
|
||||
fastlogin.bukkit.command.premium
|
||||
fastlogin.bukkit.command.cracked
|
||||
fastlogin.command.premium.other
|
||||
fastlogin.command.cracked.other
|
||||
|
||||
## Placeholder
|
||||
|
||||
This plugin supports `PlaceholderAPI` on `Spigot`. It exports the following variable
|
||||
`%fastlogin_status%`. In BungeeCord environments, the status of a player will be delivered with a delay after the player
|
||||
already successful joined the server. This takes about a couple of milliseconds. In this case the value
|
||||
will be `Unknown`.
|
||||
|
||||
Possible values: `Premium`, `Cracked`, `Unknown`
|
||||
|
||||
## Requirements
|
||||
|
||||
* Plugin:
|
||||
* [ProtocolLib](https://www.spigotmc.org/resources/protocollib.1997/) or
|
||||
* [ProtocolSupport](https://www.spigotmc.org/resources/protocolsupport.7201/)
|
||||
* [Spigot](https://www.spigotmc.org) 1.8.8+
|
||||
* Java 8+
|
||||
* Run Spigot (or a fork e.g. Paper) and/or BungeeCord (or a fork e.g. Waterfall) in offline mode
|
||||
* An auth plugin.
|
||||
|
||||
### Supported auth plugins
|
||||
|
||||
#### Spigot/Paper
|
||||
|
||||
* [AdvancedLogin (Paid)](https://www.spigotmc.org/resources/advancedlogin.10510/)
|
||||
* [AuthMe (5.X)](https://dev.bukkit.org/bukkit-plugins/authme-reloaded/)
|
||||
* [CrazyLogin](https://dev.bukkit.org/bukkit-plugins/crazylogin/)
|
||||
* [LoginSecurity](https://dev.bukkit.org/bukkit-plugins/loginsecurity/)
|
||||
* [LogIt](https://github.com/games647/LogIt)
|
||||
* [SodionAuth (2.0+)](https://github.com/Mohist-Community/SodionAuth)
|
||||
* [UltraAuth](https://dev.bukkit.org/bukkit-plugins/ultraauth-aa/)
|
||||
* [UserLogin](https://www.spigotmc.org/resources/userlogin.80669/)
|
||||
* [xAuth](https://dev.bukkit.org/bukkit-plugins/xauth/)
|
||||
|
||||
#### BungeeCord/Waterfall
|
||||
|
||||
* [BungeeAuth](https://www.spigotmc.org/resources/bungeeauth.493/)
|
||||
* [BungeeAuthenticator](https://www.spigotmc.org/resources/bungeecordauthenticator.87669/)
|
||||
|
||||
## 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
|
||||
|
||||
### 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 `allowed-proxies.txt` file in the FastLogin folder
|
||||
Put your stats id from the BungeeCord config into this file
|
||||
4. Activate ipForward in your BungeeCord config
|
||||
5. Download and Install FastLogin (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 proxy and Spigot in offline mode by setting the value onlinemode in your config.yml to false
|
||||
8. You should *always* firewall your Spigot server that it's only accessible through BungeeCord
|
||||
* https://www.spigotmc.org/wiki/bungeecord-installation/#post-installation
|
||||
* BungeeCord doesn't support SQLite per default, so you should change the configuration to MySQL or MariaDB
|
||||
|
||||
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.
259
bukkit/pom.xml
Normal file
259
bukkit/pom.xml
Normal file
@@ -0,0 +1,259 @@
|
||||
<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.4</version>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<shadedArtifactAttached>false</shadedArtifactAttached>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>com.zaxxer.hikari</pattern>
|
||||
<shadedPattern>fastlogin.hikari</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.slf4j</pattern>
|
||||
<shadedPattern>fastlogin.slf4j</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>net.md_5.bungee.config</pattern>
|
||||
<shadedPattern>fastlogin.config</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.google.gson</pattern>
|
||||
<shadedPattern>fastlogin.gson</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>io.papermc.lib</pattern>
|
||||
<shadedPattern>fastlogin.paperlib</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<!-- PaperSpigot API and PaperLib -->
|
||||
<repository>
|
||||
<id>papermc</id>
|
||||
<url>https://papermc.io/repo/repository/maven-public/</url>
|
||||
</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>
|
||||
|
||||
<!-- PaperSpigot API for correcting usercache usage -->
|
||||
<dependency>
|
||||
<groupId>com.destroystokyo.paper</groupId>
|
||||
<artifactId>paper-api</artifactId>
|
||||
<version>1.15.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- PaperLib for checking if server uses PaperSpigot -->
|
||||
<dependency>
|
||||
<groupId>io.papermc</groupId>
|
||||
<artifactId>paperlib</artifactId>
|
||||
<version>1.0.6</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!--Library for listening and sending Minecraft packets-->
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>4.6.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.8</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!--Login Plugins-->
|
||||
<dependency>
|
||||
<groupId>fr.xephi</groupId>
|
||||
<artifactId>authme</artifactId>
|
||||
<version>5.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.2</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>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.Mohist-Community.SodionAuth</groupId>
|
||||
<artifactId>SodionAuth-Bukkit</artifactId>
|
||||
<version>b74392aa34</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.github.Mohist-Community.SodionAuth</groupId>
|
||||
<artifactId>SodionAuth-Libs</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<optional>true</optional>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -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,24 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.core.SessionManager;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
public class BukkitSessionManager extends SessionManager<PlayerQuitEvent, InetSocketAddress, BukkitLoginSession>
|
||||
implements Listener {
|
||||
|
||||
@EventHandler
|
||||
@Override
|
||||
public void onPlayQuit(PlayerQuitEvent quitEvent) {
|
||||
Player player = quitEvent.getPlayer();
|
||||
UUID playerId = player.getUniqueId();
|
||||
endPlaySession(playerId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.auth.proxy.ProxyManager;
|
||||
import com.github.games647.fastlogin.bukkit.auth.protocollib.ProtocolLibListener;
|
||||
import com.github.games647.fastlogin.bukkit.auth.protocolsupport.ProtocolSupportListener;
|
||||
import com.github.games647.fastlogin.bukkit.command.CrackedCommand;
|
||||
import com.github.games647.fastlogin.bukkit.command.PremiumCommand;
|
||||
import com.github.games647.fastlogin.bukkit.hook.DelayedAuthHook;
|
||||
import com.github.games647.fastlogin.bukkit.listener.ConnectionListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.PaperPreLoginListener;
|
||||
import com.github.games647.fastlogin.core.CommonUtil;
|
||||
import com.github.games647.fastlogin.core.PremiumStatus;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.PlatformPlugin;
|
||||
|
||||
import io.papermc.lib.PaperLib;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* This plugin checks if a player has a paid account and if so tries to skip offline mode authentication.
|
||||
*/
|
||||
public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<CommandSender> {
|
||||
|
||||
private final BukkitSessionManager sessionManager = new BukkitSessionManager();
|
||||
private final Map<UUID, PremiumStatus> premiumPlayers = new ConcurrentHashMap<>();
|
||||
private final FastLoginCore<Player, CommandSender, FastLoginBukkit> core = new FastLoginCore<>(this);
|
||||
|
||||
private final Logger logger;
|
||||
private final BukkitScheduler scheduler;
|
||||
|
||||
private ProxyManager proxyManager;
|
||||
|
||||
private PremiumPlaceholder premiumPlaceholder;
|
||||
|
||||
public FastLoginBukkit() {
|
||||
this.logger = CommonUtil.createLoggerFromJDK(getLogger());
|
||||
this.scheduler = new BukkitScheduler(this, logger, getThreadFactory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
core.load();
|
||||
|
||||
if (getServer().getOnlineMode()) {
|
||||
//we need to require offline to prevent a loginSession request for a offline player
|
||||
logger.error("Server has to be in offline mode");
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
proxyManager = new ProxyManager(this);
|
||||
proxyManager.initialize();
|
||||
|
||||
PluginManager pluginManager = getServer().getPluginManager();
|
||||
if (!proxyManager.isEnabled()) {
|
||||
if (!core.setupDatabase()) {
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pluginManager.isPluginEnabled("ProtocolSupport")) {
|
||||
pluginManager.registerEvents(new ProtocolSupportListener(this, core.getRateLimiter()), this);
|
||||
} else if (pluginManager.isPluginEnabled("ProtocolLib")) {
|
||||
ProtocolLibListener.register(this, core.getRateLimiter());
|
||||
} else {
|
||||
logger.warn("Either ProtocolLib or ProtocolSupport have to be installed if you don't use proxies");
|
||||
}
|
||||
}
|
||||
|
||||
//delay dependency setup because we load the plugin very early where plugins are initialized yet
|
||||
getServer().getScheduler().runTaskLater(this, new DelayedAuthHook(this), 5L);
|
||||
|
||||
pluginManager.registerEvents(new ConnectionListener(this), this);
|
||||
|
||||
//if server is using paper - we need to add one more listener to correct the usercache usage
|
||||
if (PaperLib.isPaper()) {
|
||||
pluginManager.registerEvents(new PaperPreLoginListener(this), this);
|
||||
}
|
||||
|
||||
//register commands using a unique name
|
||||
getCommand("premium").setExecutor(new PremiumCommand(this));
|
||||
getCommand("cracked").setExecutor(new CrackedCommand(this));
|
||||
|
||||
if (pluginManager.isPluginEnabled("PlaceholderAPI")) {
|
||||
premiumPlaceholder = new PremiumPlaceholder(this);
|
||||
premiumPlaceholder.register();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
premiumPlayers.clear();
|
||||
core.close();
|
||||
|
||||
proxyManager.cleanup();
|
||||
if (getServer().getPluginManager().isPluginEnabled("PlaceholderAPI") && premiumPlaceholder != null) {
|
||||
premiumPlaceholder.unregister();
|
||||
}
|
||||
}
|
||||
|
||||
public FastLoginCore<Player, CommandSender, FastLoginBukkit> getCore() {
|
||||
return core;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the premium status of an online player.
|
||||
*
|
||||
* @param onlinePlayer
|
||||
* @return the online status or unknown if an error happened, the player isn't online or a proxy doesn't send
|
||||
* us the status message yet (This means you cannot check the login status on the PlayerJoinEvent).
|
||||
* @deprecated this method could be removed in future versions and exists only as a temporarily solution
|
||||
*/
|
||||
@Deprecated
|
||||
public PremiumStatus getStatus(UUID onlinePlayer) {
|
||||
StoredProfile playSession = sessionManager.getPlaySession(onlinePlayer);
|
||||
return Optional.ofNullable(playSession).map(profile -> {
|
||||
if (profile.isPremium())
|
||||
return PremiumStatus.PREMIUM;
|
||||
return PremiumStatus.CRACKED;
|
||||
}).orElse(PremiumStatus.UNKNOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a thread-safe map about players which are connecting to the server are being checked to be premium (paid
|
||||
* account)
|
||||
*
|
||||
* @return a thread-safe loginSession map
|
||||
*/
|
||||
public BukkitSessionManager getSessionManager() {
|
||||
return sessionManager;
|
||||
}
|
||||
|
||||
public Map<UUID, PremiumStatus> getPremiumPlayers() {
|
||||
return premiumPlayers;
|
||||
}
|
||||
|
||||
public ProxyManager getProxyManager() {
|
||||
return proxyManager;
|
||||
}
|
||||
|
||||
@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,83 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginAutoLoginEvent;
|
||||
import com.github.games647.fastlogin.core.PremiumStatus;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.message.SuccessMessage;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.auth.ForceLoginManagement;
|
||||
import com.github.games647.fastlogin.core.auth.LoginSession;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.metadata.FixedMetadataValue;
|
||||
|
||||
public class ForceLoginTask extends ForceLoginManagement<Player, CommandSender, BukkitLoginSession, FastLoginBukkit> {
|
||||
|
||||
public ForceLoginTask(FastLoginCore<Player, CommandSender, FastLoginBukkit> core, Player player,
|
||||
BukkitLoginSession session) {
|
||||
super(core, player, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// block this target player for proxy ID brute force attacks
|
||||
FastLoginBukkit plugin = core.getPlugin();
|
||||
player.setMetadata(core.getPlugin().getName(), new FixedMetadataValue(plugin, true));
|
||||
|
||||
if (session != null && !session.getUsername().equals(player.getName())) {
|
||||
String playerName = player.getName();
|
||||
plugin.getLog().warn("Player username {} is not matching session {}", playerName, session.getUsername());
|
||||
return;
|
||||
}
|
||||
|
||||
super.run();
|
||||
|
||||
PremiumStatus status = PremiumStatus.CRACKED;
|
||||
if (isOnlineMode()) {
|
||||
status = PremiumStatus.PREMIUM;
|
||||
}
|
||||
|
||||
plugin.getPremiumPlayers().put(player.getUniqueId(), status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FastLoginAutoLoginEvent callFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
|
||||
BukkitFastLoginAutoLoginEvent event = new BukkitFastLoginAutoLoginEvent(session, profile);
|
||||
core.getPlugin().getServer().getPluginManager().callEvent(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onForceActionSuccess(LoginSession session) {
|
||||
if (core.getPlugin().getProxyManager().isEnabled()) {
|
||||
core.getPlugin().getProxyManager().sendPluginMessage(player, new SuccessMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(Player player) {
|
||||
return player.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline(Player player) {
|
||||
try {
|
||||
//the player-list isn't thread-safe
|
||||
return Bukkit.getScheduler().callSyncMethod(core.getPlugin(), player::isOnline).get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
core.getPlugin().getLog().error("Failed to perform thread-safe online check for {}", player, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnlineMode() {
|
||||
return session.isVerified();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
public class PremiumPlaceholder extends PlaceholderExpansion {
|
||||
|
||||
private static final String PLACEHOLDER_VARIABLE = "status";
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public PremiumPlaceholder(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onRequest(OfflinePlayer player, String identifier) {
|
||||
// player is null if offline
|
||||
if (player != null && PLACEHOLDER_VARIABLE.equals(identifier)) {
|
||||
return plugin.getStatus(player.getUniqueId()).getReadableName();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return plugin.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequiredPlugin() {
|
||||
return plugin.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthor() {
|
||||
return String.join(", ", plugin.getDescription().getAuthors());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return plugin.getDescription().getVersion();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.github.games647.fastlogin.bukkit.auth;
|
||||
|
||||
import com.github.games647.craftapi.model.skin.SkinProperty;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.auth.LoginSession;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents a client connecting to the server.
|
||||
*
|
||||
* This session is invalid if the player disconnects or the login was successful
|
||||
*/
|
||||
public class BukkitLoginSession extends LoginSession {
|
||||
|
||||
private static final byte[] EMPTY_ARRAY = {};
|
||||
|
||||
private final String serverId;
|
||||
private final byte[] verifyToken;
|
||||
|
||||
private boolean verified;
|
||||
|
||||
private SkinProperty skinProperty;
|
||||
|
||||
public BukkitLoginSession(String username, String serverId, byte[] verifyToken, boolean registered
|
||||
, StoredProfile profile) {
|
||||
super(username, registered, profile);
|
||||
|
||||
this.serverId = serverId;
|
||||
this.verifyToken = verifyToken.clone();
|
||||
}
|
||||
|
||||
// available for proxies
|
||||
public BukkitLoginSession(String username, boolean registered) {
|
||||
this(username, "", EMPTY_ARRAY, registered, null);
|
||||
}
|
||||
|
||||
//cracked player
|
||||
public BukkitLoginSession(String username, StoredProfile profile) {
|
||||
this(username, "", EMPTY_ARRAY, false, profile);
|
||||
}
|
||||
|
||||
//ProtocolSupport
|
||||
public BukkitLoginSession(String username, boolean registered, StoredProfile profile) {
|
||||
this(username, "", EMPTY_ARRAY, registered, profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the verify token the server sent to the client.
|
||||
*
|
||||
* Empty if it's a proxy connection
|
||||
*
|
||||
* @return the verify token from the server
|
||||
*/
|
||||
public synchronized byte[] getVerifyToken() {
|
||||
return verifyToken.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return premium skin if available
|
||||
*/
|
||||
public synchronized Optional<SkinProperty> getSkin() {
|
||||
return Optional.ofNullable(skinProperty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the premium skin property which was retrieved by the session server
|
||||
* @param skinProperty premium skin
|
||||
*/
|
||||
public synchronized void setSkinProperty(SkinProperty skinProperty) {
|
||||
this.skinProperty = skinProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the player has a premium (paid account) account and valid session
|
||||
*
|
||||
* @param verified whether the player has valid session
|
||||
*/
|
||||
public synchronized void setVerified(boolean verified) {
|
||||
this.verified = verified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the player has a premium (paid account) account and valid session
|
||||
*
|
||||
* @return whether the player has a valid session
|
||||
*/
|
||||
public synchronized boolean isVerified() {
|
||||
return verified;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.github.games647.fastlogin.bukkit.auth.protocollib;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Random;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Encryption and decryption minecraft util for connection between servers
|
||||
* and paid Minecraft account clients.
|
||||
*
|
||||
* @see net.minecraft.server.MinecraftEncryption
|
||||
*/
|
||||
public class EncryptionUtil {
|
||||
|
||||
public static final int VERIFY_TOKEN_LENGTH = 4;
|
||||
public static final String KEY_PAIR_ALGORITHM = "RSA";
|
||||
|
||||
private EncryptionUtil() {
|
||||
// utility
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a RSA key pair
|
||||
*
|
||||
* @return The RSA key pair.
|
||||
*/
|
||||
public static KeyPair generateKeyPair() {
|
||||
// KeyPair b()
|
||||
try {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM);
|
||||
|
||||
keyPairGenerator.initialize(1_024);
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
} catch (NoSuchAlgorithmException nosuchalgorithmexception) {
|
||||
// Should be existing in every vm
|
||||
throw new ExceptionInInitializerError(nosuchalgorithmexception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random token. This is used to verify that we are communicating with the same player
|
||||
* in a login session.
|
||||
*
|
||||
* @param random random generator
|
||||
* @return an error with 4 bytes long
|
||||
*/
|
||||
public static byte[] generateVerifyToken(Random random) {
|
||||
// extracted from LoginListener
|
||||
byte[] token = new byte[VERIFY_TOKEN_LENGTH];
|
||||
random.nextBytes(token);
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the server id based on client and server data.
|
||||
*
|
||||
* @param sessionId session for the current login attempt
|
||||
* @param sharedSecret shared secret between the client and the server
|
||||
* @param publicKey public key of the server
|
||||
* @return the server id formatted as a hexadecimal string.
|
||||
*/
|
||||
public static String getServerIdHashString(String sessionId, SecretKey sharedSecret, PublicKey publicKey) {
|
||||
// found in LoginListener
|
||||
try {
|
||||
byte[] serverHash = getServerIdHash(sessionId, publicKey, sharedSecret);
|
||||
return (new BigInteger(serverHash)).toString(16);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts the content and extracts the key spec.
|
||||
*
|
||||
* @param privateKey private server key
|
||||
* @param sharedKey the encrypted shared key
|
||||
* @return shared secret key
|
||||
* @throws GeneralSecurityException if it fails to decrypt the data
|
||||
*/
|
||||
public static SecretKey decryptSharedKey(PrivateKey privateKey, byte[] sharedKey) throws GeneralSecurityException {
|
||||
// SecretKey a(PrivateKey var0, byte[] var1)
|
||||
return new SecretKeySpec(decrypt(privateKey, sharedKey), "AES");
|
||||
}
|
||||
|
||||
public static byte[] decrypt(PrivateKey key, byte[] data) throws GeneralSecurityException {
|
||||
// b(Key var0, byte[] var1)
|
||||
Cipher cipher = Cipher.getInstance(key.getAlgorithm());
|
||||
cipher.init(Cipher.DECRYPT_MODE, key);
|
||||
return decrypt(cipher, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypted the given data using the cipher.
|
||||
*
|
||||
* @param cipher decryption cypher initialized with the private key
|
||||
* @param data the encrypted data
|
||||
* @return clear text data
|
||||
* @throws GeneralSecurityException if it fails to decrypt the data
|
||||
*/
|
||||
private static byte[] decrypt(Cipher cipher, byte[] data) throws GeneralSecurityException {
|
||||
// inlined: byte[] a(int var0, Key var1, byte[] var2), Cipher a(int var0, String var1, Key
|
||||
// var2)
|
||||
return cipher.doFinal(data);
|
||||
}
|
||||
|
||||
private static byte[] getServerIdHash(
|
||||
String sessionId, PublicKey publicKey, SecretKey sharedSecret)
|
||||
throws NoSuchAlgorithmException {
|
||||
// byte[] a(String var0, PublicKey var1, SecretKey var2)
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
|
||||
// inlined from byte[] a(String var0, byte[]... var1)
|
||||
digest.update(sessionId.getBytes(StandardCharsets.ISO_8859_1));
|
||||
digest.update(sharedSecret.getEncoded());
|
||||
digest.update(publicKey.getEncoded());
|
||||
|
||||
return digest.digest();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.github.games647.fastlogin.bukkit.auth.protocollib;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
||||
import org.bukkit.event.player.PlayerLoginEvent.Result;
|
||||
|
||||
public class InitializedListener implements Listener {
|
||||
|
||||
private final ProtocolLibListener module;
|
||||
|
||||
protected InitializedListener(ProtocolLibListener protocolLibModule) {
|
||||
this.module = protocolLibModule;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
|
||||
if (loginEvent.getResult() == Result.ALLOWED && !module.isReadyToInject()) {
|
||||
loginEvent.disallow(Result.KICK_OTHER, module.getPlugin().getCore().getMessage("not-started"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.github.games647.fastlogin.bukkit.auth.protocollib;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.auth.JoinManagement;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.Random;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class NameCheckTask extends JoinManagement<Player, CommandSender, ProtocolLibLoginSource>
|
||||
implements Runnable {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
private final PacketEvent packetEvent;
|
||||
private final PublicKey publicKey;
|
||||
|
||||
private final Random random;
|
||||
|
||||
private final Player player;
|
||||
private final String username;
|
||||
|
||||
protected NameCheckTask(FastLoginBukkit plugin, PacketEvent packetEvent, Random random,
|
||||
Player player, String username, PublicKey publicKey) {
|
||||
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
|
||||
|
||||
this.plugin = plugin;
|
||||
this.packetEvent = packetEvent;
|
||||
this.publicKey = publicKey;
|
||||
this.random = random;
|
||||
this.player = player;
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
super.onLogin(username, new ProtocolLibLoginSource(packetEvent, player, random, publicKey));
|
||||
} finally {
|
||||
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, ProtocolLibLoginSource source, StoredProfile profile) {
|
||||
BukkitFastLoginPreLoginEvent event = new BukkitFastLoginPreLoginEvent(username, source, profile);
|
||||
plugin.getServer().getPluginManager().callEvent(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
//Minecraft server implementation
|
||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
|
||||
@Override
|
||||
public void requestPremiumLogin(ProtocolLibLoginSource source, StoredProfile profile
|
||||
, String username, boolean registered) {
|
||||
try {
|
||||
source.setOnlineMode();
|
||||
} catch (Exception ex) {
|
||||
plugin.getLog().error("Cannot send encryption packet. Falling back to cracked login for: {}", profile, ex);
|
||||
return;
|
||||
}
|
||||
|
||||
String ip = player.getAddress().getAddress().getHostAddress();
|
||||
core.getPendingLogin().put(ip + username, new Object());
|
||||
|
||||
String serverId = source.getServerId();
|
||||
byte[] verify = source.getVerifyToken();
|
||||
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId, verify, registered, profile);
|
||||
plugin.getSessionManager().startLoginSession(player.getAddress(), playerSession);
|
||||
//cancel only if the player has a paid account otherwise login as normal offline player
|
||||
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
|
||||
packetEvent.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startCrackedSession(ProtocolLibLoginSource source, StoredProfile profile, String username) {
|
||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||
plugin.getSessionManager().startLoginSession(player.getAddress(), loginSession);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.github.games647.fastlogin.bukkit.auth.protocollib;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.auth.RateLimiter;
|
||||
|
||||
import io.papermc.lib.PaperLib;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
import static com.comphenix.protocol.PacketType.Login.Client.ENCRYPTION_BEGIN;
|
||||
import static com.comphenix.protocol.PacketType.Login.Client.START;
|
||||
|
||||
public class ProtocolLibListener extends PacketAdapter {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
//just create a new once on plugin enable. This used for verify token generation
|
||||
private final SecureRandom random = new SecureRandom();
|
||||
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
|
||||
private final RateLimiter rateLimiter;
|
||||
|
||||
// Wait before the server is fully started. This is workaround, because connections right on startup are not
|
||||
// injected by ProtocolLib
|
||||
private boolean serverStarted;
|
||||
|
||||
protected ProtocolLibListener(FastLoginBukkit plugin, RateLimiter rateLimiter) {
|
||||
//run async in order to not block the server, because we are making api calls to Mojang
|
||||
super(params()
|
||||
.plugin(plugin)
|
||||
.types(START, ENCRYPTION_BEGIN)
|
||||
.optionAsync());
|
||||
|
||||
this.plugin = plugin;
|
||||
this.rateLimiter = rateLimiter;
|
||||
}
|
||||
|
||||
public static void register(FastLoginBukkit plugin, RateLimiter rateLimiter) {
|
||||
//they will be created with a static builder, because otherwise it will throw a NoClassDefFoundError
|
||||
ProtocolLibListener packetListener = new ProtocolLibListener(plugin, rateLimiter);
|
||||
ProtocolLibrary.getProtocolManager()
|
||||
.getAsynchronousManager()
|
||||
.registerAsyncHandler(packetListener)
|
||||
.start();
|
||||
|
||||
PluginManager pluginManager = Bukkit.getServer().getPluginManager();
|
||||
pluginManager.registerEvents(new InitializedListener(packetListener), plugin);
|
||||
|
||||
//if server is using paper - we need to set the skin at pre login anyway, so no need for this listener
|
||||
if (!PaperLib.isPaper() && plugin.getConfig().getBoolean("forwardSkin")) {
|
||||
pluginManager.registerEvents(new SkinApplyListener(plugin), plugin);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||
if (packetEvent.isCancelled() || plugin.getCore().getAuthPluginHook() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
markReadyToInject();
|
||||
|
||||
Player sender = packetEvent.getPlayer();
|
||||
PacketType packetType = packetEvent.getPacketType();
|
||||
if (packetType == START) {
|
||||
if (!rateLimiter.tryAcquire()) {
|
||||
plugin.getLog().warn("Join limit hit - Ignoring player {}", sender);
|
||||
return;
|
||||
}
|
||||
|
||||
onLogin(packetEvent, sender);
|
||||
} else {
|
||||
onEncryptionBegin(packetEvent, sender);
|
||||
}
|
||||
}
|
||||
|
||||
private void onEncryptionBegin(PacketEvent packetEvent, Player sender) {
|
||||
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
|
||||
|
||||
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||
Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, sharedSecret, keyPair);
|
||||
plugin.getScheduler().runAsync(verifyTask);
|
||||
}
|
||||
|
||||
private void onLogin(PacketEvent packetEvent, Player player) {
|
||||
//this includes ip:port. Should be unique for an incoming login request with a timeout of 2 minutes
|
||||
String sessionKey = player.getAddress().toString();
|
||||
|
||||
//remove old data every time on a new login in order to keep the session only for one person
|
||||
plugin.getSessionManager().endLoginSession(player.getAddress());
|
||||
|
||||
//player.getName() won't work at this state
|
||||
PacketContainer packet = packetEvent.getPacket();
|
||||
|
||||
String username = packet.getGameProfiles().read(0).getName();
|
||||
plugin.getLog().trace("GameProfile {} with {} connecting", sessionKey, username);
|
||||
|
||||
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||
Runnable nameCheckTask = new NameCheckTask(plugin, packetEvent, random, player, username, keyPair.getPublic());
|
||||
plugin.getScheduler().runAsync(nameCheckTask);
|
||||
}
|
||||
|
||||
public void markReadyToInject() {
|
||||
this.serverStarted = true;
|
||||
}
|
||||
|
||||
public boolean isReadyToInject() {
|
||||
return serverStarted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FastLoginBukkit getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.github.games647.fastlogin.bukkit.auth.protocollib;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
import com.github.games647.fastlogin.core.auth.LoginSource;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import static com.comphenix.protocol.PacketType.Login.Server.DISCONNECT;
|
||||
import static com.comphenix.protocol.PacketType.Login.Server.ENCRYPTION_BEGIN;
|
||||
|
||||
class ProtocolLibLoginSource implements LoginSource {
|
||||
|
||||
private final PacketEvent packetEvent;
|
||||
private final Player player;
|
||||
|
||||
private final Random random;
|
||||
private final PublicKey publicKey;
|
||||
|
||||
private final String serverId = "";
|
||||
private byte[] verifyToken;
|
||||
|
||||
protected ProtocolLibLoginSource(PacketEvent packetEvent, Player player, Random random, PublicKey publicKey) {
|
||||
this.packetEvent = packetEvent;
|
||||
this.player = player;
|
||||
this.random = random;
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnlineMode() throws Exception {
|
||||
verifyToken = EncryptionUtil.generateVerifyToken(random);
|
||||
|
||||
/*
|
||||
* Packet Information: https://wiki.vg/Protocol#Encryption_Request
|
||||
*
|
||||
* ServerID="" (String) key=public server key verifyToken=random 4 byte array
|
||||
*/
|
||||
PacketContainer newPacket = new PacketContainer(ENCRYPTION_BEGIN);
|
||||
|
||||
newPacket.getStrings().write(0, serverId);
|
||||
StructureModifier<PublicKey> keyModifier = newPacket.getSpecificModifier(PublicKey.class);
|
||||
int verifyField = 0;
|
||||
if (keyModifier.getFields().isEmpty()) {
|
||||
// Since 1.16.4 this is now a byte field
|
||||
newPacket.getByteArrays().write(0, publicKey.getEncoded());
|
||||
verifyField++;
|
||||
} else {
|
||||
keyModifier.write(0, publicKey);
|
||||
}
|
||||
|
||||
newPacket.getByteArrays().write(verifyField, verifyToken);
|
||||
|
||||
//serverId is a empty string
|
||||
ProtocolLibrary.getProtocolManager().sendServerPacket(player, newPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kick(String message) throws InvocationTargetException {
|
||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
PacketContainer kickPacket = new PacketContainer(DISCONNECT);
|
||||
kickPacket.getChatComponents().write(0, WrappedChatComponent.fromText(message));
|
||||
|
||||
try {
|
||||
//send kick packet at login state
|
||||
//the normal event.getPlayer.kickPlayer(String) method does only work at play state
|
||||
protocolManager.sendServerPacket(player, kickPacket);
|
||||
} finally {
|
||||
//tell the server that we want to close the connection
|
||||
player.kickPlayer("Disconnect");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getAddress() {
|
||||
return packetEvent.getPlayer().getAddress();
|
||||
}
|
||||
|
||||
public String getServerId() {
|
||||
return serverId;
|
||||
}
|
||||
|
||||
public byte[] getVerifyToken() {
|
||||
return verifyToken.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"packetEvent=" + packetEvent +
|
||||
", player=" + player +
|
||||
", random=" + random +
|
||||
", serverId='" + serverId + '\'' +
|
||||
", verifyToken=" + Arrays.toString(verifyToken) +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.github.games647.fastlogin.bukkit.auth.protocollib;
|
||||
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
|
||||
import com.github.games647.craftapi.model.skin.Textures;
|
||||
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
||||
import org.bukkit.event.player.PlayerLoginEvent.Result;
|
||||
|
||||
public class SkinApplyListener implements Listener {
|
||||
|
||||
private static final Class<?> GAME_PROFILE = MinecraftReflection.getGameProfileClass();
|
||||
private static final MethodAccessor GET_PROPERTIES = Accessors.getMethodAccessor(GAME_PROFILE, "getProperties");
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
protected SkinApplyListener(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
//run this on the loginEvent to let skins plugins see the skin like in normal Minecraft behaviour
|
||||
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
|
||||
if (loginEvent.getResult() != Result.ALLOWED) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = loginEvent.getPlayer();
|
||||
|
||||
//go through every session, because player.getAddress is null
|
||||
//loginEvent.getAddress is just a InetAddress not InetSocketAddress, so not unique enough
|
||||
BukkitLoginSession session = plugin.getSessionManager().getLoginSession(player.getAddress());
|
||||
if (session.getUsername().equals(player.getName())) {
|
||||
session.getSkin().ifPresent(skin -> applySkin(player, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
}
|
||||
|
||||
private void applySkin(Player player, String skinData, String signature) {
|
||||
WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player);
|
||||
WrappedSignedProperty skin = WrappedSignedProperty.fromValues(Textures.KEY, skinData, signature);
|
||||
gameProfile.getProperties().put(Textures.KEY, skin);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
package com.github.games647.fastlogin.bukkit.auth.protocollib;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.github.games647.craftapi.model.auth.Verification;
|
||||
import com.github.games647.craftapi.model.skin.SkinProperty;
|
||||
import com.github.games647.craftapi.resolver.MojangResolver;
|
||||
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import static com.comphenix.protocol.PacketType.Login.Client.START;
|
||||
import static com.comphenix.protocol.PacketType.Login.Server.DISCONNECT;
|
||||
|
||||
public class VerifyResponseTask implements Runnable {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
private final PacketEvent packetEvent;
|
||||
private final KeyPair serverKey;
|
||||
|
||||
private final Player player;
|
||||
|
||||
private final byte[] sharedSecret;
|
||||
|
||||
private static Method encryptMethod;
|
||||
private static Method cipherMethod;
|
||||
|
||||
protected VerifyResponseTask(FastLoginBukkit plugin, PacketEvent packetEvent, Player player,
|
||||
byte[] sharedSecret, KeyPair keyPair) {
|
||||
this.plugin = plugin;
|
||||
this.packetEvent = packetEvent;
|
||||
this.player = player;
|
||||
this.sharedSecret = Arrays.copyOf(sharedSecret, sharedSecret.length);
|
||||
this.serverKey = keyPair;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
BukkitLoginSession session = plugin.getSessionManager().getLoginSession(player.getAddress());
|
||||
if (session == null) {
|
||||
disconnect("invalid-request", true
|
||||
, "GameProfile {0} tried to send encryption response at invalid state", player.getAddress());
|
||||
} else {
|
||||
verifyResponse(session);
|
||||
}
|
||||
} finally {
|
||||
//this is a fake packet; it shouldn't be send to the server
|
||||
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
|
||||
packetEvent.setCancelled(true);
|
||||
}
|
||||
|
||||
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyResponse(BukkitLoginSession session) {
|
||||
PrivateKey privateKey = serverKey.getPrivate();
|
||||
|
||||
SecretKey loginKey;
|
||||
try {
|
||||
loginKey = EncryptionUtil.decryptSharedKey(privateKey, sharedSecret);
|
||||
} catch (GeneralSecurityException securityEx) {
|
||||
disconnect("error-kick", false, "Cannot decrypt received contents", securityEx);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!checkVerifyToken(session) || !enableEncryption(loginKey)) {
|
||||
return;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
disconnect("error-kick", false, "Cannot decrypt received contents", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
String serverId = EncryptionUtil.getServerIdHashString("", loginKey, serverKey.getPublic());
|
||||
|
||||
String requestedUsername = session.getRequestUsername();
|
||||
InetSocketAddress socketAddress = player.getAddress();
|
||||
try {
|
||||
MojangResolver resolver = plugin.getCore().getResolver();
|
||||
InetAddress address = socketAddress.getAddress();
|
||||
Optional<Verification> response = resolver.hasJoined(requestedUsername, serverId, address);
|
||||
if (response.isPresent()) {
|
||||
Verification verification = response.get();
|
||||
plugin.getLog().info("Profile {} has a verified premium account: {}", requestedUsername, verification);
|
||||
String realUsername = verification.getName();
|
||||
if (realUsername == null) {
|
||||
disconnect("invalid-session", true, "Username field null for {}", requestedUsername);
|
||||
return;
|
||||
}
|
||||
|
||||
SkinProperty[] properties = verification.getProperties();
|
||||
if (properties.length > 0) {
|
||||
session.setSkinProperty(properties[0]);
|
||||
}
|
||||
|
||||
session.setVerifiedUsername(realUsername);
|
||||
session.setUuid(verification.getId());
|
||||
session.setVerified(true);
|
||||
|
||||
setPremiumUUID(session.getUuid());
|
||||
receiveFakeStartPacket(realUsername);
|
||||
} else {
|
||||
//user tried to fake a authentication
|
||||
disconnect("invalid-session", true
|
||||
, "GameProfile {0} ({1}) tried to log in with an invalid session ServerId: {2}"
|
||||
, session.getRequestUsername(), socketAddress, serverId);
|
||||
}
|
||||
} catch (IOException ioEx) {
|
||||
disconnect("error-kick", false, "Failed to connect to session server", ioEx);
|
||||
}
|
||||
}
|
||||
|
||||
private void setPremiumUUID(UUID premiumUUID) {
|
||||
if (plugin.getConfig().getBoolean("premiumUuid") && premiumUUID != null) {
|
||||
try {
|
||||
Object networkManager = getNetworkManager();
|
||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/NetworkManager.java#L69
|
||||
FieldUtils.writeField(networkManager, "spoofedUUID", premiumUUID, true);
|
||||
} catch (Exception exc) {
|
||||
plugin.getLog().error("Error setting premium uuid of {}", player, exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkVerifyToken(BukkitLoginSession session) throws GeneralSecurityException {
|
||||
byte[] requestVerify = session.getVerifyToken();
|
||||
//encrypted verify token
|
||||
byte[] responseVerify = packetEvent.getPacket().getByteArrays().read(1);
|
||||
|
||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L182
|
||||
if (!Arrays.equals(requestVerify, EncryptionUtil.decrypt(serverKey.getPrivate(), responseVerify))) {
|
||||
//check if the verify token are equal to the server sent one
|
||||
disconnect("invalid-verify-token", true
|
||||
, "GameProfile {0} ({1}) tried to login with an invalid verify token. Server: {2} Client: {3}"
|
||||
, session.getRequestUsername(), packetEvent.getPlayer().getAddress(), requestVerify, responseVerify);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//try to get the networkManager from ProtocolLib
|
||||
private Object getNetworkManager() throws IllegalAccessException, ClassNotFoundException {
|
||||
Object injectorContainer = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
||||
|
||||
//ChannelInjector
|
||||
Class<?> injectorClass = Class.forName("com.comphenix.protocol.injector.netty.Injector");
|
||||
Object rawInjector = FuzzyReflection.getFieldValue(injectorContainer, injectorClass, true);
|
||||
return FieldUtils.readField(rawInjector, "networkManager", true);
|
||||
}
|
||||
|
||||
private boolean enableEncryption(SecretKey loginKey) throws IllegalArgumentException {
|
||||
// Initialize method reflections
|
||||
if (encryptMethod == null) {
|
||||
Class<?> networkManagerClass = MinecraftReflection.getNetworkManagerClass();
|
||||
|
||||
try {
|
||||
// Try to get the old (pre MC 1.16.4) encryption method
|
||||
encryptMethod = FuzzyReflection.fromClass(networkManagerClass)
|
||||
.getMethodByParameters("a", SecretKey.class);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
// Get the new encryption method
|
||||
encryptMethod = FuzzyReflection.fromClass(networkManagerClass)
|
||||
.getMethodByParameters("a", Cipher.class, Cipher.class);
|
||||
|
||||
// Get the needed Cipher helper method (used to generate ciphers from login key)
|
||||
Class<?> encryptionClass = MinecraftReflection.getMinecraftClass("MinecraftEncryption");
|
||||
cipherMethod = FuzzyReflection.fromClass(encryptionClass)
|
||||
.getMethodByParameters("a", int.class, Key.class);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Object networkManager = this.getNetworkManager();
|
||||
|
||||
// If cipherMethod is null - use old encryption (pre MC 1.16.4), otherwise use the new cipher one
|
||||
if (cipherMethod == null) {
|
||||
// Encrypt/decrypt packet flow, this behaviour is expected by the client
|
||||
encryptMethod.invoke(networkManager, loginKey);
|
||||
} else {
|
||||
// Create ciphers from login key
|
||||
Object decryptionCipher = cipherMethod.invoke(null, Cipher.DECRYPT_MODE, loginKey);
|
||||
Object encryptionCipher = cipherMethod.invoke(null, Cipher.ENCRYPT_MODE, loginKey);
|
||||
|
||||
// Encrypt/decrypt packet flow, this behaviour is expected by the client
|
||||
encryptMethod.invoke(networkManager, decryptionCipher, encryptionCipher);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
disconnect("error-kick", false, "Couldn't enable encryption", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void disconnect(String reasonKey, boolean debug, String logMessage, Object... arguments) {
|
||||
if (debug) {
|
||||
plugin.getLog().debug(logMessage, arguments);
|
||||
} else {
|
||||
plugin.getLog().error(logMessage, arguments);
|
||||
}
|
||||
|
||||
kickPlayer(plugin.getCore().getMessage(reasonKey));
|
||||
}
|
||||
|
||||
private void kickPlayer(String reason) {
|
||||
PacketContainer kickPacket = new PacketContainer(DISCONNECT);
|
||||
kickPacket.getChatComponents().write(0, WrappedChatComponent.fromText(reason));
|
||||
try {
|
||||
//send kick packet at login state
|
||||
//the normal event.getPlayer.kickPlayer(String) method does only work at play state
|
||||
ProtocolLibrary.getProtocolManager().sendServerPacket(player, kickPacket);
|
||||
//tell the server that we want to close the connection
|
||||
player.kickPlayer("Disconnect");
|
||||
} catch (InvocationTargetException ex) {
|
||||
plugin.getLog().error("Error sending kick packet for: {}", player, ex);
|
||||
}
|
||||
}
|
||||
|
||||
//fake a new login packet in order to let the server handle all the other stuff
|
||||
private void receiveFakeStartPacket(String username) {
|
||||
//see StartPacketListener for packet information
|
||||
PacketContainer startPacket = new PacketContainer(START);
|
||||
|
||||
//uuid is ignored by the packet definition
|
||||
WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username);
|
||||
startPacket.getGameProfiles().write(0, fakeProfile);
|
||||
try {
|
||||
//we don't want to handle our own packets so ignore filters
|
||||
ProtocolLibrary.getProtocolManager().recieveClientPacket(player, startPacket, false);
|
||||
} catch (InvocationTargetException | IllegalAccessException ex) {
|
||||
plugin.getLog().warn("Failed to fake a new start packet for: {}", username, ex);
|
||||
//cancel the event in order to prevent the server receiving an invalid packet
|
||||
kickPlayer(plugin.getCore().getMessage("error-kick"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.github.games647.fastlogin.bukkit.auth.protocolsupport;
|
||||
|
||||
import com.github.games647.fastlogin.core.auth.LoginSource;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import protocolsupport.api.events.PlayerLoginStartEvent;
|
||||
|
||||
public class ProtocolLoginSource implements LoginSource {
|
||||
|
||||
private final PlayerLoginStartEvent loginStartEvent;
|
||||
|
||||
protected ProtocolLoginSource(PlayerLoginStartEvent loginStartEvent) {
|
||||
this.loginStartEvent = loginStartEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnlineMode() {
|
||||
loginStartEvent.setOnlineMode(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kick(String message) {
|
||||
loginStartEvent.denyLogin(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getAddress() {
|
||||
return loginStartEvent.getAddress();
|
||||
}
|
||||
|
||||
public PlayerLoginStartEvent getLoginStartEvent() {
|
||||
return loginStartEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"loginStartEvent=" + loginStartEvent +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.github.games647.fastlogin.bukkit.auth.protocolsupport;
|
||||
|
||||
import com.github.games647.craftapi.UUIDAdapter;
|
||||
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
|
||||
import com.github.games647.fastlogin.core.auth.RateLimiter;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.auth.JoinManagement;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import protocolsupport.api.events.ConnectionCloseEvent;
|
||||
import protocolsupport.api.events.PlayerLoginStartEvent;
|
||||
import protocolsupport.api.events.PlayerProfileCompleteEvent;
|
||||
|
||||
public class ProtocolSupportListener extends JoinManagement<Player, CommandSender, ProtocolLoginSource>
|
||||
implements Listener {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
private final RateLimiter rateLimiter;
|
||||
|
||||
public ProtocolSupportListener(FastLoginBukkit plugin, RateLimiter rateLimiter) {
|
||||
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
|
||||
|
||||
this.plugin = plugin;
|
||||
this.rateLimiter = rateLimiter;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onLoginStart(PlayerLoginStartEvent loginStartEvent) {
|
||||
if (loginStartEvent.isLoginDenied() || plugin.getCore().getAuthPluginHook() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rateLimiter.tryAcquire()) {
|
||||
plugin.getLog().warn("Join limit hit - Ignoring player {}", loginStartEvent.getConnection());
|
||||
return;
|
||||
}
|
||||
|
||||
String username = loginStartEvent.getConnection().getProfile().getName();
|
||||
InetSocketAddress address = loginStartEvent.getAddress();
|
||||
|
||||
//remove old data every time on a new login in order to keep the session only for one person
|
||||
plugin.getSessionManager().endLoginSession(address);
|
||||
|
||||
super.onLogin(username, new ProtocolLoginSource(loginStartEvent));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onConnectionClosed(ConnectionCloseEvent closeEvent) {
|
||||
InetSocketAddress address = closeEvent.getConnection().getAddress();
|
||||
plugin.getSessionManager().endLoginSession(address);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPropertiesResolve(PlayerProfileCompleteEvent profileCompleteEvent) {
|
||||
InetSocketAddress address = profileCompleteEvent.getAddress();
|
||||
BukkitLoginSession session = plugin.getSessionManager().getLoginSession(address);
|
||||
|
||||
if (session != null && profileCompleteEvent.getConnection().getProfile().isOnlineMode()) {
|
||||
session.setVerified(true);
|
||||
|
||||
if (!plugin.getConfig().getBoolean("premiumUuid")) {
|
||||
String username = Optional.ofNullable(profileCompleteEvent.getForcedName())
|
||||
.orElse(profileCompleteEvent.getConnection().getProfile().getName());
|
||||
profileCompleteEvent.setForcedUUID(UUIDAdapter.generateOfflineId(username));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, ProtocolLoginSource source, StoredProfile profile) {
|
||||
BukkitFastLoginPreLoginEvent event = new BukkitFastLoginPreLoginEvent(username, source, profile);
|
||||
plugin.getServer().getPluginManager().callEvent(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestPremiumLogin(ProtocolLoginSource source, StoredProfile profile, String username
|
||||
, boolean registered) {
|
||||
source.setOnlineMode();
|
||||
|
||||
String ip = source.getAddress().getAddress().getHostAddress();
|
||||
plugin.getCore().getPendingLogin().put(ip + username, new Object());
|
||||
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, registered, profile);
|
||||
plugin.getSessionManager().startLoginSession(source.getAddress(), playerSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startCrackedSession(ProtocolLoginSource source, StoredProfile profile, String username) {
|
||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||
plugin.getSessionManager().startLoginSession(source.getAddress(), loginSession);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package com.github.games647.fastlogin.bukkit.auth.proxy;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.message.ChannelMessage;
|
||||
import com.github.games647.fastlogin.core.message.LoginActionMessage;
|
||||
import com.github.games647.fastlogin.core.message.NamespaceKey;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.messaging.PluginMessageRecipient;
|
||||
|
||||
import static com.github.games647.fastlogin.core.message.ChangePremiumMessage.CHANGE_CHANNEL;
|
||||
import static com.github.games647.fastlogin.core.message.SuccessMessage.SUCCESS_CHANNEL;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
public class ProxyManager {
|
||||
|
||||
private static final String LEGACY_FILE_NAME = "proxy-whitelist.txt";
|
||||
private static final String FILE_NAME = "allowed-proxies.txt";
|
||||
|
||||
//null if proxies allowed list is empty so proxy support is disabled
|
||||
private Set<UUID> proxyIds;
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
private boolean enabled;
|
||||
|
||||
private final Set<UUID> firedJoinEvents = new HashSet<>();
|
||||
|
||||
public ProxyManager(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
//remove old blocked status
|
||||
Bukkit.getOnlinePlayers().forEach(player -> player.removeMetadata(plugin.getName(), plugin));
|
||||
}
|
||||
|
||||
public void sendPluginMessage(PluginMessageRecipient player, ChannelMessage message) {
|
||||
if (player != null) {
|
||||
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
|
||||
message.writeTo(dataOutput);
|
||||
|
||||
NamespaceKey channel = new NamespaceKey(plugin.getName(), message.getChannelName());
|
||||
player.sendPluginMessage(plugin, channel.getCombinedName(), dataOutput.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
try {
|
||||
enabled = detectProxy();
|
||||
} catch (Exception ex) {
|
||||
plugin.getLog().warn("Cannot check proxy support. Fallback to non-proxy mode", ex);
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
proxyIds = loadProxyIds();
|
||||
registerPluginChannels();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean detectProxy() throws Exception {
|
||||
try {
|
||||
enabled = Class.forName("org.spigotmc.SpigotConfig").getDeclaredField("bungee").getBoolean(null);
|
||||
return enabled;
|
||||
} catch (ClassNotFoundException notFoundEx) {
|
||||
//ignore server has no proxy support
|
||||
return false;
|
||||
} catch (Exception ex) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private void registerPluginChannels() {
|
||||
Server server = Bukkit.getServer();
|
||||
|
||||
// check for incoming messages from the proxy version of this plugin
|
||||
String groupId = plugin.getName();
|
||||
String forceChannel = NamespaceKey.getCombined(groupId, LoginActionMessage.FORCE_CHANNEL);
|
||||
server.getMessenger().registerIncomingPluginChannel(plugin, forceChannel, new ProxyMessagingListener(plugin));
|
||||
|
||||
// outgoing
|
||||
String successChannel = new NamespaceKey(groupId, SUCCESS_CHANNEL).getCombinedName();
|
||||
String changeChannel = new NamespaceKey(groupId, CHANGE_CHANNEL).getCombinedName();
|
||||
server.getMessenger().registerOutgoingPluginChannel(plugin, successChannel);
|
||||
server.getMessenger().registerOutgoingPluginChannel(plugin, changeChannel);
|
||||
}
|
||||
|
||||
private Set<UUID> loadProxyIds() {
|
||||
Path proxiesFile = plugin.getPluginFolder().resolve(FILE_NAME);
|
||||
Path legacyFile = plugin.getPluginFolder().resolve(LEGACY_FILE_NAME);
|
||||
try {
|
||||
if (Files.notExists(proxiesFile)) {
|
||||
if (Files.exists(legacyFile)) {
|
||||
Files.move(legacyFile, proxiesFile);
|
||||
}
|
||||
|
||||
if (Files.notExists(legacyFile)) {
|
||||
Files.createFile(proxiesFile);
|
||||
}
|
||||
}
|
||||
|
||||
Files.deleteIfExists(legacyFile);
|
||||
try (Stream<String> lines = Files.lines(proxiesFile)) {
|
||||
return lines.map(String::trim)
|
||||
.map(UUID::fromString)
|
||||
.collect(toSet());
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
plugin.getLog().error("Failed to read proxies", ex);
|
||||
} catch (Exception ex) {
|
||||
plugin.getLog().error("Failed to retrieve proxy Id. Disabling proxy support", ex);
|
||||
}
|
||||
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
public boolean isProxyAllowed(UUID proxyId) {
|
||||
return proxyIds != null && proxyIds.contains(proxyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the event to be fired including the task delay.
|
||||
*
|
||||
* @param player
|
||||
*/
|
||||
public synchronized void markJoinEventFired(Player player) {
|
||||
firedJoinEvents.add(player.getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the event fired including with the task delay. This necessary to restore the order of processing the
|
||||
* proxy messages after the PlayerJoinEvent fires including the delay.
|
||||
*
|
||||
* If the join event fired, the delay exceeded, but it ran earlier and couldn't find the recently started login
|
||||
* session. If not fired, we can start a new force login task. This will still match the requirement that we wait
|
||||
* a certain time after the player join event fired.
|
||||
*
|
||||
* @param player
|
||||
* @return event fired including delay
|
||||
*/
|
||||
public synchronized boolean didJoinEventFired(Player player) {
|
||||
return firedJoinEvents.contains(player.getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Player quit clean up
|
||||
*
|
||||
* @param player
|
||||
*/
|
||||
public synchronized void cleanup(Player player) {
|
||||
firedJoinEvents.remove(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.github.games647.fastlogin.bukkit.auth.proxy;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.ForceLoginTask;
|
||||
import com.github.games647.fastlogin.core.PremiumStatus;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.github.games647.fastlogin.core.message.LoginActionMessage;
|
||||
import com.github.games647.fastlogin.core.message.LoginActionMessage.Type;
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
|
||||
/**
|
||||
* Responsible for receiving messages from a proxy instance.
|
||||
*
|
||||
* This class also receives the plugin message from the proxy version of this plugin in order to get notified if
|
||||
* the connection is in online mode.
|
||||
*/
|
||||
public class ProxyMessagingListener implements PluginMessageListener {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
protected ProxyMessagingListener(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
|
||||
ByteArrayDataInput dataInput = ByteStreams.newDataInput(message);
|
||||
|
||||
LoginActionMessage loginMessage = new LoginActionMessage();
|
||||
loginMessage.readFrom(dataInput);
|
||||
|
||||
plugin.getLog().debug("Received plugin message {}", loginMessage);
|
||||
|
||||
Player targetPlayer = player;
|
||||
if (!loginMessage.getPlayerName().equals(player.getName())) {
|
||||
targetPlayer = Bukkit.getPlayerExact(loginMessage.getPlayerName());;
|
||||
}
|
||||
|
||||
if (targetPlayer == null) {
|
||||
plugin.getLog().warn("Force action player {} not found", loginMessage.getPlayerName());
|
||||
return;
|
||||
}
|
||||
|
||||
// fail if target player is blocked because already authenticated or wrong proxy id
|
||||
if (targetPlayer.hasMetadata(plugin.getName())) {
|
||||
plugin.getLog().warn("Received message {} from a blocked player {}", loginMessage, targetPlayer);
|
||||
} else {
|
||||
UUID sourceId = loginMessage.getProxyId();
|
||||
if (plugin.getProxyManager().isProxyAllowed(sourceId)) {
|
||||
readMessage(targetPlayer, loginMessage);
|
||||
} else {
|
||||
plugin.getLog().warn("Received proxy id: {} that doesn't exist in the proxy file", sourceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readMessage(Player player, LoginActionMessage message) {
|
||||
String playerName = message.getPlayerName();
|
||||
Type type = message.getType();
|
||||
|
||||
InetSocketAddress address = player.getAddress();
|
||||
plugin.getLog().info("Player info {} command for {} from proxy", type, playerName);
|
||||
if (type == Type.LOGIN) {
|
||||
onLoginMessage(player, playerName, address);
|
||||
} else if (type == Type.REGISTER) {
|
||||
onRegisterMessage(player, playerName, address);
|
||||
} else if (type == Type.CRACKED) {
|
||||
//we don't start a force login task here so update it manually
|
||||
plugin.getPremiumPlayers().put(player.getUniqueId(), PremiumStatus.CRACKED);
|
||||
}
|
||||
}
|
||||
|
||||
private void onLoginMessage(Player player, String playerName, InetSocketAddress address) {
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, true);
|
||||
startLoginTaskIfReady(player, playerSession);
|
||||
}
|
||||
|
||||
private void onRegisterMessage(Player player, String playerName, InetSocketAddress address) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
AuthPlugin<Player> authPlugin = plugin.getCore().getAuthPluginHook();
|
||||
try {
|
||||
//we need to check if the player is registered on Bukkit too
|
||||
if (authPlugin == null || !authPlugin.isRegistered(playerName)) {
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, false);
|
||||
startLoginTaskIfReady(player, playerSession);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
plugin.getLog().error("Failed to query isRegistered for player: {}", player, ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startLoginTaskIfReady(Player player, BukkitLoginSession session) {
|
||||
session.setVerified(true);
|
||||
plugin.getSessionManager().startLoginSession(player.getAddress(), session);
|
||||
|
||||
// only start a new login task if the join event fired earlier. This event then didn
|
||||
boolean result = plugin.getProxyManager().didJoinEventFired(player);
|
||||
plugin.getLog().info("Delaying force login until join event fired?: {}", result);
|
||||
if (result) {
|
||||
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player, session);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, forceLoginTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.github.games647.fastlogin.bukkit.command;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPremiumToggleEvent;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import static com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent.*;
|
||||
|
||||
public class CrackedCommand extends ToggleCommand {
|
||||
|
||||
public CrackedCommand(FastLoginBukkit plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length == 0) {
|
||||
onCrackedSelf(sender, command, args);
|
||||
} else {
|
||||
onCrackedOther(sender, command, args);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onCrackedSelf(CommandSender sender, Command cmd, String[] args) {
|
||||
if (isConsole(sender)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (forwardCrackedCommand(sender, sender.getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (plugin.getProxyManager().isEnabled()) {
|
||||
sendProxyActivateMessage(sender, sender.getName(), false);
|
||||
plugin.getCore().sendLocaleMessage("wait-on-proxy", sender);
|
||||
} else {
|
||||
//todo: load async if
|
||||
StoredProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
|
||||
if (profile.isPremium()) {
|
||||
plugin.getCore().sendLocaleMessage("remove-premium", sender);
|
||||
|
||||
profile.setPremium(false);
|
||||
profile.setId(null);
|
||||
plugin.getScheduler().runAsync(() -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
plugin.getServer().getPluginManager().callEvent(
|
||||
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_OTHER));
|
||||
});
|
||||
} else {
|
||||
plugin.getCore().sendLocaleMessage("not-premium", sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onCrackedOther(CommandSender sender, Command command, String[] args) {
|
||||
if (!hasOtherPermission(sender, command)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (forwardCrackedCommand(sender, args[0])) {
|
||||
return;
|
||||
}
|
||||
|
||||
//todo: load async
|
||||
StoredProfile profile = plugin.getCore().getStorage().loadProfile(args[0]);
|
||||
if (profile == null) {
|
||||
sender.sendMessage("Error occurred");
|
||||
return;
|
||||
}
|
||||
|
||||
//existing player is already cracked
|
||||
if (profile.isSaved() && !profile.isPremium()) {
|
||||
plugin.getCore().sendLocaleMessage("not-premium-other", sender);
|
||||
} else {
|
||||
plugin.getCore().sendLocaleMessage("remove-premium", sender);
|
||||
|
||||
profile.setPremium(false);
|
||||
plugin.getScheduler().runAsync(() -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
plugin.getServer().getPluginManager().callEvent(
|
||||
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_OTHER));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean forwardCrackedCommand(CommandSender sender, String target) {
|
||||
return forwardProxyCommand(sender, target, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.github.games647.fastlogin.bukkit.command;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPremiumToggleEvent;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent.PremiumToggleReason;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Let users activate fast login by command. This only be accessible if
|
||||
* the user has access to it's account. So we can make sure that not another
|
||||
* person with a paid account and the same username can steal their account.
|
||||
*/
|
||||
public class PremiumCommand extends ToggleCommand {
|
||||
|
||||
public PremiumCommand(FastLoginBukkit plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length == 0) {
|
||||
onPremiumSelf(sender, command, args);
|
||||
} else {
|
||||
onPremiumOther(sender, command, args);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onPremiumSelf(CommandSender sender, Command cmd, String[] args) {
|
||||
if (isConsole(sender)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (forwardPremiumCommand(sender, sender.getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID id = ((Player) sender).getUniqueId();
|
||||
if (plugin.getConfig().getBoolean("premium-warning") && !plugin.getCore().getPendingConfirms().contains(id)) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("premium-warning"));
|
||||
plugin.getCore().getPendingConfirms().add(id);
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getCore().getPendingConfirms().remove(id);
|
||||
//todo: load async
|
||||
StoredProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
|
||||
if (profile.isPremium()) {
|
||||
plugin.getCore().sendLocaleMessage("already-exists", sender);
|
||||
} else {
|
||||
//todo: resolve uuid
|
||||
profile.setPremium(true);
|
||||
plugin.getScheduler().runAsync(() -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
plugin.getServer().getPluginManager().callEvent(
|
||||
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_SELF));
|
||||
});
|
||||
|
||||
plugin.getCore().sendLocaleMessage("add-premium", sender);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPremiumOther(CommandSender sender, Command command, String[] args) {
|
||||
if (!hasOtherPermission(sender, command)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (forwardPremiumCommand(sender, args[0])) {
|
||||
return;
|
||||
}
|
||||
|
||||
//todo: load async
|
||||
StoredProfile profile = plugin.getCore().getStorage().loadProfile(args[0]);
|
||||
if (profile == null) {
|
||||
plugin.getCore().sendLocaleMessage("player-unknown", sender);
|
||||
return;
|
||||
}
|
||||
|
||||
if (profile.isPremium()) {
|
||||
plugin.getCore().sendLocaleMessage("already-exists-other", sender);
|
||||
} else {
|
||||
//todo: resolve uuid
|
||||
profile.setPremium(true);
|
||||
plugin.getScheduler().runAsync(() -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
plugin.getServer().getPluginManager().callEvent(
|
||||
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_OTHER));
|
||||
});
|
||||
|
||||
plugin.getCore().sendLocaleMessage("add-premium-other", sender);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean forwardPremiumCommand(CommandSender sender, String target) {
|
||||
return forwardProxyCommand(sender, target, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.github.games647.fastlogin.bukkit.command;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
|
||||
import com.github.games647.fastlogin.core.message.ChannelMessage;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.messaging.PluginMessageRecipient;
|
||||
|
||||
public abstract class ToggleCommand implements CommandExecutor {
|
||||
|
||||
protected final FastLoginBukkit plugin;
|
||||
|
||||
public ToggleCommand(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
protected boolean hasOtherPermission(CommandSender sender, Command cmd) {
|
||||
if (!sender.hasPermission(cmd.getPermission() + ".other")) {
|
||||
plugin.getCore().sendLocaleMessage("no-permission", sender);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean forwardProxyCommand(CommandSender sender, String target, boolean activate) {
|
||||
if (plugin.getProxyManager().isEnabled()) {
|
||||
sendProxyActivateMessage(sender, target, activate);
|
||||
plugin.getCore().sendLocaleMessage("wait-on-proxy", sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isConsole(CommandSender sender) {
|
||||
if (sender instanceof Player) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//console or command block
|
||||
sender.sendMessage(plugin.getCore().getMessage("no-console"));
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void sendProxyActivateMessage(CommandSender invoker, String target, boolean activate) {
|
||||
if (invoker instanceof PluginMessageRecipient) {
|
||||
ChannelMessage message = new ChangePremiumMessage(target, activate, true);
|
||||
plugin.getProxyManager().sendPluginMessage((PluginMessageRecipient) invoker, message);
|
||||
} else {
|
||||
Optional<? extends Player> optPlayer = Bukkit.getServer().getOnlinePlayers().stream().findFirst();
|
||||
if (!optPlayer.isPresent()) {
|
||||
plugin.getLog().info("No player online to send a plugin message to the proxy");
|
||||
return;
|
||||
}
|
||||
|
||||
Player sender = optPlayer.get();
|
||||
ChannelMessage message = new ChangePremiumMessage(target, activate, false);
|
||||
plugin.getProxyManager().sendPluginMessage(sender, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.github.games647.fastlogin.bukkit.event;
|
||||
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.auth.LoginSession;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
public class BukkitFastLoginAutoLoginEvent extends Event implements FastLoginAutoLoginEvent, Cancellable {
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
private final LoginSession session;
|
||||
private final StoredProfile profile;
|
||||
private boolean cancelled;
|
||||
|
||||
public BukkitFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
|
||||
super(true);
|
||||
|
||||
this.session = session;
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled) {
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.github.games647.fastlogin.bukkit.event;
|
||||
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.auth.LoginSource;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
public class BukkitFastLoginPreLoginEvent extends Event implements FastLoginPreLoginEvent {
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
private final String username;
|
||||
private final LoginSource source;
|
||||
private final StoredProfile profile;
|
||||
|
||||
public BukkitFastLoginPreLoginEvent(String username, LoginSource source, StoredProfile profile) {
|
||||
super(true);
|
||||
|
||||
this.username = username;
|
||||
this.source = source;
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginSource getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.github.games647.fastlogin.bukkit.event;
|
||||
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
public class BukkitFastLoginPremiumToggleEvent extends Event implements FastLoginPremiumToggleEvent {
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
private final StoredProfile profile;
|
||||
private final PremiumToggleReason reason;
|
||||
|
||||
public BukkitFastLoginPremiumToggleEvent(StoredProfile profile, PremiumToggleReason reason) {
|
||||
super(true);
|
||||
this.profile = profile;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PremiumToggleReason getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.github.games647.fastlogin.bukkit.hook;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import fr.xephi.authme.api.v3.AuthMeApi;
|
||||
import fr.xephi.authme.events.RestoreSessionEvent;
|
||||
import fr.xephi.authme.process.Management;
|
||||
import fr.xephi.authme.process.register.executors.ApiPasswordRegisterParams;
|
||||
import fr.xephi.authme.process.register.executors.RegistrationMethod;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
/**
|
||||
* GitHub: https://github.com/Xephi/AuthMeReloaded/
|
||||
* <p>
|
||||
* Project page:
|
||||
* <p>
|
||||
* Bukkit: https://dev.bukkit.org/bukkit-plugins/authme-reloaded/
|
||||
* <p>
|
||||
* Spigot: https://www.spigotmc.org/resources/authme-reloaded.6269/
|
||||
*/
|
||||
public class AuthMeHook implements AuthPlugin<Player>, Listener {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
private final AuthMeApi authmeAPI;
|
||||
private Management authmeManagement;
|
||||
|
||||
protected AuthMeHook(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
this.authmeAPI = AuthMeApi.getInstance();
|
||||
|
||||
if (plugin.getConfig().getBoolean("respectIpLimit", false)) {
|
||||
try {
|
||||
Field managementField = this.authmeAPI.getClass().getDeclaredField("management");
|
||||
managementField.setAccessible(true);
|
||||
this.authmeManagement = (Management) managementField.get(this.authmeAPI);
|
||||
} catch (NoSuchFieldException | IllegalAccessException exception) {
|
||||
this.authmeManagement = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onSessionRestore(RestoreSessionEvent restoreSessionEvent) {
|
||||
Player player = restoreSessionEvent.getPlayer();
|
||||
|
||||
StoredProfile session = plugin.getSessionManager().getPlaySession(player.getUniqueId());
|
||||
if (session != null && session.isPremium()) {
|
||||
restoreSessionEvent.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
if (authmeAPI.isAuthenticated(player)) {
|
||||
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
|
||||
return false;
|
||||
}
|
||||
|
||||
//skips registration and login
|
||||
authmeAPI.forceLogin(player);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) {
|
||||
return authmeAPI.isRegistered(playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
//this automatically login the player too
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
//if we have the management - we can trigger register with IP limit checks
|
||||
if (authmeManagement != null) {
|
||||
authmeManagement.performRegister(RegistrationMethod.PASSWORD_REGISTRATION,
|
||||
ApiPasswordRegisterParams.of(player, password, true));
|
||||
} else {
|
||||
authmeAPI.forceRegister(player, password);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.github.games647.fastlogin.bukkit.hook;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import de.st_ddt.crazylogin.CrazyLogin;
|
||||
import de.st_ddt.crazylogin.data.LoginPlayerData;
|
||||
import de.st_ddt.crazylogin.databases.CrazyLoginDataDatabase;
|
||||
import de.st_ddt.crazylogin.listener.PlayerListener;
|
||||
import de.st_ddt.crazylogin.metadata.Authenticated;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* GitHub: https://github.com/ST-DDT/CrazyLogin
|
||||
* <p>
|
||||
* Project page:
|
||||
* <p>
|
||||
* Bukkit: https://dev.bukkit.org/server-mods/crazylogin/
|
||||
*/
|
||||
public class CrazyLoginHook implements AuthPlugin<Player> {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
private final CrazyLogin crazyLoginPlugin;
|
||||
private final PlayerListener playerListener;
|
||||
|
||||
protected CrazyLoginHook(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
|
||||
crazyLoginPlugin = CrazyLogin.getPlugin();
|
||||
playerListener = getListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
//not thread-safe operation
|
||||
Future<Optional<LoginPlayerData>> future = Bukkit.getScheduler().callSyncMethod(plugin, () -> {
|
||||
LoginPlayerData playerData = crazyLoginPlugin.getPlayerData(player);
|
||||
if (playerData != null) {
|
||||
//mark the account as logged in
|
||||
playerData.setLoggedIn(true);
|
||||
|
||||
String ip = player.getAddress().getAddress().getHostAddress();
|
||||
//this should be done after login to restore the inventory, show players, prevent potential memory leaks...
|
||||
//from: https://github.com/ST-DDT/CrazyLogin/blob/master/src/main/java/de/st_ddt/crazylogin/CrazyLogin.java#L1948
|
||||
playerData.resetLoginFails();
|
||||
player.setFireTicks(0);
|
||||
|
||||
if (playerListener != null) {
|
||||
playerListener.removeMovementBlocker(player);
|
||||
playerListener.disableHidenInventory(player);
|
||||
playerListener.disableSaveLogin(player);
|
||||
playerListener.unhidePlayer(player);
|
||||
}
|
||||
|
||||
//loginFailuresPerIP.remove(IP);
|
||||
//illegalCommandUsesPerIP.remove(IP);
|
||||
//tempBans.remove(IP);
|
||||
playerData.addIP(ip);
|
||||
player.setMetadata("Authenticated", new Authenticated(crazyLoginPlugin, player));
|
||||
crazyLoginPlugin.unregisterDynamicHooks();
|
||||
return Optional.of(playerData);
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
});
|
||||
|
||||
try {
|
||||
Optional<LoginPlayerData> result = future.get().filter(LoginPlayerData::isLoggedIn);
|
||||
if (result.isPresent()) {
|
||||
//SQL-Queries should run async
|
||||
crazyLoginPlugin.getCrazyDatabase().saveWithoutPassword(result.get());
|
||||
return true;
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
plugin.getLog().error("Failed to forceLogin player: {}", player, ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) {
|
||||
return crazyLoginPlugin.getPlayerData(playerName) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
CrazyLoginDataDatabase crazyDatabase = crazyLoginPlugin.getCrazyDatabase();
|
||||
|
||||
//this executes a sql query and accesses only thread safe collections so we can run it async
|
||||
LoginPlayerData playerData = crazyLoginPlugin.getPlayerData(player.getName());
|
||||
if (playerData == null) {
|
||||
//create a fake account - this will be saved to the database with the password=FAILEDLOADING
|
||||
//user cannot login with that password unless the admin uses plain text
|
||||
//this automatically marks the player as logged in
|
||||
crazyDatabase.save(new LoginPlayerData(player));
|
||||
return forceLogin(player);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private PlayerListener getListener() {
|
||||
PlayerListener listener;
|
||||
try {
|
||||
listener = (PlayerListener) FieldUtils.readField(crazyLoginPlugin, "playerListener", true);
|
||||
} catch (IllegalAccessException ex) {
|
||||
plugin.getLog().error("Failed to get the listener instance for auto login", ex);
|
||||
listener = null;
|
||||
}
|
||||
|
||||
return listener;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.github.games647.fastlogin.bukkit.hook;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
public class DelayedAuthHook implements Runnable {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public DelayedAuthHook(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
boolean hookFound = isHookFound();
|
||||
if (plugin.getProxyManager().isEnabled()) {
|
||||
plugin.getLog().info("Proxy setting detected. No auth plugin is required");
|
||||
} else if (!hookFound) {
|
||||
plugin.getLog().warn("No auth plugin were found by this plugin "
|
||||
+ "(other plugins could hook into this after the initialization of this plugin)"
|
||||
+ "and BungeeCord (or similar proxies) is deactivated. "
|
||||
+ "Either one or both of the checks have to pass in order to use this plugin");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isHookFound() {
|
||||
return plugin.getCore().getAuthPluginHook() != null || registerHooks();
|
||||
}
|
||||
|
||||
private boolean registerHooks() {
|
||||
AuthPlugin<Player> authPluginHook = getAuthHook();
|
||||
if (authPluginHook == null) {
|
||||
//run this check for exceptions (errors) and not found plugins
|
||||
plugin.getLog().warn("No support offline Auth plugin found. ");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (authPluginHook instanceof Listener) {
|
||||
Bukkit.getPluginManager().registerEvents((Listener) authPluginHook, plugin);
|
||||
}
|
||||
|
||||
if (plugin.getCore().getAuthPluginHook() == null) {
|
||||
plugin.getLog().info("Hooking into auth plugin: {}", authPluginHook.getClass().getSimpleName());
|
||||
plugin.getCore().setAuthPluginHook(authPluginHook);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private AuthPlugin<Player> getAuthHook() {
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Class<? extends AuthPlugin<Player>>> hooks = Arrays.asList(AuthMeHook.class,
|
||||
CrazyLoginHook.class, LogItHook.class, LoginSecurityHook.class,
|
||||
SodionAuthHook.class, UltraAuthHook.class, xAuthHook.class);
|
||||
|
||||
for (Class<? extends AuthPlugin<Player>> clazz : hooks) {
|
||||
String pluginName = clazz.getSimpleName().replace("Hook", "");
|
||||
//uses only member classes which uses AuthPlugin interface (skip interfaces)
|
||||
if (Bukkit.getPluginManager().isPluginEnabled(pluginName)) {
|
||||
//check only for enabled plugins. A single plugin could be disabled by plugin managers
|
||||
return newInstance(clazz);
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
plugin.getLog().error("Couldn't load the auth hook class", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private AuthPlugin<Player> newInstance(Class<? extends AuthPlugin<Player>> clazz)
|
||||
throws ReflectiveOperationException {
|
||||
try {
|
||||
Constructor<? extends AuthPlugin<Player>> cons = clazz.getDeclaredConstructor(FastLoginBukkit.class);
|
||||
return cons.newInstance(plugin);
|
||||
} catch (NoSuchMethodException noMethodEx) {
|
||||
return clazz.getDeclaredConstructor().newInstance();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.github.games647.fastlogin.bukkit.hook;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import io.github.lucaseasedup.logit.CancelledState;
|
||||
import io.github.lucaseasedup.logit.LogItCore;
|
||||
import io.github.lucaseasedup.logit.account.Account;
|
||||
import io.github.lucaseasedup.logit.session.SessionManager;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* GitHub: https://github.com/XziomekX/LogIt
|
||||
* <p>
|
||||
* Project page:
|
||||
* <p>
|
||||
* Bukkit: Unknown
|
||||
* <p>
|
||||
* Spigot: Unknown
|
||||
*/
|
||||
public class LogItHook implements AuthPlugin<Player> {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
protected LogItHook(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
SessionManager sessionManager = LogItCore.getInstance().getSessionManager();
|
||||
if (sessionManager.isSessionAlive(player)) {
|
||||
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
|
||||
return false;
|
||||
}
|
||||
|
||||
return sessionManager.startSession(player) == CancelledState.NOT_CANCELLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) {
|
||||
return LogItCore.getInstance().getAccountManager().isRegistered(playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
Account account = new Account(player.getName());
|
||||
account.changePassword(password);
|
||||
|
||||
Instant now = Instant.now();
|
||||
account.setLastActiveDate(now.getEpochSecond());
|
||||
account.setRegistrationDate(now.getEpochSecond());
|
||||
return LogItCore.getInstance().getAccountManager().insertAccount(account) == CancelledState.NOT_CANCELLED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.github.games647.fastlogin.bukkit.hook;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.lenis0012.bukkit.loginsecurity.LoginSecurity;
|
||||
import com.lenis0012.bukkit.loginsecurity.session.AuthService;
|
||||
import com.lenis0012.bukkit.loginsecurity.session.PlayerSession;
|
||||
import com.lenis0012.bukkit.loginsecurity.session.action.LoginAction;
|
||||
import com.lenis0012.bukkit.loginsecurity.session.action.RegisterAction;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* GitHub: https://github.com/lenis0012/LoginSecurity-2
|
||||
* <p>
|
||||
* Project page:
|
||||
* <p>
|
||||
* Bukkit: https://dev.bukkit.org/bukkit-plugins/loginsecurity/
|
||||
* <p>
|
||||
* Spigot: https://www.spigotmc.org/resources/loginsecurity.19362/
|
||||
*/
|
||||
public class LoginSecurityHook implements AuthPlugin<Player> {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
protected LoginSecurityHook(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
PlayerSession session = LoginSecurity.getSessionManager().getPlayerSession(player);
|
||||
if (session.isAuthorized()) {
|
||||
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
|
||||
return true;
|
||||
}
|
||||
|
||||
return session.isAuthorized()
|
||||
|| session.performAction(new LoginAction(AuthService.PLUGIN, plugin)).isSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) {
|
||||
PlayerSession session = LoginSecurity.getSessionManager().getOfflineSession(playerName);
|
||||
return session.isRegistered();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
PlayerSession session = LoginSecurity.getSessionManager().getPlayerSession(player);
|
||||
return session.performAction(new RegisterAction(AuthService.PLUGIN, plugin, password)).isSuccess();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.github.games647.fastlogin.bukkit.hook;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import org.bukkit.entity.Player;
|
||||
import red.mohist.sodionauth.bukkit.implementation.BukkitPlayer;
|
||||
import red.mohist.sodionauth.core.SodionAuthApi;
|
||||
import red.mohist.sodionauth.core.exception.AuthenticatedException;
|
||||
|
||||
/**
|
||||
* GitHub: https://github.com/Mohist-Community/SodionAuth
|
||||
* <p>
|
||||
* Project page:
|
||||
* <p>
|
||||
* Bukkit: Unknown
|
||||
* <p>
|
||||
* Spigot: Unknown
|
||||
*/
|
||||
public class SodionAuthHook implements AuthPlugin<Player> {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
protected SodionAuthHook(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
try {
|
||||
SodionAuthApi.login(new BukkitPlayer(player));
|
||||
} catch (AuthenticatedException e) {
|
||||
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
try{
|
||||
return SodionAuthApi.register(new BukkitPlayer(player), password);
|
||||
} catch (UnsupportedOperationException e){
|
||||
plugin.getLog().warn("Currently SodionAuth is not accepting forceRegister, " +
|
||||
"It may be caused by unsupported AuthBackend");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) {
|
||||
return SodionAuthApi.isRegistered(playerName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.github.games647.fastlogin.bukkit.hook;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import ultraauth.api.UltraAuthAPI;
|
||||
import ultraauth.main.Main;
|
||||
import ultraauth.managers.PlayerManager;
|
||||
|
||||
/**
|
||||
* Project page:
|
||||
* <p>
|
||||
* Bukkit: https://dev.bukkit.org/bukkit-plugins/ultraauth-aa/
|
||||
* <p>
|
||||
* Spigot: https://www.spigotmc.org/resources/ultraauth.17044/
|
||||
*/
|
||||
public class UltraAuthHook implements AuthPlugin<Player> {
|
||||
|
||||
private final Plugin ultraAuthPlugin = Main.main;
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
protected UltraAuthHook(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
//not thread-safe
|
||||
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(plugin, () -> {
|
||||
if (UltraAuthAPI.isAuthenticated(player)) {
|
||||
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
|
||||
return false;
|
||||
}
|
||||
|
||||
UltraAuthAPI.authenticatedPlayer(player);
|
||||
return UltraAuthAPI.isAuthenticated(player);
|
||||
});
|
||||
|
||||
try {
|
||||
return future.get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
plugin.getLog().error("Failed to forceLogin player: {}", player, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) {
|
||||
return UltraAuthAPI.isRegisterd(playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
UltraAuthAPI.setPlayerPasswordOnline(player, password);
|
||||
//the register method silents any exception so check if our entry was saved
|
||||
return PlayerManager.getInstance().checkPlayerPassword(player, password) && forceLogin(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.github.games647.fastlogin.bukkit.hook;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import de.luricos.bukkit.xAuth.xAuth;
|
||||
import de.luricos.bukkit.xAuth.xAuthPlayer;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* GitHub: https://github.com/LycanDevelopment/xAuth/
|
||||
* <p>
|
||||
* Project page:
|
||||
* <p>
|
||||
* Bukkit: https://dev.bukkit.org/bukkit-plugins/xauth/
|
||||
*/
|
||||
public class xAuthHook implements AuthPlugin<Player> {
|
||||
|
||||
private final xAuth xAuthPlugin = xAuth.getPlugin();
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
protected xAuthHook(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
//not thread-safe
|
||||
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(plugin, () -> {
|
||||
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(player);
|
||||
if (xAuthPlayer != null) {
|
||||
if (xAuthPlayer.isAuthenticated()) {
|
||||
plugin.getLog().warn(ALREADY_AUTHENTICATED, player);
|
||||
return false;
|
||||
}
|
||||
|
||||
//we checked that the player is premium (paid account)
|
||||
xAuthPlayer.setPremium(true);
|
||||
|
||||
//unprotect the inventory, op status...
|
||||
return xAuthPlugin.getPlayerManager().doLogin(xAuthPlayer);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
try {
|
||||
return future.get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
plugin.getLog().error("Failed to forceLogin player: {}", player, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) {
|
||||
//this will load the player if it's not in the cache
|
||||
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(playerName);
|
||||
return xAuthPlayer != null && xAuthPlayer.isRegistered();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, final String password) {
|
||||
//not thread-safe
|
||||
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(xAuthPlugin, () -> {
|
||||
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(player);
|
||||
//this should run async because the plugin executes a sql query, but the method
|
||||
//accesses non thread-safe collections :(
|
||||
return xAuthPlayer != null
|
||||
&& xAuthPlugin.getAuthClass(xAuthPlayer).adminRegister(player.getName(), password, null);
|
||||
|
||||
});
|
||||
|
||||
try {
|
||||
//login in the player after registration
|
||||
return future.get() && forceLogin(player);
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
plugin.getLog().error("Failed to forceRegister player: {}", player, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.ForceLoginTask;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerLoginEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
/**
|
||||
* This listener tells authentication plugins if the player has a premium account and we checked it successfully. So the
|
||||
* plugin can skip authentication.
|
||||
*/
|
||||
public class ConnectionListener implements Listener {
|
||||
|
||||
private static final long DELAY_LOGIN = 20L / 2;
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public ConnectionListener(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
|
||||
removeBlockedStatus(loginEvent.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerJoin(PlayerJoinEvent joinEvent) {
|
||||
Player player = joinEvent.getPlayer();
|
||||
|
||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
// session exists so the player is ready for force login
|
||||
// cases: Paper (firing proxy message before PlayerJoinEvent) or not running proxy and already
|
||||
// having the login session from the login process
|
||||
BukkitLoginSession session = plugin.getSessionManager().getLoginSession(player.getAddress());
|
||||
if (session != null) {
|
||||
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player, session);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, forceLoginTask);
|
||||
}
|
||||
|
||||
plugin.getProxyManager().markJoinEventFired(player);
|
||||
// delay the login process to let auth plugins initialize the player
|
||||
// Magic number however as there is no direct event from those plugins
|
||||
}, DELAY_LOGIN);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent quitEvent) {
|
||||
Player player = quitEvent.getPlayer();
|
||||
|
||||
removeBlockedStatus(player);
|
||||
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
|
||||
plugin.getPremiumPlayers().remove(player.getUniqueId());
|
||||
plugin.getProxyManager().cleanup(player);
|
||||
}
|
||||
|
||||
private void removeBlockedStatus(Player player) {
|
||||
player.removeMetadata(plugin.getName(), plugin);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener;
|
||||
|
||||
import com.destroystokyo.paper.profile.ProfileProperty;
|
||||
import com.github.games647.craftapi.model.skin.Textures;
|
||||
import com.github.games647.fastlogin.bukkit.auth.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
|
||||
import org.bukkit.event.player.AsyncPlayerPreLoginEvent.Result;
|
||||
|
||||
public class PaperPreLoginListener implements Listener {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public PaperPreLoginListener(final FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
//if paper is used - player skin must be set at pre login, otherwise usercache is used
|
||||
//using usercache makes premium name change basically impossible
|
||||
public void onAsyncPlayerPreLogin(AsyncPlayerPreLoginEvent event) {
|
||||
if (event.getLoginResult() != Result.ALLOWED) {
|
||||
return;
|
||||
}
|
||||
|
||||
// event gives us only IP, not the port, so we need to loop through all the sessions
|
||||
for (BukkitLoginSession session : plugin.getSessionManager().getLoginSessions().values()) {
|
||||
if (!event.getName().equals(session.getUsername())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
session.getSkin().ifPresent(skin -> event.getPlayerProfile().setProperty(new ProfileProperty(Textures.KEY,
|
||||
skin.getValue(), skin.getSignature())));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
61
bukkit/src/main/resources/plugin.yml
Normal file
61
bukkit/src/main/resources/plugin.yml
Normal file
@@ -0,0 +1,61 @@
|
||||
# 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
|
||||
# Premium variable
|
||||
- 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 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,43 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.auth.protocollib.EncryptionUtil;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class EncryptionUtilTest {
|
||||
|
||||
@Test
|
||||
public void testVerifyToken() throws Exception {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] token = EncryptionUtil.generateVerifyToken(random);
|
||||
|
||||
assertThat(token, notNullValue());
|
||||
assertThat(token.length, is(4));
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void testDecryptSharedSecret() throws Exception {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testDecryptData() throws Exception {
|
||||
//
|
||||
// }
|
||||
|
||||
// private static SecretKey createNewSharedKey() {
|
||||
// try {
|
||||
// KeyGenerator keygenerator = KeyGenerator.getInstance("AES");
|
||||
// keygenerator.init(128);
|
||||
// return keygenerator.generateKey();
|
||||
// } catch (NoSuchAlgorithmException nosuchalgorithmexception) {
|
||||
// throw new Error(nosuchalgorithmexception);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
BIN
bungee/lib/BungeeAuth-1.4.jar
Normal file
BIN
bungee/lib/BungeeAuth-1.4.jar
Normal file
Binary file not shown.
115
bungee/pom.xml
Normal file
115
bungee/pom.xml
Normal file
@@ -0,0 +1,115 @@
|
||||
<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.4</version>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<shadedArtifactAttached>false</shadedArtifactAttached>
|
||||
<artifactSet>
|
||||
<excludes>
|
||||
<!--Those classes are already present in BungeeCord version-->
|
||||
<exclude>net.md-5:bungeecord-config</exclude>
|
||||
<exclude>com.google.code.gson:gson</exclude>
|
||||
</excludes>
|
||||
</artifactSet>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>com.zaxxer.hikari</pattern>
|
||||
<shadedPattern>fastlogin.hikari</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.slf4j</pattern>
|
||||
<shadedPattern>fastlogin.slf4j</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>codemc-repo</id>
|
||||
<url>https://repo.codemc.io/repository/maven-public/</url>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>nukkitx-repo</id>
|
||||
<url>https://repo.nukkitx.com/maven-snapshots/</url>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>spigotplugins-repo</id>
|
||||
<url>https://maven.gamestrike.de/mvn/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<!--Common plugin component-->
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>fastlogin.core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--BungeeCord with also the part outside the API-->
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-proxy</artifactId>
|
||||
<version>1.16-R0.5-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Bedrock player bridge -->
|
||||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>floodgate-bungee</artifactId>
|
||||
<version>1.0-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>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.xxschrandxx.bca</groupId>
|
||||
<artifactId>BungeeCordAuthenticator</artifactId>
|
||||
<version>0.0.2-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.auth.LoginSession;
|
||||
|
||||
public class BungeeLoginSession extends LoginSession {
|
||||
|
||||
private boolean alreadySaved;
|
||||
private boolean alreadyLogged;
|
||||
|
||||
public BungeeLoginSession(String username, boolean registered, StoredProfile profile) {
|
||||
super(username, registered, profile);
|
||||
}
|
||||
|
||||
public synchronized void setRegistered(boolean registered) {
|
||||
this.registered = registered;
|
||||
}
|
||||
|
||||
public synchronized boolean isAlreadySaved() {
|
||||
return alreadySaved;
|
||||
}
|
||||
|
||||
public synchronized void setAlreadySaved(boolean alreadySaved) {
|
||||
this.alreadySaved = alreadySaved;
|
||||
}
|
||||
|
||||
public synchronized boolean isAlreadyLogged() {
|
||||
return alreadyLogged;
|
||||
}
|
||||
|
||||
public synchronized void setAlreadyLogged(boolean alreadyLogged) {
|
||||
this.alreadyLogged = alreadyLogged;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String toString() {
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"alreadySaved=" + alreadySaved +
|
||||
", alreadyLogged=" + alreadyLogged +
|
||||
", registered=" + registered +
|
||||
"} " + super.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.auth.LoginSource;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.event.PreLoginEvent;
|
||||
|
||||
public class BungeeLoginSource implements LoginSource {
|
||||
|
||||
private final PendingConnection connection;
|
||||
private final PreLoginEvent preLoginEvent;
|
||||
|
||||
public BungeeLoginSource(PendingConnection connection, PreLoginEvent preLoginEvent) {
|
||||
this.connection = connection;
|
||||
this.preLoginEvent = preLoginEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnlineMode() {
|
||||
connection.setOnlineMode(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kick(String message) {
|
||||
preLoginEvent.setCancelled(true);
|
||||
|
||||
if (message == null) {
|
||||
preLoginEvent.setCancelReason(new ComponentBuilder("Kicked").color(ChatColor.WHITE).create());
|
||||
} else {
|
||||
preLoginEvent.setCancelReason(TextComponent.fromLegacyText(message));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getAddress() {
|
||||
return connection.getAddress();
|
||||
}
|
||||
|
||||
public PendingConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"connection=" + connection +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.SessionManager;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
public class BungeeSessionManager extends SessionManager<PlayerDisconnectEvent, PendingConnection, BungeeLoginSession>
|
||||
implements Listener {
|
||||
|
||||
//todo: memory leak on cancelled login event
|
||||
@EventHandler
|
||||
public void onPlayQuit(PlayerDisconnectEvent disconnectEvent) {
|
||||
ProxiedPlayer player = disconnectEvent.getPlayer();
|
||||
UUID playerId = player.getUniqueId();
|
||||
endPlaySession(playerId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.hook.BungeeAuthHook;
|
||||
import com.github.games647.fastlogin.bungee.hook.BungeeCordAuthenticatorHook;
|
||||
import com.github.games647.fastlogin.bungee.listener.ConnectListener;
|
||||
import com.github.games647.fastlogin.bungee.listener.PluginMessageListener;
|
||||
import com.github.games647.fastlogin.core.AsyncScheduler;
|
||||
import com.github.games647.fastlogin.core.CommonUtil;
|
||||
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
|
||||
import com.github.games647.fastlogin.core.message.ChannelMessage;
|
||||
import com.github.games647.fastlogin.core.message.NamespaceKey;
|
||||
import com.github.games647.fastlogin.core.message.SuccessMessage;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.PlatformPlugin;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import java.nio.file.Path;
|
||||
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.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.api.plugin.PluginManager;
|
||||
import net.md_5.bungee.api.scheduler.GroupedThreadFactory;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* BungeeCord version of FastLogin. This plugin keeps track on online mode connections.
|
||||
*/
|
||||
public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSender> {
|
||||
|
||||
private final BungeeSessionManager sessionManager = new BungeeSessionManager();
|
||||
|
||||
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
|
||||
PluginManager pluginManager = getProxy().getPluginManager();
|
||||
boolean floodgateAvail = pluginManager.getPlugin("floodgate") != null;
|
||||
ConnectListener connectListener = new ConnectListener(this, core.getRateLimiter(), floodgateAvail);
|
||||
|
||||
pluginManager.registerListener(this, connectListener);
|
||||
pluginManager.registerListener(this, new PluginMessageListener(this));
|
||||
|
||||
//this is required to listen to incoming messages from the server
|
||||
getProxy().registerChannel(NamespaceKey.getCombined(getName(), ChangePremiumMessage.CHANGE_CHANNEL));
|
||||
getProxy().registerChannel(NamespaceKey.getCombined(getName(), SuccessMessage.SUCCESS_CHANNEL));
|
||||
|
||||
registerHook();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (core != null) {
|
||||
core.close();
|
||||
}
|
||||
}
|
||||
|
||||
public FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> getCore() {
|
||||
return core;
|
||||
}
|
||||
|
||||
private void registerHook() {
|
||||
Plugin BungeeAuth = getProxy().getPluginManager().getPlugin("BungeeAuth");
|
||||
if (BungeeAuth != null) {
|
||||
core.setAuthPluginHook(new BungeeAuthHook());
|
||||
logger.info("Hooked into BungeeAuth");
|
||||
}
|
||||
Plugin BungeeCordAuthenticatorBungee = getProxy().getPluginManager().getPlugin("BungeeCordAuthenticatorBungee");
|
||||
if (BungeeCordAuthenticatorBungee != null) {
|
||||
logger.info("Try to hook into BungeeCordAuthenticatorBungee...");
|
||||
BungeeCordAuthenticatorHook hook = new BungeeCordAuthenticatorHook(BungeeCordAuthenticatorBungee, logger);
|
||||
core.setAuthPluginHook(hook);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendPluginMessage(Server server, ChannelMessage message) {
|
||||
if (server != null) {
|
||||
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
|
||||
message.writeTo(dataOutput);
|
||||
|
||||
NamespaceKey channel = new NamespaceKey(getName(), message.getChannelName());
|
||||
server.sendData(channel.getCombinedName(), dataOutput.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getDescription().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getPluginFolder() {
|
||||
return getDataFolder().toPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLog() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(CommandSender receiver, String message) {
|
||||
receiver.sendMessage(TextComponent.fromLegacyText(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public ThreadFactory getThreadFactory() {
|
||||
return new ThreadFactoryBuilder()
|
||||
.setNameFormat(getName() + " Pool Thread #%1$d")
|
||||
//Hikari create daemons by default
|
||||
.setDaemon(true)
|
||||
.setThreadFactory(new GroupedThreadFactory(this, getName()))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.github.games647.fastlogin.bungee.event;
|
||||
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.auth.LoginSession;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
|
||||
import net.md_5.bungee.api.plugin.Cancellable;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
public class BungeeFastLoginAutoLoginEvent extends Event implements FastLoginAutoLoginEvent, Cancellable {
|
||||
|
||||
private final LoginSession session;
|
||||
private final StoredProfile profile;
|
||||
private boolean cancelled;
|
||||
|
||||
public BungeeFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
|
||||
this.session = session;
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled) {
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.github.games647.fastlogin.bungee.event;
|
||||
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.auth.LoginSource;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
public class BungeeFastLoginPreLoginEvent extends Event implements FastLoginPreLoginEvent {
|
||||
|
||||
private final String username;
|
||||
private final LoginSource source;
|
||||
private final StoredProfile profile;
|
||||
|
||||
public BungeeFastLoginPreLoginEvent(String username, LoginSource source, StoredProfile profile) {
|
||||
this.username = username;
|
||||
this.source = source;
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginSource getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.github.games647.fastlogin.bungee.event;
|
||||
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent;
|
||||
import net.md_5.bungee.api.plugin.Event;
|
||||
|
||||
public class BungeeFastLoginPremiumToggleEvent extends Event implements FastLoginPremiumToggleEvent {
|
||||
|
||||
private final StoredProfile profile;
|
||||
private final PremiumToggleReason reason;
|
||||
|
||||
public BungeeFastLoginPremiumToggleEvent(StoredProfile profile, PremiumToggleReason reason) {
|
||||
this.profile = profile;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoredProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FastLoginPremiumToggleEvent.PremiumToggleReason getReason() {
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
@@ -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,70 @@
|
||||
package com.github.games647.fastlogin.bungee.hook;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import de.xxschrandxx.bca.bungee.BungeeCordAuthenticatorBungee;
|
||||
import de.xxschrandxx.bca.bungee.api.BungeeCordAuthenticatorBungeeAPI;
|
||||
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* GitHub:
|
||||
* https://github.com/xXSchrandXx/SpigotPlugins/tree/master/BungeeCordAuthenticator
|
||||
*
|
||||
* Project page:
|
||||
*
|
||||
* Spigot: https://www.spigotmc.org/resources/bungeecordauthenticator.87669/
|
||||
*/
|
||||
public class BungeeCordAuthenticatorHook implements AuthPlugin<ProxiedPlayer> {
|
||||
|
||||
public final BungeeCordAuthenticatorBungeeAPI api;
|
||||
|
||||
public BungeeCordAuthenticatorHook(Plugin plugin, Logger logger) {
|
||||
BungeeCordAuthenticatorBungee bcab = (BungeeCordAuthenticatorBungee) plugin;
|
||||
api = bcab.getAPI();
|
||||
logger.info("BungeeCordAuthenticatorHook | Hooked successful!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(ProxiedPlayer player) {
|
||||
if (api.isAuthenticated(player)) {
|
||||
return true;
|
||||
} else {
|
||||
try {
|
||||
api.setAuthenticated(player);
|
||||
}
|
||||
catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) {
|
||||
try {
|
||||
return api.getSQL().checkPlayerEntry(playerName);
|
||||
}
|
||||
catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(ProxiedPlayer player, String password) {
|
||||
try {
|
||||
return api.createPlayerEntry(player, password);
|
||||
}
|
||||
catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package com.github.games647.fastlogin.bungee.listener;
|
||||
|
||||
import com.github.games647.craftapi.UUIDAdapter;
|
||||
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
|
||||
import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.bungee.task.AsyncPremiumCheck;
|
||||
import com.github.games647.fastlogin.bungee.task.ForceLoginTask;
|
||||
import com.github.games647.fastlogin.core.auth.RateLimiter;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.auth.LoginSession;
|
||||
import com.google.common.base.Throwables;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.UUID;
|
||||
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.event.LoginEvent;
|
||||
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
|
||||
import net.md_5.bungee.api.event.PreLoginEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.connection.LoginResult;
|
||||
import net.md_5.bungee.connection.LoginResult.Property;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import net.md_5.bungee.event.EventPriority;
|
||||
|
||||
import org.geysermc.floodgate.FloodgateAPI;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Enables online mode logins for specified users and sends plugin message to the Bukkit version of this plugin in
|
||||
* order to clear that the connection is online mode.
|
||||
*/
|
||||
public class ConnectListener implements Listener {
|
||||
|
||||
private static final String UUID_FIELD_NAME = "uniqueId";
|
||||
private static final boolean initialHandlerClazzFound;
|
||||
private static final MethodHandle uniqueIdSetter;
|
||||
|
||||
static {
|
||||
MethodHandle setHandle = null;
|
||||
boolean handlerFound = false;
|
||||
try {
|
||||
Lookup lookup = MethodHandles.lookup();
|
||||
|
||||
Class.forName("net.md_5.bungee.connection.InitialHandler");
|
||||
handlerFound = true;
|
||||
|
||||
Field uuidField = InitialHandler.class.getDeclaredField(UUID_FIELD_NAME);
|
||||
uuidField.setAccessible(true);
|
||||
setHandle = lookup.unreflectSetter(uuidField);
|
||||
} catch (ClassNotFoundException classNotFoundException) {
|
||||
Logger logger = LoggerFactory.getLogger(ConnectListener.class);
|
||||
logger.error(
|
||||
"Cannot find Bungee initial handler; Disabling premium UUID and skin won't work.",
|
||||
classNotFoundException
|
||||
);
|
||||
} catch (ReflectiveOperationException reflectiveOperationException) {
|
||||
reflectiveOperationException.printStackTrace();
|
||||
}
|
||||
|
||||
initialHandlerClazzFound = handlerFound;
|
||||
uniqueIdSetter = setHandle;
|
||||
}
|
||||
|
||||
private final FastLoginBungee plugin;
|
||||
private final RateLimiter rateLimiter;
|
||||
private final Property[] emptyProperties = {};
|
||||
private final boolean floodGateAvailable;
|
||||
|
||||
public ConnectListener(FastLoginBungee plugin, RateLimiter rateLimiter, boolean floodgateAvailable) {
|
||||
this.plugin = plugin;
|
||||
this.rateLimiter = rateLimiter;
|
||||
this.floodGateAvailable = floodgateAvailable;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPreLogin(PreLoginEvent preLoginEvent) {
|
||||
PendingConnection connection = preLoginEvent.getConnection();
|
||||
if (preLoginEvent.isCancelled() || isBedrockPlayer(connection.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rateLimiter.tryAcquire()) {
|
||||
plugin.getLog().warn("Join limit hit - Ignoring player {}", connection);
|
||||
return;
|
||||
}
|
||||
|
||||
String username = connection.getName();
|
||||
plugin.getLog().info("Incoming login request for {} from {}", username, connection.getSocketAddress());
|
||||
|
||||
preLoginEvent.registerIntent(plugin);
|
||||
Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, preLoginEvent, connection, username);
|
||||
plugin.getScheduler().runAsync(asyncPremiumCheck);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onLogin(LoginEvent loginEvent) {
|
||||
if (loginEvent.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//use the login event instead of the post login event in order to send the login success packet to the client
|
||||
//with the offline uuid this makes it possible to set the skin then
|
||||
PendingConnection connection = loginEvent.getConnection();
|
||||
if (connection.isOnlineMode()) {
|
||||
LoginSession session = plugin.getSession().get(connection);
|
||||
|
||||
UUID verifiedUUID = connection.getUniqueId();
|
||||
String verifiedUsername = connection.getName();
|
||||
session.setUuid(verifiedUUID);
|
||||
session.setVerifiedUsername(verifiedUsername);
|
||||
|
||||
StoredProfile playerProfile = session.getProfile();
|
||||
playerProfile.setId(verifiedUUID);
|
||||
|
||||
// bungeecord will do this automatically so override it on disabled option
|
||||
if (uniqueIdSetter != null) {
|
||||
InitialHandler initialHandler = (InitialHandler) connection;
|
||||
|
||||
if (!plugin.getCore().getConfig().get("premiumUuid", true)) {
|
||||
setOfflineId(initialHandler, verifiedUsername);
|
||||
}
|
||||
|
||||
if (!plugin.getCore().getConfig().get("forwardSkin", true)) {
|
||||
// this is null on offline mode
|
||||
LoginResult loginProfile = initialHandler.getLoginProfile();
|
||||
loginProfile.setProperties(emptyProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setOfflineId(InitialHandler connection, String username) {
|
||||
try {
|
||||
final UUID oldPremiumId = connection.getUniqueId();
|
||||
final UUID offlineUUID = UUIDAdapter.generateOfflineId(username);
|
||||
|
||||
// BungeeCord only allows setting the UUID in PreLogin events and before requesting online mode
|
||||
// However if online mode is requested, it will override previous values
|
||||
// So we have to do it with reflection
|
||||
uniqueIdSetter.invokeExact(connection, offlineUUID);
|
||||
|
||||
String format = "Overridden UUID from {} to {} (based of {}) on {}";
|
||||
plugin.getLog().info(format, oldPremiumId, offlineUUID, username, connection);
|
||||
} catch (Exception ex) {
|
||||
plugin.getLog().error("Failed to set offline uuid of {}", username, ex);
|
||||
} catch (Throwable throwable) {
|
||||
// throw remaining exceptions like outofmemory that we shouldn't handle ourself
|
||||
Throwables.throwIfUnchecked(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(ServerConnectedEvent serverConnectedEvent) {
|
||||
ProxiedPlayer player = serverConnectedEvent.getPlayer();
|
||||
Server server = serverConnectedEvent.getServer();
|
||||
|
||||
BungeeLoginSession session = plugin.getSession().get(player.getPendingConnection());
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// delay sending force command, because Paper will process the login event asynchronously
|
||||
// In this case it means that the force command (plugin message) is already received and processed while
|
||||
// player is still in the login phase and reported to be offline.
|
||||
Runnable loginTask = new ForceLoginTask(plugin.getCore(), player, server, session);
|
||||
plugin.getScheduler().runAsync(loginTask);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onDisconnect(PlayerDisconnectEvent disconnectEvent) {
|
||||
ProxiedPlayer player = disconnectEvent.getPlayer();
|
||||
plugin.getSession().remove(player.getPendingConnection());
|
||||
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
private boolean isBedrockPlayer(UUID correctedUUID) {
|
||||
// Floodgate will set a correct UUID at the beginning of the PreLoginEvent
|
||||
// and will cancel the online mode login for those players
|
||||
// Therefore we just ignore those
|
||||
return floodGateAvailable && FloodgateAPI.isBedrockPlayer(correctedUUID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
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.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
|
||||
import com.github.games647.fastlogin.core.message.NamespaceKey;
|
||||
import com.github.games647.fastlogin.core.message.SuccessMessage;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
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 forPlayer, String channel, byte[] data) {
|
||||
FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core = plugin.getCore();
|
||||
|
||||
ByteArrayDataInput dataInput = ByteStreams.newDataInput(data);
|
||||
if (successChannel.equals(channel)) {
|
||||
onSuccessMessage(forPlayer);
|
||||
} else if (changeChannel.equals(channel)) {
|
||||
ChangePremiumMessage changeMessage = new ChangePremiumMessage();
|
||||
changeMessage.readFrom(dataInput);
|
||||
|
||||
String playerName = changeMessage.getPlayerName();
|
||||
boolean isSourceInvoker = changeMessage.isSourceInvoker();
|
||||
if (changeMessage.shouldEnable()) {
|
||||
if (playerName.equals(forPlayer.getName()) && plugin.getCore().getConfig().get("premium-warning", true)
|
||||
&& !core.getPendingConfirms().contains(forPlayer.getUniqueId())) {
|
||||
String message = core.getMessage("premium-warning");
|
||||
forPlayer.sendMessage(TextComponent.fromLegacyText(message));
|
||||
core.getPendingConfirms().add(forPlayer.getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
core.getPendingConfirms().remove(forPlayer.getUniqueId());
|
||||
Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, true, isSourceInvoker);
|
||||
plugin.getScheduler().runAsync(task);
|
||||
} else {
|
||||
Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, false, isSourceInvoker);
|
||||
plugin.getScheduler().runAsync(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void 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()) {
|
||||
playerProfile.setPremium(true);
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
loginSession.setAlreadySaved(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.github.games647.fastlogin.bungee.task;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
|
||||
import com.github.games647.fastlogin.bungee.BungeeLoginSource;
|
||||
import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.bungee.event.BungeeFastLoginPreLoginEvent;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.auth.JoinManagement;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.PreLoginEvent;
|
||||
|
||||
public class AsyncPremiumCheck extends JoinManagement<ProxiedPlayer, CommandSender, BungeeLoginSource>
|
||||
implements Runnable {
|
||||
|
||||
private final FastLoginBungee plugin;
|
||||
private final PreLoginEvent preLoginEvent;
|
||||
|
||||
private final String username;
|
||||
private final PendingConnection connection;
|
||||
|
||||
public AsyncPremiumCheck(FastLoginBungee plugin, PreLoginEvent preLoginEvent, PendingConnection connection,
|
||||
String username) {
|
||||
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
|
||||
|
||||
this.plugin = plugin;
|
||||
this.preLoginEvent = preLoginEvent;
|
||||
this.connection = connection;
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.getSession().remove(connection);
|
||||
|
||||
try {
|
||||
super.onLogin(username, new BungeeLoginSource(connection, preLoginEvent));
|
||||
} finally {
|
||||
preLoginEvent.completeIntent(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, BungeeLoginSource source,
|
||||
StoredProfile profile) {
|
||||
return plugin.getProxy().getPluginManager()
|
||||
.callEvent(new BungeeFastLoginPreLoginEvent(username, source, profile));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestPremiumLogin(BungeeLoginSource source, StoredProfile profile,
|
||||
String username, boolean registered) {
|
||||
source.setOnlineMode();
|
||||
plugin.getSession().put(source.getConnection(), new BungeeLoginSession(username, registered, profile));
|
||||
|
||||
String ip = source.getAddress().getAddress().getHostAddress();
|
||||
plugin.getCore().getPendingLogin().put(ip + username, new Object());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startCrackedSession(BungeeLoginSource source, StoredProfile profile, String username) {
|
||||
plugin.getSession().put(source.getConnection(), new BungeeLoginSession(username, false, profile));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.github.games647.fastlogin.bungee.task;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.bungee.event.BungeeFastLoginPremiumToggleEvent;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent.PremiumToggleReason;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
public class AsyncToggleMessage implements Runnable {
|
||||
|
||||
private final FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core;
|
||||
private final ProxiedPlayer sender;
|
||||
private final String targetPlayer;
|
||||
private final boolean toPremium;
|
||||
private final boolean isPlayerSender;
|
||||
|
||||
public AsyncToggleMessage(FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core,
|
||||
ProxiedPlayer sender, String playerName, boolean toPremium, boolean playerSender) {
|
||||
this.core = core;
|
||||
this.sender = sender;
|
||||
this.targetPlayer = playerName;
|
||||
this.toPremium = toPremium;
|
||||
this.isPlayerSender = playerSender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (toPremium) {
|
||||
activatePremium();
|
||||
} else {
|
||||
turnOffPremium();
|
||||
}
|
||||
}
|
||||
|
||||
private void turnOffPremium() {
|
||||
StoredProfile playerProfile = core.getStorage().loadProfile(targetPlayer);
|
||||
//existing player is already cracked
|
||||
if (playerProfile.isSaved() && !playerProfile.isPremium()) {
|
||||
sendMessage("not-premium");
|
||||
return;
|
||||
}
|
||||
|
||||
playerProfile.setPremium(false);
|
||||
playerProfile.setId(null);
|
||||
core.getStorage().save(playerProfile);
|
||||
PremiumToggleReason reason = (!isPlayerSender || !sender.getName().equalsIgnoreCase(playerProfile.getName())) ?
|
||||
PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF;
|
||||
core.getPlugin().getProxy().getPluginManager().callEvent(
|
||||
new BungeeFastLoginPremiumToggleEvent(playerProfile, reason));
|
||||
sendMessage("remove-premium");
|
||||
}
|
||||
|
||||
private void activatePremium() {
|
||||
StoredProfile playerProfile = core.getStorage().loadProfile(targetPlayer);
|
||||
if (playerProfile.isPremium()) {
|
||||
sendMessage("already-exists");
|
||||
return;
|
||||
}
|
||||
|
||||
playerProfile.setPremium(true);
|
||||
core.getStorage().save(playerProfile);
|
||||
PremiumToggleReason reason = (!isPlayerSender || !sender.getName().equalsIgnoreCase(playerProfile.getName())) ?
|
||||
PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF;
|
||||
core.getPlugin().getProxy().getPluginManager().callEvent(
|
||||
new BungeeFastLoginPremiumToggleEvent(playerProfile, reason));
|
||||
sendMessage("add-premium");
|
||||
}
|
||||
|
||||
private void sendMessage(String localeId) {
|
||||
String message = core.getMessage(localeId);
|
||||
if (isPlayerSender) {
|
||||
sender.sendMessage(TextComponent.fromLegacyText(message));
|
||||
} else {
|
||||
CommandSender console = ProxyServer.getInstance().getConsole();
|
||||
console.sendMessage(TextComponent.fromLegacyText(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.github.games647.fastlogin.bungee.task;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
|
||||
import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.bungee.event.BungeeFastLoginAutoLoginEvent;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.message.ChannelMessage;
|
||||
import com.github.games647.fastlogin.core.message.LoginActionMessage;
|
||||
import com.github.games647.fastlogin.core.message.LoginActionMessage.Type;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.auth.ForceLoginManagement;
|
||||
import com.github.games647.fastlogin.core.auth.LoginSession;
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
|
||||
public class ForceLoginTask
|
||||
extends ForceLoginManagement<ProxiedPlayer, CommandSender, BungeeLoginSession, FastLoginBungee> {
|
||||
|
||||
private final Server server;
|
||||
|
||||
public ForceLoginTask(FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core,
|
||||
ProxiedPlayer player, Server server, BungeeLoginSession session) {
|
||||
super(core, player, session);
|
||||
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.run();
|
||||
|
||||
if (!isOnlineMode()) {
|
||||
session.setAlreadySaved(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(ProxiedPlayer player) {
|
||||
if (session.isAlreadyLogged()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
session.setAlreadyLogged(true);
|
||||
return super.forceLogin(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FastLoginAutoLoginEvent callFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
|
||||
return core.getPlugin().getProxy().getPluginManager()
|
||||
.callEvent(new BungeeFastLoginAutoLoginEvent(session, profile));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(ProxiedPlayer player) {
|
||||
return session.isAlreadyLogged() || super.forceRegister(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onForceActionSuccess(LoginSession session) {
|
||||
//sub channel name
|
||||
Type type = Type.LOGIN;
|
||||
if (session.needsRegistration()) {
|
||||
type = Type.REGISTER;
|
||||
}
|
||||
|
||||
UUID proxyId = UUID.fromString(ProxyServer.getInstance().getConfig().getUuid());
|
||||
ChannelMessage loginMessage = new LoginActionMessage(type, player.getName(), proxyId);
|
||||
|
||||
core.getPlugin().sendPluginMessage(server, loginMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ProxiedPlayer player) {
|
||||
return player.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline(ProxiedPlayer player) {
|
||||
return player.isConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnlineMode() {
|
||||
return player.getPendingConnection().isOnlineMode();
|
||||
}
|
||||
}
|
||||
17
bungee/src/main/resources/bungee.yml
Normal file
17
bungee/src/main/resources/bungee.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
# 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
|
||||
- BungeeCordAuthenticatorBungee
|
||||
|
||||
description: |
|
||||
${project.description}
|
||||
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>4.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<!--Logging framework implements slf4j which is required by hikari-->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-jdk14</artifactId>
|
||||
<version>1.7.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,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,20 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
public enum PremiumStatus {
|
||||
|
||||
PREMIUM("Premium"),
|
||||
|
||||
CRACKED("Cracked"),
|
||||
|
||||
UNKNOWN("Unknown");
|
||||
|
||||
private final String readableName;
|
||||
|
||||
PremiumStatus(String readableName) {
|
||||
this.readableName = readableName;
|
||||
}
|
||||
|
||||
public String getReadableName() {
|
||||
return readableName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import com.github.games647.fastlogin.core.auth.LoginSession;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.google.common.collect.MapMaker;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* Manages player connection sessions. Login sessions that are only valid through the login process and play
|
||||
* sessions that hold the stored profile object after the login process is finished and until the player leaves the
|
||||
* server (Spigot) or proxy (BungeeCord).
|
||||
*
|
||||
* @param <C> connection object
|
||||
* @param <S> platform dependent login session
|
||||
*/
|
||||
public abstract class SessionManager<E, C, S extends LoginSession> {
|
||||
|
||||
// 1 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang)
|
||||
// these login sessions are only meant for during the login process not be used after
|
||||
private final ConcurrentMap<C, S> loginSessions = CommonUtil.buildCache(1, 0);
|
||||
private final ConcurrentMap<UUID, StoredProfile> playSessions = new MapMaker().makeMap();
|
||||
|
||||
public void startLoginSession(C connectionId, S session) {
|
||||
loginSessions.put(connectionId, session);
|
||||
}
|
||||
|
||||
public S getLoginSession(C connectionId) {
|
||||
return loginSessions.get(connectionId);
|
||||
}
|
||||
|
||||
public void endLoginSession(C connectionId) {
|
||||
loginSessions.remove(connectionId);
|
||||
}
|
||||
|
||||
public ConcurrentMap<C, S> getLoginSessions() {
|
||||
return loginSessions;
|
||||
}
|
||||
|
||||
public S promoteSession(C connectionId, UUID playerId) {
|
||||
S loginSession = loginSessions.remove(connectionId);
|
||||
StoredProfile profile = loginSession.getProfile();
|
||||
playSessions.put(playerId, profile);
|
||||
return loginSession;
|
||||
}
|
||||
|
||||
public StoredProfile getPlaySession(UUID playerId) {
|
||||
return playSessions.get(playerId);
|
||||
}
|
||||
|
||||
public void endPlaySession(UUID playerId) {
|
||||
playSessions.remove(playerId);
|
||||
}
|
||||
|
||||
public abstract void onPlayQuit(E quitEvent);
|
||||
|
||||
public void clear() {
|
||||
loginSessions.clear();
|
||||
playSessions.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.github.games647.fastlogin.core.auth;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.PlatformPlugin;
|
||||
import com.github.games647.fastlogin.core.storage.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.storage.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)) {
|
||||
core.getPlugin().getLog().info("Player {} disconnected", player);
|
||||
return;
|
||||
}
|
||||
|
||||
if (session == null) {
|
||||
core.getPlugin().getLog().info("No valid session found for {}", player);
|
||||
return;
|
||||
}
|
||||
|
||||
AuthStorage storage = core.getStorage();
|
||||
StoredProfile playerProfile = session.getProfile();
|
||||
try {
|
||||
if (isOnlineMode()) {
|
||||
//premium player
|
||||
AuthPlugin<P> authPlugin = core.getAuthPluginHook();
|
||||
if (authPlugin == null) {
|
||||
//maybe only bungeecord plugin
|
||||
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,115 @@
|
||||
package com.github.games647.fastlogin.core.auth;
|
||||
|
||||
import com.github.games647.craftapi.model.Profile;
|
||||
import com.github.games647.craftapi.resolver.RateLimitException;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.storage.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) {
|
||||
core.getPlugin().getLog().info("Handling player {}", username);
|
||||
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()) {
|
||||
core.getPlugin().getLog().info("Requesting premium login for registered player: {}", username);
|
||||
requestPremiumLogin(source, profile, username, true);
|
||||
} else {
|
||||
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 startCrackedSession(S source, StoredProfile profile, String username);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.github.games647.fastlogin.core.auth;
|
||||
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class LoginSession {
|
||||
|
||||
private final StoredProfile profile;
|
||||
|
||||
private String requestUsername;
|
||||
private String username;
|
||||
private UUID uuid;
|
||||
|
||||
protected boolean registered;
|
||||
|
||||
public LoginSession(String requestUsername, boolean registered, StoredProfile profile) {
|
||||
this.requestUsername = requestUsername;
|
||||
this.username = requestUsername;
|
||||
|
||||
this.registered = registered;
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
public String getRequestUsername() {
|
||||
return requestUsername;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public synchronized void setVerifiedUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return This value is always false if we authenticate the player with a cracked authentication
|
||||
*/
|
||||
public synchronized 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String toString() {
|
||||
return Objects.toStringHelper(this)
|
||||
.add("profile", profile)
|
||||
.add("requestUsername", requestUsername)
|
||||
.add("username", username)
|
||||
.add("uuid", uuid)
|
||||
.add("registered", registered)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.github.games647.fastlogin.core.auth;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public interface LoginSource {
|
||||
|
||||
void setOnlineMode() throws Exception;
|
||||
|
||||
void kick(String message) throws Exception;
|
||||
|
||||
InetSocketAddress getAddress();
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.github.games647.fastlogin.core.auth;
|
||||
|
||||
/**
|
||||
* Limit the number of requests with a maximum size. Each requests expires after the specified time making it available
|
||||
* for another request.
|
||||
*/
|
||||
public class RateLimiter {
|
||||
|
||||
private final long[] requests;
|
||||
private final long expireTime;
|
||||
private int position;
|
||||
|
||||
public RateLimiter(int maxLimit, long expireTime) {
|
||||
this.requests = new long[maxLimit];
|
||||
this.expireTime = expireTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask if access is allowed. If so register the request.
|
||||
*
|
||||
* @return true if allowed - false otherwise without any side effects
|
||||
*/
|
||||
public boolean tryAcquire() {
|
||||
// current time millis is not monotonic - it can jump back depending on user choice or NTP
|
||||
long now = System.nanoTime() / 1_000_000;
|
||||
|
||||
// after this the request should be expired
|
||||
long toBeExpired = now - expireTime;
|
||||
synchronized (this) {
|
||||
// having synchronized will limit the amount of concurrency a lot
|
||||
long oldest = requests[position];
|
||||
if (oldest < toBeExpired) {
|
||||
requests[position] = now;
|
||||
position = (position + 1) % requests.length;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
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> {
|
||||
|
||||
String ALREADY_AUTHENTICATED = "Player {} is already authenticated. Cancelling force login.";
|
||||
|
||||
/**
|
||||
* 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,63 @@
|
||||
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;
|
||||
}
|
||||
|
||||
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,266 @@
|
||||
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.storage.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.CommonUtil;
|
||||
import com.github.games647.fastlogin.core.auth.RateLimiter;
|
||||
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.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
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 static final long MAX_EXPIRE_RATE = 1_000_000;
|
||||
|
||||
private final Map<String, String> localeMessages = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<String, Object> pendingLogin = CommonUtil.buildCache(5, -1);
|
||||
private final Collection<UUID> pendingConfirms = new HashSet<>();
|
||||
private final T plugin;
|
||||
|
||||
private final MojangResolver resolver = new MojangResolver();
|
||||
|
||||
private Configuration config;
|
||||
private AuthStorage storage;
|
||||
private RateLimiter rateLimiter;
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
int maxCon = config.getInt("anti-bot.connections", 200);
|
||||
long expireTime = config.getLong("anti-bot.expire", 5) * 60 * 1_000L;
|
||||
if (expireTime > MAX_EXPIRE_RATE) {
|
||||
expireTime = MAX_EXPIRE_RATE;
|
||||
}
|
||||
|
||||
rateLimiter = new RateLimiter(maxCon, expireTime);
|
||||
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() {
|
||||
if (!checkDriver(config.getString("driver"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HikariConfig databaseConfig = new HikariConfig();
|
||||
databaseConfig.setDriverClassName(config.getString("driver"));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public ConcurrentMap<String, Object> getPendingLogin() {
|
||||
return pendingLogin;
|
||||
}
|
||||
|
||||
public Collection<UUID> getPendingConfirms() {
|
||||
return pendingConfirms;
|
||||
}
|
||||
|
||||
public AuthPlugin<P> getAuthPluginHook() {
|
||||
return authPlugin;
|
||||
}
|
||||
|
||||
public RateLimiter getRateLimiter() {
|
||||
return rateLimiter;
|
||||
}
|
||||
|
||||
public void setAuthPluginHook(AuthPlugin<P> authPlugin) {
|
||||
this.authPlugin = authPlugin;
|
||||
}
|
||||
|
||||
public void saveDefaultFile(String fileName) {
|
||||
Path dataFolder = plugin.getPluginFolder();
|
||||
|
||||
try {
|
||||
Files.createDirectories(dataFolder);
|
||||
|
||||
Path configFile = dataFolder.resolve(fileName);
|
||||
if (Files.notExists(configFile)) {
|
||||
try (InputStream defaultStream = getClass().getClassLoader().getResourceAsStream(fileName)) {
|
||||
Files.copy(Objects.requireNonNull(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,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,10 @@
|
||||
package com.github.games647.fastlogin.core.shared.event;
|
||||
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.auth.LoginSession;
|
||||
|
||||
public interface FastLoginAutoLoginEvent extends FastLoginCancellableEvent {
|
||||
LoginSession getSession();
|
||||
|
||||
StoredProfile getProfile();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.github.games647.fastlogin.core.shared.event;
|
||||
|
||||
public interface FastLoginCancellableEvent {
|
||||
|
||||
boolean isCancelled();
|
||||
|
||||
void setCancelled(boolean cancelled);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.github.games647.fastlogin.core.shared.event;
|
||||
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import com.github.games647.fastlogin.core.auth.LoginSource;
|
||||
|
||||
public interface FastLoginPreLoginEvent {
|
||||
|
||||
String getUsername();
|
||||
|
||||
LoginSource getSource();
|
||||
|
||||
StoredProfile getProfile();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.github.games647.fastlogin.core.shared.event;
|
||||
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
|
||||
public interface FastLoginPremiumToggleEvent {
|
||||
|
||||
StoredProfile getProfile();
|
||||
|
||||
PremiumToggleReason getReason();
|
||||
|
||||
enum PremiumToggleReason {
|
||||
COMMAND_SELF,
|
||||
COMMAND_OTHER
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package com.github.games647.fastlogin.core.storage;
|
||||
|
||||
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.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());
|
||||
|
||||
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);
|
||||
|
||||
//a try to fix https://www.spigotmc.org/threads/fastlogin.101192/page-26#post-1874647
|
||||
// format strings retrieved by the timestamp column to match them from MySQL
|
||||
config.addDataSourceProperty("date_string_format", "yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
// TODO: test first for compatibility
|
||||
// config.addDataSourceProperty("date_precision", "seconds");
|
||||
} else {
|
||||
jdbcUrl += "mysql://" + host + ':' + port + '/' + databasePath;
|
||||
|
||||
// Require SSL on the server if requested in config - this will also verify certificate
|
||||
// Those values are deprecated in favor of sslMode
|
||||
config.addDataSourceProperty("useSSL", useSSL);
|
||||
config.addDataSourceProperty("requireSSL", useSSL);
|
||||
|
||||
// prefer encrypted if possible
|
||||
config.addDataSourceProperty("sslMode", "PREFERRED");
|
||||
|
||||
// adding paranoid hides hostname, username, version and so
|
||||
// could be useful for hiding server details
|
||||
config.addDataSourceProperty("paranoid", true);
|
||||
|
||||
// enable MySQL specific optimizations
|
||||
// disabled by default - will return the same prepared statement instance
|
||||
config.addDataSourceProperty("cachePrepStmts", true);
|
||||
// default prepStmtCacheSize 25 - amount of cached statements
|
||||
config.addDataSourceProperty("prepStmtCacheSize", 250);
|
||||
// default prepStmtCacheSqlLimit 256 - length of SQL
|
||||
config.addDataSourceProperty("prepStmtCacheSqlLimit", 2048);
|
||||
// default false - available in newer versions caches the statements server-side
|
||||
config.addDataSourceProperty("useServerPrepStmts", true);
|
||||
// default false - prefer use of local values for autocommit and
|
||||
// transaction isolation (alwaysSendSetIsolation) should only be enabled if always use the set* methods
|
||||
// instead of raw SQL
|
||||
// https://forums.mysql.com/read.php?39,626495,626512
|
||||
config.addDataSourceProperty("useLocalSessionState", true);
|
||||
// rewrite batched statements to a single statement, adding them behind each other
|
||||
// only useful for addBatch statements and inserts
|
||||
config.addDataSourceProperty("rewriteBatchedStatements", true);
|
||||
// cache result metadata
|
||||
config.addDataSourceProperty("cacheResultSetMetadata", true);
|
||||
// cache results of show variables and collation per URL
|
||||
config.addDataSourceProperty("cacheServerConfiguration", true);
|
||||
// default false - set auto commit only if not matching
|
||||
config.addDataSourceProperty("elideSetAutoCommits", true);
|
||||
|
||||
// default true - internal timers for idle calculation -> removes System.getCurrentTimeMillis call per query
|
||||
// Some platforms are slow on this and it could affect the throughput about 3% according to MySQL
|
||||
// performance gems presentation
|
||||
// In our case it can be useful to see the time in error messages
|
||||
// config.addDataSourceProperty("maintainTimeStats", false);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
playerProfile.getSaveLock().lock();
|
||||
try {
|
||||
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.next()) {
|
||||
playerProfile.setRowId(generatedKeys.getInt(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
playerProfile.getSaveLock().unlock();
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
core.getPlugin().getLog().error("Failed to save playerProfile {}", playerProfile, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
dataSource.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.github.games647.fastlogin.core.storage;
|
||||
|
||||
import com.github.games647.craftapi.model.Profile;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class StoredProfile extends Profile {
|
||||
|
||||
private long rowId;
|
||||
private final ReentrantLock saveLock = new ReentrantLock();
|
||||
|
||||
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 ReentrantLock getSaveLock() {
|
||||
return saveLock;
|
||||
}
|
||||
|
||||
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 boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof StoredProfile)) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
StoredProfile that = (StoredProfile) o;
|
||||
return rowId == that.rowId && premium == that.premium
|
||||
&& Objects.equals(lastIp, that.lastIp) && lastLogin.equals(that.lastLogin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), rowId, premium, lastIp, lastLogin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String toString() {
|
||||
return this.getClass().getSimpleName() + '{' +
|
||||
"rowId=" + rowId +
|
||||
", premium=" + premium +
|
||||
", lastIp='" + lastIp + '\'' +
|
||||
", lastLogin=" + lastLogin +
|
||||
"} " + super.toString();
|
||||
}
|
||||
}
|
||||
222
core/src/main/resources/config.yml
Normal file
222
core/src/main/resources/config.yml
Normal file
@@ -0,0 +1,222 @@
|
||||
# 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/main/core/src/main/resources/config.yml
|
||||
|
||||
# This a **very** simple anti bot protection. Recommendation is to use a a dedicated program to approach this
|
||||
# problem. Low level firewalls like uwf (or iptables direct) are more efficient than a Minecraft plugin. TCP reverse
|
||||
# proxies could also be used and offload some work even to different host.
|
||||
#
|
||||
# The settings wil limit how many connections this plugin will handle. After hitting this limit. FastLogin will
|
||||
# completely ignore incoming connections. Effectively there will be no database requests and network requests.
|
||||
# Therefore auto logins won't be possible.
|
||||
anti-bot:
|
||||
# Image the following like bucket. The following is total amount that is allowed in this bucket, while expire
|
||||
# means how long it takes for every entry to expire.
|
||||
# Total number of connections
|
||||
connections: 200
|
||||
# Amount of minutes after the first connection will expire and made available
|
||||
expire: 5
|
||||
|
||||
# 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
|
||||
|
||||
# Should FastLogin respect per IP limit of registrations (e.g. in AuthMe)
|
||||
# Because most auth plugins do their stuff async - FastLogin will still think the player was registered
|
||||
# To work best - you also need to enable auto-register-unknown
|
||||
#
|
||||
# If set to true - FastLogin will always attempt to register the player, even if the limit is exceeded
|
||||
# It is up to the auth plugin to handle the excessive registration
|
||||
# https://github.com/games647/FastLogin/issues/458
|
||||
respectIpLimit: 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
|
||||
# allowlist.
|
||||
# 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
|
||||
# autoRegister = false
|
||||
# nameChangeCheck = false
|
||||
#
|
||||
# GameProfile logins as cracked until the player invoked the command /premium. Then we could override the existing
|
||||
# database record.
|
||||
#
|
||||
# #### Case 2
|
||||
# autoRegister = false
|
||||
# nameChangeCheck = true
|
||||
#
|
||||
# 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
|
||||
# autoRegister = true
|
||||
# nameChangeCheck = false
|
||||
#
|
||||
# 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
|
||||
# autoRegister = true
|
||||
# nameChangeCheck = 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 use PaperSpigot - FastLogin will always try to set the skin, even if forwardSkin is set to false
|
||||
# It is needed to allow premium name change to work correctly
|
||||
# https://github.com/games647/FastLogin/issues/457
|
||||
#
|
||||
# If you want to use skins for your cracked player, you need an additional plugin like
|
||||
# ChangeSkin, SkinRestorer, ...
|
||||
forwardSkin: true
|
||||
|
||||
# Displays a warning message that this message SHOULD only be invoked by
|
||||
# users who actually are the owner of this account. So not by cracked players
|
||||
#
|
||||
# If they still want to invoke the command, they have to invoke /premium again
|
||||
premium-warning: 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/main/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 cracked player tries to join, who is not namely allowed
|
||||
switch-kick-message: '&4Only paid Minecraft allowed 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'
|
||||
|
||||
# FastLogin attempted to auto register user. The user account is registered to protect it from cracked players
|
||||
# If FastLogin is respecting auth plugin IP limit - the registration may have failed, however the message is still displayed
|
||||
# The password can be used if the mojang servers are down and you still want your premium users to login (PLANNED)
|
||||
auto-register: '&2Tried auto registering with password: &7%password&2. 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'
|
||||
|
||||
# Warning message if a user invoked /premium command
|
||||
premium-warning: '&c&lWARNING: &6This command should &lonly&6 be invoked if you are the owner of this paid Minecraft account
|
||||
Type &a/premium&6 again to confirm'
|
||||
|
||||
# ========= Bungee/Waterfall only ================================
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import com.github.games647.fastlogin.core.auth.RateLimiter;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class RateLimiterTest {
|
||||
|
||||
private static final long THRESHOLD_MILLI = 10;
|
||||
|
||||
/**
|
||||
* Always expired
|
||||
*/
|
||||
@Test
|
||||
public void allowExpire() throws InterruptedException {
|
||||
int size = 3;
|
||||
|
||||
// run twice the size to fill it first and then test it
|
||||
RateLimiter rateLimiter = new RateLimiter(size, 0);
|
||||
for (int i = 0; i < size; i++) {
|
||||
assertTrue("Filling up", rateLimiter.tryAcquire());
|
||||
}
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
Thread.sleep(1);
|
||||
assertTrue("Should be expired", rateLimiter.tryAcquire());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Too many requests
|
||||
*/
|
||||
@Test
|
||||
public void shoudBlock() {
|
||||
int size = 3;
|
||||
|
||||
// fill the size
|
||||
RateLimiter rateLimiter = new RateLimiter(size, TimeUnit.SECONDS.toMillis(30));
|
||||
for (int i = 0; i < size; i++) {
|
||||
assertTrue("Filling up", rateLimiter.tryAcquire());
|
||||
}
|
||||
|
||||
assertFalse("Should be full and no entry should be expired", rateLimiter.tryAcquire());
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocked attempts shouldn't replace existing ones.
|
||||
*/
|
||||
@Test
|
||||
public void blockedNotAdded() throws InterruptedException {
|
||||
// fill the size - 100ms should be reasonable high
|
||||
RateLimiter rateLimiter = new RateLimiter(1, 100);
|
||||
assertTrue("Filling up", rateLimiter.tryAcquire());
|
||||
|
||||
Thread.sleep(50);
|
||||
|
||||
// still is full - should fail
|
||||
assertFalse("Expired too early", rateLimiter.tryAcquire());
|
||||
|
||||
// wait the remaining time and add a threshold, because
|
||||
Thread.sleep(50 + THRESHOLD_MILLI);
|
||||
assertTrue("Request not released", rateLimiter.tryAcquire());
|
||||
}
|
||||
}
|
||||
87
pom.xml
Normal file
87
pom.xml
Normal file
@@ -0,0 +1,87 @@
|
||||
<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>
|
||||
|
||||
<groupId>com.github.games647</groupId>
|
||||
<!--This have to be in lowercase because it's used by plugin.yml-->
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>FastLogin</name>
|
||||
<version>1.11-SNAPSHOT</version>
|
||||
|
||||
<url>https://www.spigotmc.org/resources/fastlogin.14153/</url>
|
||||
<description>
|
||||
Automatically login premium (paid accounts) player on a offline mode server
|
||||
</description>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<modules>
|
||||
<module>core</module>
|
||||
<module>bukkit</module>
|
||||
<module>bungee</module>
|
||||
</modules>
|
||||
|
||||
<!--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>
|
||||
<!--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>pl.project13.maven</groupId>
|
||||
<artifactId>git-commit-id-plugin</artifactId>
|
||||
<version>4.0.3</version>
|
||||
<configuration>
|
||||
<failOnNoGitDirectory>false</failOnNoGitDirectory>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>get-the-git-infos</id>
|
||||
<goals>
|
||||
<goal>revision</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<!--Replace variables-->
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
Reference in New Issue
Block a user