From 0e8e92c71574e2bb8e5609fedcf8a5f8fb8405c6 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 16 Feb 2025 20:56:50 -0600 Subject: [PATCH] refactor: add 2fa --- .../dashboard/settings/users/show-users.tsx | 59 ++++++++++--------- apps/dokploy/drizzle/0067_migrate-data.sql | 49 +++++++++++++-- apps/dokploy/pages/invitation.tsx | 9 ++- packages/server/auth-schema.ts | 4 +- packages/server/src/lib/auth.ts | 11 +++- packages/server/src/services/admin.ts | 39 ++++++------ .../server/src/utils/access-log/handler.ts | 13 ++-- 7 files changed, 119 insertions(+), 65 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/users/show-users.tsx b/apps/dokploy/components/dashboard/settings/users/show-users.tsx index e0ffac13..7e3ed6f1 100644 --- a/apps/dokploy/components/dashboard/settings/users/show-users.tsx +++ b/apps/dokploy/components/dashboard/settings/users/show-users.tsx @@ -78,7 +78,9 @@ export const ShowUsers = () => { Email Role 2FA - {/* Status */} + + Is Registered + Created At @@ -104,15 +106,15 @@ export const ShowUsers = () => { - {/* {user.user.is2FAEnabled - ? "2FA Enabled" - : "2FA Not Enabled"} */} + {user.user.twoFactorEnabled + ? "Enabled" + : "Disabled"} + + + {user.user.isRegistered || user.role === "owner" + ? "Registered" + : "Not Registered"} - {/* - - {format(new Date(user.createdAt), "PPpp")} - - */} {format(new Date(user.createdAt), "PPpp")} @@ -134,29 +136,30 @@ export const ShowUsers = () => { Actions - {/* {!user.isRegistered && ( - { - copy( - `${origin}/invitation?token=${user.token}`, - ); - toast.success( - "Invitation Copied to clipboard", - ); - }} - > - Copy Invitation - - )} */} + {!user.user.isRegistered && + user.role !== "owner" && ( + { + copy( + `${origin}/invitation?token=${user.user.token}`, + ); + toast.success( + "Invitation Copied to clipboard", + ); + }} + > + Copy Invitation + + )} - {/* {user.isRegistered && ( + {user.user.isRegistered && ( - )} */} + )} - {/* {user.role !== "owner" && ( + {user.role !== "owner" && ( { Delete User - )} */} + )} diff --git a/apps/dokploy/drizzle/0067_migrate-data.sql b/apps/dokploy/drizzle/0067_migrate-data.sql index 074de9fa..4b860a32 100644 --- a/apps/dokploy/drizzle/0067_migrate-data.sql +++ b/apps/dokploy/drizzle/0067_migrate-data.sql @@ -25,7 +25,8 @@ WITH inserted_users AS ( "stripeSubscriptionId", "serversQuantity", "expirationDate", - "createdAt" + "createdAt", + "two_factor_enabled" ) SELECT a."adminId", @@ -50,11 +51,30 @@ WITH inserted_users AS ( a."stripeSubscriptionId", a."serversQuantity", NOW() + INTERVAL '1 year', - NOW() + NOW(), + COALESCE(auth."is2FAEnabled", false) FROM admin a JOIN auth ON auth.id = a."authId" RETURNING * ), +inserted_two_factor_admin AS ( + -- Insertar registros en two_factor para admins con 2FA habilitado + INSERT INTO two_factor ( + id, + secret, + backup_codes, + user_id + ) + SELECT + gen_random_uuid(), + auth.secret, + gen_random_uuid()::text, + a."adminId" + FROM admin a + JOIN auth ON auth.id = a."authId" + WHERE auth."is2FAEnabled" = true + RETURNING * +), inserted_accounts AS ( -- Insertar cuentas para los admins INSERT INTO account ( @@ -120,7 +140,8 @@ inserted_members AS ( "canDeleteServices", "accesedProjects", "accesedServices", - "expirationDate" + "expirationDate", + "two_factor_enabled" ) SELECT u."userId", @@ -141,7 +162,8 @@ inserted_members AS ( COALESCE(u."canDeleteServices", false), COALESCE(u."accesedProjects", '{}'), COALESCE(u."accesedServices", '{}'), - NOW() + INTERVAL '1 year' + NOW() + INTERVAL '1 year', + COALESCE(auth."is2FAEnabled", false) FROM "user" u JOIN admin a ON u."adminId" = a."adminId" JOIN auth ON auth.id = u."authId" @@ -173,6 +195,25 @@ inserted_member_accounts AS ( JOIN auth ON auth.id = u."authId" RETURNING * ), +inserted_two_factor_members AS ( + -- Insertar registros en two_factor para miembros con 2FA habilitado + INSERT INTO two_factor ( + id, + secret, + backup_codes, + user_id + ) + SELECT + gen_random_uuid(), + auth.secret, + gen_random_uuid()::text, + u."userId" + FROM "user" u + JOIN admin a ON u."adminId" = a."adminId" + JOIN auth ON auth.id = u."authId" + WHERE auth."is2FAEnabled" = true + RETURNING * +), inserted_admin_members AS ( -- Insertar miembros en las organizaciones (admins como owners) INSERT INTO member ( diff --git a/apps/dokploy/pages/invitation.tsx b/apps/dokploy/pages/invitation.tsx index 77f9f249..e8bfc3fc 100644 --- a/apps/dokploy/pages/invitation.tsx +++ b/apps/dokploy/pages/invitation.tsx @@ -27,6 +27,7 @@ import { type ReactElement, useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; +import superjson from "superjson"; const registerSchema = z .object({ @@ -98,9 +99,9 @@ const Invitation = ({ token, invitation, isCloud }: Props) => { }); useEffect(() => { - if (data?.auth?.email) { + if (data?.email) { form.reset({ - email: data?.auth?.email || "", + email: data?.email || "", password: "", confirmPassword: "", }); @@ -109,7 +110,7 @@ const Invitation = ({ token, invitation, isCloud }: Props) => { const onSubmit = async (values: Register) => { await mutateAsync({ - id: data?.authId, + id: data?.id, password: values.password, token: token, }) @@ -254,6 +255,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) { const { query } = ctx; const token = query.token; + console.log("query", query); if (typeof token !== "string") { return { @@ -266,6 +268,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) { try { const invitation = await getUserByToken(token); + console.log("invitation", invitation); if (invitation.isExpired) { return { diff --git a/packages/server/auth-schema.ts b/packages/server/auth-schema.ts index 045a7523..2500b615 100644 --- a/packages/server/auth-schema.ts +++ b/packages/server/auth-schema.ts @@ -1,9 +1,9 @@ import { + boolean, + integer, pgTable, text, - integer, timestamp, - boolean, } from "drizzle-orm/pg-core"; export const users_temp = pgTable("users_temp", { diff --git a/packages/server/src/lib/auth.ts b/packages/server/src/lib/auth.ts index fece335b..cc144345 100644 --- a/packages/server/src/lib/auth.ts +++ b/packages/server/src/lib/auth.ts @@ -16,7 +16,16 @@ export const auth = betterAuth({ provider: "pg", schema: schema, }), - + socialProviders: { + github: { + clientId: process.env.GITHUB_CLIENT_ID as string, + clientSecret: process.env.GITHUB_CLIENT_SECRET as string, + }, + google: { + clientId: process.env.GOOGLE_CLIENT_ID as string, + clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, + }, + }, emailAndPassword: { enabled: true, diff --git a/packages/server/src/services/admin.ts b/packages/server/src/services/admin.ts index 53de805e..eee6bb37 100644 --- a/packages/server/src/services/admin.ts +++ b/packages/server/src/services/admin.ts @@ -106,26 +106,25 @@ export const isAdminPresent = async () => { }; export const getUserByToken = async (token: string) => { - // const user = await db.query.users.findFirst({ - // where: eq(users.token, token), - // with: { - // auth: { - // columns: { - // password: false, - // }, - // }, - // }, - // }); - // if (!user) { - // throw new TRPCError({ - // code: "NOT_FOUND", - // message: "Invitation not found", - // }); - // } - // return { - // ...user, - // isExpired: user.isRegistered, - // }; + const user = await db.query.users_temp.findFirst({ + where: eq(users_temp.token, token), + columns: { + id: true, + email: true, + token: true, + isRegistered: true, + }, + }); + if (!user) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Invitation not found", + }); + } + return { + ...user, + isExpired: user.isRegistered, + }; }; export const removeUserById = async (userId: string) => { diff --git a/packages/server/src/utils/access-log/handler.ts b/packages/server/src/utils/access-log/handler.ts index 57471732..30b18ea4 100644 --- a/packages/server/src/utils/access-log/handler.ts +++ b/packages/server/src/utils/access-log/handler.ts @@ -29,16 +29,15 @@ class LogRotationManager { } private async getStateFromDB(): Promise { - const setting = await db.query.admins.findFirst({}); - return setting?.enableLogRotation ?? false; + // const setting = await db.query.admins.findFirst({}); + // return setting?.enableLogRotation ?? false; } private async setStateInDB(active: boolean): Promise { - const admin = await db.query.admins.findFirst({}); - - if (!admin) { - return; - } + // const admin = await db.query.admins.findFirst({}); + // if (!admin) { + // return; + // } // await updateAdmin(admin?.authId, { // enableLogRotation: active, // });