mirror of
https://github.com/whyour/qinglong.git
synced 2025-11-08 15:06:08 +08:00
Security improvements: Fix ownership checks, add password hashing with bcrypt
Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>
This commit is contained in:
parent
f355b4e441
commit
b2b1777c6b
|
|
@ -8,7 +8,7 @@ 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) {
|
||||
if (req.user && req.user.role === UserRole.admin) {
|
||||
return next();
|
||||
}
|
||||
return res.status(403).send({ code: 403, message: '需要管理员权限' });
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export default class CronService {
|
|||
attributes: ['id', 'userId'],
|
||||
});
|
||||
const unauthorized = crons.filter(
|
||||
(cron) => cron.userId !== userId && cron.userId !== undefined
|
||||
(cron) => cron.userId !== undefined && cron.userId !== userId
|
||||
);
|
||||
if (unauthorized.length > 0) {
|
||||
throw new Error('无权限操作该定时任务');
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export default class EnvService {
|
|||
attributes: ['id', 'userId'],
|
||||
});
|
||||
const unauthorized = envs.filter(
|
||||
(env) => env.userId !== userId && env.userId !== undefined
|
||||
(env) => env.userId !== undefined && env.userId !== userId
|
||||
);
|
||||
if (unauthorized.length > 0) {
|
||||
throw new Error('无权限操作该环境变量');
|
||||
|
|
|
|||
|
|
@ -2,11 +2,21 @@ import { Service, Inject } from 'typedi';
|
|||
import winston from 'winston';
|
||||
import { User, UserModel, UserRole, UserStatus } from '../data/user';
|
||||
import { Op } from 'sequelize';
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
@Service()
|
||||
export default class UserManagementService {
|
||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||
|
||||
private async hashPassword(password: string): Promise<string> {
|
||||
const saltRounds = 10;
|
||||
return bcrypt.hash(password, saltRounds);
|
||||
}
|
||||
|
||||
private async verifyPassword(password: string, hash: string): Promise<boolean> {
|
||||
return bcrypt.compare(password, hash);
|
||||
}
|
||||
|
||||
public async list(searchText?: string): Promise<User[]> {
|
||||
let query: any = {};
|
||||
if (searchText) {
|
||||
|
|
@ -40,11 +50,15 @@ export default class UserManagementService {
|
|||
throw new Error('用户名已存在');
|
||||
}
|
||||
|
||||
if (payload.password === 'admin') {
|
||||
throw new Error('密码不能设置为admin');
|
||||
if (payload.password.length < 6) {
|
||||
throw new Error('密码长度至少为6位');
|
||||
}
|
||||
|
||||
const doc = await UserModel.create(payload);
|
||||
// Hash the password before storing
|
||||
const hashedPassword = await this.hashPassword(payload.password);
|
||||
const userWithHashedPassword = { ...payload, password: hashedPassword };
|
||||
|
||||
const doc = await UserModel.create(userWithHashedPassword);
|
||||
return doc.get({ plain: true });
|
||||
}
|
||||
|
||||
|
|
@ -58,8 +72,8 @@ export default class UserManagementService {
|
|||
throw new Error('用户不存在');
|
||||
}
|
||||
|
||||
if (payload.password === 'admin') {
|
||||
throw new Error('密码不能设置为admin');
|
||||
if (payload.password && payload.password.length < 6) {
|
||||
throw new Error('密码长度至少为6位');
|
||||
}
|
||||
|
||||
// Check if username is being changed and if new username already exists
|
||||
|
|
@ -70,7 +84,13 @@ export default class UserManagementService {
|
|||
}
|
||||
}
|
||||
|
||||
const [, [updated]] = await UserModel.update(payload, {
|
||||
// Hash the password if it's being updated
|
||||
const updatePayload = { ...payload };
|
||||
if (payload.password) {
|
||||
updatePayload.password = await this.hashPassword(payload.password);
|
||||
}
|
||||
|
||||
const [, [updated]] = await UserModel.update(updatePayload, {
|
||||
where: { id: payload.id },
|
||||
returning: true,
|
||||
});
|
||||
|
|
@ -91,7 +111,8 @@ export default class UserManagementService {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (user.password !== password) {
|
||||
const isPasswordValid = await this.verifyPassword(password, user.password);
|
||||
if (!isPasswordValid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
42
package.json
42
package.json
|
|
@ -54,12 +54,16 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^2.10.0",
|
||||
"@grpc/grpc-js": "^1.14.0",
|
||||
"@grpc/proto-loader": "^0.8.0",
|
||||
"@keyv/sqlite": "^4.0.1",
|
||||
"@otplib/preset-default": "^12.0.1",
|
||||
"bcrypt": "^6.0.0",
|
||||
"body-parser": "^1.20.3",
|
||||
"celebrate": "^15.0.3",
|
||||
"chokidar": "^4.0.1",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"cron-parser": "^4.9.0",
|
||||
"cross-spawn": "^7.0.6",
|
||||
|
|
@ -69,51 +73,49 @@
|
|||
"express-jwt": "^8.4.1",
|
||||
"express-rate-limit": "^7.4.1",
|
||||
"express-urlrewrite": "^2.0.3",
|
||||
"undici": "^7.9.0",
|
||||
"helmet": "^8.1.0",
|
||||
"hpagent": "^1.2.0",
|
||||
"http-proxy-middleware": "^3.0.3",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"ip2region": "2.3.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"keyv": "^5.2.3",
|
||||
"lodash": "^4.17.21",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"node-schedule": "^2.1.0",
|
||||
"nodemailer": "^6.9.16",
|
||||
"p-queue-cjs": "7.3.4",
|
||||
"@bufbuild/protobuf": "^2.10.0",
|
||||
"proper-lockfile": "^4.1.2",
|
||||
"ps-tree": "^1.2.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"request-ip": "3.3.0",
|
||||
"sequelize": "^6.37.5",
|
||||
"sockjs": "^0.3.24",
|
||||
"sqlite3": "git+https://github.com/whyour/node-sqlite3.git#v1.0.3",
|
||||
"toad-scheduler": "^3.0.1",
|
||||
"typedi": "^0.10.0",
|
||||
"undici": "^7.9.0",
|
||||
"uuid": "^11.0.3",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0",
|
||||
"request-ip": "3.3.0",
|
||||
"ip2region": "2.3.0",
|
||||
"keyv": "^5.2.3",
|
||||
"@keyv/sqlite": "^4.0.1",
|
||||
"proper-lockfile": "^4.1.2",
|
||||
"compression": "^1.7.4",
|
||||
"helmet": "^8.1.0"
|
||||
"winston-daily-rotate-file": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"moment": "2.30.1",
|
||||
"@ant-design/icons": "^5.0.1",
|
||||
"@ant-design/pro-layout": "6.38.22",
|
||||
"@codemirror/view": "^6.34.1",
|
||||
"@codemirror/state": "^6.4.1",
|
||||
"@codemirror/view": "^6.34.1",
|
||||
"@monaco-editor/react": "4.2.1",
|
||||
"@react-hook/resize-observer": "^2.0.2",
|
||||
"react-router-dom": "6.26.1",
|
||||
"@types/bcrypt": "^6.0.0",
|
||||
"@types/body-parser": "^1.19.2",
|
||||
"@types/compression": "^1.7.2",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/cross-spawn": "^6.0.2",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/express-jwt": "^6.0.4",
|
||||
"@types/file-saver": "2.0.2",
|
||||
"@types/helmet": "^4.0.0",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"@types/lodash": "^4.14.185",
|
||||
|
|
@ -121,17 +123,17 @@
|
|||
"@types/node": "^17.0.21",
|
||||
"@types/node-schedule": "^1.3.2",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/proper-lockfile": "^4.1.4",
|
||||
"@types/ps-tree": "^1.1.6",
|
||||
"@types/qrcode.react": "^1.0.2",
|
||||
"@types/react": "^18.0.20",
|
||||
"@types/react-copy-to-clipboard": "^5.0.4",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/request-ip": "0.0.41",
|
||||
"@types/serve-handler": "^6.1.1",
|
||||
"@types/sockjs": "^0.3.33",
|
||||
"@types/sockjs-client": "^1.5.1",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/request-ip": "0.0.41",
|
||||
"@types/proper-lockfile": "^4.1.4",
|
||||
"@types/ps-tree": "^1.1.6",
|
||||
"@uiw/codemirror-extensions-langs": "^4.21.9",
|
||||
"@uiw/react-codemirror": "^4.21.9",
|
||||
"@umijs/max": "^4.4.4",
|
||||
|
|
@ -143,9 +145,9 @@
|
|||
"axios": "^1.4.0",
|
||||
"compression-webpack-plugin": "9.2.0",
|
||||
"concurrently": "^7.0.0",
|
||||
"react-hotkeys-hook": "^4.6.1",
|
||||
"file-saver": "2.0.2",
|
||||
"lint-staged": "^13.0.3",
|
||||
"moment": "2.30.1",
|
||||
"monaco-editor": "0.33.0",
|
||||
"nodemon": "^3.0.1",
|
||||
"prettier": "^2.5.1",
|
||||
|
|
@ -161,7 +163,9 @@
|
|||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-hotkeys-hook": "^4.6.1",
|
||||
"react-intl-universal": "^2.12.0",
|
||||
"react-router-dom": "6.26.1",
|
||||
"react-split-pane": "^0.1.92",
|
||||
"sockjs-client": "^1.6.0",
|
||||
"ts-node": "^10.9.2",
|
||||
|
|
@ -169,8 +173,6 @@
|
|||
"tslib": "^2.4.0",
|
||||
"typescript": "5.2.2",
|
||||
"vh-check": "^2.0.5",
|
||||
"virtualizedtableforantd4": "1.3.0",
|
||||
"@types/compression": "^1.7.2",
|
||||
"@types/helmet": "^4.0.0"
|
||||
"virtualizedtableforantd4": "1.3.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
lockfileVersion: '6.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
sqlite3: git+https://github.com/whyour/node-sqlite3.git#v1.0.3
|
||||
|
||||
|
|
@ -23,6 +19,9 @@ dependencies:
|
|||
'@otplib/preset-default':
|
||||
specifier: ^12.0.1
|
||||
version: 12.0.1
|
||||
bcrypt:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
body-parser:
|
||||
specifier: ^1.20.3
|
||||
version: 1.20.3
|
||||
|
|
@ -160,6 +159,9 @@ devDependencies:
|
|||
'@react-hook/resize-observer':
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2(react@18.3.1)
|
||||
'@types/bcrypt':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
'@types/body-parser':
|
||||
specifier: ^1.19.2
|
||||
version: 1.19.5
|
||||
|
|
@ -3786,6 +3788,12 @@ packages:
|
|||
'@babel/types': 7.26.0
|
||||
dev: true
|
||||
|
||||
/@types/bcrypt@6.0.0:
|
||||
resolution: {integrity: sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==}
|
||||
dependencies:
|
||||
'@types/node': 17.0.45
|
||||
dev: true
|
||||
|
||||
/@types/body-parser@1.19.5:
|
||||
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
|
||||
dependencies:
|
||||
|
|
@ -5747,6 +5755,15 @@ packages:
|
|||
/base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
/bcrypt@6.0.0:
|
||||
resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==}
|
||||
engines: {node: '>= 18'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
node-addon-api: 8.5.0
|
||||
node-gyp-build: 4.8.4
|
||||
dev: false
|
||||
|
||||
/before@0.0.1:
|
||||
resolution: {integrity: sha512-1J5SWbkoVJH9DTALN8igB4p+nPKZzPrJ/HomqBDLpfUvDXCdjdBmBUcH5McZfur0lftVssVU6BZug5NYh87zTw==}
|
||||
dev: true
|
||||
|
|
@ -10212,6 +10229,11 @@ packages:
|
|||
resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==}
|
||||
dev: false
|
||||
|
||||
/node-addon-api@8.5.0:
|
||||
resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==}
|
||||
engines: {node: ^18 || ^20 || >= 21}
|
||||
dev: false
|
||||
|
||||
/node-domexception@1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
|
|
@ -10238,6 +10260,11 @@ packages:
|
|||
formdata-polyfill: 4.0.10
|
||||
dev: true
|
||||
|
||||
/node-gyp-build@4.8.4:
|
||||
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/node-gyp@8.4.1:
|
||||
resolution: {integrity: sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==}
|
||||
engines: {node: '>= 10.12.0'}
|
||||
|
|
@ -15213,3 +15240,7 @@ packages:
|
|||
- encoding
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user