mirror of
https://github.com/whyour/qinglong.git
synced 2026-11-08 04:23:05 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a335fc506 |
@@ -310,20 +310,6 @@ export default (app: Router) => {
|
||||
},
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/notification/init',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const userService = Container.get(UserService);
|
||||
const result = await userService.updateNotificationMode(req.body);
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/avatar',
|
||||
upload.single('avatar'),
|
||||
|
||||
@@ -180,12 +180,10 @@ export default {
|
||||
'/api/user/two-factor/login',
|
||||
'/api/system',
|
||||
'/api/user/init',
|
||||
'/api/user/notification/init',
|
||||
'/open/user/login',
|
||||
'/open/user/two-factor/login',
|
||||
'/open/system',
|
||||
'/open/user/init',
|
||||
'/open/user/notification/init',
|
||||
],
|
||||
versionFile,
|
||||
lastVersionFile,
|
||||
|
||||
+24
-37
@@ -191,14 +191,7 @@ export default ({ app }: { app: Application }) => {
|
||||
|
||||
app.use(async (req, res, next) => {
|
||||
const pathLower = req.path.toLowerCase();
|
||||
if (
|
||||
![
|
||||
'/api/user/init',
|
||||
'/api/user/notification/init',
|
||||
'/open/user/init',
|
||||
'/open/user/notification/init',
|
||||
].includes(pathLower)
|
||||
) {
|
||||
if (!['/api/user/init', '/open/user/init'].includes(pathLower)) {
|
||||
return next();
|
||||
}
|
||||
const authInfo = (await shareStore.getAuthInfo()) || ({} as AuthInfo);
|
||||
@@ -238,37 +231,23 @@ export default ({ app }: { app: Application }) => {
|
||||
|
||||
app.use(
|
||||
(
|
||||
err: Error & { status: number },
|
||||
err: Error & { status: number; code?: string },
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
) => {
|
||||
if (err.name === 'UnauthorizedError') {
|
||||
return res
|
||||
.status(err.status)
|
||||
.send({ code: 401, message: err.message })
|
||||
.end();
|
||||
}
|
||||
return next(err);
|
||||
},
|
||||
);
|
||||
|
||||
app.use(
|
||||
(
|
||||
err: Error & { errors: any[] },
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
) => {
|
||||
if (err.name.includes('Sequelize')) {
|
||||
return res
|
||||
.status(500)
|
||||
.send({
|
||||
code: 400,
|
||||
message: `${err.message}`,
|
||||
errors: err.errors,
|
||||
})
|
||||
.end();
|
||||
let message = err.message;
|
||||
if (config.isProduction) {
|
||||
if (err.code === 'credentials_bad_scheme') {
|
||||
message = t('暂无权限');
|
||||
} else if (err.code === 'credentials_required') {
|
||||
message = t('请先登录');
|
||||
} else {
|
||||
message = t('Token 已失效');
|
||||
}
|
||||
}
|
||||
return res.status(401).send({ code: 401, message }).end();
|
||||
}
|
||||
return next(err);
|
||||
},
|
||||
@@ -281,10 +260,18 @@ export default ({ app }: { app: Application }) => {
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
) => {
|
||||
res.status(err.status || 500);
|
||||
const status =
|
||||
Number.isInteger(err.status) && err.status >= 400 && err.status < 600
|
||||
? err.status
|
||||
: 500;
|
||||
if (status >= 500) {
|
||||
console.error('HTTP request failed:', err);
|
||||
}
|
||||
res.status(status);
|
||||
res.json({
|
||||
code: err.status || 500,
|
||||
message: err.message,
|
||||
code: status,
|
||||
message:
|
||||
config.isProduction && status >= 500 ? t('未知错误') : err.message,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -224,7 +224,6 @@ System/user operations below execute remotely through the panel API. They do not
|
||||
| `ql user notification-get ` | GET | `user/notification` | --query |
|
||||
| `ql user notification-set ` | PUT | `user/notification` | --data, --query |
|
||||
| `ql user init ` | PUT | `user/init` | --data, --query |
|
||||
| `ql user notification-init ` | PUT | `user/notification/init` | --data, --query |
|
||||
| `ql user avatar ` | PUT | `user/avatar` | --data, --file (avatar), --query |
|
||||
| `ql system apply-reload ` | PUT | `update/reload` | --query |
|
||||
| `ql system apply-system ` | PUT | `update/system` | --query |
|
||||
|
||||
@@ -608,13 +608,6 @@ export const openOperations: readonly OpenOperation[] = [
|
||||
body: 'json',
|
||||
anonymous: true,
|
||||
},
|
||||
{
|
||||
name: 'user notification-init',
|
||||
method: 'PUT',
|
||||
path: 'user/notification/init',
|
||||
body: 'json',
|
||||
anonymous: true,
|
||||
},
|
||||
{
|
||||
name: 'user avatar',
|
||||
method: 'PUT',
|
||||
|
||||
@@ -1,38 +1,21 @@
|
||||
import intl from 'react-intl-universal';
|
||||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Row,
|
||||
Input,
|
||||
Form,
|
||||
message,
|
||||
Typography,
|
||||
Steps,
|
||||
Select,
|
||||
} from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Input, Form, Typography, Steps } from 'antd';
|
||||
import config from '@/utils/config';
|
||||
import { history } from '@umijs/max';
|
||||
import styles from './index.less';
|
||||
import { request } from '@/utils/http';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
const { Step } = Steps;
|
||||
const { Option } = Select;
|
||||
const { Link } = Typography;
|
||||
|
||||
const Initialization = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [current, setCurrent] = React.useState(0);
|
||||
const [fields, setFields] = useState<any[]>([]);
|
||||
|
||||
const next = () => {
|
||||
setCurrent(current + 1);
|
||||
};
|
||||
|
||||
const prev = () => {
|
||||
setCurrent(current - 1);
|
||||
};
|
||||
|
||||
const submitAccountSetting = (values: any) => {
|
||||
setLoading(true);
|
||||
request
|
||||
@@ -48,23 +31,6 @@ const Initialization = () => {
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
const submitNotification = (values: any) => {
|
||||
setLoading(true);
|
||||
request
|
||||
.put(`${config.apiPrefix}user/notification/init`, values)
|
||||
.then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
next();
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
const notificationModeChange = (value: string) => {
|
||||
const _fields = (config.notificationModeMap as any)[value];
|
||||
setFields(_fields || []);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.removeItem(config.authKey);
|
||||
}, []);
|
||||
@@ -95,53 +61,6 @@ const Initialization = () => {
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: intl.get('通知设置'),
|
||||
content: (
|
||||
<Form onFinish={submitNotification} layout="vertical">
|
||||
<Form.Item
|
||||
label={intl.get('通知方式')}
|
||||
name="type"
|
||||
rules={[{ required: true, message: intl.get('请选择通知方式') }]}
|
||||
style={{ maxWidth: 350 }}
|
||||
>
|
||||
<Select
|
||||
onChange={notificationModeChange}
|
||||
placeholder={intl.get('请选择通知方式')}
|
||||
>
|
||||
{config.notificationModes
|
||||
.filter((x) => x.value !== 'closed')
|
||||
.map((x) => (
|
||||
<Option key={x.value} value={x.value}>
|
||||
{x.label}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
{fields.map((x) => (
|
||||
<Form.Item
|
||||
key={x.label}
|
||||
label={x.label}
|
||||
name={x.label}
|
||||
extra={x.tip}
|
||||
rules={[{ required: x.required }]}
|
||||
style={{ maxWidth: 400 }}
|
||||
>
|
||||
<Input.TextArea
|
||||
autoSize={{ minRows: 1, maxRows: 5 }}
|
||||
placeholder={`${intl.get('请输入')} ${x.label}`}
|
||||
/>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Button type="primary" htmlType="submit" loading={loading}>
|
||||
{intl.get('保存')}
|
||||
</Button>
|
||||
<Button type="link" htmlType="button" onClick={() => next()}>
|
||||
{intl.get('跳过')}
|
||||
</Button>
|
||||
</Form>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: intl.get('账户设置'),
|
||||
content: (
|
||||
|
||||
@@ -90,7 +90,6 @@ const apiWhiteList = [
|
||||
`${config.baseUrl}api/user/two-factor/login`,
|
||||
`${config.baseUrl}api/system`,
|
||||
`${config.baseUrl}api/user/init`,
|
||||
`${config.baseUrl}api/user/notification/init`,
|
||||
];
|
||||
|
||||
_request.interceptors.request.use((_config) => {
|
||||
|
||||
@@ -46,6 +46,7 @@ test('HTTP authentication protects init, scopes, expired sessions and config sec
|
||||
writePathList: [path.join(tmp, 'scripts')],
|
||||
blackFileList: ['auth.json', 'grpc'],
|
||||
baseUrl: '/panel',
|
||||
isProduction: true,
|
||||
};
|
||||
const apps = [
|
||||
{
|
||||
@@ -105,6 +106,7 @@ test('HTTP authentication protects init, scopes, expired sessions and config sec
|
||||
let envWriteCalls = 0;
|
||||
let scopedMutationCalls = 0;
|
||||
let initialized = 0;
|
||||
let notificationCalls = 0;
|
||||
const User = class {};
|
||||
const Script = class {
|
||||
getFile(folder, filename) {
|
||||
@@ -121,6 +123,10 @@ test('HTTP authentication protects init, scopes, expired sessions and config sec
|
||||
return { code: 200 };
|
||||
},
|
||||
getAuthInfo: async () => auth,
|
||||
updateNotificationMode: async () => {
|
||||
notificationCalls++;
|
||||
return { code: 200 };
|
||||
},
|
||||
};
|
||||
const mocks = {
|
||||
'../config': config,
|
||||
@@ -209,6 +215,15 @@ test('HTTP authentication protects init, scopes, expired sessions and config sec
|
||||
commandCalls++;
|
||||
res.json({ code: 200 });
|
||||
});
|
||||
router.get('/security-test/error', (_req, _res, next) =>
|
||||
next(new Error('sensitive server detail')),
|
||||
);
|
||||
router.get('/security-test/db-error', (_req, _res, next) => {
|
||||
const error = new Error('sensitive database detail');
|
||||
error.name = 'SequelizeDatabaseError';
|
||||
error.errors = [{ message: 'sensitive row value' }];
|
||||
next(error);
|
||||
});
|
||||
return router;
|
||||
};
|
||||
const app = express();
|
||||
@@ -238,6 +253,36 @@ test('HTTP authentication protects init, scopes, expired sessions and config sec
|
||||
);
|
||||
return { status: response.status, body: await response.json() };
|
||||
};
|
||||
auth.username = 'admin';
|
||||
auth.password = 'admin';
|
||||
for (const prefix of ['/api', '/open', '/panel/api', '/panel/open']) {
|
||||
const response = await request(
|
||||
`${prefix}/user/notification/init`,
|
||||
undefined,
|
||||
'PUT',
|
||||
{ type: 'webhook', webhookUrl: 'http://127.0.0.1/?title=$title' },
|
||||
);
|
||||
assert.equal(response.status, 401, prefix);
|
||||
}
|
||||
assert.equal(notificationCalls, 0);
|
||||
auth.username = 'owner';
|
||||
auth.password = 'configured';
|
||||
assert.equal(
|
||||
(await request('/api/user/notification', valid, 'PUT', {})).body.code,
|
||||
200,
|
||||
);
|
||||
assert.equal(notificationCalls, 1);
|
||||
for (const route of ['/security-test/error', '/security-test/db-error']) {
|
||||
const response = await request(`/api${route}`, valid);
|
||||
assert.equal(response.status, 500);
|
||||
assert.deepEqual(response.body, { code: 500, message: '未知错误' });
|
||||
}
|
||||
const malformedToken = await request('/api/envs', 'not-a-jwt');
|
||||
assert.equal(malformedToken.status, 401);
|
||||
assert.deepEqual(malformedToken.body, {
|
||||
code: 401,
|
||||
message: 'Token 已失效',
|
||||
});
|
||||
for (const url of [
|
||||
'/api/user/init',
|
||||
'/open/user/init',
|
||||
|
||||
Reference in New Issue
Block a user