diff --git a/keyserver/src/deleters/siwe-nonce-deleters.js b/keyserver/src/deleters/siwe-nonce-deleters.js --- a/keyserver/src/deleters/siwe-nonce-deleters.js +++ b/keyserver/src/deleters/siwe-nonce-deleters.js @@ -14,4 +14,16 @@ await dbQuery(query); } -export { deleteStaleSIWENonceEntries }; +async function checkAndInvalidateSIWENonceEntry( + nonce: string, +): Promise { + const earliestValidCreationTime = Date.now() - nonceLifetime; + const query = SQL` + DELETE FROM siwe_nonces + WHERE nonce = ${nonce} AND creation_time > ${earliestValidCreationTime} + `; + const [result] = await dbQuery(query); + return result.affectedRows && result.affectedRows > 0; +} + +export { deleteStaleSIWENonceEntries, checkAndInvalidateSIWENonceEntry }; diff --git a/keyserver/src/responders/user-responders.js b/keyserver/src/responders/user-responders.js --- a/keyserver/src/responders/user-responders.js +++ b/keyserver/src/responders/user-responders.js @@ -50,6 +50,7 @@ import { dbQuery, SQL } from '../database/database'; import { deleteAccount } from '../deleters/account-deleters'; import { deleteCookie } from '../deleters/cookie-deleters'; +import { checkAndInvalidateSIWENonceEntry } from '../deleters/siwe-nonce-deleters.js'; import { fetchEntryInfos } from '../fetchers/entry-fetchers'; import { fetchMessageInfos } from '../fetchers/message-fetchers'; import { fetchThreadInfos } from '../fetchers/thread-fetchers'; @@ -325,6 +326,16 @@ throw new ServerError('invalid_parameters'); } + // 3. Ensure that the `nonce` exists in the `siwe_nonces` table + // AND hasn't expired. If those conditions are met, delete the entry to + // ensure that the same `nonce` can't be re-used in a future request. + const wasNonceCheckedAndInvalidated = await checkAndInvalidateSIWENonceEntry( + siweMessage.nonce, + ); + if (!wasNonceCheckedAndInvalidated) { + throw new ServerError('invalid_parameters'); + } + return false; }