diff --git a/apps/dokploy/components/dashboard/settings/profile/disable-2fa.tsx b/apps/dokploy/components/dashboard/settings/profile/disable-2fa.tsx index ceeb386d..458bf563 100644 --- a/apps/dokploy/components/dashboard/settings/profile/disable-2fa.tsx +++ b/apps/dokploy/components/dashboard/settings/profile/disable-2fa.tsx @@ -61,7 +61,7 @@ export const Disable2FA = () => { } toast.success("2FA disabled successfully"); - utils.auth.get.invalidate(); + utils.user.get.invalidate(); setIsOpen(false); } catch (_error) { form.setError("password", { diff --git a/apps/dokploy/components/dashboard/settings/profile/enable-2fa.tsx b/apps/dokploy/components/dashboard/settings/profile/enable-2fa.tsx index 28fba67d..f47c8d9c 100644 --- a/apps/dokploy/components/dashboard/settings/profile/enable-2fa.tsx +++ b/apps/dokploy/components/dashboard/settings/profile/enable-2fa.tsx @@ -125,7 +125,7 @@ export const Enable2FA = () => { } toast.success("2FA configured successfully"); - utils.auth.get.invalidate(); + utils.user.get.invalidate(); setIsDialogOpen(false); } catch (error) { if (error instanceof Error) { diff --git a/apps/dokploy/components/dashboard/settings/profile/generate-token.tsx b/apps/dokploy/components/dashboard/settings/profile/generate-token.tsx index d0299eb6..4d36ab7a 100644 --- a/apps/dokploy/components/dashboard/settings/profile/generate-token.tsx +++ b/apps/dokploy/components/dashboard/settings/profile/generate-token.tsx @@ -17,7 +17,7 @@ export const GenerateToken = () => { const { data, refetch } = api.user.get.useQuery(); const { mutateAsync: generateToken, isLoading: isLoadingToken } = - api.auth.generateToken.useMutation(); + api.user.generateToken.useMutation(); return (
@@ -51,7 +51,7 @@ export const GenerateToken = () => {
diff --git a/apps/dokploy/components/dashboard/settings/profile/remove-self-account.tsx b/apps/dokploy/components/dashboard/settings/profile/remove-self-account.tsx deleted file mode 100644 index 93501994..00000000 --- a/apps/dokploy/components/dashboard/settings/profile/remove-self-account.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { AlertBlock } from "@/components/shared/alert-block"; -import { DialogAction } from "@/components/shared/dialog-action"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useTranslation } from "next-i18next"; -import { useRouter } from "next/router"; -import { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const profileSchema = z.object({ - password: z.string().min(1, { - message: "Password is required", - }), -}); - -type Profile = z.infer; - -export const RemoveSelfAccount = () => { - const { data } = api.user.get.useQuery(); - const { mutateAsync, isLoading, error, isError } = - api.auth.removeSelfAccount.useMutation(); - const { t } = useTranslation("settings"); - const router = useRouter(); - - const form = useForm({ - defaultValues: { - password: "", - }, - resolver: zodResolver(profileSchema), - }); - - useEffect(() => { - if (data) { - form.reset({ - password: "", - }); - } - form.reset(); - }, [form, form.reset, data]); - - const onSubmit = async (values: Profile) => { - await mutateAsync({ - password: values.password, - }) - .then(async () => { - toast.success("Profile Deleted"); - router.push("/"); - }) - .catch(() => {}); - }; - - return ( -
- -
- -
- Remove Self Account - - If you want to remove your account, you can do it here - -
-
- - {isError && {error?.message}} - -
- e.preventDefault()} - onKeyDown={(e) => { - if (e.key === "Enter") { - e.preventDefault(); - } - }} - className="grid gap-4" - > -
- ( - - {t("settings.profile.password")} - - - - - - )} - /> -
-
- -
- form.handleSubmit(onSubmit)()} - > - - -
-
-
-
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/settings/servers/setup-monitoring.tsx b/apps/dokploy/components/dashboard/settings/servers/setup-monitoring.tsx index b8c69926..9a49277e 100644 --- a/apps/dokploy/components/dashboard/settings/servers/setup-monitoring.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/setup-monitoring.tsx @@ -89,7 +89,7 @@ export const SetupMonitoring = ({ serverId }: Props) => { enabled: !!serverId, }, ) - : api.user.get.useQuery(); + : api.user.getServerMetrics.useQuery(); const url = useUrl(); diff --git a/apps/dokploy/components/layouts/side.tsx b/apps/dokploy/components/layouts/side.tsx index 63155f8e..9c61fb39 100644 --- a/apps/dokploy/components/layouts/side.tsx +++ b/apps/dokploy/components/layouts/side.tsx @@ -89,7 +89,7 @@ import { UpdateServerButton } from "./update-server"; import { UserNav } from "./user-nav"; // The types of the queries we are going to use -type AuthQueryOutput = inferRouterOutputs["auth"]["get"]; +type AuthQueryOutput = inferRouterOutputs["user"]["get"]; type SingleNavItem = { isSingle?: true; diff --git a/apps/dokploy/pages/api/[...trpc].ts b/apps/dokploy/pages/api/[...trpc].ts index df85440b..85ddbb28 100644 --- a/apps/dokploy/pages/api/[...trpc].ts +++ b/apps/dokploy/pages/api/[...trpc].ts @@ -1,17 +1,11 @@ import { appRouter } from "@/server/api/root"; import { createTRPCContext } from "@/server/api/trpc"; -import { validateBearerToken, validateRequest } from "@dokploy/server"; +import { validateRequest } from "@dokploy/server"; import { createOpenApiNextHandler } from "@dokploy/trpc-openapi"; import type { NextApiRequest, NextApiResponse } from "next"; const handler = async (req: NextApiRequest, res: NextApiResponse) => { - let { session, user } = await validateBearerToken(req); - - if (!session) { - const cookieResult = await validateRequest(req, res); - session = cookieResult.session; - user = cookieResult.user; - } + const { session, user } = await validateRequest(req); if (!user || !session) { res.status(401).json({ message: "Unauthorized" }); diff --git a/apps/dokploy/pages/dashboard/projects.tsx b/apps/dokploy/pages/dashboard/projects.tsx index 49427c25..5434163a 100644 --- a/apps/dokploy/pages/dashboard/projects.tsx +++ b/apps/dokploy/pages/dashboard/projects.tsx @@ -52,7 +52,7 @@ export async function getServerSideProps( }); await helpers.settings.isCloud.prefetch(); - await helpers.auth.get.prefetch(); + await helpers.user.get.prefetch(); if (!user) { return { redirect: { diff --git a/apps/dokploy/pages/dashboard/settings/billing.tsx b/apps/dokploy/pages/dashboard/settings/billing.tsx index ee1ecdbe..7ba5717e 100644 --- a/apps/dokploy/pages/dashboard/settings/billing.tsx +++ b/apps/dokploy/pages/dashboard/settings/billing.tsx @@ -52,7 +52,7 @@ export async function getServerSideProps( transformer: superjson, }); - await helpers.auth.get.prefetch(); + await helpers.user.get.prefetch(); await helpers.settings.isCloud.prefetch(); diff --git a/apps/dokploy/pages/dashboard/settings/certificates.tsx b/apps/dokploy/pages/dashboard/settings/certificates.tsx index 96bec90b..0c82ed4f 100644 --- a/apps/dokploy/pages/dashboard/settings/certificates.tsx +++ b/apps/dokploy/pages/dashboard/settings/certificates.tsx @@ -45,7 +45,7 @@ export async function getServerSideProps( }, transformer: superjson, }); - await helpers.auth.get.prefetch(); + await helpers.user.get.prefetch(); await helpers.settings.isCloud.prefetch(); return { diff --git a/apps/dokploy/pages/dashboard/settings/cluster.tsx b/apps/dokploy/pages/dashboard/settings/cluster.tsx index 77ece29b..a1a46bb6 100644 --- a/apps/dokploy/pages/dashboard/settings/cluster.tsx +++ b/apps/dokploy/pages/dashboard/settings/cluster.tsx @@ -53,7 +53,7 @@ export async function getServerSideProps( }, transformer: superjson, }); - await helpers.auth.get.prefetch(); + await helpers.user.get.prefetch(); return { props: { diff --git a/apps/dokploy/pages/dashboard/settings/destinations.tsx b/apps/dokploy/pages/dashboard/settings/destinations.tsx index 8605a7c1..3c906b55 100644 --- a/apps/dokploy/pages/dashboard/settings/destinations.tsx +++ b/apps/dokploy/pages/dashboard/settings/destinations.tsx @@ -46,7 +46,7 @@ export async function getServerSideProps( }, transformer: superjson, }); - await helpers.auth.get.prefetch(); + await helpers.user.get.prefetch(); await helpers.settings.isCloud.prefetch(); return { diff --git a/apps/dokploy/pages/dashboard/settings/git-providers.tsx b/apps/dokploy/pages/dashboard/settings/git-providers.tsx index ce2adc9c..7a9b08df 100644 --- a/apps/dokploy/pages/dashboard/settings/git-providers.tsx +++ b/apps/dokploy/pages/dashboard/settings/git-providers.tsx @@ -45,7 +45,7 @@ export async function getServerSideProps( }, transformer: superjson, }); - await helpers.auth.get.prefetch(); + await helpers.user.get.prefetch(); try { await helpers.project.all.prefetch(); await helpers.settings.isCloud.prefetch(); diff --git a/apps/dokploy/pages/dashboard/settings/index.tsx b/apps/dokploy/pages/dashboard/settings/index.tsx index 713e5113..4c060cbb 100644 --- a/apps/dokploy/pages/dashboard/settings/index.tsx +++ b/apps/dokploy/pages/dashboard/settings/index.tsx @@ -209,7 +209,7 @@ export async function getServerSideProps( }, transformer: superjson, }); - await helpers.auth.get.prefetch(); + await helpers.user.get.prefetch(); return { props: { diff --git a/apps/dokploy/pages/dashboard/settings/notifications.tsx b/apps/dokploy/pages/dashboard/settings/notifications.tsx index 76566fdf..fbdc2e20 100644 --- a/apps/dokploy/pages/dashboard/settings/notifications.tsx +++ b/apps/dokploy/pages/dashboard/settings/notifications.tsx @@ -46,7 +46,7 @@ export async function getServerSideProps( }, transformer: superjson, }); - await helpers.auth.get.prefetch(); + await helpers.user.get.prefetch(); await helpers.settings.isCloud.prefetch(); return { diff --git a/apps/dokploy/pages/dashboard/settings/profile.tsx b/apps/dokploy/pages/dashboard/settings/profile.tsx index 446e6c87..404d400c 100644 --- a/apps/dokploy/pages/dashboard/settings/profile.tsx +++ b/apps/dokploy/pages/dashboard/settings/profile.tsx @@ -1,6 +1,5 @@ import { GenerateToken } from "@/components/dashboard/settings/profile/generate-token"; import { ProfileForm } from "@/components/dashboard/settings/profile/profile-form"; -import { RemoveSelfAccount } from "@/components/dashboard/settings/profile/remove-self-account"; import { DashboardLayout } from "@/components/layouts/dashboard-layout"; import { appRouter } from "@/server/api/root"; @@ -15,14 +14,14 @@ import superjson from "superjson"; const Page = () => { const { data } = api.user.get.useQuery(); - const { data: isCloud } = api.settings.isCloud.useQuery(); + // const { data: isCloud } = api.settings.isCloud.useQuery(); return (
{(data?.canAccessToAPI || data?.role === "owner") && } - {isCloud && } + {/* {isCloud && } */}
); @@ -53,15 +52,7 @@ export async function getServerSideProps( }); await helpers.settings.isCloud.prefetch(); - await helpers.auth.get.prefetch(); - if (user?.role === "member") { - // const userR = await helpers.user.one.fetch({ - // userId: user.id, - // }); - // await helpers.user.byAuthId.prefetch({ - // authId: user.authId, - // }); - } + await helpers.user.get.prefetch(); if (!user) { return { diff --git a/apps/dokploy/pages/dashboard/settings/registry.tsx b/apps/dokploy/pages/dashboard/settings/registry.tsx index 678e0da4..42f0627f 100644 --- a/apps/dokploy/pages/dashboard/settings/registry.tsx +++ b/apps/dokploy/pages/dashboard/settings/registry.tsx @@ -45,7 +45,7 @@ export async function getServerSideProps( }, transformer: superjson, }); - await helpers.auth.get.prefetch(); + await helpers.user.get.prefetch(); await helpers.settings.isCloud.prefetch(); return { diff --git a/apps/dokploy/pages/dashboard/settings/server.tsx b/apps/dokploy/pages/dashboard/settings/server.tsx index 4f88c794..0c5e36dc 100644 --- a/apps/dokploy/pages/dashboard/settings/server.tsx +++ b/apps/dokploy/pages/dashboard/settings/server.tsx @@ -110,7 +110,7 @@ export async function getServerSideProps( }, transformer: superjson, }); - await helpers.auth.get.prefetch(); + await helpers.user.get.prefetch(); return { props: { diff --git a/apps/dokploy/pages/dashboard/settings/servers.tsx b/apps/dokploy/pages/dashboard/settings/servers.tsx index 08d4ab69..5cc30b83 100644 --- a/apps/dokploy/pages/dashboard/settings/servers.tsx +++ b/apps/dokploy/pages/dashboard/settings/servers.tsx @@ -56,7 +56,7 @@ export async function getServerSideProps( }, transformer: superjson, }); - await helpers.auth.get.prefetch(); + await helpers.user.get.prefetch(); await helpers.settings.isCloud.prefetch(); return { diff --git a/apps/dokploy/pages/dashboard/settings/users.tsx b/apps/dokploy/pages/dashboard/settings/users.tsx index ac535521..22615314 100644 --- a/apps/dokploy/pages/dashboard/settings/users.tsx +++ b/apps/dokploy/pages/dashboard/settings/users.tsx @@ -50,7 +50,7 @@ export async function getServerSideProps( }, transformer: superjson, }); - await helpers.auth.get.prefetch(); + await helpers.user.get.prefetch(); await helpers.settings.isCloud.prefetch(); return { diff --git a/apps/dokploy/pages/reset-password.tsx b/apps/dokploy/pages/reset-password.tsx index a34a25ed..0f6cf0b3 100644 --- a/apps/dokploy/pages/reset-password.tsx +++ b/apps/dokploy/pages/reset-password.tsx @@ -12,17 +12,13 @@ import { FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; -import { db } from "@/server/db"; -import { auth } from "@/server/db/schema"; -import { api } from "@/utils/api"; +import { authClient } from "@/lib/auth-client"; import { IS_CLOUD } from "@dokploy/server"; import { zodResolver } from "@hookform/resolvers/zod"; -import { isBefore } from "date-fns"; -import { eq } from "drizzle-orm"; import type { GetServerSidePropsContext } from "next"; import Link from "next/link"; import { useRouter } from "next/router"; -import { type ReactElement, useEffect } from "react"; +import { type ReactElement, useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -54,11 +50,12 @@ const loginSchema = z type Login = z.infer; interface Props { - token: string; + tokenResetPassword: string; } -export default function Home({ token }: Props) { - const { mutateAsync, isLoading, isError, error } = - api.auth.resetPassword.useMutation(); +export default function Home({ tokenResetPassword }: Props) { + const [token, setToken] = useState(tokenResetPassword); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); const router = useRouter(); const form = useForm({ defaultValues: { @@ -68,26 +65,32 @@ export default function Home({ token }: Props) { resolver: zodResolver(loginSchema), }); + useEffect(() => { + const token = new URLSearchParams(window.location.search).get("token"); + + if (token) { + setToken(token); + } + }, [token]); + useEffect(() => { form.reset(); }, [form, form.reset, form.formState.isSubmitSuccessful]); const onSubmit = async (values: Login) => { - await mutateAsync({ - resetPasswordToken: token, - password: values.password, - }) - .then((_data) => { - toast.success("Password reset successfully", { - duration: 2000, - }); - router.push("/"); - }) - .catch(() => { - toast.error("Error resetting password", { - duration: 2000, - }); - }); + setIsLoading(true); + const { error } = await authClient.resetPassword({ + newPassword: values.password, + token: token || "", + }); + + if (error) { + setError(error.message || "An error occurred"); + } else { + toast.success("Password reset successfully"); + router.push("/"); + } + setIsLoading(false); }; return (
@@ -104,9 +107,9 @@ export default function Home({ token }: Props) {
- {isError && ( + {error && ( - {error?.message} + {error} )}
@@ -194,35 +197,9 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { }; } - const authR = await db.query.auth.findFirst({ - where: eq(auth.resetPasswordToken, token), - }); - - if (!authR || authR?.resetPasswordExpiresAt === null) { - return { - redirect: { - permanent: true, - destination: "/", - }, - }; - } - const isExpired = isBefore( - new Date(authR.resetPasswordExpiresAt), - new Date(), - ); - - if (isExpired) { - return { - redirect: { - permanent: true, - destination: "/", - }, - }; - } - return { props: { - token: authR.resetPasswordToken, + tokenResetPassword: token, }, }; } diff --git a/apps/dokploy/pages/send-reset-password.tsx b/apps/dokploy/pages/send-reset-password.tsx index 8f6902f6..10d1058a 100644 --- a/apps/dokploy/pages/send-reset-password.tsx +++ b/apps/dokploy/pages/send-reset-password.tsx @@ -12,7 +12,7 @@ import { FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; -import { api } from "@/utils/api"; +import { authClient } from "@/lib/auth-client"; import { IS_CLOUD } from "@dokploy/server"; import { zodResolver } from "@hookform/resolvers/zod"; import type { GetServerSidePropsContext } from "next"; @@ -46,8 +46,9 @@ export default function Home() { is2FAEnabled: false, authId: "", }); - const { mutateAsync, isLoading, isError, error } = - api.auth.sendResetPasswordEmail.useMutation(); + + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); const _router = useRouter(); const form = useForm({ defaultValues: { @@ -61,19 +62,20 @@ export default function Home() { }, [form, form.reset, form.formState.isSubmitSuccessful]); const onSubmit = async (values: Login) => { - await mutateAsync({ + setIsLoading(true); + const { error } = await authClient.forgetPassword({ email: values.email, - }) - .then((_data) => { - toast.success("Email sent", { - duration: 2000, - }); - }) - .catch(() => { - toast.error("Error sending email", { - duration: 2000, - }); + redirectTo: "/reset-password", + }); + if (error) { + setError(error.message || "An error occurred"); + setIsLoading(false); + } else { + toast.success("Email sent", { + duration: 2000, }); + } + setIsLoading(false); }; return (
@@ -89,9 +91,9 @@ export default function Home() {
- {isError && ( + {error && ( - {error?.message} + {error} )} {!temp.is2FAEnabled ? ( diff --git a/apps/dokploy/server/api/routers/auth.ts b/apps/dokploy/server/api/routers/auth.ts index 1a6c046e..68a2eba7 100644 --- a/apps/dokploy/server/api/routers/auth.ts +++ b/apps/dokploy/server/api/routers/auth.ts @@ -31,294 +31,268 @@ import { } from "../trpc"; export const authRouter = createTRPCRouter({ - createAdmin: publicProcedure.mutation(async ({ input }) => { - try { - if (!IS_CLOUD) { - const admin = await db.query.admins.findFirst({}); - if (admin) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Admin already exists", - }); - } - } - const newAdmin = await createAdmin(input); - - if (IS_CLOUD) { - await sendDiscordNotificationWelcome(newAdmin); - await sendVerificationEmail(newAdmin.id); - return { - status: "success", - type: "cloud", - }; - } - // const session = await lucia.createSession(newAdmin.id || "", {}); - // ctx.res.appendHeader( - // "Set-Cookie", - // lucia.createSessionCookie(session.id).serialize(), - // ); - return { - status: "success", - type: "selfhosted", - }; - } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - // @ts-ignore - message: `Error: ${error?.code === "23505" ? "Email already exists" : "Error creating admin"}`, - cause: error, - }); - } - }), - createUser: publicProcedure.mutation(async ({ input }) => { - try { - const _token = await getUserByToken(input.token); - // if (token.isExpired) { - // throw new TRPCError({ - // code: "BAD_REQUEST", - // message: "Invalid token", - // }); - // } - - // const newUser = await createUser(input); - - // if (IS_CLOUD) { - // await sendVerificationEmail(token.authId); - // return true; - // } - // const session = await lucia.createSession(newUser?.authId || "", {}); - // ctx.res.appendHeader( - // "Set-Cookie", - // lucia.createSessionCookie(session.id).serialize(), - // ); - return true; - } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error creating the user", - cause: error, - }); - } - }), - - login: publicProcedure.mutation(async ({ input }) => { - try { - const auth = await findAuthByEmail(input.email); - - const correctPassword = bcrypt.compareSync( - input.password, - auth?.password || "", - ); - - if (!correctPassword) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Credentials do not match", - }); - } - - if (auth?.confirmationToken && IS_CLOUD) { - await sendVerificationEmail(auth.id); - throw new TRPCError({ - code: "BAD_REQUEST", - message: - "Email not confirmed, we have sent you a confirmation email please check your inbox.", - }); - } - - if (auth?.is2FAEnabled) { - return { - is2FAEnabled: true, - authId: auth.id, - }; - } - - // const session = await lucia.createSession(auth?.id || "", {}); - - // ctx.res.appendHeader( - // "Set-Cookie", - // lucia.createSessionCookie(session.id).serialize(), - // ); - return { - is2FAEnabled: false, - authId: auth?.id, - }; - } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: `Error: ${error instanceof Error ? error.message : "Login error"}`, - cause: error, - }); - } - }), - - get: protectedProcedure.query(async ({ ctx }) => { - const memberResult = await db.query.member.findFirst({ - where: and( - eq(member.userId, ctx.user.id), - eq(member.organizationId, ctx.session?.activeOrganizationId || ""), - ), - with: { - user: true, - }, - }); - - return memberResult; - }), - - logout: protectedProcedure.mutation(async ({ ctx }) => { - const { req } = ctx; - const { session } = await validateRequest(req); - if (!session) return false; - - // await lucia.invalidateSession(session.id); - // res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize()); - return true; - }), - - update: protectedProcedure.mutation(async ({ ctx, input }) => { - const currentAuth = await findAuthByEmail(ctx.user.email); - - if (input.currentPassword || input.password) { - const correctPassword = bcrypt.compareSync( - input.currentPassword || "", - currentAuth?.password || "", - ); - if (!correctPassword) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Current password is incorrect", - }); - } - } - // const auth = await updateAuthById(ctx.user.authId, { - // ...(input.email && { email: input.email.toLowerCase() }), - // ...(input.password && { - // password: bcrypt.hashSync(input.password, 10), - // }), - // ...(input.image && { image: input.image }), - // }); - - return auth; - }), - removeSelfAccount: protectedProcedure - .input( - z.object({ - password: z.string().min(1), - }), - ) - .mutation(async ({ ctx, input }) => { - if (!IS_CLOUD) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "This feature is only available in the cloud version", - }); - } - const currentAuth = await findAuthByEmail(ctx.user.email); - - const correctPassword = bcrypt.compareSync( - input.password, - currentAuth?.password || "", - ); - - if (!correctPassword) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Password is incorrect", - }); - } - const { req } = ctx; - const { session } = await validateRequest(req); - if (!session) return false; - - // await lucia.invalidateSession(session.id); - // res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize()); - - // if (ctx.user.rol === "owner") { - // await removeAdminByAuthId(ctx.user.authId); - // } else { - // await removeUserByAuthId(ctx.user.authId); - // } - - return true; - }), - - generateToken: protectedProcedure.mutation(async ({ ctx }) => { - const auth = await findUserById(ctx.user.id); - console.log(auth); - - // if (auth.token) { - // await luciaToken.invalidateSession(auth.token); - // } - // const session = await luciaToken.createSession(auth?.id || "", { - // expiresIn: 60 * 60 * 24 * 30, - // }); - // await updateUser(auth.id, { - // token: session.id, - // }); - return auth; - }), - verifyToken: protectedProcedure.mutation(async () => { - return true; - }), - 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; - }), - sendResetPasswordEmail: publicProcedure - .input( - z.object({ - email: z.string().min(1).email(), - }), - ) - .mutation(async ({ input }) => { - if (!IS_CLOUD) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "This feature is only available in the cloud version", - }); - } - const authR = await db.query.auth.findFirst({ - where: eq(auth.email, input.email), - }); - if (!authR) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "User not found", - }); - } - const token = nanoid(); - await updateAuthById(authR.id, { - resetPasswordToken: token, - // Make resetPassword in 24 hours - resetPasswordExpiresAt: new Date( - new Date().getTime() + 24 * 60 * 60 * 1000, - ).toISOString(), - }); - - await sendEmailNotification( - { - fromAddress: process.env.SMTP_FROM_ADDRESS!, - toAddresses: [authR.email], - smtpServer: process.env.SMTP_SERVER!, - smtpPort: Number(process.env.SMTP_PORT), - username: process.env.SMTP_USERNAME!, - password: process.env.SMTP_PASSWORD!, - }, - "Reset Password", - ` - Reset your password by clicking the link below: - The link will expire in 24 hours. - - Reset Password - - - `, - ); - }), + // createAdmin: publicProcedure.mutation(async ({ input }) => { + // try { + // if (!IS_CLOUD) { + // const admin = await db.query.admins.findFirst({}); + // if (admin) { + // throw new TRPCError({ + // code: "BAD_REQUEST", + // message: "Admin already exists", + // }); + // } + // } + // const newAdmin = await createAdmin(input); + // if (IS_CLOUD) { + // await sendDiscordNotificationWelcome(newAdmin); + // await sendVerificationEmail(newAdmin.id); + // return { + // status: "success", + // type: "cloud", + // }; + // } + // // const session = await lucia.createSession(newAdmin.id || "", {}); + // // ctx.res.appendHeader( + // // "Set-Cookie", + // // lucia.createSessionCookie(session.id).serialize(), + // // ); + // return { + // status: "success", + // type: "selfhosted", + // }; + // } catch (error) { + // throw new TRPCError({ + // code: "BAD_REQUEST", + // // @ts-ignore + // message: `Error: ${error?.code === "23505" ? "Email already exists" : "Error creating admin"}`, + // cause: error, + // }); + // } + // }), + // createUser: publicProcedure.mutation(async ({ input }) => { + // try { + // const _token = await getUserByToken(input.token); + // // if (token.isExpired) { + // // throw new TRPCError({ + // // code: "BAD_REQUEST", + // // message: "Invalid token", + // // }); + // // } + // // const newUser = await createUser(input); + // // if (IS_CLOUD) { + // // await sendVerificationEmail(token.authId); + // // return true; + // // } + // // const session = await lucia.createSession(newUser?.authId || "", {}); + // // ctx.res.appendHeader( + // // "Set-Cookie", + // // lucia.createSessionCookie(session.id).serialize(), + // // ); + // return true; + // } catch (error) { + // throw new TRPCError({ + // code: "BAD_REQUEST", + // message: "Error creating the user", + // cause: error, + // }); + // } + // }), + // login: publicProcedure.mutation(async ({ input }) => { + // try { + // const auth = await findAuthByEmail(input.email); + // const correctPassword = bcrypt.compareSync( + // input.password, + // auth?.password || "", + // ); + // if (!correctPassword) { + // throw new TRPCError({ + // code: "BAD_REQUEST", + // message: "Credentials do not match", + // }); + // } + // if (auth?.confirmationToken && IS_CLOUD) { + // await sendVerificationEmail(auth.id); + // throw new TRPCError({ + // code: "BAD_REQUEST", + // message: + // "Email not confirmed, we have sent you a confirmation email please check your inbox.", + // }); + // } + // if (auth?.is2FAEnabled) { + // return { + // is2FAEnabled: true, + // authId: auth.id, + // }; + // } + // // const session = await lucia.createSession(auth?.id || "", {}); + // // ctx.res.appendHeader( + // // "Set-Cookie", + // // lucia.createSessionCookie(session.id).serialize(), + // // ); + // return { + // is2FAEnabled: false, + // authId: auth?.id, + // }; + // } catch (error) { + // throw new TRPCError({ + // code: "BAD_REQUEST", + // message: `Error: ${error instanceof Error ? error.message : "Login error"}`, + // cause: error, + // }); + // } + // }), + // get: protectedProcedure.query(async ({ ctx }) => { + // const memberResult = await db.query.member.findFirst({ + // where: and( + // eq(member.userId, ctx.user.id), + // eq(member.organizationId, ctx.session?.activeOrganizationId || ""), + // ), + // with: { + // user: true, + // }, + // }); + // return memberResult; + // }), + // logout: protectedProcedure.mutation(async ({ ctx }) => { + // const { req } = ctx; + // const { session } = await validateRequest(req); + // if (!session) return false; + // // await lucia.invalidateSession(session.id); + // // res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize()); + // return true; + // }), + // update: protectedProcedure.mutation(async ({ ctx, input }) => { + // const currentAuth = await findAuthByEmail(ctx.user.email); + // if (input.currentPassword || input.password) { + // const correctPassword = bcrypt.compareSync( + // input.currentPassword || "", + // currentAuth?.password || "", + // ); + // if (!correctPassword) { + // throw new TRPCError({ + // code: "BAD_REQUEST", + // message: "Current password is incorrect", + // }); + // } + // } + // // const auth = await updateAuthById(ctx.user.authId, { + // // ...(input.email && { email: input.email.toLowerCase() }), + // // ...(input.password && { + // // password: bcrypt.hashSync(input.password, 10), + // // }), + // // ...(input.image && { image: input.image }), + // // }); + // return auth; + // }), + // removeSelfAccount: protectedProcedure + // .input( + // z.object({ + // password: z.string().min(1), + // }), + // ) + // .mutation(async ({ ctx, input }) => { + // if (!IS_CLOUD) { + // throw new TRPCError({ + // code: "NOT_FOUND", + // message: "This feature is only available in the cloud version", + // }); + // } + // const currentAuth = await findAuthByEmail(ctx.user.email); + // const correctPassword = bcrypt.compareSync( + // input.password, + // currentAuth?.password || "", + // ); + // if (!correctPassword) { + // throw new TRPCError({ + // code: "BAD_REQUEST", + // message: "Password is incorrect", + // }); + // } + // const { req } = ctx; + // const { session } = await validateRequest(req); + // if (!session) return false; + // // await lucia.invalidateSession(session.id); + // // res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize()); + // // if (ctx.user.rol === "owner") { + // // await removeAdminByAuthId(ctx.user.authId); + // // } else { + // // await removeUserByAuthId(ctx.user.authId); + // // } + // return true; + // }), + // generateToken: protectedProcedure.mutation(async ({ ctx }) => { + // const auth = await findUserById(ctx.user.id); + // console.log(auth); + // // if (auth.token) { + // // await luciaToken.invalidateSession(auth.token); + // // } + // // const session = await luciaToken.createSession(auth?.id || "", { + // // expiresIn: 60 * 60 * 24 * 30, + // // }); + // // await updateUser(auth.id, { + // // token: session.id, + // // }); + // return auth; + // }), + // verifyToken: protectedProcedure.mutation(async () => { + // return true; + // }), + // 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; + // }), + // sendResetPasswordEmail: publicProcedure + // .input( + // z.object({ + // email: z.string().min(1).email(), + // }), + // ) + // .mutation(async ({ input }) => { + // if (!IS_CLOUD) { + // throw new TRPCError({ + // code: "NOT_FOUND", + // message: "This feature is only available in the cloud version", + // }); + // } + // const authR = await db.query.auth.findFirst({ + // where: eq(auth.email, input.email), + // }); + // if (!authR) { + // throw new TRPCError({ + // code: "NOT_FOUND", + // message: "User not found", + // }); + // } + // const token = nanoid(); + // await updateAuthById(authR.id, { + // resetPasswordToken: token, + // // Make resetPassword in 24 hours + // resetPasswordExpiresAt: new Date( + // new Date().getTime() + 24 * 60 * 60 * 1000, + // ).toISOString(), + // }); + // await sendEmailNotification( + // { + // fromAddress: process.env.SMTP_FROM_ADDRESS!, + // toAddresses: [authR.email], + // smtpServer: process.env.SMTP_SERVER!, + // smtpPort: Number(process.env.SMTP_PORT), + // username: process.env.SMTP_USERNAME!, + // password: process.env.SMTP_PASSWORD!, + // }, + // "Reset Password", + // ` + // Reset your password by clicking the link below: + // The link will expire in 24 hours. + // + // Reset Password + // + // `, + // ); + // }), }); // export const sendVerificationEmail = async (authId: string) => { diff --git a/apps/dokploy/server/api/routers/user.ts b/apps/dokploy/server/api/routers/user.ts index 5717d59b..b9f0adcd 100644 --- a/apps/dokploy/server/api/routers/user.ts +++ b/apps/dokploy/server/api/routers/user.ts @@ -65,6 +65,19 @@ export const userRouter = createTRPCRouter({ return memberResult; }), + getServerMetrics: protectedProcedure.query(async ({ ctx }) => { + const memberResult = await db.query.member.findFirst({ + where: and( + eq(member.userId, ctx.user.id), + eq(member.organizationId, ctx.session?.activeOrganizationId || ""), + ), + with: { + user: true, + }, + }); + + return memberResult?.user; + }), update: protectedProcedure .input(apiUpdateUser) .mutation(async ({ input, ctx }) => { @@ -112,7 +125,6 @@ export const userRouter = createTRPCRouter({ const { id, ...rest } = input; - console.log(rest); await db .update(member) .set({ @@ -202,4 +214,8 @@ export const userRouter = createTRPCRouter({ throw error; } }), + + generateToken: protectedProcedure.mutation(async () => { + return "token"; + }), }); diff --git a/packages/server/src/lib/auth.ts b/packages/server/src/lib/auth.ts index 192f6d0e..2b807a57 100644 --- a/packages/server/src/lib/auth.ts +++ b/packages/server/src/lib/auth.ts @@ -6,7 +6,7 @@ import { organization, twoFactor } from "better-auth/plugins"; import { and, desc, eq } from "drizzle-orm"; import { db } from "../db"; import * as schema from "../db/schema"; -import { sendVerificationEmail } from "../verification/send-verification-email"; +import { sendEmail } from "../verification/send-verification-email"; import { IS_CLOUD } from "../constants"; export const auth = betterAuth({ @@ -30,7 +30,11 @@ export const auth = betterAuth({ autoSignInAfterVerification: true, sendVerificationEmail: async ({ user, url }) => { console.log("Sending verification email to", user.email); - await sendVerificationEmail(user.email, url); + await sendEmail({ + email: user.email, + subject: "Verify your email", + text: `Click the link to verify your email: ${url}`, + }); }, }, emailAndPassword: { @@ -45,6 +49,13 @@ export const auth = betterAuth({ return bcrypt.compareSync(password, hash); }, }, + sendResetPassword: async ({ user, url }) => { + await sendEmail({ + email: user.email, + subject: "Reset your password", + text: `Click the link to reset your password: ${url}`, + }); + }, }, databaseHooks: { user: { diff --git a/packages/server/src/services/admin.ts b/packages/server/src/services/admin.ts index 099018f2..3509868b 100644 --- a/packages/server/src/services/admin.ts +++ b/packages/server/src/services/admin.ts @@ -1,6 +1,5 @@ import { db } from "@dokploy/server/db"; import { - type apiCreateUserInvitation, invitation, member, organization, @@ -10,42 +9,6 @@ import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; import { IS_CLOUD } from "../constants"; -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(); - // }); -}; - export const findUserById = async (userId: string) => { const user = await db.query.users_temp.findFirst({ where: eq(users_temp.id, userId), @@ -69,34 +32,6 @@ export const findOrganizationById = async (organizationId: string) => { return organizationResult; }; -export const updateUser = async (userId: string, userData: Partial) => { - const user = await db - .update(users_temp) - .set({ - ...userData, - }) - .where(eq(users_temp.id, userId)) - .returning() - .then((res) => res[0]); - - return user; -}; - -export const updateAdminById = async ( - _adminId: string, - _adminData: Partial, -) => { - // const admin = await db - // .update(admins) - // .set({ - // ...adminData, - // }) - // .where(eq(admins.adminId, adminId)) - // .returning() - // .then((res) => res[0]); - // return admin; -}; - export const isAdminPresent = async () => { const admin = await db.query.member.findFirst({ where: eq(member.role, "owner"), diff --git a/packages/server/src/services/user.ts b/packages/server/src/services/user.ts index f36d8ef6..a1901d71 100644 --- a/packages/server/src/services/user.ts +++ b/packages/server/src/services/user.ts @@ -1,5 +1,5 @@ import { db } from "@dokploy/server/db"; -import { member, type users_temp } from "@dokploy/server/db/schema"; +import { member, users_temp } from "@dokploy/server/db/schema"; import { TRPCError } from "@trpc/server"; import { and, eq } from "drizzle-orm"; @@ -235,3 +235,16 @@ export const findMemberById = async ( } return result; }; + +export const updateUser = async (userId: string, userData: Partial) => { + const user = await db + .update(users_temp) + .set({ + ...userData, + }) + .where(eq(users_temp.id, userId)) + .returning() + .then((res) => res[0]); + + return user; +}; diff --git a/packages/server/src/utils/access-log/handler.ts b/packages/server/src/utils/access-log/handler.ts index bbd32cff..d05d805f 100644 --- a/packages/server/src/utils/access-log/handler.ts +++ b/packages/server/src/utils/access-log/handler.ts @@ -30,6 +30,7 @@ class LogRotationManager { private async getStateFromDB(): Promise { // const setting = await db.query.admins.findFirst({}); // return setting?.enableLogRotation ?? false; + return false; } private async setStateInDB(_active: boolean): Promise { diff --git a/packages/server/src/utils/backups/index.ts b/packages/server/src/utils/backups/index.ts index d1b87d69..7699a42e 100644 --- a/packages/server/src/utils/backups/index.ts +++ b/packages/server/src/utils/backups/index.ts @@ -11,13 +11,14 @@ import { runMariadbBackup } from "./mariadb"; import { runMongoBackup } from "./mongo"; import { runMySqlBackup } from "./mysql"; import { runPostgresBackup } from "./postgres"; +import { findAdmin } from "../../services/admin"; export const initCronJobs = async () => { console.log("Setting up cron jobs...."); const admin = await findAdmin(); - if (admin?.enableDockerCleanup) { + if (admin?.user.enableDockerCleanup) { scheduleJob("docker-cleanup", "0 0 * * *", async () => { console.log( `Docker Cleanup ${new Date().toLocaleString()}] Running docker cleanup`, @@ -25,7 +26,7 @@ export const initCronJobs = async () => { await cleanUpUnusedImages(); await cleanUpDockerBuilder(); await cleanUpSystemPrune(); - await sendDockerCleanupNotifications(admin.adminId); + await sendDockerCleanupNotifications(admin.user.id); }); } @@ -42,7 +43,7 @@ export const initCronJobs = async () => { await cleanUpDockerBuilder(serverId); await cleanUpSystemPrune(serverId); await sendDockerCleanupNotifications( - admin.adminId, + admin.user.id, `Docker cleanup for Server ${name} (${serverId})`, ); }); diff --git a/packages/server/src/verification/send-verification-email.tsx b/packages/server/src/verification/send-verification-email.tsx index 0b3b3bdc..c673c0f7 100644 --- a/packages/server/src/verification/send-verification-email.tsx +++ b/packages/server/src/verification/send-verification-email.tsx @@ -2,7 +2,15 @@ import { sendDiscordNotification, sendEmailNotification, } from "../utils/notifications/utils"; -export const sendVerificationEmail = async (email: string, url: string) => { +export const sendEmail = async ({ + email, + subject, + text, +}: { + email: string; + subject: string; + text: string; +}) => { await sendEmailNotification( { fromAddress: process.env.SMTP_FROM_ADDRESS || "", @@ -12,14 +20,8 @@ export const sendVerificationEmail = async (email: string, url: string) => { username: process.env.SMTP_USERNAME || "", password: process.env.SMTP_PASSWORD || "", }, - "Confirm your email | Dokploy", - ` - Welcome to Dokploy! - Please confirm your email by clicking the link below: - - Confirm Email - - `, + subject, + text, ); return true;