mirror of
https://github.com/andwati/dokploy.git
synced 2026-10-21 12:34:29 +00:00
213 lines
6.2 KiB
TypeScript
213 lines
6.2 KiB
TypeScript
import { randomBytes } from "node:crypto";
|
|
import { createOTP } from "@better-auth/utils/otp";
|
|
import { db } from "@dokploy/server/db";
|
|
import { users_temp } from "@dokploy/server/db/schema";
|
|
import { getPublicIpWithFallback } from "@dokploy/server/wss/utils";
|
|
import { TRPCError } from "@trpc/server";
|
|
import * as bcrypt from "bcrypt";
|
|
import { eq } from "drizzle-orm";
|
|
import encode from "hi-base32";
|
|
import { TOTP } from "otpauth";
|
|
import QRCode from "qrcode";
|
|
import { IS_CLOUD } from "../constants";
|
|
import { findUserById } from "./admin";
|
|
import type { User } from "./user";
|
|
|
|
export const findAuthById = async (authId: string) => {
|
|
const result = await db.query.users_temp.findFirst({
|
|
where: eq(users_temp.id, authId),
|
|
columns: {
|
|
createdAt: false,
|
|
updatedAt: false,
|
|
},
|
|
});
|
|
if (!result) {
|
|
throw new TRPCError({
|
|
code: "NOT_FOUND",
|
|
message: "Auth not found",
|
|
});
|
|
}
|
|
return result;
|
|
};
|
|
|
|
const generateBase32Secret = () => {
|
|
// Generamos 32 bytes (256 bits) para asegurar que tengamos suficiente longitud
|
|
const buffer = randomBytes(32);
|
|
// Convertimos directamente a hex para Better Auth
|
|
const hex = buffer.toString("hex");
|
|
// También necesitamos la versión base32 para el QR code
|
|
const base32 = encode.encode(buffer).replace(/=/g, "").substring(0, 32);
|
|
return {
|
|
hex,
|
|
base32,
|
|
};
|
|
};
|
|
|
|
export const generate2FASecret = () => {
|
|
const secret = "46JMUCG4NJ3CIU6LQAIVFWUW";
|
|
|
|
const totp = new TOTP({
|
|
issuer: "Dokploy",
|
|
label: "[email protected]",
|
|
algorithm: "SHA1",
|
|
digits: 6,
|
|
secret: secret,
|
|
});
|
|
|
|
// Convertir los bytes del secreto a hex
|
|
const secretBytes = totp.secret.bytes;
|
|
const hexSecret = Buffer.from(secretBytes).toString("hex");
|
|
|
|
console.log("Secret bytes:", secretBytes);
|
|
console.log("Hex secret:", hexSecret);
|
|
|
|
return {
|
|
secret,
|
|
hexSecret,
|
|
totp,
|
|
};
|
|
};
|
|
|
|
export const verify2FA = async (auth: User, secret: string, pin: string) => {
|
|
const totp = new TOTP({
|
|
issuer: "Dokploy",
|
|
label: `${auth?.email}`,
|
|
algorithm: "SHA1",
|
|
digits: 6,
|
|
secret: secret,
|
|
period: 30,
|
|
});
|
|
|
|
const delta = totp.validate({ token: pin });
|
|
|
|
if (delta === null) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: "Invalid 2FA code",
|
|
});
|
|
}
|
|
return auth;
|
|
};
|
|
|
|
const convertBase32ToHex = (base32Secret: string) => {
|
|
try {
|
|
// Asegurarnos de que la longitud sea múltiplo de 8 agregando padding
|
|
let paddedSecret = base32Secret;
|
|
while (paddedSecret.length % 8 !== 0) {
|
|
paddedSecret += "=";
|
|
}
|
|
|
|
const bytes = encode.decode.asBytes(paddedSecret.toUpperCase());
|
|
let hex = Buffer.from(bytes).toString("hex");
|
|
|
|
// Asegurarnos de que el hex tenga al menos 32 caracteres (16 bytes)
|
|
while (hex.length < 32) {
|
|
hex += "0";
|
|
}
|
|
|
|
return hex;
|
|
} catch (error) {
|
|
console.error("Error converting base32 to hex:", error);
|
|
return base32Secret;
|
|
}
|
|
};
|
|
|
|
// Para probar
|
|
// const testSecret = "46JMUCG4NJ3CIU6LQAIVFWUW";
|
|
// console.log("Original:", testSecret);
|
|
// console.log("Converted:", convertBase32ToHex(testSecret));
|
|
// console.log(
|
|
// "Length in bytes:",
|
|
// Buffer.from(convertBase32ToHex(testSecret), "hex").length,
|
|
// );
|
|
// console.log(generate2FASecret().secret.secret);
|
|
|
|
// // Para probar
|
|
// const testResult = generate2FASecret();
|
|
// console.log("\nResultados:");
|
|
// console.log("Original base32:", testResult.secret);
|
|
// console.log("Hex convertido:", testResult.hexSecret);
|
|
// console.log(
|
|
// "Longitud en bytes:",
|
|
// Buffer.from(testResult.hexSecret, "hex").length,
|
|
// );
|
|
export const symmetricDecrypt = async ({ key, data }) => {
|
|
const keyAsBytes = await createHash("SHA-256").digest(key);
|
|
const dataAsBytes = hexToBytes(data);
|
|
const chacha = managedNonce(xchacha20poly1305)(new Uint8Array(keyAsBytes));
|
|
return new TextDecoder().decode(chacha.decrypt(dataAsBytes));
|
|
};
|
|
// export const migrateExistingSecret = async (
|
|
// existingBase32Secret: string,
|
|
// encryptionKey: string,
|
|
// ) => {
|
|
// try {
|
|
// // 1. Primero asegurarnos que el secreto base32 tenga el padding correcto
|
|
// let paddedSecret = existingBase32Secret;
|
|
// while (paddedSecret.length % 8 !== 0) {
|
|
// paddedSecret += "=";
|
|
// }
|
|
|
|
// // 2. Decodificar el base32 a bytes usando hi-base32
|
|
// const bytes = encode.decode.asBytes(paddedSecret.toUpperCase());
|
|
|
|
// // 3. Convertir los bytes a hex
|
|
// const hexSecret = Buffer.from(bytes).toString("hex");
|
|
|
|
// // 4. Encriptar el secreto hex usando Better Auth
|
|
// const encryptedSecret = await symmetricEncrypt({
|
|
// key: encryptionKey,
|
|
// data: hexSecret,
|
|
// });
|
|
|
|
// // 5. Crear TOTP con el secreto original para validación
|
|
// const originalTotp = new TOTP({
|
|
// issuer: "Dokploy",
|
|
// label: "migration-test",
|
|
// algorithm: "SHA1",
|
|
// digits: 6,
|
|
// secret: existingBase32Secret,
|
|
// });
|
|
|
|
// // 6. Generar un código de prueba con el secreto original
|
|
// const testCode = originalTotp.generate();
|
|
|
|
// // 7. Validar que el código funcione con el secreto original
|
|
// const isValid = originalTotp.validate({ token: testCode }) !== null;
|
|
|
|
// return {
|
|
// originalSecret: existingBase32Secret,
|
|
// hexSecret,
|
|
// encryptedSecret, // Este es el valor que debes guardar en la base de datos
|
|
// isValid,
|
|
// testCode,
|
|
// secretLength: hexSecret.length,
|
|
// };
|
|
// } catch (error: unknown) {
|
|
// const errorMessage =
|
|
// error instanceof Error ? error.message : "Unknown error";
|
|
// console.error("Error durante la migración:", errorMessage);
|
|
// throw new Error(`Error al migrar el secreto: ${errorMessage}`);
|
|
// }
|
|
// };
|
|
|
|
// // // Ejemplo de uso con el secreto de prueba
|
|
// // const testMigration = await migrateExistingSecret(
|
|
// // "46JMUCG4NJ3CIU6LQAIVFWUW",
|
|
// // process.env.BETTER_AUTH_SECRET || "your-encryption-key",
|
|
// // );
|
|
// // console.log("\nPrueba de migración:");
|
|
// // console.log("Secreto original (base32):", testMigration.originalSecret);
|
|
// // console.log("Secreto convertido (hex):", testMigration.hexSecret);
|
|
// // console.log("Secreto encriptado:", testMigration.encryptedSecret);
|
|
// // console.log("Longitud del secreto hex:", testMigration.secretLength);
|
|
// // console.log("¿Conversión válida?:", testMigration.isValid);
|
|
// // console.log("Código de prueba:", testMigration.testCode);
|
|
// const secret = "46JMUCG4NJ3CIU6LQAIVFWUW";
|
|
// const isValid = createOTP(secret, {
|
|
// digits: 6,
|
|
// period: 30,
|
|
// }).verify("123456");
|
|
|
|
// console.log(isValid.then((isValid) => console.log(isValid)));
|