mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-13 12:23:29 +08:00
Add multi-user backend infrastructure: User model, management service and API
Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>
This commit is contained in:
co-authored by
whyour
parent
4758400df6
commit
db93ca9aa9
+51
-16
@@ -24,12 +24,17 @@ import uniq from 'lodash/uniq';
|
||||
import pickBy from 'lodash/pickBy';
|
||||
import isNil from 'lodash/isNil';
|
||||
import { shareStore } from '../shared/store';
|
||||
import UserManagementService from './userManagement';
|
||||
import { UserRole } from '../data/user';
|
||||
|
||||
@Service()
|
||||
export default class UserService {
|
||||
@Inject((type) => NotificationService)
|
||||
private notificationService!: NotificationService;
|
||||
|
||||
@Inject((type) => UserManagementService)
|
||||
private userManagementService!: UserManagementService;
|
||||
|
||||
constructor(
|
||||
@Inject('logger') private logger: winston.Logger,
|
||||
private scheduleService: ScheduleService,
|
||||
@@ -93,27 +98,57 @@ export default class UserService {
|
||||
const { country, province, city, isp } = ipAddress;
|
||||
address = uniq([country, province, city, isp]).filter(Boolean).join(' ');
|
||||
}
|
||||
if (username === cUsername && password === cPassword) {
|
||||
|
||||
// Check if this is a regular user (not admin) trying to login
|
||||
let authenticatedUser = null;
|
||||
let userId: number | undefined = undefined;
|
||||
let userRole = UserRole.admin;
|
||||
|
||||
// First check if it's the system admin
|
||||
const isSystemAdmin = username === cUsername && password === cPassword;
|
||||
|
||||
if (!isSystemAdmin) {
|
||||
// Try to authenticate as a regular user
|
||||
try {
|
||||
authenticatedUser = await this.userManagementService.authenticate(username, password);
|
||||
if (authenticatedUser) {
|
||||
userId = authenticatedUser.id;
|
||||
userRole = authenticatedUser.role;
|
||||
}
|
||||
} catch (e: any) {
|
||||
// User disabled or other error
|
||||
return { code: 400, message: e.message };
|
||||
}
|
||||
}
|
||||
|
||||
if (isSystemAdmin || authenticatedUser) {
|
||||
const data = createRandomString(50, 100);
|
||||
const expiration = twoFactorActivated ? '60d' : '20d';
|
||||
let token = jwt.sign({ data }, config.jwt.secret, {
|
||||
const expiration = (isSystemAdmin && twoFactorActivated) ? '60d' : '20d';
|
||||
let token = jwt.sign(
|
||||
{ data, userId, role: userRole },
|
||||
config.jwt.secret,
|
||||
{
|
||||
expiresIn: config.jwt.expiresIn || expiration,
|
||||
algorithm: 'HS384',
|
||||
});
|
||||
|
||||
await this.updateAuthInfo(content, {
|
||||
token,
|
||||
tokens: {
|
||||
...tokens,
|
||||
[req.platform]: token,
|
||||
},
|
||||
lastlogon: timestamp,
|
||||
retries: 0,
|
||||
lastip: ip,
|
||||
lastaddr: address,
|
||||
platform: req.platform,
|
||||
isTwoFactorChecking: false,
|
||||
});
|
||||
// Only update authInfo for system admin
|
||||
if (isSystemAdmin) {
|
||||
await this.updateAuthInfo(content, {
|
||||
token,
|
||||
tokens: {
|
||||
...tokens,
|
||||
[req.platform]: token,
|
||||
},
|
||||
lastlogon: timestamp,
|
||||
retries: 0,
|
||||
lastip: ip,
|
||||
lastaddr: address,
|
||||
platform: req.platform,
|
||||
isTwoFactorChecking: false,
|
||||
});
|
||||
}
|
||||
|
||||
this.notificationService.notify(
|
||||
'登录通知',
|
||||
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import winston from 'winston';
|
||||
import { User, UserModel, UserRole, UserStatus } from '../data/user';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
@Service()
|
||||
export default class UserManagementService {
|
||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||
|
||||
public async list(searchText?: string): Promise<User[]> {
|
||||
let query: any = {};
|
||||
if (searchText) {
|
||||
query = {
|
||||
username: { [Op.like]: `%${searchText}%` },
|
||||
};
|
||||
}
|
||||
const docs = await UserModel.findAll({ where: query });
|
||||
return docs.map((x) => x.get({ plain: true }));
|
||||
}
|
||||
|
||||
public async get(id: number): Promise<User> {
|
||||
const doc = await UserModel.findByPk(id);
|
||||
if (!doc) {
|
||||
throw new Error('用户不存在');
|
||||
}
|
||||
return doc.get({ plain: true });
|
||||
}
|
||||
|
||||
public async getByUsername(username: string): Promise<User | null> {
|
||||
const doc = await UserModel.findOne({ where: { username } });
|
||||
if (!doc) {
|
||||
return null;
|
||||
}
|
||||
return doc.get({ plain: true });
|
||||
}
|
||||
|
||||
public async create(payload: User): Promise<User> {
|
||||
const existingUser = await this.getByUsername(payload.username);
|
||||
if (existingUser) {
|
||||
throw new Error('用户名已存在');
|
||||
}
|
||||
|
||||
if (payload.password === 'admin') {
|
||||
throw new Error('密码不能设置为admin');
|
||||
}
|
||||
|
||||
const doc = await UserModel.create(payload);
|
||||
return doc.get({ plain: true });
|
||||
}
|
||||
|
||||
public async update(payload: User): Promise<User> {
|
||||
if (!payload.id) {
|
||||
throw new Error('缺少用户ID');
|
||||
}
|
||||
|
||||
const existingUser = await this.get(payload.id);
|
||||
if (!existingUser) {
|
||||
throw new Error('用户不存在');
|
||||
}
|
||||
|
||||
if (payload.password === 'admin') {
|
||||
throw new Error('密码不能设置为admin');
|
||||
}
|
||||
|
||||
// Check if username is being changed and if new username already exists
|
||||
if (payload.username !== existingUser.username) {
|
||||
const userWithSameUsername = await this.getByUsername(payload.username);
|
||||
if (userWithSameUsername && userWithSameUsername.id !== payload.id) {
|
||||
throw new Error('用户名已存在');
|
||||
}
|
||||
}
|
||||
|
||||
const [, [updated]] = await UserModel.update(payload, {
|
||||
where: { id: payload.id },
|
||||
returning: true,
|
||||
});
|
||||
return updated.get({ plain: true });
|
||||
}
|
||||
|
||||
public async delete(ids: number[]): Promise<number> {
|
||||
const count = await UserModel.destroy({ where: { id: ids } });
|
||||
return count;
|
||||
}
|
||||
|
||||
public async authenticate(
|
||||
username: string,
|
||||
password: string,
|
||||
): Promise<User | null> {
|
||||
const user = await this.getByUsername(username);
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (user.password !== password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (user.status === UserStatus.disabled) {
|
||||
throw new Error('用户已被禁用');
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user