various improvements and fixes to PoW challenge UI and token refresh
This commit is contained in:
parent
ff0d3dfdcd
commit
ee26e7be65
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -38,15 +38,20 @@
|
|||
<h3>Difficulty Level</h3>
|
||||
<div>
|
||||
<label for="difficulty">Difficulty Level:</label>
|
||||
<span id="currentDifficulty">Current: <%= difficulty %></span>
|
||||
<select name="difficulty" id="difficulty">
|
||||
<select name="difficulty" id="difficulty" onchange="difficultyChanged(event)">
|
||||
<option value="low">Low</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="high">High</option>
|
||||
<option value="extreme">Extreme</option>
|
||||
<option value="custom">Custom</option>
|
||||
</select>
|
||||
<div id="custom-difficulty-container" style="display: none">
|
||||
<label for="customDifficulty">Hashes required (average):</label>
|
||||
<input type="number" id="customDifficulty" value="0" min="1" max="1000000000" />
|
||||
</div>
|
||||
<button onclick='doAction("setDifficulty")'>Update Difficulty</button>
|
||||
</div>
|
||||
<div><span id="currentDifficulty">Current Difficulty: <%= difficulty %></span></div>
|
||||
<% } %>
|
||||
<form id="maintenanceForm" action="/admin/manage/maintenance" method="post">
|
||||
<input id="_csrf" type="hidden" name="_csrf" value="<%= csrfToken %>" />
|
||||
|
@ -63,15 +68,15 @@
|
|||
<div>
|
||||
<h2>IP Whitelists and Blacklists</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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
|
||||
<a href="https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation">CIDR notation</a>. IPv6 is
|
||||
supported but not recommended for use with the current version of the proxy.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Note:</strong> Changes here are not persisted across server restarts. If you want to make changes permanent,
|
||||
you can copy the values to your deployment configuration.
|
||||
</p>
|
||||
<% for (let i = 0; i < whitelists.length; i++) { %>
|
||||
<%- include("partials/admin-cidr-widget", { list: whitelists[i] }) %>
|
||||
<% } %>
|
||||
|
@ -99,10 +104,25 @@
|
|||
</div>
|
||||
|
||||
<script>
|
||||
function difficultyChanged(event) {
|
||||
const value = event.target.value;
|
||||
if (value === "custom") {
|
||||
document.getElementById("custom-difficulty-container").style.display = "block";
|
||||
} else {
|
||||
document.getElementById("custom-difficulty-container").style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
function doAction(action) {
|
||||
document.getElementById("hiddenAction").value = action;
|
||||
if (action === "setDifficulty") {
|
||||
document.getElementById("hiddenDifficulty").value = document.getElementById("difficulty").value;
|
||||
const selected = document.getElementById("difficulty").value;
|
||||
const hiddenDifficulty = document.getElementById("hiddenDifficulty");
|
||||
if (selected === "custom") {
|
||||
hiddenDifficulty.value = document.getElementById("customDifficulty").value;
|
||||
} else {
|
||||
hiddenDifficulty.value = selected;
|
||||
}
|
||||
}
|
||||
document.getElementById("maintenanceForm").submit();
|
||||
}
|
||||
|
|
|
@ -171,7 +171,7 @@ function getSelfServiceLinks() {
|
|||
}
|
||||
|
||||
return `<div class="self-service-links">${links
|
||||
.map(([text, link]) => `<a target="_blank" href="${link}">${text}</a>`)
|
||||
.map(([text, link]) => `<a href="${link}">${text}</a>`)
|
||||
.join(" | ")}</div>`;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
<p>
|
||||
Next refresh: <time><%- nextQuotaRefresh %></time>
|
||||
</p>
|
||||
<table class="striped">
|
||||
<%
|
||||
const quotaTableId = Math.random().toString(36).slice(2);
|
||||
%>
|
||||
<div>
|
||||
<label for="quota-family-filter-<%= quotaTableId %>">Filter:</label>
|
||||
<input type="text" id="quota-family-filter-<%= quotaTableId %>" oninput="filterQuotaTable(this, '<%= quotaTableId %>')" />
|
||||
</div>
|
||||
<table class="striped" id="quota-table-<%= quotaTableId %>">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Model Family</th>
|
||||
|
@ -50,3 +57,18 @@
|
|||
<% }) %>
|
||||
</tbody>
|
||||
</table>
|
||||
<script>
|
||||
function filterQuotaTable(input, tableId) {
|
||||
const filter = input.value.toLowerCase();
|
||||
const table = document.getElementById("quota-table-" + tableId);
|
||||
const rows = table.querySelectorAll("tbody tr");
|
||||
for (const row of rows) {
|
||||
const modelFamily = row.querySelector("th").textContent;
|
||||
if (modelFamily.toLowerCase().includes(filter)) {
|
||||
row.style.display = "";
|
||||
} else {
|
||||
row.style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
</noscript>
|
||||
<style>
|
||||
#captcha-container {
|
||||
max-width: 500px;
|
||||
margin: 50px auto;
|
||||
max-width: 550px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
@media (max-width: 1000px) {
|
||||
#captcha-container {
|
||||
|
@ -42,7 +42,7 @@
|
|||
|
||||
#captcha-progress-text {
|
||||
width: 100%;
|
||||
height: 18rem;
|
||||
height: 20rem;
|
||||
resize: vertical;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
@ -70,13 +70,22 @@
|
|||
height: 100%;
|
||||
background-color: #76c7c0;
|
||||
}
|
||||
|
||||
#copy-token {
|
||||
border: none;
|
||||
background: none;
|
||||
filter: saturate(0);
|
||||
padding: 0;
|
||||
}
|
||||
#copy-token:hover {
|
||||
filter: saturate(1);
|
||||
}
|
||||
</style>
|
||||
<div style="display: none" id="captcha-container">
|
||||
<p>
|
||||
Your device needs to perform a verification task before you can receive a token. This might take anywhere from a few
|
||||
seconds to a few minutes, depending on your device and the proxy's security settings.
|
||||
Your device needs to be verified before you can receive a token. This might take anywhere from a few seconds to a
|
||||
few minutes, depending on your device and the proxy's security settings.
|
||||
</p>
|
||||
<p>Click the button below to start.</p>
|
||||
<details>
|
||||
<summary>What is this?</summary>
|
||||
<p>
|
||||
|
@ -107,18 +116,6 @@
|
|||
faster than the first one.
|
||||
</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>What is the "Workers" setting?</summary>
|
||||
<p>
|
||||
This controls how many CPU cores will be used to solve the verification task. If your device gets too hot or slows
|
||||
down too much during verification, reduce the number of workers.
|
||||
</p>
|
||||
<p>
|
||||
For fastest verification, set this to the number of physical CPU cores in your device. Setting more workers than
|
||||
you have actual cores will generally only slow down verification.
|
||||
</p>
|
||||
<p>If you don't understand what this means, leave it at the default setting.</p>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Other important information</summary>
|
||||
<ul>
|
||||
|
@ -134,15 +131,27 @@
|
|||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Settings</summary>
|
||||
<div>
|
||||
<label for="workers">Workers:</label>
|
||||
<input type="number" id="workers" value="1" min="1" max="32" onchange="spawnWorkers()" />
|
||||
</div>
|
||||
<p>
|
||||
This controls how many CPU cores will be used to solve the verification task. If your device gets too hot or slows
|
||||
down too much during verification, reduce the number of workers.
|
||||
</p>
|
||||
<p>
|
||||
For fastest verification, set this to the number of physical CPU cores in your device. Setting more workers than
|
||||
you have actual cores will generally only slow down verification.
|
||||
</p>
|
||||
<p>If you don't understand what this means, leave it at the default setting.</p>
|
||||
</details>
|
||||
<form id="captcha-form" style="display: none">
|
||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>" />
|
||||
<input type="hidden" name="tokenLifetime" value="<%= tokenLifetime %>" />
|
||||
</form>
|
||||
<div id="captcha-control">
|
||||
<div>
|
||||
<label for="workers">Workers:</label>
|
||||
<input type="number" id="workers" value="1" min="1" max="32" onchange="spawnWorkers()" />
|
||||
</div>
|
||||
<button id="worker-control" onclick="toggleWorker()">Start verification</button>
|
||||
</div>
|
||||
<div id="captcha-progress-container" style="display: none">
|
||||
|
@ -185,6 +194,9 @@
|
|||
function handleWorkerMessage(e) {
|
||||
switch (e.data.type) {
|
||||
case "progress":
|
||||
if (solution) {
|
||||
return;
|
||||
}
|
||||
totalHashes += e.data.hashes;
|
||||
reports++;
|
||||
break;
|
||||
|
@ -206,13 +218,13 @@
|
|||
}
|
||||
workers.forEach((w, i) => {
|
||||
w.postMessage({ type: "stop" });
|
||||
setTimeout(() => w.terminate(), 1000 + i * 100)
|
||||
setTimeout(() => w.terminate(), 1000 + i * 100);
|
||||
});
|
||||
workers = [];
|
||||
active = false;
|
||||
solution = e.data.nonce;
|
||||
document.getElementById("captcha-result").textContent =
|
||||
"Verification completed. Submitting solution for verification...";
|
||||
"Solution found. Verifying with server...";
|
||||
document.getElementById("captcha-control").style.display = "none";
|
||||
submitVerification();
|
||||
break;
|
||||
|
@ -233,6 +245,21 @@
|
|||
estimateProgress();
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
if (!navigator.clipboard) {
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
document.execCommand("copy");
|
||||
textArea.remove();
|
||||
} else {
|
||||
navigator.clipboard.writeText(text);
|
||||
}
|
||||
alert("Copied to clipboard.");
|
||||
}
|
||||
|
||||
function loadNewChallenge(c, s) {
|
||||
const btn = document.getElementById("worker-control");
|
||||
btn.textContent = "Start verification";
|
||||
|
@ -248,6 +275,7 @@
|
|||
startTime = 0;
|
||||
lastUpdateTime = 0;
|
||||
elapsedTime = 0;
|
||||
totalHashes = 0;
|
||||
const targetValue = challenge.d.slice(0, -1);
|
||||
const hashLength = challenge.hl;
|
||||
workFactor = Number(BigInt(2) ** BigInt(8 * hashLength) / BigInt(targetValue));
|
||||
|
@ -329,52 +357,53 @@
|
|||
document.getElementById("captcha-progress").style.display = "none";
|
||||
document.getElementById("captcha-result").innerHTML = `
|
||||
<p style="color: green">Verification complete</p>
|
||||
<p>Your user token is: <code>${data.token}</code></p>
|
||||
<p>Your user token is: <code>${data.token}</code> <button id="copy-token" onclick="copyToClipboard('${data.token}')">📋</button></p>
|
||||
<p>Valid until: ${new Date(Date.now() + lifetime * 3600 * 1000).toLocaleString()}</p>
|
||||
`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function formatTime(time) {
|
||||
if (time < 60) {
|
||||
return time.toFixed(1) + "s";
|
||||
} else if (time < 3600) {
|
||||
const minutes = Math.floor(time / 60);
|
||||
const seconds = Math.floor(time % 60);
|
||||
return minutes + "m " + seconds + "s";
|
||||
} else {
|
||||
const hours = Math.floor(time / 3600);
|
||||
const minutes = Math.floor((time % 3600) / 60);
|
||||
return hours + "h " + minutes + "m";
|
||||
}
|
||||
}
|
||||
|
||||
function estimateProgress() {
|
||||
if (reports % workers.length !== 0) {
|
||||
// if (reports % workers.length !== 0) {
|
||||
// return;
|
||||
// }
|
||||
if (Date.now() - lastUpdateTime < 500) {
|
||||
return;
|
||||
}
|
||||
elapsedTime += (Date.now() - lastUpdateTime) / 1000;
|
||||
lastUpdateTime = Date.now();
|
||||
const hashRate = totalHashes / elapsedTime;
|
||||
const timeRemaining = (workFactor - totalHashes) / hashRate;
|
||||
const progress = 100 * (1 - Math.exp(-totalHashes / workFactor));
|
||||
|
||||
const formatTime = (time) => {
|
||||
if (time < 60) {
|
||||
return time.toFixed(1) + "s";
|
||||
} else if (time < 3600) {
|
||||
const minutes = Math.floor(time / 60);
|
||||
const seconds = Math.floor(time % 60);
|
||||
return minutes + "m " + seconds + "s";
|
||||
} else {
|
||||
const hours = Math.floor(time / 3600);
|
||||
const minutes = Math.floor((time % 3600) / 60);
|
||||
return hours + "h " + minutes + "m";
|
||||
}
|
||||
};
|
||||
|
||||
// const progress = 100 * (1 - Math.exp(-totalHashes / workFactor));
|
||||
const p = 1 / workFactor;
|
||||
const odds = ((1 - p) ** totalHashes * 100).toFixed(2);
|
||||
const odds = ((1 - p) ** totalHashes * 100).toFixed(3);
|
||||
const progress = 100 - odds;
|
||||
|
||||
let note = "";
|
||||
if (odds < 33) {
|
||||
note = " (" + odds + "% odds of no solution yet)";
|
||||
}
|
||||
// let note = " (" + odds + "% odds of no solution yet)";
|
||||
|
||||
document.getElementById("captcha-progress").style.width = Math.min(progress, 100) + "%";
|
||||
document.querySelector("#captcha-progress>.progress").style.width = Math.min(progress, 100) + "%";
|
||||
document.getElementById("captcha-progress-text").value = `
|
||||
Solution probability: 1 in ${workFactor.toLocaleString()} hashes
|
||||
Hashes computed: ${totalHashes.toLocaleString()}${note}
|
||||
Hashes computed: ${totalHashes.toLocaleString()}
|
||||
Luckiness: ${odds}%
|
||||
Elapsed time: ${formatTime(elapsedTime)}
|
||||
Hash rate: ${hashRate.toFixed(2)} H/s
|
||||
Workers: ${workers.length}${isMobileWebkit ? " (iOS/iPadOS detected)" : ""}
|
||||
${active ? `Average time remaining: ${formatTime(timeRemaining)}` : "Verification task stopped"}`.trim();
|
||||
${active ? `Average time remaining: ${formatTime(timeRemaining)}` : "Verification stopped"}`.trim();
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
<%- include("partials/shared_header", { title: "Request User Token" }) %>
|
||||
|
||||
<style>
|
||||
#request-buttons {
|
||||
#request-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 20px;
|
||||
width: 400px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 20px 0;
|
||||
width: 100%;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
#request-buttons button {
|
||||
margin: 0 10px;
|
||||
#request-container button {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#refresh-token-input {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h1>Request User Token</h1>
|
||||
|
@ -28,37 +35,70 @@
|
|||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<div id="existing-token" style="display: none">
|
||||
<p>It looks like you might have an older temporary user token. If it has expired, you can try to refresh it.</p>
|
||||
<strong id="existing-token-value">Existing token:</strong>
|
||||
<div id="request-container">
|
||||
<button id="request-token" onclick="requestChallenge('new')">Get a new token</button>
|
||||
<button id="refresh-token-toggle" onclick="switchSection('refresh')">Refresh an old token</button>
|
||||
<h6 id="existing-token-value" style="display: none">Existing token:</h6>
|
||||
</div>
|
||||
<div id="request-buttons">
|
||||
<button disabled id="refresh-token" onclick="requestChallenge('refresh')">Refresh old token</button>
|
||||
<button id="request_token" onclick="requestChallenge('new')">Request new token</button>
|
||||
<div id="back-to-menu" style="display: none">
|
||||
<a href="#" onclick="switchSection('root')">« Back</a>
|
||||
</div>
|
||||
<div id="refresh-container" style="display: none">
|
||||
<div id="existing-token">
|
||||
<p>
|
||||
If you have an existing or expired token, enter it here to try to refresh it by completing a shorter verification.
|
||||
</p>
|
||||
<div>
|
||||
<label for="refresh-token-input">Existing token:</label>
|
||||
<input type="text" id="refresh-token-input" />
|
||||
<button id="refresh-token" onclick="requestChallenge('refresh')">Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%- include("partials/user_challenge_widget") %>
|
||||
<script>
|
||||
function switchSection(sectionId) {
|
||||
const backToMenu = document.getElementById("back-to-menu");
|
||||
const captchaSection = document.getElementById("captcha-container");
|
||||
const requestSection = document.getElementById("request-container");
|
||||
const refreshSection = document.getElementById("refresh-container");
|
||||
[backToMenu, captchaSection, requestSection, refreshSection].forEach((element) => (element.style.display = "none"));
|
||||
switch (sectionId) {
|
||||
case "root":
|
||||
requestSection.style.display = "flex";
|
||||
maybeLoadExistingToken();
|
||||
break;
|
||||
case "captcha":
|
||||
captchaSection.style.display = "block";
|
||||
backToMenu.style.display = "block";
|
||||
break;
|
||||
case "refresh":
|
||||
refreshSection.style.display = "block";
|
||||
backToMenu.style.display = "block";
|
||||
document.getElementById("refresh-token-input").focus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function requestChallenge(action) {
|
||||
const token = localStorage.getItem("captcha-temp-token");
|
||||
if (token && action === "new") {
|
||||
const data = JSON.parse(token);
|
||||
const { expires } = data;
|
||||
const expiresDate = new Date(expires);
|
||||
const now = new Date();
|
||||
if (expiresDate > now) {
|
||||
if (!confirm("You already have an existing token. Are you sure you want to request a new one?")) {
|
||||
return;
|
||||
}
|
||||
localStorage.removeItem("captcha-temp-token");
|
||||
document.getElementById("existing-token").style.display = "none";
|
||||
document.getElementById("refresh-token").disabled = true;
|
||||
const savedToken = localStorage.getItem("captcha-temp-token");
|
||||
const refreshInput = document.getElementById("refresh-token-input").value;
|
||||
if (savedToken && action === "new") {
|
||||
const confirmation = confirm(
|
||||
"It looks like you might already have an existing token. Are you sure you want to request a new one?"
|
||||
);
|
||||
if (!confirmation) {
|
||||
return;
|
||||
}
|
||||
} else if (!token && action === "refresh") {
|
||||
alert("You don't have an existing token to refresh");
|
||||
localStorage.removeItem("captcha-temp-token");
|
||||
document.getElementById("existing-token").style.display = "none";
|
||||
document.getElementById("refresh-token").disabled = true;
|
||||
} else if (!refreshInput?.length && action === "refresh") {
|
||||
alert("You need to provide a token to refresh.");
|
||||
return;
|
||||
}
|
||||
|
||||
const refreshToken = token && action === "refresh" ? JSON.parse(token).token : undefined;
|
||||
const refreshToken = action === "refresh" ? refreshInput : undefined;
|
||||
const keyInput = document.getElementById("proxy-key");
|
||||
const proxyKey = (keyInput && keyInput.value) || undefined;
|
||||
if (!proxyKey?.length) {
|
||||
|
@ -79,7 +119,7 @@
|
|||
}
|
||||
const { challenge, signature } = data;
|
||||
loadNewChallenge(challenge, signature);
|
||||
document.getElementById("request-buttons").style.display = "none";
|
||||
switchSection("captcha");
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error(error);
|
||||
|
@ -87,22 +127,26 @@
|
|||
});
|
||||
}
|
||||
|
||||
const existingToken = localStorage.getItem("captcha-temp-token");
|
||||
if (existingToken) {
|
||||
const data = JSON.parse(existingToken);
|
||||
const { token, expires } = data;
|
||||
const expiresDate = new Date(expires);
|
||||
document.getElementById(
|
||||
"existing-token-value"
|
||||
).textContent = `Your token: ${token} (valid until ${expiresDate.toLocaleString()})`;
|
||||
document.getElementById("existing-token").style.display = "block";
|
||||
document.getElementById("refresh-token").disabled = false;
|
||||
function maybeLoadExistingToken() {
|
||||
const existingToken = localStorage.getItem("captcha-temp-token");
|
||||
if (existingToken) {
|
||||
const data = JSON.parse(existingToken);
|
||||
const { token, expires } = data;
|
||||
const expiresDate = new Date(expires);
|
||||
document.getElementById(
|
||||
"existing-token-value"
|
||||
).textContent = `User token: ${token} (valid until ${expiresDate.toLocaleString()})`;
|
||||
document.getElementById("existing-token-value").style.display = "block";
|
||||
document.getElementById("refresh-token-input").value = token;
|
||||
}
|
||||
|
||||
const proxyKey = localStorage.getItem("captcha-proxy-key");
|
||||
if (proxyKey && document.getElementById("proxy-key")) {
|
||||
document.getElementById("proxy-key").value = proxyKey;
|
||||
}
|
||||
}
|
||||
|
||||
const proxyKey = localStorage.getItem("captcha-proxy-key");
|
||||
if (proxyKey && document.getElementById("proxy-key")) {
|
||||
document.getElementById("proxy-key").value = proxyKey;
|
||||
}
|
||||
switchSection("root");
|
||||
</script>
|
||||
|
||||
<%- include("partials/user_footer") %>
|
||||
|
|
Loading…
Reference in New Issue