Compare commits
No commits in common. "99d304899d4d13969fc44669e1b283effd9fe032" and "cf9f9bea7cbb0df323d9a20b50a01ccccae1b4c9" have entirely different histories.
99d304899d
...
cf9f9bea7c
36
README.MD
36
README.MD
@ -4,41 +4,7 @@
|
|||||||
|
|
||||||
[](https://996.icu)
|
[](https://996.icu)
|
||||||
[](https://github.com/996icu/996.ICU/blob/master/LICENSE)
|
[](https://github.com/996icu/996.ICU/blob/master/LICENSE)
|
||||||
[](https://ci.186526.xyz/Lama3L9R/lsp-yggdrasil)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
TODO:
|
## WIP
|
||||||
|
|
||||||
- [ ] Basic API
|
|
||||||
+ [x] /authserver
|
|
||||||
+ [ ] /sessionserver
|
|
||||||
+ [ ] /api
|
|
||||||
- [ ] Advanced API
|
|
||||||
- [ ] Skin uploading & security checks for texture
|
|
||||||
+ [ ] Texture RSAsigning
|
|
||||||
- [ ] S3 Storage backend
|
|
||||||
- [ ] Server status
|
|
||||||
- [ ] Authlib meta
|
|
||||||
- [ ] Telegram Bot
|
|
||||||
- [ ] Unit test
|
|
||||||
+ [ ] API
|
|
||||||
- [ ] /authserver
|
|
||||||
- [ ] /sessionserver
|
|
||||||
- [ ] /api
|
|
||||||
- [ ] Advanced API
|
|
||||||
+ [ ] Utils
|
|
||||||
|
|
||||||
## WIP
|
|
||||||
|
|
||||||
推荐 Node.js 版本:`16.15.0 LTS (Latest LTS)`
|
|
||||||
|
|
||||||
*因为魔法,在 Node14 也能跑*
|
|
||||||
|
|
||||||
构建方法
|
|
||||||
```
|
|
||||||
$ yarn install && node ./build.js
|
|
||||||
```
|
|
||||||
|
|
||||||
**记得手动复制`config.js`!放在和主程序一个目录就行,构建完了看`./production`**
|
|
||||||
**因为我设置了项目为`module`,构建出来的是`cjs`因此不能直接在项目目录下跑,扔到别的地方跑**
|
|
@ -29,10 +29,6 @@ export const server = fastify({
|
|||||||
|
|
||||||
config.custom.preRouting(server)
|
config.custom.preRouting(server)
|
||||||
server.route(AuthenticateRoutings.authenticate)
|
server.route(AuthenticateRoutings.authenticate)
|
||||||
server.route(AuthenticateRoutings.refresh)
|
|
||||||
server.route(AuthenticateRoutings.validate)
|
|
||||||
server.route(AuthenticateRoutings.invalidate)
|
|
||||||
server.route(AuthenticateRoutings.signout)
|
|
||||||
config.custom.postRouting(server)
|
config.custom.postRouting(server)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -4,7 +4,7 @@ const { Schema } = mongoose
|
|||||||
export const TokenSchema = new Schema({
|
export const TokenSchema = new Schema({
|
||||||
uuid: String,
|
uuid: String,
|
||||||
token: String,
|
token: String,
|
||||||
clientToken: String,
|
|
||||||
expireDate: Number,
|
expireDate: Number,
|
||||||
deadDate: Number,
|
deadDate: Number,
|
||||||
|
state: String, // alive, linbo, dead
|
||||||
})
|
})
|
@ -55,19 +55,10 @@ export const authenticate = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
preHandler: async function(req, rep) {
|
preHandler: async function(req, rep) {
|
||||||
this.conf.custom.overridePrehandler('/authserver/authenticate', req, rep)
|
this.conf.custom.overridePrehandler('/authserver/authenticate')
|
||||||
},
|
},
|
||||||
handler: async function (req, rep) {
|
handler: async function (req, rep) {
|
||||||
let { username, password, clientToken, requestUser, agent } = req.body
|
let { username, password, clientToken, requestUser, agent } = req.body
|
||||||
|
|
||||||
if(!username || !password || !agent || agent.name.toLowerCase() !== 'minecraft') {
|
|
||||||
rep.code(418).send({
|
|
||||||
error: "ForbiddenOperationException",
|
|
||||||
errorMessage: "无效应用名,此服务端仅支援 Minecraft",
|
|
||||||
cause: "此服务器只支持 agent.name: minecraft"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const player = await this.models.Player.findOne({ email: username, password: createHash('sha256').update(password).digest().toString('hex').toLowerCase() })
|
const player = await this.models.Player.findOne({ email: username, password: createHash('sha256').update(password).digest().toString('hex').toLowerCase() })
|
||||||
if(!player || !player.permissions.some((it) => {
|
if(!player || !player.permissions.some((it) => {
|
||||||
return it.node === 'login' && it.allowed && (it.duration === 0 || it.startDate + it.duration > Date.now())
|
return it.node === 'login' && it.allowed && (it.duration === 0 || it.startDate + it.duration > Date.now())
|
||||||
@ -109,13 +100,6 @@ export const authenticate = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(player.textures.skin && player.textures.skin != 0) { // Must be '!=' if this change to '!==' will never works
|
|
||||||
textures.textures.CAPE = {
|
|
||||||
url: player.textures.cape,
|
|
||||||
metadata
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const profile = {
|
const profile = {
|
||||||
uuid: uuidToNoSymboUUID(player.uuid),
|
uuid: uuidToNoSymboUUID(player.uuid),
|
||||||
name: player.username,
|
name: player.username,
|
||||||
@ -130,9 +114,9 @@ export const authenticate = {
|
|||||||
new this.models.Token({
|
new this.models.Token({
|
||||||
uuid: player.uuid,
|
uuid: player.uuid,
|
||||||
token: token,
|
token: token,
|
||||||
clientToken: clientToken,
|
|
||||||
expireDate: Date.now() + 1000 * 60 * 60 * 24 * 15,
|
expireDate: Date.now() + 1000 * 60 * 60 * 24 * 15,
|
||||||
deadDate: Date.now() + 1000 * 60 * 60 * 24 * 30,
|
deadDate: Date.now() + 1000 * 60 * 60 * 24 * 30,
|
||||||
|
state: 'alive'
|
||||||
}).save()
|
}).save()
|
||||||
|
|
||||||
return await rep.send({
|
return await rep.send({
|
||||||
@ -142,311 +126,5 @@ export const authenticate = {
|
|||||||
selectedProfile: profile,
|
selectedProfile: profile,
|
||||||
user: account
|
user: account
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const refresh = {
|
|
||||||
method: 'POST',
|
|
||||||
url: '/authserver/refresh',
|
|
||||||
schema: {
|
|
||||||
body: {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"accessToken": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"clientToken": {
|
|
||||||
"type": "string",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"requestUser": {
|
|
||||||
"type": "boolean",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"selectedProfile": {
|
|
||||||
"optional": true,
|
|
||||||
...PlayerModel.PlayerSeriliazationSchema
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
response: {
|
|
||||||
200: {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"accessToken": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"clientToken": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"selectedProfile": PlayerModel.PlayerSeriliazationSchema,
|
|
||||||
"user": PlayerModel.PlayerAccountSerializationSchema
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
preHandler: async function(req, rep) {
|
|
||||||
this.conf.custom.overridePrehandler('/authserver/refresh', req, rep)
|
|
||||||
},
|
|
||||||
handler: async function (req, rep) {
|
|
||||||
const { accessToken, clientToken, requestUser, selectedProfile } = req.body
|
|
||||||
|
|
||||||
const query = {
|
|
||||||
token: accessToken
|
|
||||||
}
|
|
||||||
|
|
||||||
if(clientToken) {
|
|
||||||
query.clientToken = clientToken
|
|
||||||
}
|
|
||||||
const token = await this.models.Token.findOne(query)
|
|
||||||
|
|
||||||
if(!token) {
|
|
||||||
return await rep.code(401).send({
|
|
||||||
error: "Unauthorized",
|
|
||||||
errorMessage: "accessToken无效",
|
|
||||||
cause: "accessToken无效"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const { deadDate, uuid } = token
|
|
||||||
|
|
||||||
if(deadDate < Date.now()) {
|
|
||||||
return await rep.code(401).send({
|
|
||||||
error: "Unauthorized",
|
|
||||||
errorMessage: "accessToken无效",
|
|
||||||
cause: "accessToken无效"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const [newToken, key] = generateToken(token.uuid)
|
|
||||||
this.log.info(`/authserver/authenticate > 为玩家 ${token.uuid} 刷新令牌: ${token.uuid} 为 ${newToken} | 随机 key = ${key}`)
|
|
||||||
|
|
||||||
await this.models.Token.updateOne({
|
|
||||||
token: accessToken,
|
|
||||||
clientToken: clientToken ?? undefined
|
|
||||||
}, {
|
|
||||||
$set: {
|
|
||||||
expireDate: 0,
|
|
||||||
deadDate: 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
new this.models.Token({
|
|
||||||
uuid: uuid,
|
|
||||||
token: newToken,
|
|
||||||
clientToken: clientToken ?? token.clientToken,
|
|
||||||
expireDate: Date.now() + 1000 * 60 * 60 * 24 * 15,
|
|
||||||
deadDate: Date.now() + 1000 * 60 * 60 * 24 * 30,
|
|
||||||
}).save()
|
|
||||||
|
|
||||||
const response = {
|
|
||||||
accessToken: newToken,
|
|
||||||
clientToken: clientToken ?? token.clientToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
if(requestUser || selectedProfile) {
|
|
||||||
const player = await this.models.Player.findOne({ uuid })
|
|
||||||
|
|
||||||
if(requestUser) {
|
|
||||||
response.user = {
|
|
||||||
id: uuidToNoSymboUUID(player.uuid),
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
name: "preferredLanguage",
|
|
||||||
value: "zh_CN"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(selectedProfile) {
|
|
||||||
const textures = {
|
|
||||||
timestamp: 0,
|
|
||||||
profileId: uuidToNoSymboUUID(player.uuid),
|
|
||||||
profileName: player.username,
|
|
||||||
textures: { }
|
|
||||||
}
|
|
||||||
|
|
||||||
if(player.textures.skin && player.textures.skin != 0) { // Must be '!=' if this change to '!==' will never works
|
|
||||||
textures.textures.SKIN = {
|
|
||||||
url: player.textures.skin,
|
|
||||||
metadata
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(player.textures.skin && player.textures.skin != 0) { // Must be '!=' if this change to '!==' will never works
|
|
||||||
textures.textures.CAPE = {
|
|
||||||
url: player.textures.cape,
|
|
||||||
metadata
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response.selectedProfile = {
|
|
||||||
uuid: uuidToNoSymboUUID(uuid),
|
|
||||||
name: player.username,
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
name: "texturs",
|
|
||||||
value: Buffer.from(JSON.stringify(textures)).toString('base64')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await rep.send(response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const validate = {
|
|
||||||
method: 'POST',
|
|
||||||
url: '/authserver/validate',
|
|
||||||
schema: {
|
|
||||||
body: {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"accessToken": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"clientToken": {
|
|
||||||
"type": "string",
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
response: {
|
|
||||||
204: {
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
preHandler: async function(req, rep) {
|
|
||||||
this.conf.custom.overridePrehandler('/authserver/validate', req, rep)
|
|
||||||
},
|
|
||||||
handler: async function (req, rep) {
|
|
||||||
const { accessToken, clientToken } = req.body
|
|
||||||
|
|
||||||
const query = {
|
|
||||||
token: accessToken
|
|
||||||
}
|
|
||||||
|
|
||||||
if(clientToken) {
|
|
||||||
query.clientToken = clientToken
|
|
||||||
}
|
|
||||||
const token = await this.models.Token.findOne(query)
|
|
||||||
|
|
||||||
if(!token) {
|
|
||||||
return await rep.code(401).send({
|
|
||||||
error: "Unauthorized",
|
|
||||||
errorMessage: "accessToken无效",
|
|
||||||
cause: "accessToken无效"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const { expireDate } = token
|
|
||||||
|
|
||||||
if(expireDate < Date.now()) {
|
|
||||||
return await rep.code(401).send({
|
|
||||||
error: "Unauthorized",
|
|
||||||
errorMessage: "accessToken无效",
|
|
||||||
cause: "accessToken无效"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return await rep.code(204).send()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const invalidate = {
|
|
||||||
method: 'POST',
|
|
||||||
url: '/authserver/invalidate',
|
|
||||||
schema: {
|
|
||||||
body: {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"accessToken": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"clientToken": {
|
|
||||||
"type": "string",
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
response: {
|
|
||||||
204: {
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
,
|
|
||||||
preHandler: async function(req, rep) {
|
|
||||||
this.conf.custom.overridePrehandler('/authserver/invalidate', req, rep)
|
|
||||||
},
|
|
||||||
handler: async function (req, rep) {
|
|
||||||
const { accessToken } = req.body
|
|
||||||
|
|
||||||
const { modifiedCount } = await this.models.Token.updateOne({
|
|
||||||
token: accessToken
|
|
||||||
}, {
|
|
||||||
$set: {
|
|
||||||
expireDate: 0,
|
|
||||||
deadDate: 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if(modifiedCount === 0) {
|
|
||||||
return await rep.code(401).send({
|
|
||||||
error: "Unauthorized",
|
|
||||||
errorMessage: "accessToken无效",
|
|
||||||
cause: "accessToken无效"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return await rep.code(204).send()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const signout = {
|
|
||||||
method: 'POST',
|
|
||||||
url: '/authserver/signout',
|
|
||||||
schema: {
|
|
||||||
body: {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"username": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"password": {
|
|
||||||
"type": "string",
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
response: {
|
|
||||||
204: {
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
preHandler: async function(req, rep) {
|
|
||||||
this.conf.custom.overridePrehandler('/authserver/logout', req, rep)
|
|
||||||
},
|
|
||||||
handler: async function (req, rep) {
|
|
||||||
const { username, password } = req.body
|
|
||||||
|
|
||||||
const player = await this.models.Player.findOne({ email: username, password: createHash('sha256').update(password).digest().toString('hex').toLowerCase() })
|
|
||||||
if(!player) {
|
|
||||||
return await rep.code(401).send({
|
|
||||||
error: "Unauthorized",
|
|
||||||
errorMessage: "用户名或密码错误",
|
|
||||||
cause: "用户名或密码错误"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.models.Token.deleteMany({
|
|
||||||
uuid: player.uuid
|
|
||||||
})
|
|
||||||
|
|
||||||
rep.code(204).send()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user