refactor: add 2fa

This commit is contained in:
Mauricio Siu 2025-02-16 20:56:50 -06:00
parent e1632cbdb3
commit 0e8e92c715
7 changed files with 119 additions and 65 deletions

View File

@ -78,7 +78,9 @@ export const ShowUsers = () => {
<TableHead className="w-[100px]">Email</TableHead>
<TableHead className="text-center">Role</TableHead>
<TableHead className="text-center">2FA</TableHead>
{/* <TableHead className="text-center">Status</TableHead> */}
<TableHead className="text-center">
Is Registered
</TableHead>
<TableHead className="text-center">
Created At
</TableHead>
@ -104,15 +106,15 @@ export const ShowUsers = () => {
</Badge>
</TableCell>
<TableCell className="text-center">
{/* {user.user.is2FAEnabled
? "2FA Enabled"
: "2FA Not Enabled"} */}
{user.user.twoFactorEnabled
? "Enabled"
: "Disabled"}
</TableCell>
<TableCell className="text-center">
{user.user.isRegistered || user.role === "owner"
? "Registered"
: "Not Registered"}
</TableCell>
{/* <TableCell className="text-right">
<span className="text-sm text-muted-foreground">
{format(new Date(user.createdAt), "PPpp")}
</span>
</TableCell> */}
<TableCell className="text-right">
<span className="text-sm text-muted-foreground">
{format(new Date(user.createdAt), "PPpp")}
@ -134,29 +136,30 @@ export const ShowUsers = () => {
<DropdownMenuLabel>
Actions
</DropdownMenuLabel>
{/* {!user.isRegistered && (
<DropdownMenuItem
className="w-full cursor-pointer"
onSelect={(e) => {
copy(
`${origin}/invitation?token=${user.token}`,
);
toast.success(
"Invitation Copied to clipboard",
);
}}
>
Copy Invitation
</DropdownMenuItem>
)} */}
{!user.user.isRegistered &&
user.role !== "owner" && (
<DropdownMenuItem
className="w-full cursor-pointer"
onSelect={(e) => {
copy(
`${origin}/invitation?token=${user.user.token}`,
);
toast.success(
"Invitation Copied to clipboard",
);
}}
>
Copy Invitation
</DropdownMenuItem>
)}
{/* {user.isRegistered && (
{user.user.isRegistered && (
<AddUserPermissions
userId={user.userId}
/>
)} */}
)}
{/* {user.role !== "owner" && (
{user.role !== "owner" && (
<DialogAction
title="Delete User"
description="Are you sure you want to delete this user?"
@ -185,7 +188,7 @@ export const ShowUsers = () => {
Delete User
</DropdownMenuItem>
</DialogAction>
)} */}
)}
</DropdownMenuContent>
</DropdownMenu>
</TableCell>

View File

@ -25,7 +25,8 @@ WITH inserted_users AS (
"stripeSubscriptionId",
"serversQuantity",
"expirationDate",
"createdAt"
"createdAt",
"two_factor_enabled"
)
SELECT
a."adminId",
@ -50,11 +51,30 @@ WITH inserted_users AS (
a."stripeSubscriptionId",
a."serversQuantity",
NOW() + INTERVAL '1 year',
NOW()
NOW(),
COALESCE(auth."is2FAEnabled", false)
FROM admin a
JOIN auth ON auth.id = a."authId"
RETURNING *
),
inserted_two_factor_admin AS (
-- Insertar registros en two_factor para admins con 2FA habilitado
INSERT INTO two_factor (
id,
secret,
backup_codes,
user_id
)
SELECT
gen_random_uuid(),
auth.secret,
gen_random_uuid()::text,
a."adminId"
FROM admin a
JOIN auth ON auth.id = a."authId"
WHERE auth."is2FAEnabled" = true
RETURNING *
),
inserted_accounts AS (
-- Insertar cuentas para los admins
INSERT INTO account (
@ -120,7 +140,8 @@ inserted_members AS (
"canDeleteServices",
"accesedProjects",
"accesedServices",
"expirationDate"
"expirationDate",
"two_factor_enabled"
)
SELECT
u."userId",
@ -141,7 +162,8 @@ inserted_members AS (
COALESCE(u."canDeleteServices", false),
COALESCE(u."accesedProjects", '{}'),
COALESCE(u."accesedServices", '{}'),
NOW() + INTERVAL '1 year'
NOW() + INTERVAL '1 year',
COALESCE(auth."is2FAEnabled", false)
FROM "user" u
JOIN admin a ON u."adminId" = a."adminId"
JOIN auth ON auth.id = u."authId"
@ -173,6 +195,25 @@ inserted_member_accounts AS (
JOIN auth ON auth.id = u."authId"
RETURNING *
),
inserted_two_factor_members AS (
-- Insertar registros en two_factor para miembros con 2FA habilitado
INSERT INTO two_factor (
id,
secret,
backup_codes,
user_id
)
SELECT
gen_random_uuid(),
auth.secret,
gen_random_uuid()::text,
u."userId"
FROM "user" u
JOIN admin a ON u."adminId" = a."adminId"
JOIN auth ON auth.id = u."authId"
WHERE auth."is2FAEnabled" = true
RETURNING *
),
inserted_admin_members AS (
-- Insertar miembros en las organizaciones (admins como owners)
INSERT INTO member (

View File

@ -27,6 +27,7 @@ import { type ReactElement, useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import superjson from "superjson";
const registerSchema = z
.object({
@ -98,9 +99,9 @@ const Invitation = ({ token, invitation, isCloud }: Props) => {
});
useEffect(() => {
if (data?.auth?.email) {
if (data?.email) {
form.reset({
email: data?.auth?.email || "",
email: data?.email || "",
password: "",
confirmPassword: "",
});
@ -109,7 +110,7 @@ const Invitation = ({ token, invitation, isCloud }: Props) => {
const onSubmit = async (values: Register) => {
await mutateAsync({
id: data?.authId,
id: data?.id,
password: values.password,
token: token,
})
@ -254,6 +255,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
const { query } = ctx;
const token = query.token;
console.log("query", query);
if (typeof token !== "string") {
return {
@ -266,6 +268,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
try {
const invitation = await getUserByToken(token);
console.log("invitation", invitation);
if (invitation.isExpired) {
return {

View File

@ -1,9 +1,9 @@
import {
boolean,
integer,
pgTable,
text,
integer,
timestamp,
boolean,
} from "drizzle-orm/pg-core";
export const users_temp = pgTable("users_temp", {

View File

@ -16,7 +16,16 @@ export const auth = betterAuth({
provider: "pg",
schema: schema,
}),
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID as string,
clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
},
},
emailAndPassword: {
enabled: true,

View File

@ -106,26 +106,25 @@ export const isAdminPresent = async () => {
};
export const getUserByToken = async (token: string) => {
// const user = await db.query.users.findFirst({
// where: eq(users.token, token),
// with: {
// auth: {
// columns: {
// password: false,
// },
// },
// },
// });
// if (!user) {
// throw new TRPCError({
// code: "NOT_FOUND",
// message: "Invitation not found",
// });
// }
// return {
// ...user,
// isExpired: user.isRegistered,
// };
const user = await db.query.users_temp.findFirst({
where: eq(users_temp.token, token),
columns: {
id: true,
email: true,
token: true,
isRegistered: true,
},
});
if (!user) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Invitation not found",
});
}
return {
...user,
isExpired: user.isRegistered,
};
};
export const removeUserById = async (userId: string) => {

View File

@ -29,16 +29,15 @@ class LogRotationManager {
}
private async getStateFromDB(): Promise<boolean> {
const setting = await db.query.admins.findFirst({});
return setting?.enableLogRotation ?? false;
// const setting = await db.query.admins.findFirst({});
// return setting?.enableLogRotation ?? false;
}
private async setStateInDB(active: boolean): Promise<void> {
const admin = await db.query.admins.findFirst({});
if (!admin) {
return;
}
// const admin = await db.query.admins.findFirst({});
// if (!admin) {
// return;
// }
// await updateAdmin(admin?.authId, {
// enableLogRotation: active,
// });