mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
refactor: migrate authentication routes to user router and update related components
This commit continues the refactoring of authentication-related code by: - Moving authentication routes from `auth.ts` to `user.ts` - Updating import paths and function calls across components - Removing commented-out authentication code - Simplifying user-related queries and mutations - Updating server-side authentication handling
This commit is contained in:
@@ -61,7 +61,7 @@ export const Disable2FA = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toast.success("2FA disabled successfully");
|
toast.success("2FA disabled successfully");
|
||||||
utils.auth.get.invalidate();
|
utils.user.get.invalidate();
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
form.setError("password", {
|
form.setError("password", {
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export const Enable2FA = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toast.success("2FA configured successfully");
|
toast.success("2FA configured successfully");
|
||||||
utils.auth.get.invalidate();
|
utils.user.get.invalidate();
|
||||||
setIsDialogOpen(false);
|
setIsDialogOpen(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const GenerateToken = () => {
|
|||||||
const { data, refetch } = api.user.get.useQuery();
|
const { data, refetch } = api.user.get.useQuery();
|
||||||
|
|
||||||
const { mutateAsync: generateToken, isLoading: isLoadingToken } =
|
const { mutateAsync: generateToken, isLoading: isLoadingToken } =
|
||||||
api.auth.generateToken.useMutation();
|
api.user.generateToken.useMutation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
@@ -51,7 +51,7 @@ export const GenerateToken = () => {
|
|||||||
<Label>Token</Label>
|
<Label>Token</Label>
|
||||||
<ToggleVisibilityInput
|
<ToggleVisibilityInput
|
||||||
placeholder="Token"
|
placeholder="Token"
|
||||||
value={data?.user?.token || ""}
|
value={data || ""}
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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<typeof profileSchema>;
|
|
||||||
|
|
||||||
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<Profile>({
|
|
||||||
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 (
|
|
||||||
<div className="w-full">
|
|
||||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-5xl mx-auto">
|
|
||||||
<div className="rounded-xl bg-background shadow-md ">
|
|
||||||
<CardHeader className="flex flex-row gap-2 flex-wrap justify-between items-center">
|
|
||||||
<div>
|
|
||||||
<CardTitle className="text-xl">Remove Self Account</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
If you want to remove your account, you can do it here
|
|
||||||
</CardDescription>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-2">
|
|
||||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
|
||||||
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
onSubmit={(e) => e.preventDefault()}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="grid gap-4"
|
|
||||||
>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="password"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>{t("settings.profile.password")}</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
type="password"
|
|
||||||
placeholder={t("settings.profile.password")}
|
|
||||||
{...field}
|
|
||||||
value={field.value || ""}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
<div className="flex justify-end pt-2">
|
|
||||||
<DialogAction
|
|
||||||
title="Are you sure you want to remove your account?"
|
|
||||||
description="This action cannot be undone, all your projects/servers will be deleted."
|
|
||||||
onClick={() => form.handleSubmit(onSubmit)()}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
isLoading={isLoading}
|
|
||||||
variant="destructive"
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
</DialogAction>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -89,7 +89,7 @@ export const SetupMonitoring = ({ serverId }: Props) => {
|
|||||||
enabled: !!serverId,
|
enabled: !!serverId,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: api.user.get.useQuery();
|
: api.user.getServerMetrics.useQuery();
|
||||||
|
|
||||||
const url = useUrl();
|
const url = useUrl();
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ import { UpdateServerButton } from "./update-server";
|
|||||||
import { UserNav } from "./user-nav";
|
import { UserNav } from "./user-nav";
|
||||||
|
|
||||||
// The types of the queries we are going to use
|
// The types of the queries we are going to use
|
||||||
type AuthQueryOutput = inferRouterOutputs<AppRouter>["auth"]["get"];
|
type AuthQueryOutput = inferRouterOutputs<AppRouter>["user"]["get"];
|
||||||
|
|
||||||
type SingleNavItem = {
|
type SingleNavItem = {
|
||||||
isSingle?: true;
|
isSingle?: true;
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
import { appRouter } from "@/server/api/root";
|
import { appRouter } from "@/server/api/root";
|
||||||
import { createTRPCContext } from "@/server/api/trpc";
|
import { createTRPCContext } from "@/server/api/trpc";
|
||||||
import { validateBearerToken, validateRequest } from "@dokploy/server";
|
import { validateRequest } from "@dokploy/server";
|
||||||
import { createOpenApiNextHandler } from "@dokploy/trpc-openapi";
|
import { createOpenApiNextHandler } from "@dokploy/trpc-openapi";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
let { session, user } = await validateBearerToken(req);
|
const { session, user } = await validateRequest(req);
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
const cookieResult = await validateRequest(req, res);
|
|
||||||
session = cookieResult.session;
|
|
||||||
user = cookieResult.user;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user || !session) {
|
if (!user || !session) {
|
||||||
res.status(401).json({ message: "Unauthorized" });
|
res.status(401).json({ message: "Unauthorized" });
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export async function getServerSideProps(
|
|||||||
});
|
});
|
||||||
|
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
await helpers.auth.get.prefetch();
|
await helpers.user.get.prefetch();
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export async function getServerSideProps(
|
|||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
|
|
||||||
await helpers.auth.get.prefetch();
|
await helpers.user.get.prefetch();
|
||||||
|
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
await helpers.auth.get.prefetch();
|
await helpers.user.get.prefetch();
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
await helpers.auth.get.prefetch();
|
await helpers.user.get.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
await helpers.auth.get.prefetch();
|
await helpers.user.get.prefetch();
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
await helpers.auth.get.prefetch();
|
await helpers.user.get.prefetch();
|
||||||
try {
|
try {
|
||||||
await helpers.project.all.prefetch();
|
await helpers.project.all.prefetch();
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
await helpers.auth.get.prefetch();
|
await helpers.user.get.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
await helpers.auth.get.prefetch();
|
await helpers.user.get.prefetch();
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { GenerateToken } from "@/components/dashboard/settings/profile/generate-token";
|
import { GenerateToken } from "@/components/dashboard/settings/profile/generate-token";
|
||||||
import { ProfileForm } from "@/components/dashboard/settings/profile/profile-form";
|
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 { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||||
|
|
||||||
import { appRouter } from "@/server/api/root";
|
import { appRouter } from "@/server/api/root";
|
||||||
@@ -15,14 +14,14 @@ import superjson from "superjson";
|
|||||||
const Page = () => {
|
const Page = () => {
|
||||||
const { data } = api.user.get.useQuery();
|
const { data } = api.user.get.useQuery();
|
||||||
|
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
// const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="h-full rounded-xl max-w-5xl mx-auto flex flex-col gap-4">
|
<div className="h-full rounded-xl max-w-5xl mx-auto flex flex-col gap-4">
|
||||||
<ProfileForm />
|
<ProfileForm />
|
||||||
{(data?.canAccessToAPI || data?.role === "owner") && <GenerateToken />}
|
{(data?.canAccessToAPI || data?.role === "owner") && <GenerateToken />}
|
||||||
|
|
||||||
{isCloud && <RemoveSelfAccount />}
|
{/* {isCloud && <RemoveSelfAccount />} */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -53,15 +52,7 @@ export async function getServerSideProps(
|
|||||||
});
|
});
|
||||||
|
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
await helpers.auth.get.prefetch();
|
await helpers.user.get.prefetch();
|
||||||
if (user?.role === "member") {
|
|
||||||
// const userR = await helpers.user.one.fetch({
|
|
||||||
// userId: user.id,
|
|
||||||
// });
|
|
||||||
// await helpers.user.byAuthId.prefetch({
|
|
||||||
// authId: user.authId,
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
await helpers.auth.get.prefetch();
|
await helpers.user.get.prefetch();
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
await helpers.auth.get.prefetch();
|
await helpers.user.get.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
await helpers.auth.get.prefetch();
|
await helpers.user.get.prefetch();
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
await helpers.auth.get.prefetch();
|
await helpers.user.get.prefetch();
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -12,17 +12,13 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { db } from "@/server/db";
|
import { authClient } from "@/lib/auth-client";
|
||||||
import { auth } from "@/server/db/schema";
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { IS_CLOUD } from "@dokploy/server";
|
import { IS_CLOUD } from "@dokploy/server";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { isBefore } from "date-fns";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import type { GetServerSidePropsContext } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
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 { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -54,11 +50,12 @@ const loginSchema = z
|
|||||||
type Login = z.infer<typeof loginSchema>;
|
type Login = z.infer<typeof loginSchema>;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
token: string;
|
tokenResetPassword: string;
|
||||||
}
|
}
|
||||||
export default function Home({ token }: Props) {
|
export default function Home({ tokenResetPassword }: Props) {
|
||||||
const { mutateAsync, isLoading, isError, error } =
|
const [token, setToken] = useState<string | null>(tokenResetPassword);
|
||||||
api.auth.resetPassword.useMutation();
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const form = useForm<Login>({
|
const form = useForm<Login>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -68,26 +65,32 @@ export default function Home({ token }: Props) {
|
|||||||
resolver: zodResolver(loginSchema),
|
resolver: zodResolver(loginSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const token = new URLSearchParams(window.location.search).get("token");
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
setToken(token);
|
||||||
|
}
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.reset();
|
form.reset();
|
||||||
}, [form, form.reset, form.formState.isSubmitSuccessful]);
|
}, [form, form.reset, form.formState.isSubmitSuccessful]);
|
||||||
|
|
||||||
const onSubmit = async (values: Login) => {
|
const onSubmit = async (values: Login) => {
|
||||||
await mutateAsync({
|
setIsLoading(true);
|
||||||
resetPasswordToken: token,
|
const { error } = await authClient.resetPassword({
|
||||||
password: values.password,
|
newPassword: values.password,
|
||||||
})
|
token: token || "",
|
||||||
.then((_data) => {
|
});
|
||||||
toast.success("Password reset successfully", {
|
|
||||||
duration: 2000,
|
if (error) {
|
||||||
});
|
setError(error.message || "An error occurred");
|
||||||
router.push("/");
|
} else {
|
||||||
})
|
toast.success("Password reset successfully");
|
||||||
.catch(() => {
|
router.push("/");
|
||||||
toast.error("Error resetting password", {
|
}
|
||||||
duration: 2000,
|
setIsLoading(false);
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen w-full items-center justify-center ">
|
<div className="flex h-screen w-full items-center justify-center ">
|
||||||
@@ -104,9 +107,9 @@ export default function Home({ token }: Props) {
|
|||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<CardContent className="p-0">
|
<CardContent className="p-0">
|
||||||
{isError && (
|
{error && (
|
||||||
<AlertBlock type="error" className="my-2">
|
<AlertBlock type="error" className="my-2">
|
||||||
{error?.message}
|
{error}
|
||||||
</AlertBlock>
|
</AlertBlock>
|
||||||
)}
|
)}
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
@@ -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 {
|
return {
|
||||||
props: {
|
props: {
|
||||||
token: authR.resetPasswordToken,
|
tokenResetPassword: token,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
import { authClient } from "@/lib/auth-client";
|
||||||
import { IS_CLOUD } from "@dokploy/server";
|
import { IS_CLOUD } from "@dokploy/server";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import type { GetServerSidePropsContext } from "next";
|
||||||
@@ -46,8 +46,9 @@ export default function Home() {
|
|||||||
is2FAEnabled: false,
|
is2FAEnabled: false,
|
||||||
authId: "",
|
authId: "",
|
||||||
});
|
});
|
||||||
const { mutateAsync, isLoading, isError, error } =
|
|
||||||
api.auth.sendResetPasswordEmail.useMutation();
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const _router = useRouter();
|
const _router = useRouter();
|
||||||
const form = useForm<Login>({
|
const form = useForm<Login>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -61,19 +62,20 @@ export default function Home() {
|
|||||||
}, [form, form.reset, form.formState.isSubmitSuccessful]);
|
}, [form, form.reset, form.formState.isSubmitSuccessful]);
|
||||||
|
|
||||||
const onSubmit = async (values: Login) => {
|
const onSubmit = async (values: Login) => {
|
||||||
await mutateAsync({
|
setIsLoading(true);
|
||||||
|
const { error } = await authClient.forgetPassword({
|
||||||
email: values.email,
|
email: values.email,
|
||||||
})
|
redirectTo: "/reset-password",
|
||||||
.then((_data) => {
|
});
|
||||||
toast.success("Email sent", {
|
if (error) {
|
||||||
duration: 2000,
|
setError(error.message || "An error occurred");
|
||||||
});
|
setIsLoading(false);
|
||||||
})
|
} else {
|
||||||
.catch(() => {
|
toast.success("Email sent", {
|
||||||
toast.error("Error sending email", {
|
duration: 2000,
|
||||||
duration: 2000,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full items-center justify-center ">
|
<div className="flex w-full items-center justify-center ">
|
||||||
@@ -89,9 +91,9 @@ export default function Home() {
|
|||||||
|
|
||||||
<div className="mx-auto w-full max-w-lg bg-transparent ">
|
<div className="mx-auto w-full max-w-lg bg-transparent ">
|
||||||
<CardContent className="p-0">
|
<CardContent className="p-0">
|
||||||
{isError && (
|
{error && (
|
||||||
<AlertBlock type="error" className="my-2">
|
<AlertBlock type="error" className="my-2">
|
||||||
{error?.message}
|
{error}
|
||||||
</AlertBlock>
|
</AlertBlock>
|
||||||
)}
|
)}
|
||||||
{!temp.is2FAEnabled ? (
|
{!temp.is2FAEnabled ? (
|
||||||
|
|||||||
@@ -31,294 +31,268 @@ import {
|
|||||||
} from "../trpc";
|
} from "../trpc";
|
||||||
|
|
||||||
export const authRouter = createTRPCRouter({
|
export const authRouter = createTRPCRouter({
|
||||||
createAdmin: publicProcedure.mutation(async ({ input }) => {
|
// createAdmin: publicProcedure.mutation(async ({ input }) => {
|
||||||
try {
|
// try {
|
||||||
if (!IS_CLOUD) {
|
// if (!IS_CLOUD) {
|
||||||
const admin = await db.query.admins.findFirst({});
|
// const admin = await db.query.admins.findFirst({});
|
||||||
if (admin) {
|
// if (admin) {
|
||||||
throw new TRPCError({
|
// throw new TRPCError({
|
||||||
code: "BAD_REQUEST",
|
// code: "BAD_REQUEST",
|
||||||
message: "Admin already exists",
|
// message: "Admin already exists",
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
const newAdmin = await createAdmin(input);
|
// const newAdmin = await createAdmin(input);
|
||||||
|
// if (IS_CLOUD) {
|
||||||
if (IS_CLOUD) {
|
// await sendDiscordNotificationWelcome(newAdmin);
|
||||||
await sendDiscordNotificationWelcome(newAdmin);
|
// await sendVerificationEmail(newAdmin.id);
|
||||||
await sendVerificationEmail(newAdmin.id);
|
// return {
|
||||||
return {
|
// status: "success",
|
||||||
status: "success",
|
// type: "cloud",
|
||||||
type: "cloud",
|
// };
|
||||||
};
|
// }
|
||||||
}
|
// // const session = await lucia.createSession(newAdmin.id || "", {});
|
||||||
// const session = await lucia.createSession(newAdmin.id || "", {});
|
// // ctx.res.appendHeader(
|
||||||
// ctx.res.appendHeader(
|
// // "Set-Cookie",
|
||||||
// "Set-Cookie",
|
// // lucia.createSessionCookie(session.id).serialize(),
|
||||||
// lucia.createSessionCookie(session.id).serialize(),
|
// // );
|
||||||
// );
|
// return {
|
||||||
return {
|
// status: "success",
|
||||||
status: "success",
|
// type: "selfhosted",
|
||||||
type: "selfhosted",
|
// };
|
||||||
};
|
// } catch (error) {
|
||||||
} catch (error) {
|
// throw new TRPCError({
|
||||||
throw new TRPCError({
|
// code: "BAD_REQUEST",
|
||||||
code: "BAD_REQUEST",
|
// // @ts-ignore
|
||||||
// @ts-ignore
|
// message: `Error: ${error?.code === "23505" ? "Email already exists" : "Error creating admin"}`,
|
||||||
message: `Error: ${error?.code === "23505" ? "Email already exists" : "Error creating admin"}`,
|
// cause: error,
|
||||||
cause: error,
|
// });
|
||||||
});
|
// }
|
||||||
}
|
// }),
|
||||||
}),
|
// createUser: publicProcedure.mutation(async ({ input }) => {
|
||||||
createUser: publicProcedure.mutation(async ({ input }) => {
|
// try {
|
||||||
try {
|
// const _token = await getUserByToken(input.token);
|
||||||
const _token = await getUserByToken(input.token);
|
// // if (token.isExpired) {
|
||||||
// if (token.isExpired) {
|
// // throw new TRPCError({
|
||||||
// throw new TRPCError({
|
// // code: "BAD_REQUEST",
|
||||||
// code: "BAD_REQUEST",
|
// // message: "Invalid token",
|
||||||
// message: "Invalid token",
|
// // });
|
||||||
// });
|
// // }
|
||||||
// }
|
// // const newUser = await createUser(input);
|
||||||
|
// // if (IS_CLOUD) {
|
||||||
// const newUser = await createUser(input);
|
// // await sendVerificationEmail(token.authId);
|
||||||
|
// // return true;
|
||||||
// if (IS_CLOUD) {
|
// // }
|
||||||
// await sendVerificationEmail(token.authId);
|
// // const session = await lucia.createSession(newUser?.authId || "", {});
|
||||||
// return true;
|
// // ctx.res.appendHeader(
|
||||||
// }
|
// // "Set-Cookie",
|
||||||
// const session = await lucia.createSession(newUser?.authId || "", {});
|
// // lucia.createSessionCookie(session.id).serialize(),
|
||||||
// ctx.res.appendHeader(
|
// // );
|
||||||
// "Set-Cookie",
|
// return true;
|
||||||
// lucia.createSessionCookie(session.id).serialize(),
|
// } catch (error) {
|
||||||
// );
|
// throw new TRPCError({
|
||||||
return true;
|
// code: "BAD_REQUEST",
|
||||||
} catch (error) {
|
// message: "Error creating the user",
|
||||||
throw new TRPCError({
|
// cause: error,
|
||||||
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(
|
||||||
login: publicProcedure.mutation(async ({ input }) => {
|
// input.password,
|
||||||
try {
|
// auth?.password || "",
|
||||||
const auth = await findAuthByEmail(input.email);
|
// );
|
||||||
|
// if (!correctPassword) {
|
||||||
const correctPassword = bcrypt.compareSync(
|
// throw new TRPCError({
|
||||||
input.password,
|
// code: "BAD_REQUEST",
|
||||||
auth?.password || "",
|
// message: "Credentials do not match",
|
||||||
);
|
// });
|
||||||
|
// }
|
||||||
if (!correctPassword) {
|
// if (auth?.confirmationToken && IS_CLOUD) {
|
||||||
throw new TRPCError({
|
// await sendVerificationEmail(auth.id);
|
||||||
code: "BAD_REQUEST",
|
// throw new TRPCError({
|
||||||
message: "Credentials do not match",
|
// code: "BAD_REQUEST",
|
||||||
});
|
// message:
|
||||||
}
|
// "Email not confirmed, we have sent you a confirmation email please check your inbox.",
|
||||||
|
// });
|
||||||
if (auth?.confirmationToken && IS_CLOUD) {
|
// }
|
||||||
await sendVerificationEmail(auth.id);
|
// if (auth?.is2FAEnabled) {
|
||||||
throw new TRPCError({
|
// return {
|
||||||
code: "BAD_REQUEST",
|
// is2FAEnabled: true,
|
||||||
message:
|
// authId: auth.id,
|
||||||
"Email not confirmed, we have sent you a confirmation email please check your inbox.",
|
// };
|
||||||
});
|
// }
|
||||||
}
|
// // const session = await lucia.createSession(auth?.id || "", {});
|
||||||
|
// // ctx.res.appendHeader(
|
||||||
if (auth?.is2FAEnabled) {
|
// // "Set-Cookie",
|
||||||
return {
|
// // lucia.createSessionCookie(session.id).serialize(),
|
||||||
is2FAEnabled: true,
|
// // );
|
||||||
authId: auth.id,
|
// return {
|
||||||
};
|
// is2FAEnabled: false,
|
||||||
}
|
// authId: auth?.id,
|
||||||
|
// };
|
||||||
// const session = await lucia.createSession(auth?.id || "", {});
|
// } catch (error) {
|
||||||
|
// throw new TRPCError({
|
||||||
// ctx.res.appendHeader(
|
// code: "BAD_REQUEST",
|
||||||
// "Set-Cookie",
|
// message: `Error: ${error instanceof Error ? error.message : "Login error"}`,
|
||||||
// lucia.createSessionCookie(session.id).serialize(),
|
// cause: error,
|
||||||
// );
|
// });
|
||||||
return {
|
// }
|
||||||
is2FAEnabled: false,
|
// }),
|
||||||
authId: auth?.id,
|
// get: protectedProcedure.query(async ({ ctx }) => {
|
||||||
};
|
// const memberResult = await db.query.member.findFirst({
|
||||||
} catch (error) {
|
// where: and(
|
||||||
throw new TRPCError({
|
// eq(member.userId, ctx.user.id),
|
||||||
code: "BAD_REQUEST",
|
// eq(member.organizationId, ctx.session?.activeOrganizationId || ""),
|
||||||
message: `Error: ${error instanceof Error ? error.message : "Login error"}`,
|
// ),
|
||||||
cause: error,
|
// with: {
|
||||||
});
|
// user: true,
|
||||||
}
|
// },
|
||||||
}),
|
// });
|
||||||
|
// return memberResult;
|
||||||
get: protectedProcedure.query(async ({ ctx }) => {
|
// }),
|
||||||
const memberResult = await db.query.member.findFirst({
|
// logout: protectedProcedure.mutation(async ({ ctx }) => {
|
||||||
where: and(
|
// const { req } = ctx;
|
||||||
eq(member.userId, ctx.user.id),
|
// const { session } = await validateRequest(req);
|
||||||
eq(member.organizationId, ctx.session?.activeOrganizationId || ""),
|
// if (!session) return false;
|
||||||
),
|
// // await lucia.invalidateSession(session.id);
|
||||||
with: {
|
// // res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
|
||||||
user: true,
|
// return true;
|
||||||
},
|
// }),
|
||||||
});
|
// update: protectedProcedure.mutation(async ({ ctx, input }) => {
|
||||||
|
// const currentAuth = await findAuthByEmail(ctx.user.email);
|
||||||
return memberResult;
|
// if (input.currentPassword || input.password) {
|
||||||
}),
|
// const correctPassword = bcrypt.compareSync(
|
||||||
|
// input.currentPassword || "",
|
||||||
logout: protectedProcedure.mutation(async ({ ctx }) => {
|
// currentAuth?.password || "",
|
||||||
const { req } = ctx;
|
// );
|
||||||
const { session } = await validateRequest(req);
|
// if (!correctPassword) {
|
||||||
if (!session) return false;
|
// throw new TRPCError({
|
||||||
|
// code: "BAD_REQUEST",
|
||||||
// await lucia.invalidateSession(session.id);
|
// message: "Current password is incorrect",
|
||||||
// res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
|
// });
|
||||||
return true;
|
// }
|
||||||
}),
|
// }
|
||||||
|
// // const auth = await updateAuthById(ctx.user.authId, {
|
||||||
update: protectedProcedure.mutation(async ({ ctx, input }) => {
|
// // ...(input.email && { email: input.email.toLowerCase() }),
|
||||||
const currentAuth = await findAuthByEmail(ctx.user.email);
|
// // ...(input.password && {
|
||||||
|
// // password: bcrypt.hashSync(input.password, 10),
|
||||||
if (input.currentPassword || input.password) {
|
// // }),
|
||||||
const correctPassword = bcrypt.compareSync(
|
// // ...(input.image && { image: input.image }),
|
||||||
input.currentPassword || "",
|
// // });
|
||||||
currentAuth?.password || "",
|
// return auth;
|
||||||
);
|
// }),
|
||||||
if (!correctPassword) {
|
// removeSelfAccount: protectedProcedure
|
||||||
throw new TRPCError({
|
// .input(
|
||||||
code: "BAD_REQUEST",
|
// z.object({
|
||||||
message: "Current password is incorrect",
|
// password: z.string().min(1),
|
||||||
});
|
// }),
|
||||||
}
|
// )
|
||||||
}
|
// .mutation(async ({ ctx, input }) => {
|
||||||
// const auth = await updateAuthById(ctx.user.authId, {
|
// if (!IS_CLOUD) {
|
||||||
// ...(input.email && { email: input.email.toLowerCase() }),
|
// throw new TRPCError({
|
||||||
// ...(input.password && {
|
// code: "NOT_FOUND",
|
||||||
// password: bcrypt.hashSync(input.password, 10),
|
// message: "This feature is only available in the cloud version",
|
||||||
// }),
|
// });
|
||||||
// ...(input.image && { image: input.image }),
|
// }
|
||||||
// });
|
// const currentAuth = await findAuthByEmail(ctx.user.email);
|
||||||
|
// const correctPassword = bcrypt.compareSync(
|
||||||
return auth;
|
// input.password,
|
||||||
}),
|
// currentAuth?.password || "",
|
||||||
removeSelfAccount: protectedProcedure
|
// );
|
||||||
.input(
|
// if (!correctPassword) {
|
||||||
z.object({
|
// throw new TRPCError({
|
||||||
password: z.string().min(1),
|
// code: "BAD_REQUEST",
|
||||||
}),
|
// message: "Password is incorrect",
|
||||||
)
|
// });
|
||||||
.mutation(async ({ ctx, input }) => {
|
// }
|
||||||
if (!IS_CLOUD) {
|
// const { req } = ctx;
|
||||||
throw new TRPCError({
|
// const { session } = await validateRequest(req);
|
||||||
code: "NOT_FOUND",
|
// if (!session) return false;
|
||||||
message: "This feature is only available in the cloud version",
|
// // await lucia.invalidateSession(session.id);
|
||||||
});
|
// // res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
|
||||||
}
|
// // if (ctx.user.rol === "owner") {
|
||||||
const currentAuth = await findAuthByEmail(ctx.user.email);
|
// // await removeAdminByAuthId(ctx.user.authId);
|
||||||
|
// // } else {
|
||||||
const correctPassword = bcrypt.compareSync(
|
// // await removeUserByAuthId(ctx.user.authId);
|
||||||
input.password,
|
// // }
|
||||||
currentAuth?.password || "",
|
// return true;
|
||||||
);
|
// }),
|
||||||
|
// generateToken: protectedProcedure.mutation(async ({ ctx }) => {
|
||||||
if (!correctPassword) {
|
// const auth = await findUserById(ctx.user.id);
|
||||||
throw new TRPCError({
|
// console.log(auth);
|
||||||
code: "BAD_REQUEST",
|
// // if (auth.token) {
|
||||||
message: "Password is incorrect",
|
// // await luciaToken.invalidateSession(auth.token);
|
||||||
});
|
// // }
|
||||||
}
|
// // const session = await luciaToken.createSession(auth?.id || "", {
|
||||||
const { req } = ctx;
|
// // expiresIn: 60 * 60 * 24 * 30,
|
||||||
const { session } = await validateRequest(req);
|
// // });
|
||||||
if (!session) return false;
|
// // await updateUser(auth.id, {
|
||||||
|
// // token: session.id,
|
||||||
// await lucia.invalidateSession(session.id);
|
// // });
|
||||||
// res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
|
// return auth;
|
||||||
|
// }),
|
||||||
// if (ctx.user.rol === "owner") {
|
// verifyToken: protectedProcedure.mutation(async () => {
|
||||||
// await removeAdminByAuthId(ctx.user.authId);
|
// return true;
|
||||||
// } else {
|
// }),
|
||||||
// await removeUserByAuthId(ctx.user.authId);
|
// one: adminProcedure
|
||||||
// }
|
// .input(z.object({ userId: z.string().min(1) }))
|
||||||
|
// .query(async ({ input }) => {
|
||||||
return true;
|
// // TODO: Check if the user is admin or member
|
||||||
}),
|
// const user = await findUserById(input.userId);
|
||||||
|
// return user;
|
||||||
generateToken: protectedProcedure.mutation(async ({ ctx }) => {
|
// }),
|
||||||
const auth = await findUserById(ctx.user.id);
|
// sendResetPasswordEmail: publicProcedure
|
||||||
console.log(auth);
|
// .input(
|
||||||
|
// z.object({
|
||||||
// if (auth.token) {
|
// email: z.string().min(1).email(),
|
||||||
// await luciaToken.invalidateSession(auth.token);
|
// }),
|
||||||
// }
|
// )
|
||||||
// const session = await luciaToken.createSession(auth?.id || "", {
|
// .mutation(async ({ input }) => {
|
||||||
// expiresIn: 60 * 60 * 24 * 30,
|
// if (!IS_CLOUD) {
|
||||||
// });
|
// throw new TRPCError({
|
||||||
// await updateUser(auth.id, {
|
// code: "NOT_FOUND",
|
||||||
// token: session.id,
|
// message: "This feature is only available in the cloud version",
|
||||||
// });
|
// });
|
||||||
return auth;
|
// }
|
||||||
}),
|
// const authR = await db.query.auth.findFirst({
|
||||||
verifyToken: protectedProcedure.mutation(async () => {
|
// where: eq(auth.email, input.email),
|
||||||
return true;
|
// });
|
||||||
}),
|
// if (!authR) {
|
||||||
one: adminProcedure
|
// throw new TRPCError({
|
||||||
.input(z.object({ userId: z.string().min(1) }))
|
// code: "NOT_FOUND",
|
||||||
.query(async ({ input }) => {
|
// message: "User not found",
|
||||||
// TODO: Check if the user is admin or member
|
// });
|
||||||
const user = await findUserById(input.userId);
|
// }
|
||||||
return user;
|
// const token = nanoid();
|
||||||
}),
|
// await updateAuthById(authR.id, {
|
||||||
sendResetPasswordEmail: publicProcedure
|
// resetPasswordToken: token,
|
||||||
.input(
|
// // Make resetPassword in 24 hours
|
||||||
z.object({
|
// resetPasswordExpiresAt: new Date(
|
||||||
email: z.string().min(1).email(),
|
// new Date().getTime() + 24 * 60 * 60 * 1000,
|
||||||
}),
|
// ).toISOString(),
|
||||||
)
|
// });
|
||||||
.mutation(async ({ input }) => {
|
// await sendEmailNotification(
|
||||||
if (!IS_CLOUD) {
|
// {
|
||||||
throw new TRPCError({
|
// fromAddress: process.env.SMTP_FROM_ADDRESS!,
|
||||||
code: "NOT_FOUND",
|
// toAddresses: [authR.email],
|
||||||
message: "This feature is only available in the cloud version",
|
// smtpServer: process.env.SMTP_SERVER!,
|
||||||
});
|
// smtpPort: Number(process.env.SMTP_PORT),
|
||||||
}
|
// username: process.env.SMTP_USERNAME!,
|
||||||
const authR = await db.query.auth.findFirst({
|
// password: process.env.SMTP_PASSWORD!,
|
||||||
where: eq(auth.email, input.email),
|
// },
|
||||||
});
|
// "Reset Password",
|
||||||
if (!authR) {
|
// `
|
||||||
throw new TRPCError({
|
// Reset your password by clicking the link below:
|
||||||
code: "NOT_FOUND",
|
// The link will expire in 24 hours.
|
||||||
message: "User not found",
|
// <a href="${WEBSITE_URL}/reset-password?token=${token}">
|
||||||
});
|
// Reset Password
|
||||||
}
|
// </a>
|
||||||
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.
|
|
||||||
<a href="${WEBSITE_URL}/reset-password?token=${token}">
|
|
||||||
Reset Password
|
|
||||||
</a>
|
|
||||||
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// export const sendVerificationEmail = async (authId: string) => {
|
// export const sendVerificationEmail = async (authId: string) => {
|
||||||
|
|||||||
@@ -65,6 +65,19 @@ export const userRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return memberResult;
|
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
|
update: protectedProcedure
|
||||||
.input(apiUpdateUser)
|
.input(apiUpdateUser)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
@@ -112,7 +125,6 @@ export const userRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const { id, ...rest } = input;
|
const { id, ...rest } = input;
|
||||||
|
|
||||||
console.log(rest);
|
|
||||||
await db
|
await db
|
||||||
.update(member)
|
.update(member)
|
||||||
.set({
|
.set({
|
||||||
@@ -202,4 +214,8 @@ export const userRouter = createTRPCRouter({
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
generateToken: protectedProcedure.mutation(async () => {
|
||||||
|
return "token";
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { organization, twoFactor } from "better-auth/plugins";
|
|||||||
import { and, desc, eq } from "drizzle-orm";
|
import { and, desc, eq } from "drizzle-orm";
|
||||||
import { db } from "../db";
|
import { db } from "../db";
|
||||||
import * as schema from "../db/schema";
|
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";
|
import { IS_CLOUD } from "../constants";
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
@@ -30,7 +30,11 @@ export const auth = betterAuth({
|
|||||||
autoSignInAfterVerification: true,
|
autoSignInAfterVerification: true,
|
||||||
sendVerificationEmail: async ({ user, url }) => {
|
sendVerificationEmail: async ({ user, url }) => {
|
||||||
console.log("Sending verification email to", user.email);
|
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: {
|
emailAndPassword: {
|
||||||
@@ -45,6 +49,13 @@ export const auth = betterAuth({
|
|||||||
return bcrypt.compareSync(password, hash);
|
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: {
|
databaseHooks: {
|
||||||
user: {
|
user: {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { db } from "@dokploy/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import {
|
import {
|
||||||
type apiCreateUserInvitation,
|
|
||||||
invitation,
|
invitation,
|
||||||
member,
|
member,
|
||||||
organization,
|
organization,
|
||||||
@@ -10,42 +9,6 @@ import { TRPCError } from "@trpc/server";
|
|||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { IS_CLOUD } from "../constants";
|
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) => {
|
export const findUserById = async (userId: string) => {
|
||||||
const user = await db.query.users_temp.findFirst({
|
const user = await db.query.users_temp.findFirst({
|
||||||
where: eq(users_temp.id, userId),
|
where: eq(users_temp.id, userId),
|
||||||
@@ -69,34 +32,6 @@ export const findOrganizationById = async (organizationId: string) => {
|
|||||||
return organizationResult;
|
return organizationResult;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateUser = async (userId: string, userData: Partial<User>) => {
|
|
||||||
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<User>,
|
|
||||||
) => {
|
|
||||||
// const admin = await db
|
|
||||||
// .update(admins)
|
|
||||||
// .set({
|
|
||||||
// ...adminData,
|
|
||||||
// })
|
|
||||||
// .where(eq(admins.adminId, adminId))
|
|
||||||
// .returning()
|
|
||||||
// .then((res) => res[0]);
|
|
||||||
// return admin;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isAdminPresent = async () => {
|
export const isAdminPresent = async () => {
|
||||||
const admin = await db.query.member.findFirst({
|
const admin = await db.query.member.findFirst({
|
||||||
where: eq(member.role, "owner"),
|
where: eq(member.role, "owner"),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { db } from "@dokploy/server/db";
|
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 { TRPCError } from "@trpc/server";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
|
|
||||||
@@ -235,3 +235,16 @@ export const findMemberById = async (
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateUser = async (userId: string, userData: Partial<User>) => {
|
||||||
|
const user = await db
|
||||||
|
.update(users_temp)
|
||||||
|
.set({
|
||||||
|
...userData,
|
||||||
|
})
|
||||||
|
.where(eq(users_temp.id, userId))
|
||||||
|
.returning()
|
||||||
|
.then((res) => res[0]);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class LogRotationManager {
|
|||||||
private async getStateFromDB(): Promise<boolean> {
|
private async getStateFromDB(): Promise<boolean> {
|
||||||
// const setting = await db.query.admins.findFirst({});
|
// const setting = await db.query.admins.findFirst({});
|
||||||
// return setting?.enableLogRotation ?? false;
|
// return setting?.enableLogRotation ?? false;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setStateInDB(_active: boolean): Promise<void> {
|
private async setStateInDB(_active: boolean): Promise<void> {
|
||||||
|
|||||||
@@ -11,13 +11,14 @@ import { runMariadbBackup } from "./mariadb";
|
|||||||
import { runMongoBackup } from "./mongo";
|
import { runMongoBackup } from "./mongo";
|
||||||
import { runMySqlBackup } from "./mysql";
|
import { runMySqlBackup } from "./mysql";
|
||||||
import { runPostgresBackup } from "./postgres";
|
import { runPostgresBackup } from "./postgres";
|
||||||
|
import { findAdmin } from "../../services/admin";
|
||||||
|
|
||||||
export const initCronJobs = async () => {
|
export const initCronJobs = async () => {
|
||||||
console.log("Setting up cron jobs....");
|
console.log("Setting up cron jobs....");
|
||||||
|
|
||||||
const admin = await findAdmin();
|
const admin = await findAdmin();
|
||||||
|
|
||||||
if (admin?.enableDockerCleanup) {
|
if (admin?.user.enableDockerCleanup) {
|
||||||
scheduleJob("docker-cleanup", "0 0 * * *", async () => {
|
scheduleJob("docker-cleanup", "0 0 * * *", async () => {
|
||||||
console.log(
|
console.log(
|
||||||
`Docker Cleanup ${new Date().toLocaleString()}] Running docker cleanup`,
|
`Docker Cleanup ${new Date().toLocaleString()}] Running docker cleanup`,
|
||||||
@@ -25,7 +26,7 @@ export const initCronJobs = async () => {
|
|||||||
await cleanUpUnusedImages();
|
await cleanUpUnusedImages();
|
||||||
await cleanUpDockerBuilder();
|
await cleanUpDockerBuilder();
|
||||||
await cleanUpSystemPrune();
|
await cleanUpSystemPrune();
|
||||||
await sendDockerCleanupNotifications(admin.adminId);
|
await sendDockerCleanupNotifications(admin.user.id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ export const initCronJobs = async () => {
|
|||||||
await cleanUpDockerBuilder(serverId);
|
await cleanUpDockerBuilder(serverId);
|
||||||
await cleanUpSystemPrune(serverId);
|
await cleanUpSystemPrune(serverId);
|
||||||
await sendDockerCleanupNotifications(
|
await sendDockerCleanupNotifications(
|
||||||
admin.adminId,
|
admin.user.id,
|
||||||
`Docker cleanup for Server ${name} (${serverId})`,
|
`Docker cleanup for Server ${name} (${serverId})`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,15 @@ import {
|
|||||||
sendDiscordNotification,
|
sendDiscordNotification,
|
||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
} from "../utils/notifications/utils";
|
} 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(
|
await sendEmailNotification(
|
||||||
{
|
{
|
||||||
fromAddress: process.env.SMTP_FROM_ADDRESS || "",
|
fromAddress: process.env.SMTP_FROM_ADDRESS || "",
|
||||||
@@ -12,14 +20,8 @@ export const sendVerificationEmail = async (email: string, url: string) => {
|
|||||||
username: process.env.SMTP_USERNAME || "",
|
username: process.env.SMTP_USERNAME || "",
|
||||||
password: process.env.SMTP_PASSWORD || "",
|
password: process.env.SMTP_PASSWORD || "",
|
||||||
},
|
},
|
||||||
"Confirm your email | Dokploy",
|
subject,
|
||||||
`
|
text,
|
||||||
Welcome to Dokploy!
|
|
||||||
Please confirm your email by clicking the link below:
|
|
||||||
<a href="${url}">
|
|
||||||
Confirm Email
|
|
||||||
</a>
|
|
||||||
`,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user