fix: harden token search with pagination, rate limiting and input validation

- Add configurable per-user token creation limit (max_user_tokens)
- Sanitize search input patterns to prevent expensive queries
- Add per-user search rate limiting (by user ID)
- Add pagination to search endpoint with strict page size cap
- Skip empty search fields instead of matching nothing
- Hide internal errors from API responses
- Fix Interface2String float64 formatting causing config parse failures
- Add float-string fallback in config system for int/uint fields
This commit is contained in:
CaIon
2026-02-06 17:47:34 +08:00
parent a02c7b903a
commit 327cc5fa23
11 changed files with 282 additions and 20 deletions

View File

@@ -40,6 +40,7 @@ export const useTokensData = (openFluentNotification) => {
const [tokenCount, setTokenCount] = useState(0);
const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
const [searching, setSearching] = useState(false);
const [searchMode, setSearchMode] = useState(false); // 是否处于搜索结果视图
// Selection state
const [selectedKeys, setSelectedKeys] = useState([]);
@@ -91,6 +92,7 @@ export const useTokensData = (openFluentNotification) => {
// Load tokens function
const loadTokens = async (page = 1, size = pageSize) => {
setLoading(true);
setSearchMode(false);
const res = await API.get(`/api/token/?p=${page}&size=${size}`);
const { success, message, data } = res.data;
if (success) {
@@ -188,21 +190,21 @@ export const useTokensData = (openFluentNotification) => {
};
// Search tokens function
const searchTokens = async () => {
const searchTokens = async (page = 1, size = pageSize) => {
const { searchKeyword, searchToken } = getFormValues();
if (searchKeyword === '' && searchToken === '') {
setSearchMode(false);
await loadTokens(1);
return;
}
setSearching(true);
const res = await API.get(
`/api/token/search?keyword=${searchKeyword}&token=${searchToken}`,
`/api/token/search?keyword=${encodeURIComponent(searchKeyword)}&token=${encodeURIComponent(searchToken)}&p=${page}&size=${size}`,
);
const { success, message, data } = res.data;
if (success) {
setTokens(data);
setTokenCount(data.length);
setActivePage(1);
setSearchMode(true);
syncPageData(data);
} else {
showError(message);
}
@@ -226,12 +228,20 @@ export const useTokensData = (openFluentNotification) => {
// Page handlers
const handlePageChange = (page) => {
loadTokens(page, pageSize).then();
if (searchMode) {
searchTokens(page, pageSize).then();
} else {
loadTokens(page, pageSize).then();
}
};
const handlePageSizeChange = async (size) => {
setPageSize(size);
await loadTokens(1, size);
if (searchMode) {
await searchTokens(1, size);
} else {
await loadTokens(1, size);
}
};
// Row selection handlers