mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-01 12:05:23 +02:00
feat(auth): implement migration script for auth secret and refactor secret handling
- Added a new script `migrate-auth-secret.ts` to facilitate the migration of 2FA secrets when changing the BETTER_AUTH_SECRET. - Updated `package.json` to include a command for running the migration script. - Refactored the handling of BETTER_AUTH_SECRET to improve security by removing the hardcoded default and introducing a fallback mechanism using environment variables or Docker secrets. - Updated the authentication logic to utilize the new `betterAuthSecret` function for retrieving the secret.
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
"wait-for-postgres-dev": "tsx -r dotenv/config wait-for-postgres.ts",
|
||||
"reset-password": "node -r dotenv/config dist/reset-password.mjs",
|
||||
"reset-2fa": "node -r dotenv/config dist/reset-2fa.mjs",
|
||||
"migrate-auth-secret": "tsx -r dotenv/config scripts/migrate-auth-secret.ts",
|
||||
"dev": "tsx -r dotenv/config ./server/server.ts --project tsconfig.server.json ",
|
||||
"studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
|
||||
"migration:generate": "drizzle-kit generate --config ./server/db/drizzle.config.ts",
|
||||
|
||||
97
apps/dokploy/scripts/migrate-auth-secret.ts
Normal file
97
apps/dokploy/scripts/migrate-auth-secret.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Use this command to automatically migrate the auth secret: curl -sSL https://dokploy.com/security/0.29.3.sh | bash
|
||||
* Migration script: re-encrypt 2FA secrets after rotating BETTER_AUTH_SECRET.
|
||||
*
|
||||
* Usage:
|
||||
* OLD_SECRET=<old_secret> NEW_SECRET=<new_secret> npx tsx apps/dokploy/scripts/migrate-auth-secret.ts
|
||||
*
|
||||
* Both OLD_SECRET and NEW_SECRET are required.
|
||||
* Run this BEFORE restarting Dokploy with the new secret.
|
||||
*/
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { twoFactor } from "@dokploy/server/db/schema";
|
||||
import { symmetricDecrypt, symmetricEncrypt } from "better-auth/crypto";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
const OLD_SECRET = process.env.OLD_SECRET as string;
|
||||
const NEW_SECRET = process.env.NEW_SECRET as string;
|
||||
|
||||
if (!OLD_SECRET || !NEW_SECRET) {
|
||||
console.error(
|
||||
"❌ OLD_SECRET and NEW_SECRET environment variables are required.",
|
||||
);
|
||||
console.error(
|
||||
" Usage: OLD_SECRET=<old> NEW_SECRET=<new> npx tsx apps/dokploy/scripts/migrate-auth-secret.ts",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (OLD_SECRET === NEW_SECRET) {
|
||||
console.error("❌ OLD_SECRET and NEW_SECRET must be different.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function reEncrypt(
|
||||
value: string,
|
||||
oldSecret: string,
|
||||
newSecret: string,
|
||||
): Promise<string> {
|
||||
const plaintext = await symmetricDecrypt({ key: oldSecret, data: value });
|
||||
return symmetricEncrypt({ key: newSecret, data: plaintext });
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("🔍 Fetching 2FA records...");
|
||||
const records = await db.select().from(twoFactor);
|
||||
|
||||
if (records.length === 0) {
|
||||
console.log("✅ No 2FA records found, nothing to migrate.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`📦 Found ${records.length} 2FA record(s) to migrate.`);
|
||||
|
||||
let migrated = 0;
|
||||
let failed = 0;
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
for (const record of records) {
|
||||
try {
|
||||
const [newSecret, newBackupCodes] = await Promise.all([
|
||||
reEncrypt(record.secret, OLD_SECRET, NEW_SECRET),
|
||||
reEncrypt(record.backupCodes, OLD_SECRET, NEW_SECRET),
|
||||
]);
|
||||
|
||||
await tx
|
||||
.update(twoFactor)
|
||||
.set({ secret: newSecret, backupCodes: newBackupCodes })
|
||||
.where(eq(twoFactor.id, record.id));
|
||||
|
||||
migrated++;
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`❌ Failed to migrate record ${record.id} (userId: ${record.userId}):`,
|
||||
err,
|
||||
);
|
||||
failed++;
|
||||
throw err; // rollback the whole transaction
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`✅ Migrated ${migrated} record(s) successfully.`);
|
||||
|
||||
if (failed > 0) {
|
||||
console.error(
|
||||
`❌ ${failed} record(s) failed — transaction was rolled back.`,
|
||||
);
|
||||
process.exit(1);
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("❌ Migration failed:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user