Files
competition-website/app/service/auth.js
WorkBuddy a303cf2d53 chore: 后端首次基线提交
运行必须源码:app/(controller/model/service/middleware/lib/utils/validate/router)、config/、database/(migrations/seeders/sql)、启动脚本、app/public/templates 模板。
已忽略运行非必须:node_modules/、logs/、run/、app/public 运行时上传数据、.env*、构建/缓存产物。
2026-08-17 12:25:42 +08:00

117 lines
3.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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;