fix: resolve build errors and improve database migration system

- Remove unused SQLite migration script 1.8.1.ts that was causing
  TypeScript compilation errors during PostgreSQL builds
- Fix verifyTotp.ts type error by adding proper null check for password
  parameter before passing to verifyPassword function
- Fix SQLite migration script 1.7.0.ts syntax errors in transaction
  structure and error handling
**- Update SQLite migration system to not drop tables by default, as this was used during testing and should not be in production.**

Fixes build failures for both "make build" (SQLite) and "make build-pg"
(PostgreSQL) Docker image builds.
This commit is contained in:
Adrian Astles 2025-07-15 06:40:31 +08:00
parent 5278c4d6f2
commit ec8d3569d3
10 changed files with 96 additions and 114 deletions

View file

@ -1175,7 +1175,7 @@
"saveSecuritySettings": "Save Security Settings",
"sendEmailNotification": "Send Email Notification",
"linkCopied": "Link Copied",
"linkCopiedDescription": "The reset link has been copied to your clipboard"
"linkCopiedDescription": "The reset link has been copied to your clipboard",
"securityKeyManage": "Manage Security Keys",
"securityKeyDescription": "Add or remove security keys for passwordless authentication",
"securityKeyRegister": "Register New Security Key",

2
package-lock.json generated
View file

@ -33,9 +33,9 @@
"@radix-ui/react-toast": "1.2.14",
"@react-email/components": "0.3.1",
"@react-email/render": "^1.1.2",
"@react-email/tailwind": "1.2.1",
"@simplewebauthn/browser": "^13.1.0",
"@simplewebauthn/server": "^9.0.3",
"@react-email/tailwind": "1.2.1",
"@tailwindcss/forms": "^0.5.10",
"@tanstack/react-table": "8.21.3",
"arctic": "^3.7.0",

View file

@ -1,46 +1,13 @@
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
import db from "./driver";
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 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 () => {
console.log("Running migrations...");
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, {
await migrate(db as any, {
migrationsFolder: migrationsFolder,
});
console.log("Migrations completed successfully.");

View file

@ -91,6 +91,16 @@ export async function verifyTotp(
);
}
// Add type guard to ensure password is defined
if (!password) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Password is required for two-factor authentication"
)
);
}
const validPassword = await verifyPassword(
password,
user.passwordHash!

View file

@ -15,6 +15,4 @@ export { updateUser } from "./updateUser";
export { adminUpdateUser } from "./adminUpdateUser";
export { adminResetUserPassword } from "./adminResetUserPassword";
export type { AdminResetUserPasswordBody, AdminResetUserPasswordResponse } from "./adminResetUserPassword";
export * from "./updateUser2FA";
export * from "./adminUpdateUser2FA";
export * from "./adminGetUser";
export * from "./adminUpdateUser2FA";

View file

@ -6,6 +6,7 @@ import { __DIRNAME, APP_VERSION } from "@server/lib/consts";
import path from "path";
import m1 from "./scriptsPg/1.6.0";
import m2 from "./scriptsPg/1.7.0";
import m3 from "./scriptsPg/1.8.0";
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
// EXCEPT FOR THE DATABASE AND THE SCHEMA
@ -13,7 +14,8 @@ import m2 from "./scriptsPg/1.7.0";
// Define the migration list with versions and their corresponding functions
const migrations = [
{ version: "1.6.0", run: m1 },
{ version: "1.7.0", run: m2 }
{ version: "1.7.0", run: m2 },
{ version: "1.8.0", run: m3 }
// Add new migrations here as they are created
] as {
version: string;

View file

@ -0,0 +1,31 @@
import { db } from "@server/db/pg";
export default async function migrate() {
try {
console.log("Starting webauthnChallenge table creation...");
// Create the table (PostgreSQL already has the correct table name)
await db.execute(`
CREATE TABLE IF NOT EXISTS webauthnChallenge (
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 the index
await db.execute(`
CREATE INDEX IF NOT EXISTS idx_webauthnChallenge_expiresAt ON webauthnChallenge(expiresAt);
`);
console.log("Successfully created webauthnChallenge table and index");
return true;
} catch (error: any) {
console.error("Unable to create webauthnChallenge table:", error);
console.error("Error details:", error.message);
return false;
}
}

View file

@ -17,12 +17,17 @@ export default async function migration() {
db.exec(`
ALTER TABLE orgs ADD COLUMN passwordResetTokenExpiryHours INTEGER NOT NULL DEFAULT 1;
`);
})(); // <-- executes the transaction immediately
})(); // executes the transaction immediately
db.pragma("foreign_keys = ON");
console.log(`Added passwordResetTokenExpiryHours column to orgs table`);
} catch (e) {
console.log("Error adding passwordResetTokenExpiryHours column to orgs table:");
console.log(e);
}
try {
db.pragma("foreign_keys = OFF");
db.transaction(() => {
db.exec(`
CREATE TABLE IF NOT EXISTS securityKey (
credentialId TEXT PRIMARY KEY,

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;
}
}

View file

@ -186,52 +186,48 @@ export default function UsersTable({ users }: Props) {
cell: ({ row }) => {
const r = row.original;
return (
<>
<div className="flex items-center justify-end gap-2">
<AdminUserManagement
userId={r.id}
userEmail={r.email || ""}
userName={r.name || r.username}
userType={r.type}
userUsername={r.username}
/>
<Button
variant={"outlinePrimary"}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="h-8 w-8 p-0"
>
<span className="sr-only">
Open menu
</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => {
setSelected(r);
setIsDeleteModalOpen(true);
}}
>
{t("delete")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Button
variant={"secondary"}
size="sm"
onClick={() => {
router.push(`/admin/users/${r.id}`);
}}
>
{t("edit")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</div>
</>
<div className="flex items-center justify-end gap-2">
<AdminUserManagement
userId={r.id}
userEmail={r.email || ""}
userName={r.name || r.username}
userType={r.type}
userUsername={r.username}
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="h-8 w-8 p-0"
>
<span className="sr-only">
Open menu
</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => {
setSelected(r);
setIsDeleteModalOpen(true);
}}
>
{t("delete")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Button
variant="secondary"
size="sm"
onClick={() => {
router.push(`/admin/users/${r.id}`);
}}
>
{t("edit")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</div>
);
}
}