diff --git a/standalone/src/cap.js b/standalone/src/cap.js index 886e622..47b94cc 100644 --- a/standalone/src/cap.js +++ b/standalone/src/cap.js @@ -29,11 +29,11 @@ export const capServer = new Elysia({ const cap = new Cap({ noFSState: true, }); - const [_keyConfig] = await db`SELECT (config) FROM keys WHERE siteKey = ${params.siteKey}`; + const [_keyConfig] = await db`SELECT config FROM keys WHERE siteKey = ${params.siteKey}`; if (!_keyConfig) { set.status = 404; - return { error: "Invalid site key or secret" }; + return { error: "Site key not found" }; } const keyConfig = JSON.parse(_keyConfig.config); @@ -102,7 +102,7 @@ export const capServer = new Elysia({ INSERT INTO solutions (siteKey, bucket, count) VALUES (${params.siteKey}, ${hourlyBucket}, 1) ON CONFLICT (siteKey, bucket) - DO UPDATE SET count = count + 1 + DO UPDATE SET count = solutions.count + 1 `; return { diff --git a/standalone/src/db.js b/standalone/src/db.js index 2253216..fe9be0d 100644 --- a/standalone/src/db.js +++ b/standalone/src/db.js @@ -1,6 +1,6 @@ import fs from "node:fs"; import { join } from "node:path"; -import { SQL } from "bun"; +import { SQL, sql } from "bun"; fs.mkdirSync(process.env.DATA_PATH || "./.data", { recursive: true, @@ -13,66 +13,82 @@ async function initDb() { db = new SQL(dbUrl); + const isSqlite = db.options.adapter === "sqlite"; + const isPostgres = db.options.adapter === "postgres"; + const changeIntToBigInt = async (tbl, col) => { + if (isSqlite) return; // Irrelevant in SQLite. + if (isPostgres) { + await db`alter table ${sql(tbl)} alter column ${sql(col)} type bigint`.simple(); + } else { + await db`alter table ${sql(tbl)} modify column ${sql(col)} bigint`.simple(); + } + }; + // MySQL requires a prefix-length for indexing text. 4096 is an arbitrarily chosen number. + const indexableTextColType = isPostgres || isSqlite ? sql`text` : sql`varchar(4096)`; + await db`create table if not exists sessions ( - token text primary key not null, - expires integer not null, - created integer not null + token ${indexableTextColType} primary key not null, + expires bigint not null, + created bigint not null )`.simple(); + await changeIntToBigInt("sessions", "expires"); + await changeIntToBigInt("sessions", "created"); await db`create table if not exists keys ( - siteKey text primary key not null, + siteKey ${indexableTextColType} primary key not null, name text not null, secretHash text not null, config text not null, - created integer not null + created bigint not null )`.simple(); + await changeIntToBigInt("keys", "created"); await db`create table if not exists solutions ( - siteKey text not null, - bucket integer not null, + siteKey ${indexableTextColType} not null, + bucket bigint not null, count integer default 0, primary key (siteKey, bucket) )`.simple(); + await changeIntToBigInt("solutions", "bucket"); await db`create table if not exists challenges ( - siteKey text not null, - token text not null, + siteKey ${indexableTextColType} not null, + token ${indexableTextColType} not null, data text not null, - expires integer not null, + expires bigint not null, primary key (siteKey, token) )`.simple(); + await changeIntToBigInt("challenges", "expires"); await db`create table if not exists tokens ( - siteKey text not null, - token text not null, - expires integer not null, + siteKey ${indexableTextColType} not null, + token ${indexableTextColType} not null, + expires bigint not null, primary key (siteKey, token) )`.simple(); + await changeIntToBigInt("tokens", "expires"); await db`create table if not exists api_keys ( - id text not null, + id ${indexableTextColType} not null, name text not null, - tokenHash text not null, - created integer not null, + tokenHash ${indexableTextColType} not null, + created bigint not null, primary key (id, tokenHash) )`.simple(); + await changeIntToBigInt("api_keys", "created"); - setInterval(async () => { - const now = Date.now(); - - await db`delete from sessions where expires < ${now}`; - await db`delete from tokens where expires < ${now}`; - await db`delete from challenges where expires < ${now}`; - }, 60 * 1000); + setInterval(periodicCleanup, 60 * 1000); + await periodicCleanup(); + return db; +} +async function periodicCleanup() { const now = Date.now(); await db`delete from sessions where expires < ${now}`; await db`delete from tokens where expires < ${now}`; await db`delete from challenges where expires < ${now}`; - - return db; } db = await initDb(); diff --git a/standalone/src/siteverify.js b/standalone/src/siteverify.js index f510c37..0108324 100644 --- a/standalone/src/siteverify.js +++ b/standalone/src/siteverify.js @@ -38,7 +38,7 @@ export const siteverifyServer = new Elysia({ set.headers["X-RateLimit-Limit"] = "1"; set.headers["X-RateLimit-Remaining"] = "0"; set.headers["X-RateLimit-Reset"] = Math.ceil(unblockTime / 1000).toString(); - return { error: "You were temporarily for using an invalid secret key. Please try again later." }; + return { error: "You were temporarily blocked for using an invalid secret key. Please try again later." }; } const sitekey = params.siteKey; @@ -50,10 +50,10 @@ export const siteverifyServer = new Elysia({ } const [keyData] = await db`SELECT * FROM keys WHERE siteKey = ${sitekey}`; - const keyHash = keyData?.secretHash; - if (!keyHash || !secret) { + const keyHash = keyData?.secretHash || keyData?.secrethash; + if (!keyHash) { set.status = 404; - return { error: "Invalid site key or secret" }; + return { error: "Site key not found" }; } const isValidSecret = await Bun.password.verify(secret, keyHash); @@ -61,7 +61,7 @@ export const siteverifyServer = new Elysia({ if (!isValidSecret) { blockedIPs.set(ip, now + 250); set.status = 403; - return { error: "Invalid site key or secret" }; + return { error: "Invalid secret" }; } const [token] = await db`SELECT * FROM tokens WHERE siteKey = ${params.siteKey} AND token = ${response}`;