diff --git a/public/js/hash-worker.js b/public/js/hash-worker.js index 32775b6..77f5cbd 100644 --- a/public/js/hash-worker.js +++ b/public/js/hash-worker.js @@ -30,7 +30,6 @@ self.onmessage = async (event) => { nonce = data.nonce; const c = data.challenge; - // decode salt to Uint8Array const salt = new Uint8Array(c.s.length / 2); for (let i = 0; i < c.s.length; i += 2) { salt[i / 2] = parseInt(c.s.slice(i, i + 2), 16); @@ -99,7 +98,7 @@ const solve = async () => { self.postMessage({ type: "solved", nonce: solution.nonce }); active = false; } else { - if (Date.now() - lastNotify > 1000) { + if (Date.now() - lastNotify >= 500) { console.log("Last nonce", nonce, "Hashes", hashesSinceLastNotify); self.postMessage({ type: "progress", hashes: hashesSinceLastNotify }); lastNotify = Date.now(); diff --git a/src/admin/web/manage.ts b/src/admin/web/manage.ts index 62421ec..de52288 100644 --- a/src/admin/web/manage.ts +++ b/src/admin/web/manage.ts @@ -344,10 +344,11 @@ router.post("/maintenance", (req, res) => { case "setDifficulty": { const selected = req.body["pow-difficulty"]; const valid = ["low", "medium", "high", "extreme"]; - if (!selected || !valid.includes(selected)) { - throw new HttpError(400, "Invalid difficulty" + selected); + const isNumber = Number.isInteger(Number(selected)); + if (!selected || !valid.includes(selected) && !isNumber) { + throw new HttpError(400, "Invalid difficulty " + selected); } - config.powDifficultyLevel = selected; + config.powDifficultyLevel = isNumber ? Number(selected) : selected; invalidatePowChallenges(); break; } diff --git a/src/admin/web/views/admin_anti-abuse.ejs b/src/admin/web/views/admin_anti-abuse.ejs index 954839c..36c39e1 100644 --- a/src/admin/web/views/admin_anti-abuse.ejs +++ b/src/admin/web/views/admin_anti-abuse.ejs @@ -38,15 +38,20 @@

Difficulty Level

- Current: <%= difficulty %> - + +
+
Current Difficulty: <%= difficulty %>
<% } %>
@@ -63,15 +68,15 @@

IP Whitelists and Blacklists

- You can specify IP ranges to whitelist or blacklist from accessing the proxy. Note that changes here are not - persisted across server restarts. If you want to make changes permanent, you can copy the values to your deployment - configuration. -

-

- Entries can be specified as single addresses or + You can specify IP ranges to whitelist or blacklist from accessing the proxy. Entries can be specified as single + addresses or CIDR notation. IPv6 is supported but not recommended for use with the current version of the proxy.

+

+ Note: Changes here are not persisted across server restarts. If you want to make changes permanent, + you can copy the values to your deployment configuration. +

<% for (let i = 0; i < whitelists.length; i++) { %> <%- include("partials/admin-cidr-widget", { list: whitelists[i] }) %> <% } %> @@ -99,10 +104,25 @@
diff --git a/src/user/web/pow-captcha.ts b/src/user/web/pow-captcha.ts index 9d5ff8c..43b28be 100644 --- a/src/user/web/pow-captcha.ts +++ b/src/user/web/pow-captcha.ts @@ -187,7 +187,7 @@ function verifyTokenRefreshable(token: string, req: express.Request) { } } - req.log.info({ token }, "Allowing token refresh"); + req.log.info({ token: `...${token.slice(-5)}` }, "Allowing token refresh"); return true; } @@ -227,51 +227,57 @@ router.post("/verify", async (req, res) => { const ip = req.ip; req.log.info("Got verification request"); if (recentAttempts.has(ip)) { - res - .status(429) - .json({ error: "Rate limited; wait a minute before trying again" }); + const error = "Rate limited; wait a minute before trying again"; + req.log.info({ error }, "Verification rejected"); + res.status(429).json({ error }); return; } const result = verifySchema.safeParse(req.body); if (!result.success) { - res - .status(400) - .json({ error: "Invalid verify request", details: result.error }); + const error = "Invalid verify request"; + req.log.info({ error, result }, "Verification rejected"); + res.status(400).json({ error, details: result.error }); return; } const { challenge, signature, solution } = result.data; if (signMessage(challenge, powKeySalt) !== signature) { - res.status(400).json({ - error: - "Invalid signature; server may have restarted since challenge was issued. Please request a new challenge.", - }); + const error = + "Invalid signature; server may have restarted since challenge was issued. Please request a new challenge."; + req.log.info({ error }, "Verification rejected"); + res.status(400).json({ error }); return; } if (config.proxyKey && result.data.proxyKey !== config.proxyKey) { - res.status(401).json({ error: "Invalid proxy password" }); + const error = "Invalid proxy password"; + req.log.info({ error }, "Verification rejected"); + res.status(401).json({ error, password: result.data.proxyKey }); return; } if (challenge.ip && challenge.ip !== ip) { - req.log.warn("Attempt to verify from different IP address"); - res.status(400).json({ - error: "Solution must be verified from original IP address", - }); + const error = "Solution must be verified from original IP address"; + req.log.info( + { error, challengeIp: challenge.ip, clientIp: ip }, + "Verification rejected" + ); + res.status(400).json({ error }); return; } if (solves.has(signature)) { - req.log.warn("Attempt to reuse signature"); - res.status(400).json({ error: "Reused signature" }); + const error = "Reused signature"; + req.log.info({ error }, "Verification rejected"); + res.status(400).json({ error }); return; } if (Date.now() > challenge.e) { - req.log.warn("Verification took too long"); - res.status(400).json({ error: "Verification took too long" }); + const error = "Verification took too long"; + req.log.info({ error }, "Verification rejected"); + res.status(400).json({ error }); return; } @@ -285,7 +291,7 @@ router.post("/verify", async (req, res) => { const success = await verifySolution(challenge, solution, req.log); if (!success) { recentAttempts.set(ip, Date.now() + 1000 * 60 * 60 * 6); - req.log.warn("Solution failed verification"); + req.log.warn("Bogus solution, client blocked"); res.status(400).json({ error: "Solution failed verification" }); return; } @@ -299,10 +305,12 @@ router.post("/verify", async (req, res) => { if (challenge.token) { const user = getUser(challenge.token); if (user) { - user.expiresAt = Date.now() + config.powTokenHours * 60 * 60 * 1000; - user.disabledAt = undefined; - user.disabledReason = undefined; - upsertUser(user); + upsertUser({ + token: challenge.token, + expiresAt: Date.now() + config.powTokenHours * 60 * 60 * 1000, + disabledAt: null, + disabledReason: null, + }); req.log.info( { token: `...${challenge.token.slice(-5)}` }, "Token refreshed" diff --git a/src/user/web/views/partials/user_challenge_widget.ejs b/src/user/web/views/partials/user_challenge_widget.ejs index 4e5d579..ab8df3e 100644 --- a/src/user/web/views/partials/user_challenge_widget.ejs +++ b/src/user/web/views/partials/user_challenge_widget.ejs @@ -5,8 +5,8 @@
<% } %> -