diff --git a/apps/remix/app/components/dialogs/admin-user-delete-dialog.tsx b/apps/remix/app/components/dialogs/admin-user-delete-dialog.tsx index d2f0cf8b7..157cc5284 100644 --- a/apps/remix/app/components/dialogs/admin-user-delete-dialog.tsx +++ b/apps/remix/app/components/dialogs/admin-user-delete-dialog.tsx @@ -3,12 +3,12 @@ import { useState } from 'react'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; -import type { User } from '@prisma/client'; import { useNavigate } from 'react-router'; import { match } from 'ts-pattern'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { trpc } from '@documenso/trpc/react'; +import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types'; import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert'; import { Button } from '@documenso/ui/primitives/button'; import { @@ -25,7 +25,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast'; export type AdminUserDeleteDialogProps = { className?: string; - user: User; + user: TGetUserResponse; }; export const AdminUserDeleteDialog = ({ className, user }: AdminUserDeleteDialogProps) => { diff --git a/apps/remix/app/components/dialogs/admin-user-disable-dialog.tsx b/apps/remix/app/components/dialogs/admin-user-disable-dialog.tsx index 5d995ccdf..347532a19 100644 --- a/apps/remix/app/components/dialogs/admin-user-disable-dialog.tsx +++ b/apps/remix/app/components/dialogs/admin-user-disable-dialog.tsx @@ -3,11 +3,11 @@ import { useState } from 'react'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; -import type { User } from '@prisma/client'; import { match } from 'ts-pattern'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { trpc } from '@documenso/trpc/react'; +import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types'; import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert'; import { Button } from '@documenso/ui/primitives/button'; import { @@ -24,7 +24,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast'; export type AdminUserDisableDialogProps = { className?: string; - userToDisable: User; + userToDisable: TGetUserResponse; }; export const AdminUserDisableDialog = ({ diff --git a/apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx b/apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx index 702b2d73b..64f9aa72d 100644 --- a/apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx +++ b/apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx @@ -3,11 +3,11 @@ import { useState } from 'react'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; -import type { User } from '@prisma/client'; import { match } from 'ts-pattern'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { trpc } from '@documenso/trpc/react'; +import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types'; import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert'; import { Button } from '@documenso/ui/primitives/button'; import { @@ -24,7 +24,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast'; export type AdminUserEnableDialogProps = { className?: string; - userToEnable: User; + userToEnable: TGetUserResponse; }; export const AdminUserEnableDialog = ({ className, userToEnable }: AdminUserEnableDialogProps) => { diff --git a/apps/remix/app/components/dialogs/admin-user-reset-two-factor-dialog.tsx b/apps/remix/app/components/dialogs/admin-user-reset-two-factor-dialog.tsx index f95657d9f..59372ecc9 100644 --- a/apps/remix/app/components/dialogs/admin-user-reset-two-factor-dialog.tsx +++ b/apps/remix/app/components/dialogs/admin-user-reset-two-factor-dialog.tsx @@ -3,12 +3,12 @@ import { useState } from 'react'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; -import type { User } from '@prisma/client'; import { useRevalidator } from 'react-router'; import { match } from 'ts-pattern'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { trpc } from '@documenso/trpc/react'; +import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types'; import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert'; import { Button } from '@documenso/ui/primitives/button'; import { @@ -25,7 +25,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast'; export type AdminUserResetTwoFactorDialogProps = { className?: string; - user: User; + user: TGetUserResponse; }; export const AdminUserResetTwoFactorDialog = ({ diff --git a/apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx b/apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx index 9242d3dd5..3cd0f5853 100644 --- a/apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx +++ b/apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx @@ -2,13 +2,13 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; -import type { User } from '@prisma/client'; import { useForm } from 'react-hook-form'; import { useRevalidator } from 'react-router'; import { Link } from 'react-router'; import type { z } from 'zod'; import { trpc } from '@documenso/trpc/react'; +import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types'; import { ZUpdateUserRequestSchema } from '@documenso/trpc/server/admin-router/update-user.types'; import { Button } from '@documenso/ui/primitives/button'; import { @@ -38,7 +38,7 @@ const ZUserFormSchema = ZUpdateUserRequestSchema.omit({ id: true }); type TUserFormSchema = z.infer; export default function UserPage({ params }: { params: { id: number } }) { - const { data: user, isLoading: isLoadingUser } = trpc.profile.getUser.useQuery( + const { data: user, isLoading: isLoadingUser } = trpc.admin.user.get.useQuery( { id: Number(params.id), }, @@ -78,7 +78,7 @@ export default function UserPage({ params }: { params: { id: number } }) { return ; } -const AdminUserPage = ({ user }: { user: User }) => { +const AdminUserPage = ({ user }: { user: TGetUserResponse }) => { const { _ } = useLingui(); const { toast } = useToast(); const { revalidate } = useRevalidator(); diff --git a/apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx b/apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx index 5fa253ca3..39acc2194 100644 --- a/apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx +++ b/apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx @@ -45,6 +45,9 @@ export async function loader({ params, request }: Route.LoaderArgs) { mode: 'insensitive', }, }, + select: { + id: true, + }, }); // Directly convert the team member invite to a team member if they already have an account. diff --git a/packages/auth/server/lib/utils/handle-oauth-callback-url.ts b/packages/auth/server/lib/utils/handle-oauth-callback-url.ts index 96967cc4e..15fc1f7fa 100644 --- a/packages/auth/server/lib/utils/handle-oauth-callback-url.ts +++ b/packages/auth/server/lib/utils/handle-oauth-callback-url.ts @@ -111,6 +111,10 @@ export const handleOAuthCallbackUrl = async (options: HandleOAuthCallbackUrlOpti where: { email: email, }, + select: { + id: true, + emailVerified: true, + }, }); // Handle existing user but no account. diff --git a/packages/lib/jobs/definitions/emails/send-signing-email.handler.ts b/packages/lib/jobs/definitions/emails/send-signing-email.handler.ts index 7bdee4ab9..7d62ed1d2 100644 --- a/packages/lib/jobs/definitions/emails/send-signing-email.handler.ts +++ b/packages/lib/jobs/definitions/emails/send-signing-email.handler.ts @@ -42,6 +42,11 @@ export const run = async ({ where: { id: userId, }, + select: { + id: true, + email: true, + name: true, + }, }), prisma.document.findFirstOrThrow({ where: { diff --git a/packages/lib/server-only/admin/update-user.ts b/packages/lib/server-only/admin/update-user.ts index c600fe863..cc9dcb80d 100644 --- a/packages/lib/server-only/admin/update-user.ts +++ b/packages/lib/server-only/admin/update-user.ts @@ -10,12 +10,6 @@ export type UpdateUserOptions = { }; export const updateUser = async ({ id, name, email, roles }: UpdateUserOptions) => { - await prisma.user.findFirstOrThrow({ - where: { - id, - }, - }); - await prisma.user.update({ where: { id, diff --git a/packages/lib/server-only/user/get-user-by-id.ts b/packages/lib/server-only/user/get-user-by-id.ts index a01447206..26e0fcc7b 100644 --- a/packages/lib/server-only/user/get-user-by-id.ts +++ b/packages/lib/server-only/user/get-user-by-id.ts @@ -1,13 +1,31 @@ import { prisma } from '@documenso/prisma'; +import { AppError, AppErrorCode } from '../../errors/app-error'; + export interface GetUserByIdOptions { id: number; } export const getUserById = async ({ id }: GetUserByIdOptions) => { - return await prisma.user.findFirstOrThrow({ + const user = await prisma.user.findFirst({ where: { id, }, + select: { + id: true, + name: true, + email: true, + emailVerified: true, + roles: true, + disabled: true, + twoFactorEnabled: true, + signature: true, + }, }); + + if (!user) { + throw new AppError(AppErrorCode.NOT_FOUND); + } + + return user; }; diff --git a/packages/lib/server-only/user/update-profile.ts b/packages/lib/server-only/user/update-profile.ts index b156a06af..766a09e3e 100644 --- a/packages/lib/server-only/user/update-profile.ts +++ b/packages/lib/server-only/user/update-profile.ts @@ -24,7 +24,7 @@ export const updateProfile = async ({ }, }); - return await prisma.$transaction(async (tx) => { + await prisma.$transaction(async (tx) => { await tx.userSecurityAuditLog.create({ data: { userId, @@ -34,7 +34,7 @@ export const updateProfile = async ({ }, }); - return await tx.user.update({ + await tx.user.update({ where: { id: userId, }, diff --git a/packages/trpc/server/admin-router/get-user.ts b/packages/trpc/server/admin-router/get-user.ts new file mode 100644 index 000000000..c60fd6b21 --- /dev/null +++ b/packages/trpc/server/admin-router/get-user.ts @@ -0,0 +1,19 @@ +import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id'; + +import { adminProcedure } from '../trpc'; +import { ZGetUserRequestSchema, ZGetUserResponseSchema } from './get-user.types'; + +export const getUserRoute = adminProcedure + .input(ZGetUserRequestSchema) + .output(ZGetUserResponseSchema) + .query(async ({ input, ctx }) => { + const { id } = input; + + ctx.logger.info({ + input: { + id, + }, + }); + + return await getUserById({ id }); + }); diff --git a/packages/trpc/server/admin-router/get-user.types.ts b/packages/trpc/server/admin-router/get-user.types.ts new file mode 100644 index 000000000..fe4ebac5e --- /dev/null +++ b/packages/trpc/server/admin-router/get-user.types.ts @@ -0,0 +1,21 @@ +import { z } from 'zod'; + +import UserSchema from '@documenso/prisma/generated/zod/modelSchema/UserSchema'; + +export const ZGetUserRequestSchema = z.object({ + id: z.number().min(1), +}); + +export const ZGetUserResponseSchema = UserSchema.pick({ + id: true, + name: true, + email: true, + emailVerified: true, + roles: true, + disabled: true, + twoFactorEnabled: true, + signature: true, +}); + +export type TGetUserRequest = z.infer; +export type TGetUserResponse = z.infer; diff --git a/packages/trpc/server/admin-router/router.ts b/packages/trpc/server/admin-router/router.ts index 7c6f1558b..f8d472a79 100644 --- a/packages/trpc/server/admin-router/router.ts +++ b/packages/trpc/server/admin-router/router.ts @@ -11,6 +11,7 @@ import { findAdminOrganisationsRoute } from './find-admin-organisations'; import { findDocumentsRoute } from './find-documents'; import { findSubscriptionClaimsRoute } from './find-subscription-claims'; import { getAdminOrganisationRoute } from './get-admin-organisation'; +import { getUserRoute } from './get-user'; import { resealDocumentRoute } from './reseal-document'; import { resetTwoFactorRoute } from './reset-two-factor-authentication'; import { updateAdminOrganisationRoute } from './update-admin-organisation'; @@ -36,6 +37,7 @@ export const adminRouter = router({ createCustomer: createStripeCustomerRoute, }, user: { + get: getUserRoute, update: updateUserRoute, delete: deleteUserRoute, enable: enableUserRoute, diff --git a/packages/trpc/server/profile-router/router.ts b/packages/trpc/server/profile-router/router.ts index 60b7c1323..3f903eb49 100644 --- a/packages/trpc/server/profile-router/router.ts +++ b/packages/trpc/server/profile-router/router.ts @@ -3,14 +3,12 @@ import type { SetAvatarImageOptions } from '@documenso/lib/server-only/profile/s import { setAvatarImage } from '@documenso/lib/server-only/profile/set-avatar-image'; import { deleteUser } from '@documenso/lib/server-only/user/delete-user'; import { findUserSecurityAuditLogs } from '@documenso/lib/server-only/user/find-user-security-audit-logs'; -import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id'; import { submitSupportTicket } from '@documenso/lib/server-only/user/submit-support-ticket'; import { updateProfile } from '@documenso/lib/server-only/user/update-profile'; -import { adminProcedure, authenticatedProcedure, router } from '../trpc'; +import { authenticatedProcedure, router } from '../trpc'; import { ZFindUserSecurityAuditLogsSchema, - ZRetrieveUserByIdQuerySchema, ZSetProfileImageMutationSchema, ZSubmitSupportTicketMutationSchema, ZUpdateProfileMutationSchema, @@ -26,24 +24,12 @@ export const profileRouter = router({ }); }), - getUser: adminProcedure.input(ZRetrieveUserByIdQuerySchema).query(async ({ input, ctx }) => { - const { id } = input; - - ctx.logger.info({ - input: { - id, - }, - }); - - return await getUserById({ id }); - }), - updateProfile: authenticatedProcedure .input(ZUpdateProfileMutationSchema) .mutation(async ({ input, ctx }) => { const { name, signature } = input; - return await updateProfile({ + await updateProfile({ userId: ctx.user.id, name, signature, @@ -52,7 +38,7 @@ export const profileRouter = router({ }), deleteAccount: authenticatedProcedure.mutation(async ({ ctx }) => { - return await deleteUser({ + await deleteUser({ id: ctx.user.id, }); }), diff --git a/packages/trpc/server/profile-router/schema.ts b/packages/trpc/server/profile-router/schema.ts index e1db5dd78..2d6a85dc3 100644 --- a/packages/trpc/server/profile-router/schema.ts +++ b/packages/trpc/server/profile-router/schema.ts @@ -7,12 +7,6 @@ export const ZFindUserSecurityAuditLogsSchema = z.object({ export type TFindUserSecurityAuditLogsSchema = z.infer; -export const ZRetrieveUserByIdQuerySchema = z.object({ - id: z.number().min(1), -}); - -export type TRetrieveUserByIdQuerySchema = z.infer; - export const ZUpdateProfileMutationSchema = z.object({ name: z.string().min(1), signature: z.string(),