117 lines
3.3 KiB
JavaScript
117 lines
3.3 KiB
JavaScript
|
|
'use strict';
|
|||
|
|
|
|||
|
|
const Service = require('egg').Service;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 认证服务 - AuthService
|
|||
|
|
*
|
|||
|
|
* 封装认证相关的数据库操作和业务逻辑。
|
|||
|
|
* 使用 Contestant 模型进行选手认证,Admin 模型进行管理员认证。
|
|||
|
|
* 密码采用 bcrypt 加盐哈希存储;存量 SHA256(无盐)密码在下次成功登录时透明升级为 bcrypt。
|
|||
|
|
*/
|
|||
|
|
class AuthService extends Service {
|
|||
|
|
/**
|
|||
|
|
* 根据手机号查找选手
|
|||
|
|
* @param {string} phone - 手机号
|
|||
|
|
* @returns {object|null} 选手信息
|
|||
|
|
*/
|
|||
|
|
async findByPhone(phone) {
|
|||
|
|
const { ctx } = this;
|
|||
|
|
const contestant = await ctx.model.Contestant.findOne({
|
|||
|
|
where: { phone },
|
|||
|
|
attributes: ['id', 'id_number', 'name', 'phone', 'group', 'password', 'activated', 'score'],
|
|||
|
|
});
|
|||
|
|
return contestant;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据身份证号查找选手(用于激活和忘记密码)
|
|||
|
|
* @param {string} idNumber - 身份证号
|
|||
|
|
* @returns {object|null} 选手信息
|
|||
|
|
*/
|
|||
|
|
async findByAccount(idNumber) {
|
|||
|
|
const { ctx } = this;
|
|||
|
|
|
|||
|
|
return await ctx.model.Contestant.findOne({
|
|||
|
|
where: { id_number: idNumber },
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据用户名查找管理员
|
|||
|
|
*
|
|||
|
|
* @param {string} username - 管理员用户名
|
|||
|
|
* @returns {Promise<Model|null>} Admin 模型对象或 null
|
|||
|
|
*/
|
|||
|
|
async findAdmin(username) {
|
|||
|
|
const { ctx } = this;
|
|||
|
|
|
|||
|
|
return await ctx.model.Admin.findOne({
|
|||
|
|
where: { username },
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 验证密码
|
|||
|
|
*
|
|||
|
|
* 兼容 bcrypt 哈希与存量 SHA256(无盐)哈希,便于平滑迁移。
|
|||
|
|
*
|
|||
|
|
* @param {string} inputPassword - 用户输入的明文密码
|
|||
|
|
* @param {string} hashedPassword - 数据库中存储的哈希密码
|
|||
|
|
* @returns {Promise<boolean>} 密码是否匹配
|
|||
|
|
*/
|
|||
|
|
async verifyPassword(inputPassword, hashedPassword) {
|
|||
|
|
const { ctx } = this;
|
|||
|
|
return ctx.helper.comparePassword(inputPassword, hashedPassword);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 透明升级:若账号仍使用旧版 SHA256 哈希,在验证通过后重写为 bcrypt。
|
|||
|
|
* 存量账号会在下一次成功登录时自动完成迁移,无需批量刷库。
|
|||
|
|
*
|
|||
|
|
* @param {Model} account - 含 password 字段的模型实例(contestant 或 admin)
|
|||
|
|
* @param {string} plainPassword - 明文密码
|
|||
|
|
* @returns {Promise<void>}
|
|||
|
|
*/
|
|||
|
|
async upgradePasswordIfLegacy(account, plainPassword) {
|
|||
|
|
const { ctx } = this;
|
|||
|
|
if (account && account.password && !ctx.helper.isBcryptHash(account.password)) {
|
|||
|
|
try {
|
|||
|
|
const newHash = await ctx.helper.hashPassword(plainPassword);
|
|||
|
|
await account.update({ password: newHash });
|
|||
|
|
} catch (e) {
|
|||
|
|
// 升级失败不影响本次登录,仅记录日志,下次登录再尝试
|
|||
|
|
ctx.logger.warn('[密码迁移] bcrypt 升级失败:', e.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 激活账号
|
|||
|
|
*
|
|||
|
|
* 将指定 id 的选手设置为已激活状态,并更新密码。
|
|||
|
|
*
|
|||
|
|
* @param {number} id - 选手 id
|
|||
|
|
* @param {string} password - SHA256 哈希后的密码
|
|||
|
|
* @returns {Promise<Model>} 更新后的 Contestant 模型对象
|
|||
|
|
*/
|
|||
|
|
async activateAccount(id, password) {
|
|||
|
|
const { ctx } = this;
|
|||
|
|
|
|||
|
|
const contestant = await ctx.model.Contestant.findOne({
|
|||
|
|
where: { id },
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!contestant) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return await contestant.update({
|
|||
|
|
activated: 1,
|
|||
|
|
password,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = AuthService;
|