diff --git a/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx b/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx index 7c1f50375..bde2e71ca 100644 --- a/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx +++ b/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx @@ -52,7 +52,7 @@ interface Props { export const AddUserPermissions = ({ userId }: Props) => { const { data: projects } = api.project.all.useQuery(); - const { data, refetch } = api.user.byUserId.useQuery( + const { data, refetch } = api.auth.one.useQuery( { userId, }, @@ -92,7 +92,7 @@ export const AddUserPermissions = ({ userId }: Props) => { const onSubmit = async (data: AddPermissions) => { await mutateAsync({ - userId, + id: userId, canCreateServices: data.canCreateServices, canCreateProjects: data.canCreateProjects, canDeleteServices: data.canDeleteServices, diff --git a/apps/dokploy/components/dashboard/settings/users/show-users.tsx b/apps/dokploy/components/dashboard/settings/users/show-users.tsx index 8aa2a37b4..e0ffac139 100644 --- a/apps/dokploy/components/dashboard/settings/users/show-users.tsx +++ b/apps/dokploy/components/dashboard/settings/users/show-users.tsx @@ -104,9 +104,9 @@ export const ShowUsers = () => { - {user.user.is2FAEnabled + {/* {user.user.is2FAEnabled ? "2FA Enabled" - : "2FA Not Enabled"} + : "2FA Not Enabled"} */} {/* @@ -156,7 +156,7 @@ export const ShowUsers = () => { /> )} */} - {user.role !== "owner" && ( + {/* {user.role !== "owner" && ( { Delete User - )} + )} */} diff --git a/apps/dokploy/components/dashboard/settings/web-server/update-server-ip.tsx b/apps/dokploy/components/dashboard/settings/web-server/update-server-ip.tsx index dd3f81fdc..07103d5ba 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/update-server-ip.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/update-server-ip.tsx @@ -1,5 +1,4 @@ import { AlertBlock } from "@/components/shared/alert-block"; -import { CodeEditor } from "@/components/shared/code-editor"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -47,11 +46,11 @@ interface Props { export const UpdateServerIp = ({ children, serverId }: Props) => { const [isOpen, setIsOpen] = useState(false); - const { data } = api.admin.one.useQuery(); + const { data } = api.user.get.useQuery(); const { data: ip } = api.server.publicIp.useQuery(); const { mutateAsync, isLoading, error, isError } = - api.admin.update.useMutation(); + api.user.update.useMutation(); const form = useForm({ defaultValues: { diff --git a/apps/dokploy/drizzle/0066_yielding_echo.sql b/apps/dokploy/drizzle/0066_yielding_echo.sql index c66e2318b..d69a4c659 100644 --- a/apps/dokploy/drizzle/0066_yielding_echo.sql +++ b/apps/dokploy/drizzle/0066_yielding_echo.sql @@ -16,6 +16,7 @@ CREATE TABLE "user_temp" ( "canAccessToTraefikFiles" boolean DEFAULT false NOT NULL, "accesedProjects" text[] DEFAULT ARRAY[]::text[] NOT NULL, "accesedServices" text[] DEFAULT ARRAY[]::text[] NOT NULL, + "two_factor_enabled" boolean DEFAULT false NOT NULL, "email" text NOT NULL, "email_verified" boolean NOT NULL, "image" text, @@ -113,6 +114,13 @@ CREATE TABLE "verification" ( "created_at" timestamp, "updated_at" timestamp ); + +CREATE TABLE "two_factor" ( + "id" text PRIMARY KEY NOT NULL, + "secret" text NOT NULL, + "backup_codes" text NOT NULL, + "user_id" text NOT NULL +); --> statement-breakpoint ALTER TABLE "certificate" ALTER COLUMN "adminId" SET NOT NULL;--> statement-breakpoint ALTER TABLE "notification" ALTER COLUMN "adminId" SET NOT NULL;--> statement-breakpoint @@ -124,4 +132,5 @@ ALTER TABLE "invitation" ADD CONSTRAINT "invitation_organization_id_organization ALTER TABLE "invitation" ADD CONSTRAINT "invitation_inviter_id_user_temp_id_fk" FOREIGN KEY ("inviter_id") REFERENCES "public"."user_temp"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "member" ADD CONSTRAINT "member_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "member" ADD CONSTRAINT "member_user_id_user_temp_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user_temp"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "organization" ADD CONSTRAINT "organization_owner_id_user_temp_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."user_temp"("id") ON DELETE no action ON UPDATE no action; \ No newline at end of file +ALTER TABLE "organization" ADD CONSTRAINT "organization_owner_id_user_temp_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."user_temp"("id") ON DELETE no action ON UPDATE no action; +ALTER TABLE "two_factor" ADD CONSTRAINT "two_factor_user_id_user_temp_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user_temp"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint diff --git a/apps/dokploy/drizzle/0073_brave_wolfpack.sql b/apps/dokploy/drizzle/0073_polite_miss_america.sql similarity index 90% rename from apps/dokploy/drizzle/0073_brave_wolfpack.sql rename to apps/dokploy/drizzle/0073_polite_miss_america.sql index 05bf5cb47..030f8a883 100644 --- a/apps/dokploy/drizzle/0073_brave_wolfpack.sql +++ b/apps/dokploy/drizzle/0073_polite_miss_america.sql @@ -1,3 +1,4 @@ +--> statement-breakpoint DROP TABLE "user" CASCADE;--> statement-breakpoint DROP TABLE "admin" CASCADE;--> statement-breakpoint DROP TABLE "auth" CASCADE;--> statement-breakpoint diff --git a/apps/dokploy/drizzle/meta/0066_snapshot.json b/apps/dokploy/drizzle/meta/0066_snapshot.json index bcb7807a7..06b899346 100644 --- a/apps/dokploy/drizzle/meta/0066_snapshot.json +++ b/apps/dokploy/drizzle/meta/0066_snapshot.json @@ -1010,6 +1010,12 @@ "notNull": true, "default": "ARRAY[]::text[]" }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, "email": { "name": "email", "type": "text", @@ -5045,6 +5051,57 @@ "checkConstraints": {}, "isRLSEnabled": false }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_temp_id_fk": { + "name": "two_factor_user_id_user_temp_id_fk", + "tableFrom": "two_factor", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, "public.verification": { "name": "verification", "schema": "", diff --git a/apps/dokploy/drizzle/meta/0067_snapshot.json b/apps/dokploy/drizzle/meta/0067_snapshot.json index 69d4a4ee8..be43b406b 100644 --- a/apps/dokploy/drizzle/meta/0067_snapshot.json +++ b/apps/dokploy/drizzle/meta/0067_snapshot.json @@ -1010,6 +1010,12 @@ "notNull": true, "default": "ARRAY[]::text[]" }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, "email": { "name": "email", "type": "text", @@ -5045,6 +5051,57 @@ "checkConstraints": {}, "isRLSEnabled": false }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_temp_id_fk": { + "name": "two_factor_user_id_user_temp_id_fk", + "tableFrom": "two_factor", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, "public.verification": { "name": "verification", "schema": "", diff --git a/apps/dokploy/drizzle/meta/0068_snapshot.json b/apps/dokploy/drizzle/meta/0068_snapshot.json index 02e33084f..2139b1321 100644 --- a/apps/dokploy/drizzle/meta/0068_snapshot.json +++ b/apps/dokploy/drizzle/meta/0068_snapshot.json @@ -1010,6 +1010,12 @@ "notNull": true, "default": "ARRAY[]::text[]" }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, "email": { "name": "email", "type": "text", @@ -5045,6 +5051,57 @@ "checkConstraints": {}, "isRLSEnabled": false }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_temp_id_fk": { + "name": "two_factor_user_id_user_temp_id_fk", + "tableFrom": "two_factor", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, "public.verification": { "name": "verification", "schema": "", diff --git a/apps/dokploy/drizzle/meta/0069_snapshot.json b/apps/dokploy/drizzle/meta/0069_snapshot.json index 41b53582e..d3e5be98b 100644 --- a/apps/dokploy/drizzle/meta/0069_snapshot.json +++ b/apps/dokploy/drizzle/meta/0069_snapshot.json @@ -1018,6 +1018,12 @@ "notNull": true, "default": "ARRAY[]::text[]" }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, "email": { "name": "email", "type": "text", @@ -5053,6 +5059,57 @@ "checkConstraints": {}, "isRLSEnabled": false }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_temp_id_fk": { + "name": "two_factor_user_id_user_temp_id_fk", + "tableFrom": "two_factor", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, "public.verification": { "name": "verification", "schema": "", diff --git a/apps/dokploy/drizzle/meta/0070_snapshot.json b/apps/dokploy/drizzle/meta/0070_snapshot.json index 681bded7e..e3864a19c 100644 --- a/apps/dokploy/drizzle/meta/0070_snapshot.json +++ b/apps/dokploy/drizzle/meta/0070_snapshot.json @@ -1018,6 +1018,12 @@ "notNull": true, "default": "ARRAY[]::text[]" }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, "email": { "name": "email", "type": "text", @@ -5205,6 +5211,57 @@ "checkConstraints": {}, "isRLSEnabled": false }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_temp_id_fk": { + "name": "two_factor_user_id_user_temp_id_fk", + "tableFrom": "two_factor", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, "public.verification": { "name": "verification", "schema": "", diff --git a/apps/dokploy/drizzle/meta/0071_snapshot.json b/apps/dokploy/drizzle/meta/0071_snapshot.json index 4214343a0..cce94ce93 100644 --- a/apps/dokploy/drizzle/meta/0071_snapshot.json +++ b/apps/dokploy/drizzle/meta/0071_snapshot.json @@ -1018,6 +1018,12 @@ "notNull": true, "default": "ARRAY[]::text[]" }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, "email": { "name": "email", "type": "text", @@ -5205,6 +5211,57 @@ "checkConstraints": {}, "isRLSEnabled": false }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_temp_id_fk": { + "name": "two_factor_user_id_user_temp_id_fk", + "tableFrom": "two_factor", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, "public.verification": { "name": "verification", "schema": "", diff --git a/apps/dokploy/drizzle/meta/0072_snapshot.json b/apps/dokploy/drizzle/meta/0072_snapshot.json index 2f38d98d9..53797c0b9 100644 --- a/apps/dokploy/drizzle/meta/0072_snapshot.json +++ b/apps/dokploy/drizzle/meta/0072_snapshot.json @@ -1018,6 +1018,12 @@ "notNull": true, "default": "ARRAY[]::text[]" }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, "email": { "name": "email", "type": "text", @@ -5053,6 +5059,57 @@ "checkConstraints": {}, "isRLSEnabled": false }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_temp_id_fk": { + "name": "two_factor_user_id_user_temp_id_fk", + "tableFrom": "two_factor", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, "public.verification": { "name": "verification", "schema": "", diff --git a/apps/dokploy/drizzle/meta/0073_snapshot.json b/apps/dokploy/drizzle/meta/0073_snapshot.json index e61b447ca..6b65df2f7 100644 --- a/apps/dokploy/drizzle/meta/0073_snapshot.json +++ b/apps/dokploy/drizzle/meta/0073_snapshot.json @@ -1,5 +1,5 @@ { - "id": "07170d9f-4d67-48f5-890f-393043396973", + "id": "e357a19a-dd1e-4843-b567-0c0243ade7a8", "prevId": "4eb71c0e-5bdb-427b-b198-39b1059dcd16", "version": "7", "dialect": "postgresql", @@ -858,6 +858,12 @@ "notNull": true, "default": "ARRAY[]::text[]" }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, "email": { "name": "email", "type": "text", @@ -4602,6 +4608,57 @@ "checkConstraints": {}, "isRLSEnabled": false }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_temp_id_fk": { + "name": "two_factor_user_id_user_temp_id_fk", + "tableFrom": "two_factor", + "tableTo": "user_temp", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, "public.verification": { "name": "verification", "schema": "", diff --git a/apps/dokploy/drizzle/meta/_journal.json b/apps/dokploy/drizzle/meta/_journal.json index b591cceda..39903f65a 100644 --- a/apps/dokploy/drizzle/meta/_journal.json +++ b/apps/dokploy/drizzle/meta/_journal.json @@ -516,8 +516,8 @@ { "idx": 73, "version": "7", - "when": 1739735739336, - "tag": "0073_brave_wolfpack", + "when": 1739740193879, + "tag": "0073_polite_miss_america", "breakpoints": true } ] diff --git a/apps/dokploy/pages/dashboard/docker.tsx b/apps/dokploy/pages/dashboard/docker.tsx index 202935aa6..fee202fce 100644 --- a/apps/dokploy/pages/dashboard/docker.tsx +++ b/apps/dokploy/pages/dashboard/docker.tsx @@ -54,7 +54,7 @@ export async function getServerSideProps( await helpers.project.all.prefetch(); if (user.role === "member") { - const userR = await helpers.user.get.fetch({ + const userR = await helpers.user.one.fetch({ userId: user.id, }); diff --git a/apps/dokploy/pages/dashboard/settings/git-providers.tsx b/apps/dokploy/pages/dashboard/settings/git-providers.tsx index d0d9ca7f1..cfded9915 100644 --- a/apps/dokploy/pages/dashboard/settings/git-providers.tsx +++ b/apps/dokploy/pages/dashboard/settings/git-providers.tsx @@ -50,7 +50,7 @@ export async function getServerSideProps( await helpers.project.all.prefetch(); await helpers.settings.isCloud.prefetch(); if (user.role === "member") { - const userR = await helpers.user.get.fetch({ + const userR = await helpers.user.one.fetch({ userId: user.id, }); diff --git a/apps/dokploy/pages/dashboard/settings/profile.tsx b/apps/dokploy/pages/dashboard/settings/profile.tsx index bfae14e62..a84fb4dba 100644 --- a/apps/dokploy/pages/dashboard/settings/profile.tsx +++ b/apps/dokploy/pages/dashboard/settings/profile.tsx @@ -57,7 +57,7 @@ export async function getServerSideProps( await helpers.settings.isCloud.prefetch(); await helpers.auth.get.prefetch(); if (user?.role === "member") { - // const userR = await helpers.user.get.fetch({ + // const userR = await helpers.user.one.fetch({ // userId: user.id, // }); // await helpers.user.byAuthId.prefetch({ diff --git a/apps/dokploy/pages/dashboard/settings/ssh-keys.tsx b/apps/dokploy/pages/dashboard/settings/ssh-keys.tsx index f3b9cf1b8..c97df7ba1 100644 --- a/apps/dokploy/pages/dashboard/settings/ssh-keys.tsx +++ b/apps/dokploy/pages/dashboard/settings/ssh-keys.tsx @@ -51,7 +51,7 @@ export async function getServerSideProps( await helpers.settings.isCloud.prefetch(); if (user.role === "member") { - const userR = await helpers.user.get.fetch({ + const userR = await helpers.user.one.fetch({ userId: user.id, }); diff --git a/apps/dokploy/pages/dashboard/swarm.tsx b/apps/dokploy/pages/dashboard/swarm.tsx index 8278ed181..3b59c47b0 100644 --- a/apps/dokploy/pages/dashboard/swarm.tsx +++ b/apps/dokploy/pages/dashboard/swarm.tsx @@ -54,7 +54,7 @@ export async function getServerSideProps( await helpers.project.all.prefetch(); if (user.role === "member") { - const userR = await helpers.user.get.fetch({ + const userR = await helpers.user.one.fetch({ userId: user.id, }); diff --git a/apps/dokploy/pages/dashboard/traefik.tsx b/apps/dokploy/pages/dashboard/traefik.tsx index 6939eabdf..8dcd3f084 100644 --- a/apps/dokploy/pages/dashboard/traefik.tsx +++ b/apps/dokploy/pages/dashboard/traefik.tsx @@ -54,7 +54,7 @@ export async function getServerSideProps( await helpers.project.all.prefetch(); if (user.role === "member") { - const userR = await helpers.user.get.fetch({ + const userR = await helpers.user.one.fetch({ userId: user.id, }); diff --git a/apps/dokploy/pages/swagger.tsx b/apps/dokploy/pages/swagger.tsx index 765194f1c..e4a6fac8d 100644 --- a/apps/dokploy/pages/swagger.tsx +++ b/apps/dokploy/pages/swagger.tsx @@ -38,7 +38,7 @@ const Home: NextPage = () => { export default Home; export async function getServerSideProps(context: GetServerSidePropsContext) { const { req, res } = context; - const { user, session } = await validateRequest(context.req, context.res); + const { user, session } = await validateRequest(context.req); if (!user) { return { redirect: { @@ -53,17 +53,17 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { req: req as any, res: res as any, db: null as any, - session: session, - user: user, + session: session as any, + user: user as any, }, transformer: superjson, }); if (user.role === "member") { - const result = await helpers.user.byAuthId.fetch({ - authId: user.id, + const userR = await helpers.user.one.fetch({ + userId: user.id, }); - if (!result.canAccessToAPI) { + if (!userR.canAccessToAPI) { return { redirect: { permanent: true, diff --git a/apps/dokploy/server/api/routers/admin.ts b/apps/dokploy/server/api/routers/admin.ts index 695cdb336..97e3a1fa4 100644 --- a/apps/dokploy/server/api/routers/admin.ts +++ b/apps/dokploy/server/api/routers/admin.ts @@ -35,17 +35,22 @@ export const adminRouter = createTRPCRouter({ ...rest, }; }), - update: adminProcedure.mutation(async ({ input, ctx }) => { - if (ctx.user.rol === "member") { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not allowed to update this admin", - }); - } - const { id } = await findUserById(ctx.user.id); - // @ts-ignore - return updateAdmin(id, input); - }), + update: adminProcedure + .input( + z.object({ + enableDockerCleanup: z.boolean(), + }), + ) + .mutation(async ({ input, ctx }) => { + if (ctx.user.rol === "member") { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to update this admin", + }); + } + const user = await findUserById(ctx.user.ownerId); + return updateUser(user.id, {}); + }), createUserInvitation: adminProcedure .input(apiCreateUserInvitation) .mutation(async ({ input, ctx }) => { diff --git a/apps/dokploy/server/api/routers/auth.ts b/apps/dokploy/server/api/routers/auth.ts index ad2fab07e..4cfbe71a2 100644 --- a/apps/dokploy/server/api/routers/auth.ts +++ b/apps/dokploy/server/api/routers/auth.ts @@ -266,10 +266,13 @@ export const authRouter = createTRPCRouter({ verifyToken: protectedProcedure.mutation(async () => { return true; }), - one: adminProcedure.query(async ({ input }) => { - const auth = await findAuthById(input.id); - return auth; - }), + one: adminProcedure + .input(z.object({ userId: z.string().min(1) })) + .query(async ({ input }) => { + // TODO: Check if the user is admin or member + const user = await findUserById(input.userId); + return user; + }), generate2FASecret: protectedProcedure.query(async ({ ctx }) => { return await generate2FASecret(ctx.user.id); diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts index cd1f8bd3b..ee69da22d 100644 --- a/apps/dokploy/server/api/routers/settings.ts +++ b/apps/dokploy/server/api/routers/settings.ts @@ -22,9 +22,8 @@ import { cleanUpUnusedVolumes, execAsync, execAsyncRemote, - findAdmin, - findAdminById, findServerById, + findUserById, getDokployImage, getDokployImageTag, getUpdateData, @@ -50,6 +49,7 @@ import { updateLetsEncryptEmail, updateServerById, updateServerTraefik, + updateUser, writeConfig, writeMainConfig, writeTraefikConfigInPath, @@ -163,7 +163,7 @@ export const settingsRouter = createTRPCRouter({ if (IS_CLOUD) { return true; } - await updateAdmin(ctx.user.authId, { + await updateUser(ctx.user.id, { sshPrivateKey: input.sshPrivateKey, }); @@ -175,7 +175,7 @@ export const settingsRouter = createTRPCRouter({ if (IS_CLOUD) { return true; } - const admin = await updateAdmin(ctx.user.authId, { + const user = await updateUser(ctx.user.id, { host: input.host, ...(input.letsEncryptEmail && { letsEncryptEmail: input.letsEncryptEmail, @@ -183,25 +183,25 @@ export const settingsRouter = createTRPCRouter({ certificateType: input.certificateType, }); - if (!admin) { + if (!user) { throw new TRPCError({ code: "NOT_FOUND", - message: "Admin not found", + message: "User not found", }); } - updateServerTraefik(admin, input.host); + updateServerTraefik(user, input.host); if (input.letsEncryptEmail) { updateLetsEncryptEmail(input.letsEncryptEmail); } - return admin; + return user; }), cleanSSHPrivateKey: adminProcedure.mutation(async ({ ctx }) => { if (IS_CLOUD) { return true; } - await updateAdmin(ctx.user.authId, { + await updateUser(ctx.user.id, { sshPrivateKey: null, }); return true; @@ -216,7 +216,7 @@ export const settingsRouter = createTRPCRouter({ const server = await findServerById(input.serverId); - if (server.adminId !== ctx.user.adminId) { + if (server.organizationId !== ctx.session?.activeOrganizationId) { throw new TRPCError({ code: "UNAUTHORIZED", message: "You are not authorized to access this server", @@ -245,7 +245,7 @@ export const settingsRouter = createTRPCRouter({ await cleanUpUnusedImages(server.serverId); await cleanUpDockerBuilder(server.serverId); await cleanUpSystemPrune(server.serverId); - await sendDockerCleanupNotifications(server.adminId); + await sendDockerCleanupNotifications(server.organizationId); }); } } else { @@ -261,19 +261,11 @@ export const settingsRouter = createTRPCRouter({ } } } else if (!IS_CLOUD) { - const admin = await findAdminById(ctx.user.adminId); - - if (admin.adminId !== ctx.user.adminId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "You are not authorized to access this admin", - }); - } - const adminUpdated = await updateAdmin(ctx.user.authId, { + const userUpdated = await updateUser(ctx.user.id, { enableDockerCleanup: input.enableDockerCleanup, }); - if (adminUpdated?.enableDockerCleanup) { + if (userUpdated?.enableDockerCleanup) { scheduleJob("docker-cleanup", "0 0 * * *", async () => { console.log( `Docker Cleanup ${new Date().toLocaleString()}] Running...`, @@ -281,7 +273,9 @@ export const settingsRouter = createTRPCRouter({ await cleanUpUnusedImages(); await cleanUpDockerBuilder(); await cleanUpSystemPrune(); - await sendDockerCleanupNotifications(admin.adminId); + await sendDockerCleanupNotifications( + ctx.session.activeOrganizationId, + ); }); } else { const currentJob = scheduledJobs["docker-cleanup"]; @@ -383,7 +377,7 @@ export const settingsRouter = createTRPCRouter({ .query(async ({ ctx, input }) => { try { if (ctx.user.rol === "member") { - const canAccess = await canAccessToTraefikFiles(ctx.user.authId); + const canAccess = await canAccessToTraefikFiles(ctx.user.id); if (!canAccess) { throw new TRPCError({ code: "UNAUTHORIZED" }); @@ -401,7 +395,7 @@ export const settingsRouter = createTRPCRouter({ .input(apiModifyTraefikConfig) .mutation(async ({ input, ctx }) => { if (ctx.user.rol === "member") { - const canAccess = await canAccessToTraefikFiles(ctx.user.authId); + const canAccess = await canAccessToTraefikFiles(ctx.user.id); if (!canAccess) { throw new TRPCError({ code: "UNAUTHORIZED" }); @@ -419,7 +413,7 @@ export const settingsRouter = createTRPCRouter({ .input(apiReadTraefikConfig) .query(async ({ input, ctx }) => { if (ctx.user.rol === "member") { - const canAccess = await canAccessToTraefikFiles(ctx.user.authId); + const canAccess = await canAccessToTraefikFiles(ctx.user.id); if (!canAccess) { throw new TRPCError({ code: "UNAUTHORIZED" }); @@ -427,12 +421,12 @@ export const settingsRouter = createTRPCRouter({ } return readConfigInPath(input.path, input.serverId); }), - getIp: protectedProcedure.query(async () => { + getIp: protectedProcedure.query(async ({ ctx }) => { if (IS_CLOUD) { return true; } - const admin = await findAdmin(); - return admin.serverIp; + const user = await findUserById(ctx.user.ownerId); + return user.serverIp; }), getOpenApiDocument: protectedProcedure.query( diff --git a/apps/dokploy/server/api/routers/user.ts b/apps/dokploy/server/api/routers/user.ts index f4de4d9f7..addbdb23c 100644 --- a/apps/dokploy/server/api/routers/user.ts +++ b/apps/dokploy/server/api/routers/user.ts @@ -1,7 +1,12 @@ import { apiFindOneUser, apiFindOneUserByAuth } from "@/server/db/schema"; -import { findUserByAuthId, findUserById } from "@dokploy/server"; +import { + findUserByAuthId, + findUserById, + updateUser, + verify2FA, +} from "@dokploy/server"; import { db } from "@dokploy/server/db"; -import { member } from "@dokploy/server/db/schema"; +import { apiUpdateUser, member } from "@dokploy/server/db/schema"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; import { z } from "zod"; @@ -15,7 +20,7 @@ export const userRouter = createTRPCRouter({ }, }); }), - get: protectedProcedure + one: protectedProcedure .input( z.object({ userId: z.string(), @@ -31,16 +36,27 @@ export const userRouter = createTRPCRouter({ // } return user; }), - // byUserId: protectedProcedure - // .input(apiFindOneUser) - // .query(async ({ input, ctx }) => { - // const user = await findUserById(input.userId); - // if (user.adminId !== ctx.user.adminId) { - // throw new TRPCError({ - // code: "UNAUTHORIZED", - // message: "You are not allowed to access this user", - // }); - // } - // return user; - // }), + get: protectedProcedure.query(async ({ ctx }) => { + return await findUserById(ctx.user.id); + }), + update: protectedProcedure + .input(apiUpdateUser) + .mutation(async ({ input, ctx }) => { + return await updateUser(ctx.user.id, input); + }), + verify2FASetup: protectedProcedure + .input( + z.object({ + secret: z.string(), + pin: z.string(), + }), + ) + .mutation(async ({ ctx, input }) => { + const user = await findUserById(ctx.user.id); + await verify2FA(user, input.secret, input.pin); + await updateUser(user.id, { + secret: input.secret, + }); + return user; + }), }); diff --git a/packages/server/auth-schema.ts b/packages/server/auth-schema.ts index 38839afbf..045a75235 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", { @@ -14,6 +14,7 @@ export const users_temp = pgTable("users_temp", { image: text("image"), createdAt: timestamp("created_at").notNull(), updatedAt: timestamp("updated_at").notNull(), + twoFactorEnabled: boolean("two_factor_enabled"), role: text("role").notNull(), ownerId: text("owner_id").notNull(), }); @@ -59,6 +60,15 @@ export const verification = pgTable("verification", { updatedAt: timestamp("updated_at"), }); +export const twoFactor = pgTable("two_factor", { + id: text("id").primaryKey(), + secret: text("secret").notNull(), + backupCodes: text("backup_codes").notNull(), + userId: text("user_id") + .notNull() + .references(() => users_temp.id, { onDelete: "cascade" }), +}); + export const organization = pgTable("organization", { id: text("id").primaryKey(), name: text("name").notNull(), diff --git a/packages/server/src/db/schema/account.ts b/packages/server/src/db/schema/account.ts index 3bb7dcfca..1c0b10e9a 100644 --- a/packages/server/src/db/schema/account.ts +++ b/packages/server/src/db/schema/account.ts @@ -119,3 +119,12 @@ export const invitationRelations = relations(invitation, ({ one }) => ({ references: [organization.id], }), })); + +export const twoFactor = pgTable("two_factor", { + id: text("id").primaryKey(), + secret: text("secret").notNull(), + backupCodes: text("backup_codes").notNull(), + userId: text("user_id") + .notNull() + .references(() => users_temp.id, { onDelete: "cascade" }), +}); diff --git a/packages/server/src/db/schema/user.ts b/packages/server/src/db/schema/user.ts index 33e9e4fcd..a8f4cbcf0 100644 --- a/packages/server/src/db/schema/user.ts +++ b/packages/server/src/db/schema/user.ts @@ -59,10 +59,12 @@ export const users_temp = pgTable("user_temp", { .array() .notNull() .default(sql`ARRAY[]::text[]`), + // authId: text("authId") // .notNull() // .references(() => auth.id, { onDelete: "cascade" }), // Auth + twoFactorEnabled: boolean("two_factor_enabled"), email: text("email").notNull().unique(), emailVerified: boolean("email_verified").notNull(), image: text("image"), @@ -151,10 +153,8 @@ export const usersRelations = relations(users_temp, ({ one, many }) => ({ const createSchema = createInsertSchema(users_temp, { id: z.string().min(1), - // authId: z.string().min(1), token: z.string().min(1), isRegistered: z.boolean().optional(), - // adminId: z.string(), accessedProjects: z.array(z.string()).optional(), accessedServices: z.array(z.string()).optional(), canCreateProjects: z.boolean().optional(), @@ -297,3 +297,30 @@ export const apiUpdateWebServerMonitoring = z.object({ }) .required(), }); + +export const apiUpdateUser = createSchema.partial().extend({ + metricsConfig: z + .object({ + server: z.object({ + type: z.enum(["Dokploy", "Remote"]), + refreshRate: z.number(), + port: z.number(), + token: z.string(), + urlCallback: z.string(), + retentionDays: z.number(), + cronJob: z.string(), + thresholds: z.object({ + cpu: z.number(), + memory: z.number(), + }), + }), + containers: z.object({ + refreshRate: z.number(), + services: z.object({ + include: z.array(z.string()), + exclude: z.array(z.string()), + }), + }), + }) + .optional(), +}); diff --git a/packages/server/src/lib/auth.ts b/packages/server/src/lib/auth.ts index a7fbde9a5..fece335b2 100644 --- a/packages/server/src/lib/auth.ts +++ b/packages/server/src/lib/auth.ts @@ -2,7 +2,11 @@ import type { IncomingMessage } from "node:http"; import * as bcrypt from "bcrypt"; import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; -import { createAuthMiddleware, organization } from "better-auth/plugins"; +import { + createAuthMiddleware, + organization, + twoFactor, +} from "better-auth/plugins"; import { desc, eq } from "drizzle-orm"; import { db } from "../db"; import * as schema from "../db/schema"; @@ -85,6 +89,7 @@ export const auth = betterAuth({ }, plugins: [ + twoFactor(), organization({ async sendInvitationEmail(data, request) { const inviteLink = `https://example.com/accept-invitation/${data.id}`; diff --git a/packages/server/src/services/admin.ts b/packages/server/src/services/admin.ts index 4e1b1bb33..53de805e9 100644 --- a/packages/server/src/services/admin.ts +++ b/packages/server/src/services/admin.ts @@ -12,41 +12,40 @@ import * as bcrypt from "bcrypt"; import { eq } from "drizzle-orm"; import { IS_CLOUD } from "../constants"; -export type Admin = typeof users_temp.$inferSelect; +export type User = typeof users_temp.$inferSelect; export const createInvitation = async ( input: typeof apiCreateUserInvitation._type, adminId: string, ) => { - await db.transaction(async (tx) => { - const result = await tx - .insert(auth) - .values({ - email: input.email.toLowerCase(), - rol: "user", - password: bcrypt.hashSync("01231203012312", 10), - }) - .returning() - .then((res) => res[0]); - - if (!result) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error creating the user", - }); - } - const expiresIn24Hours = new Date(); - expiresIn24Hours.setDate(expiresIn24Hours.getDate() + 1); - const token = randomBytes(32).toString("hex"); - // await tx - // .insert(users) - // .values({ - // adminId: adminId, - // authId: result.id, - // token, - // expirationDate: expiresIn24Hours.toISOString(), - // }) - // .returning(); - }); + // await db.transaction(async (tx) => { + // const result = await tx + // .insert(auth) + // .values({ + // email: input.email.toLowerCase(), + // rol: "user", + // password: bcrypt.hashSync("01231203012312", 10), + // }) + // .returning() + // .then((res) => res[0]); + // if (!result) { + // throw new TRPCError({ + // code: "BAD_REQUEST", + // message: "Error creating the user", + // }); + // } + // const expiresIn24Hours = new Date(); + // expiresIn24Hours.setDate(expiresIn24Hours.getDate() + 1); + // const token = randomBytes(32).toString("hex"); + // await tx + // .insert(users) + // .values({ + // adminId: adminId, + // authId: result.id, + // token, + // expirationDate: expiresIn24Hours.toISOString(), + // }) + // .returning(); + // }); }; export const findUserById = async (userId: string) => { @@ -65,7 +64,7 @@ export const findUserById = async (userId: string) => { return user; }; -export const updateUser = async (userId: string, userData: Partial) => { +export const updateUser = async (userId: string, userData: Partial) => { const user = await db .update(users_temp) .set({ @@ -80,7 +79,7 @@ export const updateUser = async (userId: string, userData: Partial) => { export const updateAdminById = async ( adminId: string, - adminData: Partial, + adminData: Partial, ) => { // const admin = await db // .update(admins) @@ -93,13 +92,6 @@ export const updateAdminById = async ( // return admin; }; -export const findAdminById = async (userId: string) => { - const admin = await db.query.admins.findFirst({ - // where: eq(admins.userId, userId), - }); - return admin; -}; - export const isAdminPresent = async () => { const admin = await db.query.member.findFirst({ where: eq(member.role, "owner"), @@ -113,33 +105,6 @@ export const isAdminPresent = async () => { return true; }; -export const findAdminByAuthId = async (authId: string) => { - const admin = await db.query.admins.findFirst({ - where: eq(admins.authId, authId), - with: { - users: true, - }, - }); - if (!admin) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Admin not found", - }); - } - return admin; -}; - -export const findAdmin = async () => { - const admin = await db.query.admins.findFirst({}); - if (!admin) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Admin not found", - }); - } - return admin; -}; - export const getUserByToken = async (token: string) => { // const user = await db.query.users.findFirst({ // where: eq(users.token, token), @@ -171,24 +136,6 @@ export const removeUserById = async (userId: string) => { .then((res) => res[0]); }; -export const removeAdminByAuthId = async (authId: string) => { - const admin = await findAdminByAuthId(authId); - if (!admin) return null; - - // First delete all associated users - const users = admin.users; - - // for (const user of users) { - // await removeUserById(user.id); - // } - // Then delete the auth record which will cascade delete the admin - return await db - .delete(auth) - .where(eq(auth.id, authId)) - .returning() - .then((res) => res[0]); -}; - export const getDokployUrl = async () => { if (IS_CLOUD) { return "https://app.dokploy.com"; diff --git a/packages/server/src/services/auth.ts b/packages/server/src/services/auth.ts index dbdf538b8..8f3564be9 100644 --- a/packages/server/src/services/auth.ts +++ b/packages/server/src/services/auth.ts @@ -10,6 +10,7 @@ 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({ @@ -51,11 +52,7 @@ export const generate2FASecret = async (userId: string) => { }; }; -export const verify2FA = async ( - auth: Omit, - secret: string, - pin: string, -) => { +export const verify2FA = async (auth: User, secret: string, pin: string) => { const totp = new TOTP({ issuer: "Dokploy", label: `${auth?.email}`, diff --git a/packages/server/src/utils/backups/index.ts b/packages/server/src/utils/backups/index.ts index 922232a0c..d1b87d692 100644 --- a/packages/server/src/utils/backups/index.ts +++ b/packages/server/src/utils/backups/index.ts @@ -1,4 +1,3 @@ -import { findAdmin } from "@dokploy/server/services/admin"; import { getAllServers } from "@dokploy/server/services/server"; import { scheduleJob } from "node-schedule"; import { db } from "../../db/index"; diff --git a/packages/server/src/utils/traefik/web-server.ts b/packages/server/src/utils/traefik/web-server.ts index 76733e751..78046c673 100644 --- a/packages/server/src/utils/traefik/web-server.ts +++ b/packages/server/src/utils/traefik/web-server.ts @@ -1,11 +1,11 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { paths } from "@dokploy/server/constants"; +import type { User } from "@dokploy/server/services/user"; import { dump, load } from "js-yaml"; import { loadOrCreateConfig, writeTraefikConfig } from "./application"; import type { FileConfig } from "./file-types"; import type { MainTraefikConfig } from "./types"; -import type { User } from "@dokploy/server/services/user"; export const updateServerTraefik = ( user: User | null,