This commit is contained in:
		
							
								
								
									
										27
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								src/index.js
									
									
									
									
									
								
							@ -1,6 +1,5 @@
 | 
			
		||||
import { fastify } from 'fastify'
 | 
			
		||||
import { mongoose } from 'mongoose'
 | 
			
		||||
import { registerModels } from './models/index.js';
 | 
			
		||||
import * as Hooks from './hooks.js'
 | 
			
		||||
import * as AuthenticateRoutings from './routes/authenticate.js'
 | 
			
		||||
import * as SessionServerRoutings from './routes/session.js'
 | 
			
		||||
@ -10,6 +9,7 @@ import { config } from './config.js'
 | 
			
		||||
import { readFileSync } from 'fs'
 | 
			
		||||
import { Scenes, session, Telegraf } from 'telegraf'
 | 
			
		||||
import { allScenes, registerAllPlayerCommands } from './telegram/player-commands.js';
 | 
			
		||||
import { Player } from './models/player.js'
 | 
			
		||||
 | 
			
		||||
export const server = fastify({
 | 
			
		||||
    logger: {
 | 
			
		||||
@ -21,13 +21,12 @@ export const server = fastify({
 | 
			
		||||
export const telegraf = new Telegraf(config.telegram.token)
 | 
			
		||||
 | 
			
		||||
export const setup = async () => {
 | 
			
		||||
    const mongooseClient = await mongoose.connect(config.database.url)
 | 
			
		||||
    const models = registerModels(mongooseClient)
 | 
			
		||||
    server.log.info("老色批世界树 > 初始化中...")
 | 
			
		||||
 | 
			
		||||
    await mongoose.connect(config.database.url)
 | 
			
		||||
    const publicKey = readFileSync(config.signing.public).toString()
 | 
			
		||||
    const privateKey = readFileSync(config.signing.private).toString()
 | 
			
		||||
 | 
			
		||||
    server.decorate('mongoose', mongooseClient)
 | 
			
		||||
    server.decorate('models', models)
 | 
			
		||||
    server.decorate('keys', { publicKey, privateKey })
 | 
			
		||||
 | 
			
		||||
    config.custom.preHooks(server)
 | 
			
		||||
@ -55,7 +54,7 @@ export const setup = async () => {
 | 
			
		||||
 | 
			
		||||
    if(process.env["UNIT_TEST"] || process.env["DEVEL_FIRST_RUN"]) {
 | 
			
		||||
    // Create a test player
 | 
			
		||||
        await new models.Player({
 | 
			
		||||
        await new Player({
 | 
			
		||||
            username: 'test',
 | 
			
		||||
            password: '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92',
 | 
			
		||||
            email: 'i@lama.icu',
 | 
			
		||||
@ -81,9 +80,8 @@ export const setup = async () => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const launch = async () => { 
 | 
			
		||||
    process.on('SIGINT', () => {
 | 
			
		||||
        new Promise(shutdown)
 | 
			
		||||
    })
 | 
			
		||||
    process.on('SIGINT', shutdown)
 | 
			
		||||
    process.on('SIGTERM', shutdown)
 | 
			
		||||
 | 
			
		||||
    telegraf.launch()
 | 
			
		||||
    
 | 
			
		||||
@ -93,9 +91,16 @@ const launch = async () => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const shutdown = async () => {
 | 
			
		||||
    server.close()
 | 
			
		||||
    telegraf.stop()
 | 
			
		||||
    await server.close()
 | 
			
		||||
    server.log.info("老色批世界树 > HTTP 服务器已关闭")
 | 
			
		||||
    try {
 | 
			
		||||
        telegraf.stop()
 | 
			
		||||
        server.log.info("老色批世界树 > Telegram Bot 已关闭")
 | 
			
		||||
    } catch(err) {
 | 
			
		||||
        server.log.info("老色批世界树 > Telegram Bot 未运行,已跳过")
 | 
			
		||||
    }
 | 
			
		||||
    mongoose.disconnect()
 | 
			
		||||
    server.log.info("老色批世界树 > 数据库连接已断开,服务器已关闭")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
(async () => {
 | 
			
		||||
 | 
			
		||||
@ -1,11 +0,0 @@
 | 
			
		||||
import { TokenSchema } from "./token.js";
 | 
			
		||||
import { PlayerSchema } from "./player.js";
 | 
			
		||||
import { InviteTokenSchema } from "./invite.js";
 | 
			
		||||
 | 
			
		||||
export function registerModels(mongoose) {
 | 
			
		||||
    return {
 | 
			
		||||
        Token: mongoose.model("Token", TokenSchema),
 | 
			
		||||
        Player: mongoose.model("Player", PlayerSchema),
 | 
			
		||||
        Invitation: mongoose.model('Invitation', InviteTokenSchema)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
import mongoose from 'mongoose'
 | 
			
		||||
const { Schema } = mongoose
 | 
			
		||||
 | 
			
		||||
export const InviteTokenSchema = new Schema({
 | 
			
		||||
    by: String, // Telegram username
 | 
			
		||||
    token: String
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// { from: string, id: number, sign: string }
 | 
			
		||||
@ -1,9 +1,8 @@
 | 
			
		||||
import mongoose from 'mongoose'
 | 
			
		||||
const { Schema } = mongoose
 | 
			
		||||
import { uuidToNoSymboUUID } from '../generator.js'
 | 
			
		||||
import { ImageSecurity } from '../secure.js'
 | 
			
		||||
 | 
			
		||||
export const PlayerSchema = new Schema({
 | 
			
		||||
export const Player = mongoose.model("Player", new mongoose.Schema({
 | 
			
		||||
    username: String, // 有符号 UUID
 | 
			
		||||
    password: String,
 | 
			
		||||
    email: String,
 | 
			
		||||
@ -18,7 +17,7 @@ export const PlayerSchema = new Schema({
 | 
			
		||||
        username: String,
 | 
			
		||||
        verified: Boolean,
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
}))
 | 
			
		||||
 | 
			
		||||
export const PlayerSeriliazationSchema = {
 | 
			
		||||
    "type": "object",
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,9 @@
 | 
			
		||||
import mongoose from 'mongoose'
 | 
			
		||||
const { Schema } = mongoose
 | 
			
		||||
 | 
			
		||||
export const TokenSchema = new Schema({
 | 
			
		||||
export const Token = mongoose.model("Token", new mongoose.Schema({
 | 
			
		||||
    uuid: String,
 | 
			
		||||
    token: String,
 | 
			
		||||
    clientToken: String,
 | 
			
		||||
    expireDate: Number,
 | 
			
		||||
    deadDate: Number,
 | 
			
		||||
})
 | 
			
		||||
}))
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
import { getOverrideHandler, getOverridePreHandler } from "../config.js"
 | 
			
		||||
import { uuidToNoSymboUUID } from "../generator.js"
 | 
			
		||||
import { Player } from "../models/player.js"
 | 
			
		||||
 | 
			
		||||
export const profiles = {
 | 
			
		||||
    method: 'POST',
 | 
			
		||||
@ -39,7 +40,7 @@ export const profiles = {
 | 
			
		||||
    },
 | 
			
		||||
    handler: getOverrideHandler('/api/profiles/minecraft') ?? async function (req, rep) {
 | 
			
		||||
        const { body } = req
 | 
			
		||||
        const profiles = await this.models.Player.find({ username: { $in: body } })
 | 
			
		||||
        const profiles = await Player.find({ username: { $in: body } })
 | 
			
		||||
        return await rep.code(200).send(profiles.map(profile => ({
 | 
			
		||||
            id: uuidToNoSymboUUID(profile.uuid),
 | 
			
		||||
            name: profile.username
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ 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',
 | 
			
		||||
@ -67,7 +68,7 @@ export const authenticate = {
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const player = await this.models.Player.findOne({ email: username, password: createHash('sha256').update(password).digest().toString('hex').toLowerCase() })
 | 
			
		||||
        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())
 | 
			
		||||
        })) {
 | 
			
		||||
@ -104,7 +105,7 @@ export const authenticate = {
 | 
			
		||||
 | 
			
		||||
        const profile = PlayerModel.getPlayerSerialization(player)
 | 
			
		||||
 | 
			
		||||
        new this.models.Token({
 | 
			
		||||
        new Token({
 | 
			
		||||
            uuid: player.uuid,
 | 
			
		||||
            token: token,
 | 
			
		||||
            clientToken: clientToken,
 | 
			
		||||
@ -173,7 +174,7 @@ export const refresh = {
 | 
			
		||||
        if(clientToken) {
 | 
			
		||||
            query.clientToken = clientToken
 | 
			
		||||
        }
 | 
			
		||||
        const token = await this.models.Token.findOne(query)
 | 
			
		||||
        const token = await Token.findOne(query)
 | 
			
		||||
 | 
			
		||||
        if(!token) {
 | 
			
		||||
            return await rep.code(401).send({
 | 
			
		||||
@ -196,7 +197,7 @@ export const refresh = {
 | 
			
		||||
        const [newToken, key] = generateToken(token.uuid)
 | 
			
		||||
        this.log.info(`/authserver/authenticate > 为玩家 ${token.uuid} 刷新令牌: ${token.uuid} 为 ${newToken} | 随机 key = ${key}`)
 | 
			
		||||
 | 
			
		||||
        await this.models.Token.updateOne({
 | 
			
		||||
        await Token.updateOne({
 | 
			
		||||
            token: accessToken,
 | 
			
		||||
            clientToken: clientToken ?? undefined
 | 
			
		||||
        }, {
 | 
			
		||||
@ -206,7 +207,7 @@ export const refresh = {
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        new this.models.Token({
 | 
			
		||||
        new Token({
 | 
			
		||||
            uuid: uuid,
 | 
			
		||||
            token: newToken,
 | 
			
		||||
            clientToken: clientToken ?? token.clientToken,
 | 
			
		||||
@ -220,7 +221,7 @@ export const refresh = {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(requestUser || selectedProfile) {
 | 
			
		||||
            const player = await this.models.Player.findOne({ uuid })
 | 
			
		||||
            const player = await PlayerModel.Player.findOne({ uuid })
 | 
			
		||||
            
 | 
			
		||||
            if(requestUser) {
 | 
			
		||||
                response.user = {
 | 
			
		||||
@ -276,7 +277,7 @@ export const validate = {
 | 
			
		||||
        if(clientToken) {
 | 
			
		||||
            query.clientToken = clientToken
 | 
			
		||||
        }
 | 
			
		||||
        const token = await this.models.Token.findOne(query)
 | 
			
		||||
        const token = await Token.findOne(query)
 | 
			
		||||
 | 
			
		||||
        if(!token) {
 | 
			
		||||
            return await rep.code(401).send({
 | 
			
		||||
@ -327,7 +328,7 @@ export const invalidate = {
 | 
			
		||||
    handler: getOverrideHandler("/authserver/authenticate") ?? async function (req, rep) {
 | 
			
		||||
        const { accessToken } = req.body
 | 
			
		||||
 | 
			
		||||
        const { modifiedCount } = await this.models.Token.updateOne({
 | 
			
		||||
        const { modifiedCount } = await Token.updateOne({
 | 
			
		||||
            token: accessToken
 | 
			
		||||
        }, {
 | 
			
		||||
            $set: {
 | 
			
		||||
@ -373,7 +374,7 @@ export const signout = {
 | 
			
		||||
    handler: getOverrideHandler("/authserver/signout") ?? 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() })
 | 
			
		||||
        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",
 | 
			
		||||
@ -382,7 +383,7 @@ export const signout = {
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await this.models.Token.deleteMany({
 | 
			
		||||
        await Token.deleteMany({
 | 
			
		||||
            uuid: player.uuid
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import { getOverrideHandler, getOverridePreHandler } from '../config.js'
 | 
			
		||||
import { toSymboUUID } from '../generator.js'
 | 
			
		||||
import { getPlayerSerialization, PlayerSeriliazationSchema } from '../models/player.js'
 | 
			
		||||
import { getPlayerSerialization, Player, PlayerSeriliazationSchema } from '../models/player.js'
 | 
			
		||||
import { Token } from '../models/token.js'
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
    Key: string Username
 | 
			
		||||
@ -36,7 +37,7 @@ export const join = {
 | 
			
		||||
    preHandler: getOverridePreHandler('/sessionserver/session/minecraft/join'),
 | 
			
		||||
    handler: getOverrideHandler('/sessionserver/session/minecraft/join') ?? async function (req, rep) {
 | 
			
		||||
        const { accessToken, selectedProfile, serverId } = req.body
 | 
			
		||||
        const user = await await this.models.Player.findOne({ uuid: toSymboUUID(selectedProfile) })
 | 
			
		||||
        const user = await await Player.findOne({ uuid: toSymboUUID(selectedProfile) })
 | 
			
		||||
        if (!user) {
 | 
			
		||||
            return await rep.code(400).send({
 | 
			
		||||
                error: "IllegalArgumentException",
 | 
			
		||||
@ -45,7 +46,7 @@ export const join = {
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const session = await this.models.Token.findOne({ token: accessToken })
 | 
			
		||||
        const session = await Token.findOne({ token: accessToken })
 | 
			
		||||
        if (!session) {
 | 
			
		||||
            return await rep.code(401).send({
 | 
			
		||||
                error: "IllegalArgumentException",
 | 
			
		||||
@ -99,7 +100,7 @@ export const hasJoined = {
 | 
			
		||||
    handler: getOverrideHandler('/sessionserver/session/minecraft/hasJoined') ?? async function (req, rep) {
 | 
			
		||||
        const { username, serverId, ip } = req.query
 | 
			
		||||
 | 
			
		||||
        const player = await this.models.Player.findOne({ username })
 | 
			
		||||
        const player = await Player.findOne({ username })
 | 
			
		||||
        if (!player) {
 | 
			
		||||
            return await rep.code(400).send({
 | 
			
		||||
                error: "IllegalArgumentException",
 | 
			
		||||
@ -155,7 +156,7 @@ export const profile = {
 | 
			
		||||
    preHandler: getOverridePreHandler('/sessionserver/session/minecraft/profile/:uuid'),
 | 
			
		||||
    handler: getOverrideHandler('/sessionserver/session/minecraft/profile/:uuid') ?? async function (req, rep) {
 | 
			
		||||
        const { uuid } = req.params
 | 
			
		||||
        const player = await this.models.Player.findOne({ uuid: toSymboUUID(uuid) })
 | 
			
		||||
        const player = await Player.findOne({ uuid: toSymboUUID(uuid) })
 | 
			
		||||
        if (!player) {
 | 
			
		||||
            return await rep.code(204).send()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ import { telegraf, server } from '../index.js'
 | 
			
		||||
import { createHash } from 'crypto'
 | 
			
		||||
import axios from 'axios'
 | 
			
		||||
import { uuid } from '../generator.js'
 | 
			
		||||
import { Player } from '../models/player.js'
 | 
			
		||||
 | 
			
		||||
export const registerAllPlayerCommands = async () => {
 | 
			
		||||
    await register()
 | 
			
		||||
@ -38,7 +39,7 @@ const registerWizard = new Scenes.WizardScene('REGISTRIATION_WIZARD',
 | 
			
		||||
 | 
			
		||||
        ctx.scene.session.data.password = createHash('sha256').update(ctx.message.text).digest('hex')
 | 
			
		||||
 | 
			
		||||
        const duplicated = await server.models.Player.findOne({ password: ctx.scene.session.data.password })
 | 
			
		||||
        const duplicated = await Player.findOne({ password: ctx.scene.session.data.password })
 | 
			
		||||
        if(duplicated) {
 | 
			
		||||
            return ctx.reply(`该密码hash为:${ctx.scene.session.data.password.substring(3,10)} 已检测到数据库中存在重复密码(${JSON.stringify({d: duplicated})}),为了安全请重新输入`)
 | 
			
		||||
        }
 | 
			
		||||
@ -139,7 +140,7 @@ const registerWizard = new Scenes.WizardScene('REGISTRIATION_WIZARD',
 | 
			
		||||
            let verified = false
 | 
			
		||||
            if(ctx.scene.session.data.inviteCode) {
 | 
			
		||||
                await telegraf.telegram.editMessageText(ctx.chat.id, message.message_id, null, message.text + '\n验证邀请码中...')
 | 
			
		||||
                const invi = server.models.Invitation.findOne({
 | 
			
		||||
                const invi = Invitation.findOne({
 | 
			
		||||
                    token: ctx.scene.session.data.inviteCode
 | 
			
		||||
                })
 | 
			
		||||
                if(invi) {
 | 
			
		||||
@ -150,7 +151,7 @@ const registerWizard = new Scenes.WizardScene('REGISTRIATION_WIZARD',
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const player = new server.models.Player({
 | 
			
		||||
            const player = new Player({
 | 
			
		||||
                uuid: uuid("LSP-yggdrasil:" + ctx.scene.session.data.playerUsername),
 | 
			
		||||
                username: ctx.scene.session.data.playerUsername,
 | 
			
		||||
                password: ctx.scene.session.data.password,
 | 
			
		||||
@ -192,7 +193,7 @@ const register = async () => {
 | 
			
		||||
            return ctx.reply("请设置 Telegram 用户名!")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const player = await server.models.Player.findOne({ "telegramBind.username": username })
 | 
			
		||||
        const player = await Player.findOne({ "telegramBind.username": username })
 | 
			
		||||
        server.log.info(player)
 | 
			
		||||
        if(!player) {
 | 
			
		||||
            return ctx.scene.enter('REGISTRIATION_WIZARD')
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@ beforeAll(() => {
 | 
			
		||||
        writeFileSync('private.key', privateKey)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    config.database.url = 'mongodb://setup-database:27017/yggdrasil?readPreference=primary&appname=MongoDB%20Compass&directConnection=true&ssl=false'
 | 
			
		||||
    //config.database.url = 'mongodb://setup-database:27017/yggdrasil?readPreference=primary&appname=MongoDB%20Compass&directConnection=true&ssl=false'
 | 
			
		||||
    return setup()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user