* Redeems a challenge solution in exchange for a token * @param {Solution} param0 - Challenge solution data * @returns {Promise<{success: boolean, message?: string, token?: string, expires?: number}>}
({ token, solutions })
| 299 | * @returns {Promise<{success: boolean, message?: string, token?: string, expires?: number}>} |
| 300 | */ |
| 301 | async redeemChallenge({ token, solutions }) { |
| 302 | if ( |
| 303 | !token || |
| 304 | !solutions || |
| 305 | !Array.isArray(solutions) || |
| 306 | solutions.some((s) => typeof s !== "number") |
| 307 | ) { |
| 308 | return { success: false, message: "Invalid body" }; |
| 309 | } |
| 310 | |
| 311 | await this._lazyCleanup(); |
| 312 | |
| 313 | const challengeData = await this._getChallenge(token); |
| 314 | await this._deleteChallenge(token); |
| 315 | |
| 316 | if (!challengeData || challengeData.expires < Date.now()) { |
| 317 | return { success: false, message: "Challenge invalid or expired" }; |
| 318 | } |
| 319 | |
| 320 | let i = 0; |
| 321 | |
| 322 | const challenges = Array.from({ length: challengeData.challenge.c }, () => { |
| 323 | i = i + 1; |
| 324 | |
| 325 | return [ |
| 326 | prng(`${token}${i}`, challengeData.challenge.s), |
| 327 | prng(`${token}${i}d`, challengeData.challenge.d), |
| 328 | ]; |
| 329 | }); |
| 330 | |
| 331 | const hashes = await Promise.all( |
| 332 | challenges.map(([salt, target], i) => { |
| 333 | if (typeof solutions[i] !== "number") return null; |
| 334 | return sha256(salt + solutions[i]).then((h) => [h, target]); |
| 335 | }) |
| 336 | ); |
| 337 | |
| 338 | const isValid = hashes.every((pair) => pair?.[0].startsWith(pair[1])); |
| 339 | |
| 340 | if (!isValid) return { success: false, message: "Invalid solution" }; |
| 341 | |
| 342 | const vertoken = await randomHex(15); |
| 343 | const expires = Date.now() + 20 * 60 * 1000; |
| 344 | const hash = await sha256(vertoken); |
| 345 | const id = await randomHex(8); |
| 346 | const tokenKey = `${id}:${hash}`; |
| 347 | |
| 348 | if (this.config.storage?.tokens?.store) { |
| 349 | await this.config.storage.tokens.store(tokenKey, expires); |
| 350 | } else { |
| 351 | if (this?.config?.state?.tokensList) { |
| 352 | this.config.state.tokensList[tokenKey] = expires; |
| 353 | } |
| 354 | |
| 355 | if (!this.config.noFSState) { |
| 356 | await fs.writeFile( |
| 357 | this.config.tokens_store_path, |
| 358 | JSON.stringify(this.config.state.tokensList), |
no test coverage detected