mirror of
https://github.com/gethomepage/homepage.git
synced 2026-09-29 07:21:16 -07:00
Enhancement: better Crowdsec auth parsing, caching, and retries (#6419)
This commit is contained in:
@@ -25,13 +25,25 @@ async function login(widget, service) {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const dataParsed = JSON.parse(data);
|
let dataParsed;
|
||||||
|
try {
|
||||||
|
dataParsed = JSON.parse(data);
|
||||||
|
} catch {
|
||||||
|
logger.error("Failed to parse Crowdsec login response, status: %d", status);
|
||||||
|
cache.del(`${sessionTokenCacheKey}.${service}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(status === 200) || !dataParsed.token) {
|
if (status !== 200 || !dataParsed.token) {
|
||||||
logger.error("Failed to login to Crowdsec API, status: %d", status);
|
logger.error("Failed to login to Crowdsec API, status: %d", status);
|
||||||
cache.del(`${sessionTokenCacheKey}.${service}`);
|
cache.del(`${sessionTokenCacheKey}.${service}`);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
cache.put(`${sessionTokenCacheKey}.${service}`, dataParsed.token, new Date(dataParsed.expire) - new Date());
|
|
||||||
|
const ttl = Math.max(new Date(dataParsed.expire) - new Date(), 1);
|
||||||
|
cache.put(`${sessionTokenCacheKey}.${service}`, dataParsed.token, ttl);
|
||||||
|
|
||||||
|
return dataParsed.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function crowdsecProxyHandler(req, res) {
|
export default async function crowdsecProxyHandler(req, res) {
|
||||||
@@ -48,11 +60,10 @@ export default async function crowdsecProxyHandler(req, res) {
|
|||||||
return res.status(400).json({ error: "Invalid widget configuration" });
|
return res.status(400).json({ error: "Invalid widget configuration" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cache.get(`${sessionTokenCacheKey}.${service}`)) {
|
let token = cache.get(`${sessionTokenCacheKey}.${service}`);
|
||||||
await login(widget, service);
|
if (!token) {
|
||||||
|
token = await login(widget, service);
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = cache.get(`${sessionTokenCacheKey}.${service}`);
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return res.status(500).json({ error: "Failed to authenticate with Crowdsec" });
|
return res.status(500).json({ error: "Failed to authenticate with Crowdsec" });
|
||||||
}
|
}
|
||||||
@@ -71,7 +82,20 @@ export default async function crowdsecProxyHandler(req, res) {
|
|||||||
|
|
||||||
logger.debug("Calling Crowdsec API endpoint: %s", endpoint);
|
logger.debug("Calling Crowdsec API endpoint: %s", endpoint);
|
||||||
|
|
||||||
const [status, , data] = await httpProxy(url, params);
|
let [status, , data] = await httpProxy(url, params);
|
||||||
|
|
||||||
|
if (status === 401) {
|
||||||
|
logger.debug("Crowdsec API returned 401, refreshing token and retrying request");
|
||||||
|
cache.del(`${sessionTokenCacheKey}.${service}`);
|
||||||
|
const refreshedToken = await login(widget, service);
|
||||||
|
|
||||||
|
if (!refreshedToken) {
|
||||||
|
return res.status(500).json({ error: "Failed to authenticate with Crowdsec" });
|
||||||
|
}
|
||||||
|
|
||||||
|
params.headers.Authorization = `Bearer ${refreshedToken}`;
|
||||||
|
[status, , data] = await httpProxy(url, params);
|
||||||
|
}
|
||||||
|
|
||||||
if (status !== 200) {
|
if (status !== 200) {
|
||||||
logger.error("Error calling Crowdsec API: %d. Data: %s", status, data);
|
logger.error("Error calling Crowdsec API: %d. Data: %s", status, data);
|
||||||
|
|||||||
@@ -89,4 +89,76 @@ describe("widgets/crowdsec/proxy", () => {
|
|||||||
expect(res.statusCode).toBe(500);
|
expect(res.statusCode).toBe(500);
|
||||||
expect(res.body).toEqual({ error: "Failed to authenticate with Crowdsec" });
|
expect(res.body).toEqual({ error: "Failed to authenticate with Crowdsec" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("re-authenticates and retries once when API returns 401", async () => {
|
||||||
|
getServiceWidget.mockResolvedValue({
|
||||||
|
type: "crowdsec",
|
||||||
|
url: "http://cs",
|
||||||
|
username: "machine",
|
||||||
|
password: "pw",
|
||||||
|
});
|
||||||
|
|
||||||
|
httpProxy
|
||||||
|
.mockResolvedValueOnce([
|
||||||
|
200,
|
||||||
|
"application/json",
|
||||||
|
JSON.stringify({ token: "tok-old", expire: new Date(Date.now() + 60_000).toISOString() }),
|
||||||
|
])
|
||||||
|
.mockResolvedValueOnce([401, "application/json", Buffer.from("bad token")])
|
||||||
|
.mockResolvedValueOnce([
|
||||||
|
200,
|
||||||
|
"application/json",
|
||||||
|
JSON.stringify({ token: "tok-new", expire: new Date(Date.now() + 60_000).toISOString() }),
|
||||||
|
])
|
||||||
|
.mockResolvedValueOnce([200, "application/json", Buffer.from("data")]);
|
||||||
|
|
||||||
|
const req = { query: { group: "g", service: "svc", endpoint: "alerts", index: "0" } };
|
||||||
|
const res = createMockRes();
|
||||||
|
|
||||||
|
await crowdsecProxyHandler(req, res);
|
||||||
|
|
||||||
|
expect(httpProxy).toHaveBeenCalledTimes(4);
|
||||||
|
expect(httpProxy.mock.calls[3][1].headers.Authorization).toBe("Bearer tok-new");
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
expect(res.body).toEqual(Buffer.from("data"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns 500 when 401 refresh fails to get a new token", async () => {
|
||||||
|
getServiceWidget.mockResolvedValue({
|
||||||
|
type: "crowdsec",
|
||||||
|
url: "http://cs",
|
||||||
|
username: "machine",
|
||||||
|
password: "pw",
|
||||||
|
});
|
||||||
|
|
||||||
|
httpProxy
|
||||||
|
.mockResolvedValueOnce([
|
||||||
|
200,
|
||||||
|
"application/json",
|
||||||
|
JSON.stringify({ token: "tok-old", expire: new Date(Date.now() + 60_000).toISOString() }),
|
||||||
|
])
|
||||||
|
.mockResolvedValueOnce([401, "application/json", Buffer.from("bad token")])
|
||||||
|
.mockResolvedValueOnce([500, "application/json", JSON.stringify({ error: "no token" })]);
|
||||||
|
|
||||||
|
const req = { query: { group: "g", service: "svc", endpoint: "alerts", index: "0" } };
|
||||||
|
const res = createMockRes();
|
||||||
|
|
||||||
|
await crowdsecProxyHandler(req, res);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(500);
|
||||||
|
expect(res.body).toEqual({ error: "Failed to authenticate with Crowdsec" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns 500 when login response is not JSON", async () => {
|
||||||
|
getServiceWidget.mockResolvedValue({ type: "crowdsec", url: "http://cs", username: "machine", password: "pw" });
|
||||||
|
httpProxy.mockResolvedValueOnce([200, "text/plain", "not-json"]);
|
||||||
|
|
||||||
|
const req = { query: { group: "g", service: "svc", endpoint: "alerts", index: "0" } };
|
||||||
|
const res = createMockRes();
|
||||||
|
|
||||||
|
await crowdsecProxyHandler(req, res);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(500);
|
||||||
|
expect(res.body).toEqual({ error: "Failed to authenticate with Crowdsec" });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user