import { getOverrideHandler, getOverridePreHandler } from "../config.js" import { Player } from "../models/player.js" import { createHash } from "crypto" import { generateToken, uuid } from "../generator.js" import { Token } from "../models/token.js" const BASE_RESPONSE = { err: { type: "number", description: "错误类型, 1.048596代表无错误", example: 1.048596 }, msg: { type: "string", description: "错误信息,如果有错误则返回错误信息,否则返回空字符串 << 感谢 Copilot 的补全", example: "你号被ban了" } } const identifiers = new Map() async function identifierValidator(req, rep) { const identifier = req.headers['X-LSP-Idenitifier'] if(!identifier) { return await rep.code(401).send({ err: 1.143688, }) } } export const login = { method: 'POST', url: '/api/login', schema: { summary: "登录", description: `登录到 webapi,后续请求需要携带请求头 'X-LSP-Idenitifier': ''`, tags: [ 'webapi' ], body: { type: 'object', properties: { username: { type: 'string', description: '用户名', example: 'test' }, password: { type: 'string', description: '密码', example: '123456' }, createToken: { type: 'boolean', description: '是否创建一个 accessToken', example: false } } }, response: { 200: { type: 'object', properties: { ...BASE_RESPONSE, extra: { type: 'object', description: '额外信息', example: { identifier: '', textures: { skin: '', cape: '' }, username: '', uuid: '' }, properties: { identifier: { type: 'string', description: 'identifier,后面请求必须带 X-LSP-Idenitifier: 请求头', example: '' }, textures: { type: 'object', description: '用户皮肤和披风', example: { skin: '', cape: '' }, properties: { skin: { type: 'string', description: '用户皮肤', example: '', optional: true, }, cape: { type: 'string', description: '用户披风', example: '', optional: true, } }, }, username: { type: 'string', description: '用户名', example: '' }, uuid: { type: 'string', description: '用户唯一标识', example: '' } } } } }, 401: { type: 'object', properties: { err: { type: 'number', description: '错误类型', example: 1.048596 }, msg: { type: 'string', description: '错误内容,展示给用户看的', example: "您输入的密码似乎是用户 lama 的,请确保用户名没有打错!" }, } } }, }, preHandler: getOverridePreHandler('/api/login'), handler: getOverrideHandler('/api/login') ?? async function(req, rep) { const { username, password, createToken } = req.body; const user = await Player.findOne({ email: username, password: createHash("sha256").update(password).digest('hex') }); if (!user) { return rep.code(401).send({ err: 1.143688, msg: "用户名或密码错误" }); } if(!user.permissions.some((it) => { s return it.node === 'login' && it.allowed && (it.duration === 0 || it.startDate + it.duration > Date.now()) })) { return await rep.code(401).send({ err: 0.337187, msg: "泻药,宁滴账号已被封禁" }); } const [token, key] = generateToken(`webapi:${user.username}`) this.log.info(`/api/login > 为玩家 webapi:${user.username} 生成令牌: ${token} | 随机 key = ${key}`) identifiers.set(token, { uuid: user.uuid, t: Date.now() + 1000 * 60 * 60 * 24 * 1, }) if(createToken) { new Token({ uuid: user.uuid, token: token, clientToken: `${req.headers['x-forwarded-for'] || req.ip}:${token.substring(3, 8)}`, expireDate: Date.now() + 1000 * 60 * 60 * 24 * 15, deadDate: Date.now() + 1000 * 60 * 60 * 24 * 30, }).save() } return await rep.code(200).send(JSON.stringify({ err: 1.048596, msg: '', extra: { identifier: token, textures: user.textures, username: user.username, uuid: user.uuid, } })) } } export const register = { method: 'POST', url: '/api/register', schema: { summary: "注册", description: `注册到 webapi,后续请求需要先登录获取identifier,然后携带请求头 'X-LSP-Idenitifier': '',200正确返回 <<< Copilot自己补全的`, tags: [ 'webapi' ], body: { type: 'object', properties: { username: { type: 'string', description: '用户名', example: 'test' }, password: { type: 'string', description: '密码', example: '123456' }, email: { type: 'string', description: '邮箱', example: '' }, telegramId: { type: 'string', description: 'telegramId', example: '' }, textureMigrations: { type: 'object', description: '纹理迁移', optional: true, properties: { skin: { type: 'string', description: '皮肤', optional: true, example: 'https://assets.lama.icu/textures/skin/steve.png' }, cape: { type: 'string', description: '披风', optional: true, example: 'https://assets.lama.icu/textures/cape/steve.png' } } } } }, response: { 200: { type: 'object', properties: { ...BASE_RESPONSE, extra: { username: { type: 'string', description: '用户名', example: 'test' }, password: { type: 'string', description: '密码', example: '123456' }, email: { type: 'string', description: '邮箱', example: '' }, telegramId: { type: 'string', description: 'telegramId', example: '' }, textureMigrations: { type: 'object', description: '纹理迁移', optional: true, properties: { skin: { type: 'string', description: '皮肤', optional: true, example: 'https://assets.lama.icu/textures/skin/steve.png' }, cape: { type: 'string', description: '披风', optional: true, example: 'https://assets.lama.icu/textures/cape/steve.png' } } } } } }, }, }, preHandler: getOverridePreHandler('/api/register'), handler: getOverrideHandler('/api/register') ?? async function(req, rep) { const { username, password, email, telegramId, textureMigrations } = req.body const user = await Player.findOne({ $or: [ { email: email }, { username: username } ] }) if (user) { return await rep.code(401).send({ err: 1, msg: "用户名已存在" }) } if(username == 0 || password == 0 || email == 0 || telegramId == 0) { return await rep.code(401).send({ err: 1, msg: "用户名/密码/邮箱/telegramId不能为空" }) } const textues = { } if(textureMigrations) { if(textureMigrations.skin != 0 && textureMigrations.skin) { textues.skin = textureMigrations.skin } if(textureMigrations.cape != 0 && textureMigrations.cape) { textues.cape = textureMigrations.cape } } const newUser = new Player({ username, password: createHash("sha256").update(password).digest('hex'), email, uuid: uuid('LSPlayer:' + email), textues, registerDate: Date.now(), permissions: [{ node: 'login', allowed: true, duration: -1, startDate: Date.now(), highPriority: false }], telegramBind: { username: telegramId, verified: false } }); await newUser.save() return await rep.code(200).send({ err: 1.048596, msg: '', }) } }