refactor: update reset password and authentication flows

This commit removes several authentication-related components and simplifies the password reset process:

- Removed login-2fa component
- Deleted confirm-email page
- Updated reset password logic to use Drizzle ORM directly
- Removed unused authentication-related functions
- Simplified server-side authentication routes
This commit is contained in:
Mauricio Siu
2025-02-22 21:09:21 -06:00
parent 8ab6d6b282
commit b00c12965a
14 changed files with 214 additions and 404 deletions

View File

@@ -14,13 +14,11 @@ import {
IS_CLOUD,
findUserById,
getUserByToken,
sendDiscordNotification,
sendEmailNotification,
validateRequest,
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import * as bcrypt from "bcrypt";
import { isBefore } from "date-fns";
import { and, eq } from "drizzle-orm";
import { nanoid } from "nanoid";
import { z } from "zod";
@@ -321,157 +319,64 @@ export const authRouter = createTRPCRouter({
`,
);
}),
resetPassword: publicProcedure
.input(
z.object({
resetPasswordToken: z.string().min(1),
password: z.string().min(1),
}),
)
.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.resetPasswordToken, input.resetPasswordToken),
});
if (!authR || authR.resetPasswordExpiresAt === null) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Token not found",
});
}
const isExpired = isBefore(
new Date(authR.resetPasswordExpiresAt),
new Date(),
);
if (isExpired) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Token expired",
});
}
await updateAuthById(authR.id, {
resetPasswordExpiresAt: null,
resetPasswordToken: null,
password: bcrypt.hashSync(input.password, 10),
});
return true;
}),
confirmEmail: adminProcedure
.input(
z.object({
confirmationToken: z.string().min(1),
}),
)
.mutation(async ({ input }) => {
if (!IS_CLOUD) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Functionality not available in cloud version",
});
}
const authR = await db.query.auth.findFirst({
where: eq(auth.confirmationToken, input.confirmationToken),
});
if (!authR || authR.confirmationExpiresAt === null) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Token not found",
});
}
if (authR.confirmationToken !== input.confirmationToken) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Confirmation Token not found",
});
}
const isExpired = isBefore(
new Date(authR.confirmationExpiresAt),
new Date(),
);
if (isExpired) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Confirmation Token expired",
});
}
1;
await updateAuthById(authR.id, {
confirmationToken: null,
confirmationExpiresAt: null,
});
return true;
}),
});
export const sendVerificationEmail = async (authId: string) => {
const token = nanoid();
const result = await updateAuthById(authId, {
confirmationToken: token,
confirmationExpiresAt: new Date(
new Date().getTime() + 24 * 60 * 60 * 1000,
).toISOString(),
});
// export const sendVerificationEmail = async (authId: string) => {
// const token = nanoid();
// const result = await updateAuthById(authId, {
// confirmationToken: token,
// confirmationExpiresAt: new Date(
// new Date().getTime() + 24 * 60 * 60 * 1000,
// ).toISOString(),
// });
if (!result) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "User not found",
});
}
await sendEmailNotification(
{
fromAddress: process.env.SMTP_FROM_ADDRESS || "",
toAddresses: [result?.email],
smtpServer: process.env.SMTP_SERVER || "",
smtpPort: Number(process.env.SMTP_PORT),
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:
<a href="${WEBSITE_URL}/confirm-email?token=${result?.confirmationToken}">
Confirm Email
</a>
`,
);
// if (!result) {
// throw new TRPCError({
// code: "BAD_REQUEST",
// message: "User not found",
// });
// }
// await sendEmailNotification(
// {
// fromAddress: process.env.SMTP_FROM_ADDRESS || "",
// toAddresses: [result?.email],
// smtpServer: process.env.SMTP_SERVER || "",
// smtpPort: Number(process.env.SMTP_PORT),
// 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:
// <a href="${WEBSITE_URL}/confirm-email?token=${result?.confirmationToken}">
// Confirm Email
// </a>
// `,
// );
return true;
};
// return true;
// };
export const sendDiscordNotificationWelcome = async (newAdmin: Auth) => {
await sendDiscordNotification(
{
webhookUrl: process.env.DISCORD_WEBHOOK_URL || "",
},
{
title: "New User Registered",
color: 0x00ff00,
fields: [
{
name: "Email",
value: newAdmin.email,
inline: true,
},
],
timestamp: newAdmin.createdAt,
footer: {
text: "Dokploy User Registration Notification",
},
},
);
};
// export const sendDiscordNotificationWelcome = async (newAdmin: Auth) => {
// await sendDiscordNotification(
// {
// webhookUrl: process.env.DISCORD_WEBHOOK_URL || "",
// },
// {
// title: "New User Registered",
// color: 0x00ff00,
// fields: [
// {
// name: "Email",
// value: newAdmin.email,
// inline: true,
// },
// ],
// timestamp: newAdmin.createdAt,
// footer: {
// text: "Dokploy User Registration Notification",
// },
// },
// );
// };

View File

@@ -37,6 +37,7 @@ import {
import { TRPCError } from "@trpc/server";
import { observable } from "@trpc/server/observable";
import { and, desc, eq, getTableColumns, isNotNull, sql } from "drizzle-orm";
import { z } from "zod";
export const serverRouter = createTRPCRouter({
create: protectedProcedure
@@ -378,4 +379,62 @@ export const serverRouter = createTRPCRouter({
const ip = await getPublicIpWithFallback();
return ip;
}),
getServerMetrics: protectedProcedure
.input(
z.object({
url: z.string(),
token: z.string(),
dataPoints: z.string(),
}),
)
.query(async ({ input }) => {
try {
const url = new URL(input.url);
url.searchParams.append("limit", input.dataPoints);
const response = await fetch(url.toString(), {
headers: {
Authorization: `Bearer ${input.token}`,
},
});
if (!response.ok) {
throw new Error(
`Error ${response.status}: ${response.statusText}. Ensure the container is running and this service is included in the monitoring configuration.`,
);
}
const data = await response.json();
if (!Array.isArray(data) || data.length === 0) {
throw new Error(
[
"No monitoring data available. This could be because:",
"",
"1. You don't have setup the monitoring service, you can do in web server section.",
"2. If you already have setup the monitoring service, wait a few minutes and refresh the page.",
].join("\n"),
);
}
return data as {
cpu: string;
cpuModel: string;
cpuCores: number;
cpuPhysicalCores: number;
cpuSpeed: number;
os: string;
distro: string;
kernel: string;
arch: string;
memUsed: string;
memUsedGB: string;
memTotal: string;
uptime: number;
diskUsed: string;
totalDisk: string;
networkIn: string;
networkOut: string;
timestamp: string;
}[];
} catch (error) {
throw error;
}
}),
});

View File

@@ -143,4 +143,63 @@ export const userRouter = createTRPCRouter({
},
});
}),
getContainerMetrics: protectedProcedure
.input(
z.object({
url: z.string(),
token: z.string(),
appName: z.string(),
dataPoints: z.string(),
}),
)
.query(async ({ input }) => {
try {
if (!input.appName) {
throw new Error(
[
"No Application Selected:",
"",
"Make Sure to select an application to monitor.",
].join("\n"),
);
}
const url = new URL(`${input.url}/metrics/containers`);
url.searchParams.append("limit", input.dataPoints);
url.searchParams.append("appName", input.appName);
const response = await fetch(url.toString(), {
headers: {
Authorization: `Bearer ${input.token}`,
},
});
if (!response.ok) {
throw new Error(
`Error ${response.status}: ${response.statusText}. Please verify that the application "${input.appName}" is running and this service is included in the monitoring configuration.`,
);
}
const data = await response.json();
if (!Array.isArray(data) || data.length === 0) {
throw new Error(
[
`No monitoring data available for "${input.appName}". This could be because:`,
"",
"1. The container was recently started - wait a few minutes for data to be collected",
"2. The container is not running - verify its status",
"3. The service is not included in your monitoring configuration",
].join("\n"),
);
}
return data as {
containerId: string;
containerName: string;
containerImage: string;
containerLabels: string;
containerCommand: string;
containerCreated: string;
}[];
} catch (error) {
throw error;
}
}),
});