339 lines
12 KiB
JavaScript
339 lines
12 KiB
JavaScript
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': '<token>'`,
|
||
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: '<token>',
|
||
textures: {
|
||
skin: '<url>',
|
||
cape: '<url>'
|
||
},
|
||
username: '<username>',
|
||
uuid: '<uuid>'
|
||
},
|
||
properties: {
|
||
identifier: {
|
||
type: 'string',
|
||
description: 'identifier,后面请求必须带 X-LSP-Idenitifier: <token> 请求头',
|
||
example: '<token>'
|
||
},
|
||
textures: {
|
||
type: 'object',
|
||
description: '用户皮肤和披风',
|
||
example: {
|
||
skin: '<url>',
|
||
cape: '<url>'
|
||
},
|
||
properties: {
|
||
skin: {
|
||
type: 'string',
|
||
description: '用户皮肤',
|
||
example: '<url>',
|
||
optional: true,
|
||
},
|
||
cape: {
|
||
type: 'string',
|
||
description: '用户披风',
|
||
example: '<url>',
|
||
optional: true,
|
||
}
|
||
},
|
||
},
|
||
username: {
|
||
type: 'string',
|
||
description: '用户名',
|
||
example: '<username>'
|
||
},
|
||
uuid: {
|
||
type: 'string',
|
||
description: '用户唯一标识',
|
||
example: '<uuid>'
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
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': '<token>',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: '',
|
||
})
|
||
}
|
||
} |