import * as PlayerModel from '../models/player.js' import { createHash } from 'crypto' import { generateToken, uuidToNoSymboUUID } from '../generator.js' export const authenticate = { method: 'POST', url: '/authserver/authenticate', schema: { 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: async function(req, rep) { this.conf.custom.overridePrehandler('/authserver/authenticate', req, rep) }, handler: async function (req, rep) { 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() }) 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(!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 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 } } const profile = { uuid: uuidToNoSymboUUID(player.uuid), name: player.username, properties: [ { name: "texturs", value: Buffer.from(JSON.stringify(textures)).toString('base64') } ] } new this.models.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: { 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() } }