feat: add organizations and members

This commit is contained in:
Mauricio Siu
2025-02-17 02:48:42 -06:00
parent c7d47a6003
commit b73e4102dd
17 changed files with 5385 additions and 329 deletions

View File

@@ -71,6 +71,7 @@ export const organizationRelations = relations(
}),
servers: many(server),
projects: many(projects),
members: many(member),
}),
);

View File

@@ -12,7 +12,7 @@ export const session = pgTable("session_temp", {
userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
.references(() => users_temp.id),
.references(() => users_temp.id, { onDelete: "cascade" }),
impersonatedBy: text("impersonated_by"),
activeOrganizationId: text("active_organization_id"),
});

View File

@@ -7,7 +7,7 @@ import {
organization,
twoFactor,
} from "better-auth/plugins";
import { desc, eq } from "drizzle-orm";
import { and, desc, eq } from "drizzle-orm";
import { db } from "../db";
import * as schema from "../db/schema";
@@ -43,22 +43,25 @@ export const auth = betterAuth({
after: createAuthMiddleware(async (ctx) => {
if (ctx.path.startsWith("/sign-up")) {
const newSession = ctx.context.newSession;
const organization = await db
.insert(schema.organization)
.values({
name: "My Organization",
ownerId: newSession?.user?.id || "",
createdAt: new Date(),
})
.returning()
.then((res) => res[0]);
if (ctx.headers?.get("x-dokploy-token")) {
} else {
const organization = await db
.insert(schema.organization)
.values({
name: "My Organization",
ownerId: newSession?.user?.id || "",
createdAt: new Date(),
})
.returning()
.then((res) => res[0]);
await db.insert(schema.member).values({
userId: newSession?.user?.id || "",
organizationId: organization?.id || "",
role: "owner",
createdAt: new Date(),
});
await db.insert(schema.member).values({
userId: newSession?.user?.id || "",
organizationId: organization?.id || "",
role: "owner",
createdAt: new Date(),
});
}
}
}),
},
@@ -89,11 +92,13 @@ export const auth = betterAuth({
additionalFields: {
role: {
type: "string",
required: true,
// required: true,
input: false,
},
ownerId: {
type: "string",
required: true,
// required: true,
input: false,
},
},
},
@@ -133,7 +138,13 @@ export const validateRequest = async (request: IncomingMessage) => {
if (session?.user) {
const member = await db.query.member.findFirst({
where: eq(schema.member.userId, session.user.id),
where: and(
eq(schema.member.userId, session.user.id),
eq(
schema.member.organizationId,
session.session.activeOrganizationId || "",
),
),
with: {
organization: true,
},

View File

@@ -3,6 +3,7 @@ import { db } from "@dokploy/server/db";
import {
account,
type apiCreateUserInvitation,
invitation,
member,
organization,
users_temp,
@@ -64,6 +65,13 @@ export const findUserById = async (userId: string) => {
return user;
};
export const findOrganizationById = async (organizationId: string) => {
const organizationResult = await db.query.organization.findFirst({
where: eq(organization.id, organizationId),
});
return organizationResult;
};
export const updateUser = async (userId: string, userData: Partial<User>) => {
const user = await db
.update(users_temp)
@@ -106,24 +114,34 @@ export const isAdminPresent = async () => {
};
export const getUserByToken = async (token: string) => {
const user = await db.query.users_temp.findFirst({
where: eq(users_temp.token, token),
const user = await db.query.invitation.findFirst({
where: eq(invitation.id, token),
columns: {
id: true,
email: true,
token: true,
isRegistered: true,
status: true,
expiresAt: true,
role: true,
inviterId: true,
},
});
if (!user) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Invitation not found",
});
}
const userAlreadyExists = await db.query.users_temp.findFirst({
where: eq(users_temp.email, user?.email || ""),
});
const { expiresAt, ...rest } = user;
return {
...user,
isExpired: user.isRegistered,
...rest,
isExpired: user.expiresAt < new Date(),
userAlreadyExists: !!userAlreadyExists,
};
};

View File

@@ -137,76 +137,76 @@ export const symmetricDecrypt = async ({ key, 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 += "=";
}
// 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());
// // 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");
// // 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,
});
// // 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,
});
// // 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();
// // 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;
// // 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}`);
}
};
// 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");
// // // 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)));
// console.log(isValid.then((isValid) => console.log(isValid)));