mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-29 06:08:15 +02:00
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:
parent
5278c4d6f2
commit
ec8d3569d3
10 changed files with 96 additions and 114 deletions
|
@ -1175,7 +1175,7 @@
|
||||||
"saveSecuritySettings": "Save Security Settings",
|
"saveSecuritySettings": "Save Security Settings",
|
||||||
"sendEmailNotification": "Send Email Notification",
|
"sendEmailNotification": "Send Email Notification",
|
||||||
"linkCopied": "Link Copied",
|
"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",
|
"securityKeyManage": "Manage Security Keys",
|
||||||
"securityKeyDescription": "Add or remove security keys for passwordless authentication",
|
"securityKeyDescription": "Add or remove security keys for passwordless authentication",
|
||||||
"securityKeyRegister": "Register New Security Key",
|
"securityKeyRegister": "Register New Security Key",
|
||||||
|
|
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -33,9 +33,9 @@
|
||||||
"@radix-ui/react-toast": "1.2.14",
|
"@radix-ui/react-toast": "1.2.14",
|
||||||
"@react-email/components": "0.3.1",
|
"@react-email/components": "0.3.1",
|
||||||
"@react-email/render": "^1.1.2",
|
"@react-email/render": "^1.1.2",
|
||||||
|
"@react-email/tailwind": "1.2.1",
|
||||||
"@simplewebauthn/browser": "^13.1.0",
|
"@simplewebauthn/browser": "^13.1.0",
|
||||||
"@simplewebauthn/server": "^9.0.3",
|
"@simplewebauthn/server": "^9.0.3",
|
||||||
"@react-email/tailwind": "1.2.1",
|
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
"@tanstack/react-table": "8.21.3",
|
"@tanstack/react-table": "8.21.3",
|
||||||
"arctic": "^3.7.0",
|
"arctic": "^3.7.0",
|
||||||
|
|
|
@ -1,46 +1,13 @@
|
||||||
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
|
await migrate(db as any, {
|
||||||
const sqlite = new Database(location) as BetterSqlite3Database;
|
|
||||||
|
|
||||||
// Drop all existing tables first
|
|
||||||
dropAllTables(sqlite);
|
|
||||||
|
|
||||||
// Run the migrations
|
|
||||||
migrate(db as any, {
|
|
||||||
migrationsFolder: migrationsFolder,
|
migrationsFolder: migrationsFolder,
|
||||||
});
|
});
|
||||||
console.log("Migrations completed successfully.");
|
console.log("Migrations completed successfully.");
|
||||||
|
|
|
@ -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(
|
const validPassword = await verifyPassword(
|
||||||
password,
|
password,
|
||||||
user.passwordHash!
|
user.passwordHash!
|
||||||
|
|
|
@ -15,6 +15,4 @@ export { updateUser } from "./updateUser";
|
||||||
export { adminUpdateUser } from "./adminUpdateUser";
|
export { adminUpdateUser } from "./adminUpdateUser";
|
||||||
export { adminResetUserPassword } from "./adminResetUserPassword";
|
export { adminResetUserPassword } from "./adminResetUserPassword";
|
||||||
export type { AdminResetUserPasswordBody, AdminResetUserPasswordResponse } from "./adminResetUserPassword";
|
export type { AdminResetUserPasswordBody, AdminResetUserPasswordResponse } from "./adminResetUserPassword";
|
||||||
export * from "./updateUser2FA";
|
|
||||||
export * from "./adminUpdateUser2FA";
|
export * from "./adminUpdateUser2FA";
|
||||||
export * from "./adminGetUser";
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { __DIRNAME, APP_VERSION } from "@server/lib/consts";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import m1 from "./scriptsPg/1.6.0";
|
import m1 from "./scriptsPg/1.6.0";
|
||||||
import m2 from "./scriptsPg/1.7.0";
|
import m2 from "./scriptsPg/1.7.0";
|
||||||
|
import m3 from "./scriptsPg/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
|
||||||
|
@ -13,7 +14,8 @@ import m2 from "./scriptsPg/1.7.0";
|
||||||
// Define the migration list with versions and their corresponding functions
|
// Define the migration list with versions and their corresponding functions
|
||||||
const migrations = [
|
const migrations = [
|
||||||
{ version: "1.6.0", run: m1 },
|
{ 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
|
// Add new migrations here as they are created
|
||||||
] as {
|
] as {
|
||||||
version: string;
|
version: string;
|
||||||
|
|
31
server/setup/scriptsPg/1.8.0.ts
Normal file
31
server/setup/scriptsPg/1.8.0.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,12 +17,17 @@ export default async function migration() {
|
||||||
db.exec(`
|
db.exec(`
|
||||||
ALTER TABLE orgs ADD COLUMN passwordResetTokenExpiryHours INTEGER NOT NULL DEFAULT 1;
|
ALTER TABLE orgs ADD COLUMN passwordResetTokenExpiryHours INTEGER NOT NULL DEFAULT 1;
|
||||||
`);
|
`);
|
||||||
})(); // <-- executes the transaction immediately
|
})(); // executes the transaction immediately
|
||||||
db.pragma("foreign_keys = ON");
|
db.pragma("foreign_keys = ON");
|
||||||
console.log(`Added passwordResetTokenExpiryHours column to orgs table`);
|
console.log(`Added passwordResetTokenExpiryHours column to orgs table`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Error adding passwordResetTokenExpiryHours column to orgs table:");
|
console.log("Error adding passwordResetTokenExpiryHours column to orgs table:");
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.pragma("foreign_keys = OFF");
|
||||||
|
db.transaction(() => {
|
||||||
db.exec(`
|
db.exec(`
|
||||||
CREATE TABLE IF NOT EXISTS securityKey (
|
CREATE TABLE IF NOT EXISTS securityKey (
|
||||||
credentialId TEXT PRIMARY KEY,
|
credentialId TEXT PRIMARY KEY,
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -186,52 +186,48 @@ export default function UsersTable({ users }: Props) {
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const r = row.original;
|
const r = row.original;
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex items-center justify-end gap-2">
|
||||||
<div className="flex items-center justify-end gap-2">
|
<AdminUserManagement
|
||||||
<AdminUserManagement
|
userId={r.id}
|
||||||
userId={r.id}
|
userEmail={r.email || ""}
|
||||||
userEmail={r.email || ""}
|
userName={r.name || r.username}
|
||||||
userName={r.name || r.username}
|
userType={r.type}
|
||||||
userType={r.type}
|
userUsername={r.username}
|
||||||
userUsername={r.username}
|
/>
|
||||||
/>
|
<DropdownMenu>
|
||||||
<Button
|
<DropdownMenuTrigger asChild>
|
||||||
variant={"outlinePrimary"}
|
<Button
|
||||||
<DropdownMenu>
|
variant="ghost"
|
||||||
<DropdownMenuTrigger asChild>
|
className="h-8 w-8 p-0"
|
||||||
<Button
|
>
|
||||||
variant="ghost"
|
<span className="sr-only">
|
||||||
className="h-8 w-8 p-0"
|
Open menu
|
||||||
>
|
</span>
|
||||||
<span className="sr-only">
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
Open menu
|
</Button>
|
||||||
</span>
|
</DropdownMenuTrigger>
|
||||||
<MoreHorizontal className="h-4 w-4" />
|
<DropdownMenuContent align="end">
|
||||||
</Button>
|
<DropdownMenuItem
|
||||||
</DropdownMenuTrigger>
|
onClick={() => {
|
||||||
<DropdownMenuContent align="end">
|
setSelected(r);
|
||||||
<DropdownMenuItem
|
setIsDeleteModalOpen(true);
|
||||||
onClick={() => {
|
}}
|
||||||
setSelected(r);
|
>
|
||||||
setIsDeleteModalOpen(true);
|
{t("delete")}
|
||||||
}}
|
</DropdownMenuItem>
|
||||||
>
|
</DropdownMenuContent>
|
||||||
{t("delete")}
|
</DropdownMenu>
|
||||||
</DropdownMenuItem>
|
<Button
|
||||||
</DropdownMenuContent>
|
variant="secondary"
|
||||||
</DropdownMenu>
|
size="sm"
|
||||||
<Button
|
onClick={() => {
|
||||||
variant={"secondary"}
|
router.push(`/admin/users/${r.id}`);
|
||||||
size="sm"
|
}}
|
||||||
onClick={() => {
|
>
|
||||||
router.push(`/admin/users/${r.id}`);
|
{t("edit")}
|
||||||
}}
|
<ArrowRight className="ml-2 w-4 h-4" />
|
||||||
>
|
</Button>
|
||||||
{t("edit")}
|
</div>
|
||||||
<ArrowRight className="ml-2 w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue