mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat: add organizations and members
This commit is contained in:
@@ -71,6 +71,7 @@ export const organizationRelations = relations(
|
||||
}),
|
||||
servers: many(server),
|
||||
projects: many(projects),
|
||||
members: many(member),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -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"),
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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)));
|
||||
|
||||
Reference in New Issue
Block a user