This commit is contained in:
		@ -15,7 +15,8 @@
 | 
			
		||||
#### Beta 1.0: 
 | 
			
		||||
- [ ] 基础世界树 API
 | 
			
		||||
  + [x] /authserver
 | 
			
		||||
  + [ ] /sessionserver
 | 
			
		||||
  + [x] /sessionserver
 | 
			
		||||
    + [ ] 测试
 | 
			
		||||
  + [ ] /api
 | 
			
		||||
- [ ] 进阶 API
 | 
			
		||||
  - [ ] 皮肤上传和安全检查
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
 | 
			
		||||
export async function headerValidation(req, rep) {
 | 
			
		||||
    if(!(/(authserver)|(sessionserver)|(api)/g).test(req.url)) {
 | 
			
		||||
    if(!(/(authserver)|(sessionserver)|(api)/g).test(req.url) || req.method !== 'POST') {
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ 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'
 | 
			
		||||
import { config } from './config.js'
 | 
			
		||||
 | 
			
		||||
export const server = fastify({
 | 
			
		||||
@ -34,6 +35,9 @@ export const setup = async () => {
 | 
			
		||||
    server.route(AuthenticateRoutings.invalidate)
 | 
			
		||||
    server.route(AuthenticateRoutings.signout)
 | 
			
		||||
 | 
			
		||||
    server.route(SessionServerRoutings.join)
 | 
			
		||||
    server.route(SessionServerRoutings.hasJoined)
 | 
			
		||||
 | 
			
		||||
    config.custom.postRouting(server)
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
import mongoose from 'mongoose'
 | 
			
		||||
const { Schema } = mongoose
 | 
			
		||||
import { uuidToNoSymboUUID } from '../generator.js'
 | 
			
		||||
 | 
			
		||||
export const PlayerSchema = new Schema({
 | 
			
		||||
    username: String, // 有符号 UUID
 | 
			
		||||
@ -64,4 +65,38 @@ export const PlayerAccountSerializationSchema = {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getPlayerSerialization(player) {
 | 
			
		||||
    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
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        uuid: uuidToNoSymboUUID(player.uuid),
 | 
			
		||||
        name: player.username,
 | 
			
		||||
        properties: [
 | 
			
		||||
            { 
 | 
			
		||||
                name: "texturs",
 | 
			
		||||
                value: Buffer.from(JSON.stringify(textures)).toString('base64')
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -60,7 +60,7 @@ export const authenticate = {
 | 
			
		||||
        let { username, password, clientToken, requestUser, agent } = req.body
 | 
			
		||||
 | 
			
		||||
        if(!username || !password || !agent || agent.name.toLowerCase() !== 'minecraft') {
 | 
			
		||||
            rep.code(418).send({
 | 
			
		||||
            return await rep.code(418).send({
 | 
			
		||||
                error: "ForbiddenOperationException",
 | 
			
		||||
                errorMessage: "无效应用名,此服务端仅支援 Minecraft",
 | 
			
		||||
                cause: "此服务器只支持 agent.name: minecraft"
 | 
			
		||||
@ -94,37 +94,7 @@ export const authenticate = {
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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')
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
        const profile = PlayerModel.getPlayerSerialization(player)
 | 
			
		||||
 | 
			
		||||
        new this.models.Token({
 | 
			
		||||
            uuid: player.uuid,
 | 
			
		||||
@ -257,37 +227,7 @@ export const refresh = {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            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')
 | 
			
		||||
                        }
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
                response.selectedProfile = PlayerModel.getPlayerSerialization(player)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										162
									
								
								src/routes/session.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								src/routes/session.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,162 @@
 | 
			
		||||
import { getOverrideHandler, getOverridePreHandler } from '../config.js'
 | 
			
		||||
import { toSymboUUID } from '../generator'
 | 
			
		||||
import { getPlayerSerialization, PlayerSeriliazationSchema } from '../models/player'
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
    Key: string Username
 | 
			
		||||
    Value: {
 | 
			
		||||
        accessToken: string,
 | 
			
		||||
        serverId: string,
 | 
			
		||||
        ip: string
 | 
			
		||||
    }
 | 
			
		||||
*/
 | 
			
		||||
const joinServerRequest = new Map()
 | 
			
		||||
 | 
			
		||||
export const join = {
 | 
			
		||||
    method: 'POST',
 | 
			
		||||
    url: '/sessionserver/session/minecraft/join',
 | 
			
		||||
    schema: {
 | 
			
		||||
        body: {
 | 
			
		||||
            "accessToken": {
 | 
			
		||||
                "type": "string"
 | 
			
		||||
            },
 | 
			
		||||
            "selectedProfile": {
 | 
			
		||||
                "type": "string"
 | 
			
		||||
            },
 | 
			
		||||
            "serverId": {
 | 
			
		||||
                "type": "string"
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        response: {
 | 
			
		||||
            204: {
 | 
			
		||||
                type: "null"
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    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) })
 | 
			
		||||
        if (!user) {
 | 
			
		||||
            return await rep.code(400).send({
 | 
			
		||||
                error: "IllegalArgumentException",
 | 
			
		||||
                errorMessage: "请求内容不正确",
 | 
			
		||||
                cause: "用户不存在"
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const session = await this.models.Session.findOne({ token: accessToken })
 | 
			
		||||
        if (!session) {
 | 
			
		||||
            return await rep.code(401).send({
 | 
			
		||||
                error: "IllegalArgumentException",
 | 
			
		||||
                errorMessage: "无效会话",
 | 
			
		||||
                cause: "无效会话"
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (Date.now() > session.expireDate) {
 | 
			
		||||
            return await rep.code(401).send({
 | 
			
		||||
                error: "IllegalArgumentException",
 | 
			
		||||
                errorMessage: "无效会话",
 | 
			
		||||
                cause: "会话已过期"
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        joinServerRequest.set(session.uuid, {
 | 
			
		||||
            accessToken,
 | 
			
		||||
            serverId,
 | 
			
		||||
            ip: req.headers['x-forwarded-for'] || req.info.remoteAddress
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        await rep.code(204).send()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const hasJoined = {
 | 
			
		||||
    method: 'GET',
 | 
			
		||||
    url: '/sessionserver/session/minecraft/hasJoined',
 | 
			
		||||
    schema: {
 | 
			
		||||
        query: {
 | 
			
		||||
            "username": {
 | 
			
		||||
                "type": "string"
 | 
			
		||||
            },
 | 
			
		||||
            "serverId": {
 | 
			
		||||
                "type": "string"
 | 
			
		||||
            },
 | 
			
		||||
            "ip": {
 | 
			
		||||
                "type": "string"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        response: {
 | 
			
		||||
            200: PlayerSeriliazationSchema,
 | 
			
		||||
            204: {
 | 
			
		||||
                type: "null"
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    preHandler: getOverridePreHandler('/sessionserver/session/minecraft/hasJoined'),
 | 
			
		||||
    handler: getOverrideHandler('/sessionserver/session/minecraft/hasJoined') ?? async function (req, rep) {
 | 
			
		||||
        const { username, serverId, ip } = req.query
 | 
			
		||||
        const { ssID, sIP } = joinServerRequest.get(username)
 | 
			
		||||
        if(ip) {
 | 
			
		||||
            if(ip !== sIP) {
 | 
			
		||||
                return await rep.code(401).send({
 | 
			
		||||
                    error: "IllegalArgumentException",
 | 
			
		||||
                    errorMessage: "无效会话",
 | 
			
		||||
                    cause: "IP 不匹配"
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(serverId !== ssID) { 
 | 
			
		||||
            return await rep.code(401).send({
 | 
			
		||||
                error: "IllegalArgumentException",
 | 
			
		||||
                errorMessage: "无效会话",
 | 
			
		||||
                cause: "服务器 ID 不匹配"
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const player = await this.models.Player.findOne({ uuid: toSymboUUID(username) })
 | 
			
		||||
        if (!player) {
 | 
			
		||||
            return await rep.code(400).send({
 | 
			
		||||
                error: "IllegalArgumentException",
 | 
			
		||||
                errorMessage: "请求内容不正确",
 | 
			
		||||
                cause: "用户不存在"
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        await rep.code(200).send(getPlayerSerialization(player))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const profile = {
 | 
			
		||||
    method: 'GET',
 | 
			
		||||
    url: '/sessionserver/session/minecraft/profile/:uuid',
 | 
			
		||||
    schema: {
 | 
			
		||||
        params: {
 | 
			
		||||
            "uuid": {
 | 
			
		||||
                "type": "string"
 | 
			
		||||
            },
 | 
			
		||||
            unsigned: {
 | 
			
		||||
                "type": ["boolean", "null"]
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        response: {
 | 
			
		||||
            200: PlayerSeriliazationSchema,
 | 
			
		||||
            204: {
 | 
			
		||||
                type: "null"
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    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) })
 | 
			
		||||
        if (!player) {
 | 
			
		||||
            return await rep.code(204).send()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await rep.code(200).send(getPlayerSerialization(player))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user