lsp-yggdrasil/src/routes/authenticate.js

407 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as PlayerModel from '../models/player.js'
import { createHash } from 'crypto'
import { generateToken, uuidToNoSymboUUID } from '../generator.js'
import { getOverrideHandler, getOverridePreHandler } from '../config.js'
import { Token } from '../models/token.js'
export const authenticate = {
method: 'POST',
url: '/authserver/authenticate',
schema: {
summary: "登录",
description: "登陆账号,用于获取 accessToken。如果账号没有绑定 Telegram 则会被禁止登陆。agent.name 必须为 'minecraft'。详情请见 authlib-injector 文档。",
tags: [ "Authserver" ],
body: {
"type": "object",
"properties": {
"username": {
"type": "string"
},
"password": {
"type": "string"
},
"clientToken": {
"type": "string"
},
"requestUser": {
"type": "boolean"
},
"agent": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "integer"
}
}
}
}
},
response: {
200: {
"type": "object",
"properties": {
"accessToken": {
"type": "string"
},
"clientToken": {
"type": "string"
},
"availableProfiles": {
"type": "array",
"items": [PlayerModel.PlayerSeriliazationSchema]
},
"selectedProfile": PlayerModel.PlayerSeriliazationSchema,
"user": PlayerModel.PlayerAccountSerializationSchema
}
}
}
},
preHandler: getOverridePreHandler("/authserver/authenticate"),
handler: getOverrideHandler("/authserver/authenticate") ?? async function (req, rep) {
let { username, password, clientToken, requestUser, agent } = req.body
if( !agent || agent.name.toLowerCase() !== 'minecraft') {
return await rep.code(418).send({
error: "ForbiddenOperationException",
errorMessage: "无效应用名,此服务端仅支援 Minecraft",
cause: "此服务器只支持 agent.name: minecraft"
})
}
const player = await PlayerModel.Player.findOne({ email: username, password: createHash('sha256').update(password).digest().toString('hex').toLowerCase() })
if(!player || !player.permissions.some((it) => {
return it.node === 'login' && it.allowed && (it.duration === 0 || it.startDate + it.duration > Date.now())
})) {
return await rep.code(401).send({
error: "Unauthorized",
errorMessage: "用户名或密码错误",
cause: "用户名或密码错误"
})
}
if(!player.telegramBind || !player.telegramBind.verified) {
return await rep.code(401).send({
error: "Unauthorized",
errorMessage: "未绑定 Telegram 账号或账号验证未通过,登录请求已禁止",
cause: "未绑定 Telegram 账号或账号验证未通,登录请求已禁止"
})
}
if(!clientToken) {
clientToken = createHash('sha256').update( "" + Math.random() * 1.048596).digest().toString('hex')
}
const [token, key] = await generateToken(clientToken, requestUser, agent)
this.log.info(`/authserver/authenticate > 为玩家 ${username} 生成令牌: ${token} | 随机 key = ${key}`)
const account = {
id: uuidToNoSymboUUID(player.uuid),
properties: [
{
preferredLanguage: "zh_CN"
}
]
}
const profile = PlayerModel.getPlayerSerialization(player)
new Token({
uuid: player.uuid,
token: token,
clientToken: clientToken,
expireDate: Date.now() + 1000 * 60 * 60 * 24 * 15,
deadDate: Date.now() + 1000 * 60 * 60 * 24 * 30,
}).save()
return await rep.send({
accessToken: token,
clientToken: clientToken,
availableProfiles: [ profile ],
selectedProfile: profile,
user: account
})
}
}
export const refresh = {
method: 'POST',
url: '/authserver/refresh',
schema: {
summary: "刷新令牌",
description: "可以刷新的令牌仅限令牌处于 'limbo(半吊销状态)'默认为3天有效期3天后进入 'limbo'7天后进入 'dead(失效)'。详情请见 authlib-injector 文档。",
tags: [ "Authserver" ],
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: getOverridePreHandler("/authserver/refresh"),
handler: getOverrideHandler("/authserver/authenticate") ?? async function (req, rep) {
const { accessToken, clientToken, requestUser, selectedProfile } = req.body
const query = {
token: accessToken
}
if(clientToken) {
query.clientToken = clientToken
}
const token = await 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 Token.updateOne({
token: accessToken,
clientToken: clientToken ?? undefined
}, {
$set: {
expireDate: 0,
deadDate: 0
}
})
new 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 PlayerModel.Player.findOne({ uuid })
if(requestUser) {
response.user = {
id: uuidToNoSymboUUID(player.uuid),
properties: [
{
name: "preferredLanguage",
value: "zh_CN"
}
]
}
}
if(selectedProfile) {
response.selectedProfile = PlayerModel.getPlayerSerialization(player)
}
}
return await rep.send(response)
}
}
export const validate = {
method: 'POST',
url: '/authserver/validate',
schema: {
summary: "验证令牌",
description: "验证令牌是否有效,若携带 clientToken 则会检查 clientToken是否正确否则不会检查。详情请见 authlib-injector 文档。",
tags: [ "Authserver" ],
body: {
"type": "object",
"properties": {
"accessToken": {
"type": "string"
},
"clientToken": {
"type": "string",
"optional": true
}
}
},
response: {
204: {
"type": "null"
}
}
},
preHandler: getOverridePreHandler("/authserver/validate"),
handler: getOverrideHandler("/authserver/validate") ?? async function (req, rep) {
const { accessToken, clientToken } = req.body
const query = {
token: accessToken
}
if(clientToken) {
query.clientToken = clientToken
}
const token = await 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: {
summary: "主动吊销令牌",
description: "吊销指定令牌,处理时根据规范永远忽略 clientToken 参数。详情请见 authlib-injector 文档。",
tags: [ "Authserver" ],
body: {
"type": "object",
"properties": {
"accessToken": {
"type": "string"
},
"clientToken": {
"type": "string",
"optional": true
}
}
},
response: {
204: {
"type": "null"
}
}
}
,
preHandler: getOverridePreHandler("/authserver/invalidate"),
handler: getOverrideHandler("/authserver/authenticate") ?? async function (req, rep) {
const { accessToken } = req.body
const { modifiedCount } = await 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: {
summary: "登出",
description: "登出账号,吊销所有令牌。详情请见 authlib-injector 文档。",
tags: [ "Authserver" ],
body: {
"type": "object",
"properties": {
"username": {
"type": "string"
},
"password": {
"type": "string",
"optional": true
}
}
},
response: {
204: {
"type": "null"
}
}
},
preHandler: getOverridePreHandler("/authserver/signout"),
handler: getOverrideHandler("/authserver/signout") ?? async function (req, rep) {
const { username, password } = req.body
const player = await PlayerModel.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 Token.deleteMany({
uuid: player.uuid
})
rep.code(204).send()
}
}