mirror of
https://github.com/whyour/qinglong.git
synced 2025-11-10 00:26:09 +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:
parent
4758400df6
commit
db93ca9aa9
|
|
@ -11,6 +11,7 @@ import system from './system';
|
||||||
import subscription from './subscription';
|
import subscription from './subscription';
|
||||||
import update from './update';
|
import update from './update';
|
||||||
import health from './health';
|
import health from './health';
|
||||||
|
import userManagement from './userManagement';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const app = Router();
|
const app = Router();
|
||||||
|
|
@ -26,6 +27,7 @@ export default () => {
|
||||||
subscription(app);
|
subscription(app);
|
||||||
update(app);
|
update(app);
|
||||||
health(app);
|
health(app);
|
||||||
|
userManagement(app);
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
114
back/api/userManagement.ts
Normal file
114
back/api/userManagement.ts
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
|
import { Container } from 'typedi';
|
||||||
|
import { celebrate, Joi } from 'celebrate';
|
||||||
|
import UserManagementService from '../services/userManagement';
|
||||||
|
import { UserRole } from '../data/user';
|
||||||
|
|
||||||
|
const route = Router();
|
||||||
|
|
||||||
|
// Middleware to check if user is admin
|
||||||
|
const requireAdmin = (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
if (req.user && (req.user as any).role === UserRole.admin) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
return res.status(403).send({ code: 403, message: '需要管理员权限' });
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (app: Router) => {
|
||||||
|
app.use('/user-management', route);
|
||||||
|
|
||||||
|
// List all users (admin only)
|
||||||
|
route.get(
|
||||||
|
'/',
|
||||||
|
requireAdmin,
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const userManagementService = Container.get(UserManagementService);
|
||||||
|
const data = await userManagementService.list(req.query.searchValue as string);
|
||||||
|
res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get a specific user (admin only)
|
||||||
|
route.get(
|
||||||
|
'/:id',
|
||||||
|
requireAdmin,
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const userManagementService = Container.get(UserManagementService);
|
||||||
|
const data = await userManagementService.get(Number(req.params.id));
|
||||||
|
res.send({ code: 200, data });
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a new user (admin only)
|
||||||
|
route.post(
|
||||||
|
'/',
|
||||||
|
requireAdmin,
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
username: Joi.string().required(),
|
||||||
|
password: Joi.string().required(),
|
||||||
|
role: Joi.number().valid(UserRole.admin, UserRole.user).default(UserRole.user),
|
||||||
|
status: Joi.number().optional(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const userManagementService = Container.get(UserManagementService);
|
||||||
|
const data = await userManagementService.create(req.body);
|
||||||
|
res.send({ code: 200, data, message: '创建用户成功' });
|
||||||
|
} catch (e: any) {
|
||||||
|
return res.send({ code: 400, message: e.message });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update a user (admin only)
|
||||||
|
route.put(
|
||||||
|
'/',
|
||||||
|
requireAdmin,
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
id: Joi.number().required(),
|
||||||
|
username: Joi.string().required(),
|
||||||
|
password: Joi.string().required(),
|
||||||
|
role: Joi.number().valid(UserRole.admin, UserRole.user),
|
||||||
|
status: Joi.number().optional(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const userManagementService = Container.get(UserManagementService);
|
||||||
|
const data = await userManagementService.update(req.body);
|
||||||
|
res.send({ code: 200, data, message: '更新用户成功' });
|
||||||
|
} catch (e: any) {
|
||||||
|
return res.send({ code: 400, message: e.message });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Delete users (admin only)
|
||||||
|
route.delete(
|
||||||
|
'/',
|
||||||
|
requireAdmin,
|
||||||
|
celebrate({
|
||||||
|
body: Joi.array().items(Joi.number()).required(),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const userManagementService = Container.get(UserManagementService);
|
||||||
|
const count = await userManagementService.delete(req.body);
|
||||||
|
res.send({ code: 200, data: count, message: '删除用户成功' });
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -21,6 +21,7 @@ export class Crontab {
|
||||||
extra_schedules?: Array<{ schedule: string }>;
|
extra_schedules?: Array<{ schedule: string }>;
|
||||||
task_before?: string;
|
task_before?: string;
|
||||||
task_after?: string;
|
task_after?: string;
|
||||||
|
userId?: number;
|
||||||
|
|
||||||
constructor(options: Crontab) {
|
constructor(options: Crontab) {
|
||||||
this.name = options.name;
|
this.name = options.name;
|
||||||
|
|
@ -45,6 +46,7 @@ export class Crontab {
|
||||||
this.extra_schedules = options.extra_schedules;
|
this.extra_schedules = options.extra_schedules;
|
||||||
this.task_before = options.task_before;
|
this.task_before = options.task_before;
|
||||||
this.task_after = options.task_after;
|
this.task_after = options.task_after;
|
||||||
|
this.userId = options.userId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,4 +86,5 @@ export const CrontabModel = sequelize.define<CronInstance>('Crontab', {
|
||||||
extra_schedules: DataTypes.JSON,
|
extra_schedules: DataTypes.JSON,
|
||||||
task_before: DataTypes.STRING,
|
task_before: DataTypes.STRING,
|
||||||
task_after: DataTypes.STRING,
|
task_after: DataTypes.STRING,
|
||||||
|
userId: { type: DataTypes.NUMBER, allowNull: true },
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ export class Dependence {
|
||||||
name: string;
|
name: string;
|
||||||
log?: string[];
|
log?: string[];
|
||||||
remark?: string;
|
remark?: string;
|
||||||
|
userId?: number;
|
||||||
|
|
||||||
constructor(options: Dependence) {
|
constructor(options: Dependence) {
|
||||||
this.id = options.id;
|
this.id = options.id;
|
||||||
|
|
@ -21,6 +22,7 @@ export class Dependence {
|
||||||
this.name = options.name.trim();
|
this.name = options.name.trim();
|
||||||
this.log = options.log || [];
|
this.log = options.log || [];
|
||||||
this.remark = options.remark || '';
|
this.remark = options.remark || '';
|
||||||
|
this.userId = options.userId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,5 +61,6 @@ export const DependenceModel = sequelize.define<DependenceInstance>(
|
||||||
status: DataTypes.NUMBER,
|
status: DataTypes.NUMBER,
|
||||||
log: DataTypes.JSON,
|
log: DataTypes.JSON,
|
||||||
remark: DataTypes.STRING,
|
remark: DataTypes.STRING,
|
||||||
|
userId: { type: DataTypes.NUMBER, allowNull: true },
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ export class Env {
|
||||||
position?: number;
|
position?: number;
|
||||||
name?: string;
|
name?: string;
|
||||||
remarks?: string;
|
remarks?: string;
|
||||||
|
userId?: number;
|
||||||
|
|
||||||
constructor(options: Env) {
|
constructor(options: Env) {
|
||||||
this.value = options.value;
|
this.value = options.value;
|
||||||
|
|
@ -21,6 +22,7 @@ export class Env {
|
||||||
this.position = options.position;
|
this.position = options.position;
|
||||||
this.name = options.name;
|
this.name = options.name;
|
||||||
this.remarks = options.remarks || '';
|
this.remarks = options.remarks || '';
|
||||||
|
this.userId = options.userId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,4 +44,5 @@ export const EnvModel = sequelize.define<EnvInstance>('Env', {
|
||||||
position: DataTypes.NUMBER,
|
position: DataTypes.NUMBER,
|
||||||
name: { type: DataTypes.STRING, unique: 'compositeIndex' },
|
name: { type: DataTypes.STRING, unique: 'compositeIndex' },
|
||||||
remarks: DataTypes.STRING,
|
remarks: DataTypes.STRING,
|
||||||
|
userId: { type: DataTypes.NUMBER, allowNull: true },
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ export class Subscription {
|
||||||
proxy?: string;
|
proxy?: string;
|
||||||
autoAddCron?: 1 | 0;
|
autoAddCron?: 1 | 0;
|
||||||
autoDelCron?: 1 | 0;
|
autoDelCron?: 1 | 0;
|
||||||
|
userId?: number;
|
||||||
|
|
||||||
constructor(options: Subscription) {
|
constructor(options: Subscription) {
|
||||||
this.id = options.id;
|
this.id = options.id;
|
||||||
|
|
@ -60,6 +61,7 @@ export class Subscription {
|
||||||
this.proxy = options.proxy;
|
this.proxy = options.proxy;
|
||||||
this.autoAddCron = options.autoAddCron ? 1 : 0;
|
this.autoAddCron = options.autoAddCron ? 1 : 0;
|
||||||
this.autoDelCron = options.autoDelCron ? 1 : 0;
|
this.autoDelCron = options.autoDelCron ? 1 : 0;
|
||||||
|
this.userId = options.userId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,5 +113,6 @@ export const SubscriptionModel = sequelize.define<SubscriptionInstance>(
|
||||||
proxy: { type: DataTypes.STRING, allowNull: true },
|
proxy: { type: DataTypes.STRING, allowNull: true },
|
||||||
autoAddCron: { type: DataTypes.NUMBER, allowNull: true },
|
autoAddCron: { type: DataTypes.NUMBER, allowNull: true },
|
||||||
autoDelCron: { type: DataTypes.NUMBER, allowNull: true },
|
autoDelCron: { type: DataTypes.NUMBER, allowNull: true },
|
||||||
|
userId: { type: DataTypes.NUMBER, allowNull: true },
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
56
back/data/user.ts
Normal file
56
back/data/user.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { sequelize } from '.';
|
||||||
|
import { DataTypes, Model } from 'sequelize';
|
||||||
|
|
||||||
|
export class User {
|
||||||
|
id?: number;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
role: UserRole;
|
||||||
|
status: UserStatus;
|
||||||
|
createdAt?: Date;
|
||||||
|
updatedAt?: Date;
|
||||||
|
|
||||||
|
constructor(options: User) {
|
||||||
|
this.id = options.id;
|
||||||
|
this.username = options.username;
|
||||||
|
this.password = options.password;
|
||||||
|
this.role = options.role || UserRole.user;
|
||||||
|
this.status =
|
||||||
|
typeof options.status === 'number' && UserStatus[options.status]
|
||||||
|
? options.status
|
||||||
|
: UserStatus.active;
|
||||||
|
this.createdAt = options.createdAt;
|
||||||
|
this.updatedAt = options.updatedAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum UserRole {
|
||||||
|
'admin' = 0,
|
||||||
|
'user' = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum UserStatus {
|
||||||
|
'active' = 0,
|
||||||
|
'disabled' = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserInstance extends Model<User, User>, User {}
|
||||||
|
export const UserModel = sequelize.define<UserInstance>('User', {
|
||||||
|
username: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
unique: true,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
type: DataTypes.NUMBER,
|
||||||
|
defaultValue: UserRole.user,
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: DataTypes.NUMBER,
|
||||||
|
defaultValue: UserStatus.active,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -42,6 +42,18 @@ export default ({ app }: { app: Application }) => {
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Extract userId and role from JWT
|
||||||
|
app.use((req: Request, res, next) => {
|
||||||
|
if (req.auth) {
|
||||||
|
const payload = req.auth as any;
|
||||||
|
req.user = {
|
||||||
|
userId: payload.userId,
|
||||||
|
role: payload.role,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
|
||||||
app.use(async (req: Request, res, next) => {
|
app.use(async (req: Request, res, next) => {
|
||||||
if (!['/open/', '/api/'].some((x) => req.path.startsWith(x))) {
|
if (!['/open/', '/api/'].some((x) => req.path.startsWith(x))) {
|
||||||
return next();
|
return next();
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,17 @@ import uniq from 'lodash/uniq';
|
||||||
import pickBy from 'lodash/pickBy';
|
import pickBy from 'lodash/pickBy';
|
||||||
import isNil from 'lodash/isNil';
|
import isNil from 'lodash/isNil';
|
||||||
import { shareStore } from '../shared/store';
|
import { shareStore } from '../shared/store';
|
||||||
|
import UserManagementService from './userManagement';
|
||||||
|
import { UserRole } from '../data/user';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class UserService {
|
export default class UserService {
|
||||||
@Inject((type) => NotificationService)
|
@Inject((type) => NotificationService)
|
||||||
private notificationService!: NotificationService;
|
private notificationService!: NotificationService;
|
||||||
|
|
||||||
|
@Inject((type) => UserManagementService)
|
||||||
|
private userManagementService!: UserManagementService;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject('logger') private logger: winston.Logger,
|
@Inject('logger') private logger: winston.Logger,
|
||||||
private scheduleService: ScheduleService,
|
private scheduleService: ScheduleService,
|
||||||
|
|
@ -93,27 +98,57 @@ export default class UserService {
|
||||||
const { country, province, city, isp } = ipAddress;
|
const { country, province, city, isp } = ipAddress;
|
||||||
address = uniq([country, province, city, isp]).filter(Boolean).join(' ');
|
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 data = createRandomString(50, 100);
|
||||||
const expiration = twoFactorActivated ? '60d' : '20d';
|
const expiration = (isSystemAdmin && twoFactorActivated) ? '60d' : '20d';
|
||||||
let token = jwt.sign({ data }, config.jwt.secret, {
|
let token = jwt.sign(
|
||||||
|
{ data, userId, role: userRole },
|
||||||
|
config.jwt.secret,
|
||||||
|
{
|
||||||
expiresIn: config.jwt.expiresIn || expiration,
|
expiresIn: config.jwt.expiresIn || expiration,
|
||||||
algorithm: 'HS384',
|
algorithm: 'HS384',
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.updateAuthInfo(content, {
|
// Only update authInfo for system admin
|
||||||
token,
|
if (isSystemAdmin) {
|
||||||
tokens: {
|
await this.updateAuthInfo(content, {
|
||||||
...tokens,
|
token,
|
||||||
[req.platform]: token,
|
tokens: {
|
||||||
},
|
...tokens,
|
||||||
lastlogon: timestamp,
|
[req.platform]: token,
|
||||||
retries: 0,
|
},
|
||||||
lastip: ip,
|
lastlogon: timestamp,
|
||||||
lastaddr: address,
|
retries: 0,
|
||||||
platform: req.platform,
|
lastip: ip,
|
||||||
isTwoFactorChecking: false,
|
lastaddr: address,
|
||||||
});
|
platform: req.platform,
|
||||||
|
isTwoFactorChecking: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.notificationService.notify(
|
this.notificationService.notify(
|
||||||
'登录通知',
|
'登录通知',
|
||||||
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${
|
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}在 ${address} ${
|
||||||
|
|
|
||||||
104
back/services/userManagement.ts
Normal file
104
back/services/userManagement.ts
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
back/types/express.d.ts
vendored
5
back/types/express.d.ts
vendored
|
|
@ -6,6 +6,11 @@ declare global {
|
||||||
namespace Express {
|
namespace Express {
|
||||||
interface Request {
|
interface Request {
|
||||||
platform: 'desktop' | 'mobile';
|
platform: 'desktop' | 'mobile';
|
||||||
|
user?: {
|
||||||
|
userId?: number;
|
||||||
|
role?: number;
|
||||||
|
};
|
||||||
|
auth?: any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user