Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions standalone/src/cap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down
68 changes: 42 additions & 26 deletions standalone/src/db.js
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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();
Expand Down
10 changes: 5 additions & 5 deletions standalone/src/siteverify.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -50,18 +50,18 @@ 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);

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}`;
Expand Down