feat(security): Suppport for whitelisting and logout on 401#66
Conversation
Only auth endpoints used to trigger a logout on 401. A dashboard tab left open past session expiry kept polling with a dead token, and the resulting stream of failed requests looked like a brute-force attack to the security module, which repeatedly auto-blocked the operator's IP. The first 401 outside the login and setup pages now clears the session and returns to the login page, which also stops all pollers.
…shboard The security whitelist could only be edited through the API and the detection thresholds only through the config file. The Security view now lists whitelisted IPs, networks, and paths with add and remove controls, and the settings tab exposes the detection window, per-window limits, and auto-block duration. Threshold changes persist and apply to the running detector immediately.
Code Review SummaryThis PR adds comprehensive support for security whitelisting (IP, CIDR, and Paths) and dynamic configuration of detection thresholds. It also improves session management by handling 401 Unauthorized responses correctly in the API interceptor. 🚀 Key Improvements
💡 Minor Suggestions
|
| const saveThresholds = async () => { | ||
| savingThresholds.value = true; | ||
| try { | ||
| const changed = thresholdFields.filter((f) => thresholds[f.key] !== loadedThresholds.value[f.key]); |
There was a problem hiding this comment.
Sequential await calls for saving thresholds can be slow if there are many changes. While the current list is small, using Promise.all would improve performance by executing these independent network requests in parallel.
| const changed = thresholdFields.filter((f) => thresholds[f.key] !== loadedThresholds.value[f.key]); | |
| const changed = thresholdFields.filter((f) => thresholds[f.key] !== loadedThresholds.value[f.key]); | |
| await Promise.all(changed.map(field => configApi.set(`security.${field.key}`, thresholds[field.key]))); | |
| for (const field of changed) { | |
| loadedThresholds.value[field.key] = thresholds[field.key]; | |
| } |
Deploying flatrun-ui with
|
| Latest commit: |
a9910ee
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://c0900e9a.flatrun-ui.pages.dev |
| Branch Preview URL: | https://fix-security-session-and-whi.flatrun-ui.pages.dev |
| const failedURL: string = error.config?.url || ""; | ||
| const isSessionEndpoint = /\/auth\/|\/users\/me(\b|\/)/.test(failedURL); | ||
| if (isSessionEndpoint) { | ||
| const isLoginAttempt = failedURL.includes("/auth/login"); |
There was a problem hiding this comment.
The logic to prevent redirecting on the login page could be simplified. Checking window.location.pathname for the exact match or using a whitelist of non-auth paths is safer than partial string matches which might catch sub-routes unexpectedly.
| const isLoginAttempt = failedURL.includes("/auth/login"); | |
| const isLoginAttempt = failedURL.includes("/auth/login"); | |
| const onAuthPage = ["/login", "/setup"].some(path => window.location.pathname.startsWith(path)); | |
| if (!isLoginAttempt && !onAuthPage) { |
| }; | ||
|
|
||
| const handleAddWhitelistEntry = async () => { | ||
| if (!whitelistForm.value) return; |
There was a problem hiding this comment.
It's best practice to trim the whitelist value to prevent accidental whitespace leading to invalid IP or path entries which might not match correctly on the server.
| if (!whitelistForm.value) return; | |
| const val = whitelistForm.value.trim(); | |
| if (!val) return; | |
| addingWhitelistEntry.value = true; | |
| try { | |
| await securityStore.addWhitelistEntry({ | |
| value: val, |
No description provided.