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

View File

@ -25,7 +25,8 @@ WITH inserted_users AS (
"stripeSubscriptionId", "stripeSubscriptionId",
"serversQuantity", "serversQuantity",
"expirationDate", "expirationDate",
"createdAt" "createdAt",
"two_factor_enabled"
) )
SELECT SELECT
a."adminId", a."adminId",
@ -50,11 +51,30 @@ WITH inserted_users AS (
a."stripeSubscriptionId", a."stripeSubscriptionId",
a."serversQuantity", a."serversQuantity",
NOW() + INTERVAL '1 year', NOW() + INTERVAL '1 year',
NOW() NOW(),
COALESCE(auth."is2FAEnabled", false)
FROM admin a FROM admin a
JOIN auth ON auth.id = a."authId" JOIN auth ON auth.id = a."authId"
RETURNING * 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 ( inserted_accounts AS (
-- Insertar cuentas para los admins -- Insertar cuentas para los admins
INSERT INTO account ( INSERT INTO account (
@ -120,7 +140,8 @@ inserted_members AS (
"canDeleteServices", "canDeleteServices",
"accesedProjects", "accesedProjects",
"accesedServices", "accesedServices",
"expirationDate" "expirationDate",
"two_factor_enabled"
) )
SELECT SELECT
u."userId", u."userId",
@ -141,7 +162,8 @@ inserted_members AS (
COALESCE(u."canDeleteServices", false), COALESCE(u."canDeleteServices", false),
COALESCE(u."accesedProjects", '{}'), COALESCE(u."accesedProjects", '{}'),
COALESCE(u."accesedServices", '{}'), COALESCE(u."accesedServices", '{}'),
NOW() + INTERVAL '1 year' NOW() + INTERVAL '1 year',
COALESCE(auth."is2FAEnabled", false)
FROM "user" u FROM "user" u
JOIN admin a ON u."adminId" = a."adminId" JOIN admin a ON u."adminId" = a."adminId"
JOIN auth ON auth.id = u."authId" JOIN auth ON auth.id = u."authId"
@ -173,6 +195,25 @@ inserted_member_accounts AS (
JOIN auth ON auth.id = u."authId" JOIN auth ON auth.id = u."authId"
RETURNING * 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 ( inserted_admin_members AS (
-- Insertar miembros en las organizaciones (admins como owners) -- Insertar miembros en las organizaciones (admins como owners)
INSERT INTO member ( INSERT INTO member (

View File

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

View File

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

View File

@ -16,7 +16,16 @@ export const auth = betterAuth({
provider: "pg", provider: "pg",
schema: schema, 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: { emailAndPassword: {
enabled: true, enabled: true,

View File

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

View File

@ -29,16 +29,15 @@ 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;
} }
private async setStateInDB(active: boolean): Promise<void> { private async setStateInDB(active: boolean): Promise<void> {
const admin = await db.query.admins.findFirst({}); // const admin = await db.query.admins.findFirst({});
// if (!admin) {
if (!admin) { // return;
return; // }
}
// await updateAdmin(admin?.authId, { // await updateAdmin(admin?.authId, {
// enableLogRotation: active, // enableLogRotation: active,
// }); // });