restore migrations and fix rate limit

This commit is contained in:
miloschwartz 2025-07-14 14:55:09 -07:00
parent d3383f0f1a
commit 0a97d91aed
No known key found for this signature in database
7 changed files with 10 additions and 186 deletions

View file

@ -1,45 +1,12 @@
import { migrate } from "drizzle-orm/better-sqlite3/migrator"; import { migrate } from "drizzle-orm/better-sqlite3/migrator";
import db from "./driver"; import db from "./driver";
import path from "path"; import path from "path";
import { location } from "./driver";
import Database from "better-sqlite3";
import type { Database as BetterSqlite3Database } from "better-sqlite3";
const migrationsFolder = path.join("server/migrations"); const migrationsFolder = path.join("server/migrations");
const dropAllTables = (sqlite: BetterSqlite3Database) => {
console.log("Dropping all existing tables...");
// Disable foreign key checks
sqlite.pragma('foreign_keys = OFF');
// Get all tables
const tables = sqlite.prepare(`
SELECT name FROM sqlite_master
WHERE type='table'
AND name NOT LIKE 'sqlite_%'
`).all() as { name: string }[];
// Drop each table
for (const table of tables) {
console.log(`Dropping table: ${table.name}`);
sqlite.prepare(`DROP TABLE IF EXISTS "${table.name}"`).run();
}
// Re-enable foreign key checks
sqlite.pragma('foreign_keys = ON');
};
const runMigrations = async () => { const runMigrations = async () => {
console.log("Running migrations..."); console.log("Running migrations...");
try { try {
// Initialize the database file with a valid SQLite header
const sqlite = new Database(location) as BetterSqlite3Database;
// Drop all existing tables first
dropAllTables(sqlite);
// Run the migrations
migrate(db as any, { migrate(db as any, {
migrationsFolder: migrationsFolder, migrationsFolder: migrationsFolder,
}); });

View file

@ -794,17 +794,17 @@ authRouter.get("/initial-setup-complete", auth.initialSetupComplete);
// Security Key routes // Security Key routes
authRouter.post( authRouter.post(
"/security-key/register/start", "/security-key/register/start",
verifySessionUserMiddleware,
rateLimit({ rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Allow 5 security key registrations per 15 minutes per IP max: 5, // Allow 5 security key registrations per 15 minutes
keyGenerator: (req) => `securityKeyRegister:${req.ip}:${req.user?.userId}`, keyGenerator: (req) => `securityKeyRegister:${req.user?.userId}`,
handler: (req, res, next) => { handler: (req, res, next) => {
const message = `You can only register ${5} security keys every ${15} minutes. Please try again later.`; const message = `You can only register ${5} security keys every ${15} minutes. Please try again later.`;
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message)); return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
} }
}), }),
verifySessionUserMiddleware,
auth.startRegistration auth.startRegistration
); );
authRouter.post("/security-key/register/verify", verifySessionUserMiddleware, auth.verifyRegistration); authRouter.post("/security-key/register/verify", verifySessionUserMiddleware, auth.verifyRegistration);

View file

@ -22,8 +22,6 @@ import m18 from "./scriptsSqlite/1.2.0";
import m19 from "./scriptsSqlite/1.3.0"; import m19 from "./scriptsSqlite/1.3.0";
import m20 from "./scriptsSqlite/1.5.0"; import m20 from "./scriptsSqlite/1.5.0";
import m21 from "./scriptsSqlite/1.6.0"; import m21 from "./scriptsSqlite/1.6.0";
import m22 from "./scriptsSqlite/1.7.0";
import m23 from "./scriptsSqlite/1.8.0";
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER // THIS CANNOT IMPORT ANYTHING FROM THE SERVER
// EXCEPT FOR THE DATABASE AND THE SCHEMA // EXCEPT FOR THE DATABASE AND THE SCHEMA
@ -45,9 +43,7 @@ const migrations = [
{ version: "1.2.0", run: m18 }, { version: "1.2.0", run: m18 },
{ version: "1.3.0", run: m19 }, { version: "1.3.0", run: m19 },
{ version: "1.5.0", run: m20 }, { version: "1.5.0", run: m20 },
{ version: "1.6.0", run: m21 }, { version: "1.6.0", run: m21 }
{ version: "1.7.0", run: m22 },
{ version: "1.8.0", run: m23 }
// Add new migrations here as they are created // Add new migrations here as they are created
] as const; ] as const;
@ -83,21 +79,17 @@ export async function runMigrations() {
try { try {
const appVersion = APP_VERSION; const appVersion = APP_VERSION;
// Check if the database file exists and has tables if (exists) {
const hasTables = await db.select().from(versionMigrations).limit(1).catch(() => false);
if (hasTables) {
await executeScripts(); await executeScripts();
} else { } else {
console.log("Running initial migrations..."); console.log("Running migrations...");
try { try {
migrate(db, { migrate(db, {
migrationsFolder: path.join(APP_PATH, "server", "migrations") migrationsFolder: path.join(__DIRNAME, "init") // put here during the docker build
}); });
console.log("Initial migrations completed successfully."); console.log("Migrations completed successfully.");
} catch (error) { } catch (error) {
console.error("Error running initial migrations:", error); console.error("Error running migrations:", error);
throw error;
} }
await db await db

View file

@ -1,31 +0,0 @@
import { db } from "../../db/sqlite";
import { sql } from "drizzle-orm";
const version = "1.4.0";
export default async function migration() {
console.log(`Running setup script ${version}...`);
try {
db.transaction((trx) => {
trx.run(sql`CREATE TABLE 'securityKey' (
'credentialId' text PRIMARY KEY NOT NULL,
'userId' text NOT NULL,
'publicKey' text NOT NULL,
'signCount' integer NOT NULL,
'transports' text,
'name' text,
'lastUsed' text NOT NULL,
'dateCreated' text NOT NULL,
FOREIGN KEY ('userId') REFERENCES 'user'('id') ON DELETE CASCADE
);`);
});
console.log(`Migrated database schema`);
} catch (e) {
console.log("Unable to migrate database schema");
throw e;
}
console.log(`${version} migration complete`);
}

View file

@ -1,39 +0,0 @@
import { APP_PATH } from "@server/lib/consts";
import Database from "better-sqlite3";
import path from "path";
const version = "1.7.0";
export default async function migration() {
console.log(`Running setup script ${version}...`);
const location = path.join(APP_PATH, "db", "db.sqlite");
const db = new Database(location);
try {
db.pragma("foreign_keys = OFF");
db.transaction(() => {
db.exec(`
CREATE TABLE IF NOT EXISTS securityKey (
credentialId TEXT PRIMARY KEY,
userId TEXT NOT NULL,
publicKey TEXT NOT NULL,
signCount INTEGER NOT NULL,
transports TEXT,
name TEXT,
lastUsed TEXT NOT NULL,
dateCreated TEXT NOT NULL,
FOREIGN KEY (userId) REFERENCES user(id) ON DELETE CASCADE
);
`);
})(); // executes the transaction immediately
db.pragma("foreign_keys = ON");
console.log(`Created securityKey table`);
} catch (e) {
console.error("Unable to create securityKey table");
console.error(e);
throw e;
}
console.log(`${version} migration complete`);
}

View file

@ -1,38 +0,0 @@
import { APP_PATH } from "@server/lib/consts";
import Database from "better-sqlite3";
import path from "path";
const version = "1.8.0";
export default async function migration() {
console.log(`Running setup script ${version}...`);
const location = path.join(APP_PATH, "db", "db.sqlite");
const db = new Database(location);
try {
db.pragma("foreign_keys = OFF");
db.transaction(() => {
db.exec(`
CREATE TABLE IF NOT EXISTS securityKeyChallenge (
sessionId TEXT PRIMARY KEY,
challenge TEXT NOT NULL,
securityKeyName TEXT,
userId TEXT,
expiresAt INTEGER NOT NULL,
FOREIGN KEY (userId) REFERENCES user(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_securityKeyChallenge_expiresAt ON securityKeyChallenge(expiresAt);
`);
})(); // executes the transaction immediately
db.pragma("foreign_keys = ON");
console.log(`Created securityKeyChallenge table`);
} catch (e) {
console.error("Unable to create securityKeyChallenge table");
console.error(e);
throw e;
}
console.log(`${version} migration complete`);
}

View file

@ -1,27 +0,0 @@
import { db } from "@server/db";
export default async function migrate() {
try {
console.log("Starting table rename migration...");
// Rename the table
await db.run(`
ALTER TABLE securityKeyChallenge RENAME TO webauthnChallenge;
`);
console.log("Successfully renamed table");
// Rename the index
await db.run(`
DROP INDEX IF EXISTS idx_securityKeyChallenge_expiresAt;
CREATE INDEX IF NOT EXISTS idx_webauthnChallenge_expiresAt ON webauthnChallenge(expiresAt);
`);
console.log("Successfully updated index");
console.log(`Renamed securityKeyChallenge table to webauthnChallenge`);
return true;
} catch (error: any) {
console.error("Unable to rename securityKeyChallenge table:", error);
console.error("Error details:", error.message);
return false;
}
}