Merge branch 'main' into feat/external-2fa-codes

This commit is contained in:
Ephraim Duncan
2026-05-27 13:45:46 +00:00
committed by GitHub
88 changed files with 4121 additions and 1673 deletions
@@ -0,0 +1,32 @@
import { jobsClient } from '@documenso/lib/jobs/client';
import { createAdminUser } from '@documenso/lib/server-only/user/create-admin-user';
import { adminProcedure } from '../trpc';
import { ZCreateUserRequestSchema, ZCreateUserResponseSchema } from './create-user.types';
export const createUserRoute = adminProcedure
.input(ZCreateUserRequestSchema)
.output(ZCreateUserResponseSchema)
.mutation(async ({ input, ctx }) => {
const { email, name } = input;
const user = await createAdminUser({
name,
email,
});
ctx.logger.info({
createdUserId: user.id,
});
await jobsClient.triggerJob({
name: 'send.admin.user.created.email',
payload: {
userId: user.id,
},
});
return {
userId: user.id,
};
});
@@ -0,0 +1,15 @@
import { ZNameSchema } from '@documenso/lib/constants/auth';
import { z } from 'zod';
export const ZCreateUserRequestSchema = z.object({
email: z.string().email().min(1),
name: ZNameSchema,
});
export type TCreateUserRequest = z.infer<typeof ZCreateUserRequestSchema>;
export const ZCreateUserResponseSchema = z.object({
userId: z.number(),
});
export type TCreateUserResponse = z.infer<typeof ZCreateUserResponseSchema>;
@@ -2,6 +2,7 @@ import { router } from '../trpc';
import { createAdminOrganisationRoute } from './create-admin-organisation';
import { createStripeCustomerRoute } from './create-stripe-customer';
import { createSubscriptionClaimRoute } from './create-subscription-claim';
import { createUserRoute } from './create-user';
import { deleteDocumentRoute } from './delete-document';
import { deleteOrganisationRoute } from './delete-organisation';
import { deleteAdminOrganisationMemberRoute } from './delete-organisation-member';
@@ -64,6 +65,7 @@ export const adminRouter = router({
},
user: {
get: getUserRoute,
create: createUserRoute,
update: updateUserRoute,
delete: deleteUserRoute,
enable: enableUserRoute,
@@ -3,6 +3,8 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { stripe } from '@documenso/lib/server-only/stripe';
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
import { prisma } from '@documenso/prisma';
import { Prisma } from '@prisma/client';
import { z } from 'zod';
import { authenticatedProcedure } from '../trpc';
import { ZUpdateOrganisationRequestSchema, ZUpdateOrganisationResponseSchema } from './update-organisation.types';
@@ -36,15 +38,33 @@ export const updateOrganisationRoute = authenticatedProcedure
});
}
const updatedOrganisation = await prisma.organisation.update({
where: {
id: organisationId,
},
data: {
name: data.name,
url: data.url,
},
});
const updatedOrganisation = await prisma.organisation
.update({
where: {
id: organisationId,
},
data: {
name: data.name,
url: data.url,
},
})
.catch((err) => {
console.error(err);
if (!(err instanceof Prisma.PrismaClientKnownRequestError)) {
throw err;
}
const target = z.array(z.string()).safeParse(err.meta?.target);
if (err.code === 'P2002' && target.success && target.data.includes('url')) {
throw new AppError(AppErrorCode.ALREADY_EXISTS, {
message: 'Organisation URL already exists.',
});
}
throw err;
});
if (updatedOrganisation.customerId) {
await stripe.customers.update(updatedOrganisation.customerId, {
+67 -30
View File
@@ -70,9 +70,12 @@ const t = initTRPC
* Middlewares
*/
export const authenticatedMiddleware = t.middleware(async ({ ctx, next, path, meta }) => {
const infoToLog: TrpcApiLog = {
// Auth-independent log bindings. `auth` is set per-branch below since it
// depends on which auth path was taken; `ctx.metadata.auth` here is still
// `null` (the resolved value is set in the `next()` call below).
const baseLogAttributes: TrpcApiLog = {
path,
auth: ctx.metadata.auth,
auth: null,
source: ctx.metadata.source,
trpcMiddleware: 'authenticated',
unverifiedTeamId: ctx.teamId,
@@ -93,15 +96,21 @@ export const authenticatedMiddleware = t.middleware(async ({ ctx, next, path, me
const apiToken = await getApiTokenByToken({ token });
ctx.logger.info({
...infoToLog,
const trpcApiV2Logger = ctx.logger.child({
...baseLogAttributes,
auth: 'api',
userId: apiToken.user.id,
apiTokenId: apiToken.id,
} satisfies TrpcApiLog);
trpcApiV2Logger.info({
position: 'trpcProcedure',
});
return await next({
ctx: {
...ctx,
logger: trpcApiV2Logger,
user: apiToken.user,
teamId: apiToken.teamId,
session: null,
@@ -131,17 +140,21 @@ export const authenticatedMiddleware = t.middleware(async ({ ctx, next, path, me
});
}
// Recreate the logger with a sub request ID to differentiate between batched requests.
// Recreate the logger with a sub request ID to differentiate between batched
// requests, as well as identifying attributes so every subsequent log line
// (including errors) inherits them.
const trpcSessionLogger = ctx.logger.child({
...baseLogAttributes,
auth: 'session',
nonBatchedRequestId: alphaid(),
});
trpcSessionLogger.info({
...infoToLog,
userId: ctx.user.id,
apiTokenId: null,
} satisfies TrpcApiLog);
trpcSessionLogger.info({
position: 'trpcProcedure',
});
return await next({
ctx: {
...ctx,
@@ -163,14 +176,9 @@ export const authenticatedMiddleware = t.middleware(async ({ ctx, next, path, me
});
export const maybeAuthenticatedMiddleware = t.middleware(async ({ ctx, next, path, meta }) => {
// Recreate the logger with a sub request ID to differentiate between batched requests.
const trpcSessionLogger = ctx.logger.child({
nonBatchedRequestId: alphaid(),
});
const infoToLog: TrpcApiLog = {
const baseLogAttributes: TrpcApiLog = {
path,
auth: ctx.metadata.auth,
auth: null,
source: ctx.metadata.source,
trpcMiddleware: 'maybeAuthenticated',
unverifiedTeamId: ctx.teamId,
@@ -191,15 +199,23 @@ export const maybeAuthenticatedMiddleware = t.middleware(async ({ ctx, next, pat
const apiToken = await getApiTokenByToken({ token });
ctx.logger.info({
...infoToLog,
// Attach identifying attributes to the logger so every subsequent log line
// within this request (including errors) inherits them.
const trpcApiV2Logger = ctx.logger.child({
...baseLogAttributes,
auth: 'api',
userId: apiToken.user.id,
apiTokenId: apiToken.id,
} satisfies TrpcApiLog);
trpcApiV2Logger.info({
position: 'trpcProcedure',
});
return await next({
ctx: {
...ctx,
logger: trpcApiV2Logger,
user: apiToken.user,
teamId: apiToken.teamId,
session: null,
@@ -222,12 +238,25 @@ export const maybeAuthenticatedMiddleware = t.middleware(async ({ ctx, next, pat
});
}
trpcSessionLogger.info({
...infoToLog,
// Resolve `auth` once so it stays in sync between the logger bindings and
// the outgoing metadata.
const auth = ctx.session ? 'session' : null;
// Recreate the logger with a sub request ID to differentiate between batched
// requests, as well as identifying attributes so every subsequent log line
// (including errors) inherits them.
const trpcSessionLogger = ctx.logger.child({
...baseLogAttributes,
auth,
nonBatchedRequestId: alphaid(),
userId: ctx.user?.id,
apiTokenId: null,
} satisfies TrpcApiLog);
trpcSessionLogger.info({
position: 'trpcProcedure',
});
return await next({
ctx: {
...ctx,
@@ -243,7 +272,7 @@ export const maybeAuthenticatedMiddleware = t.middleware(async ({ ctx, next, pat
email: ctx.user.email,
}
: undefined,
auth: ctx.session ? 'session' : null,
auth,
} satisfies ApiRequestMetadata,
},
});
@@ -266,20 +295,24 @@ export const adminMiddleware = t.middleware(async ({ ctx, next, path }) => {
});
}
// Recreate the logger with a sub request ID to differentiate between batched requests.
// Recreate the logger with a sub request ID to differentiate between batched
// requests, as well as identifying attributes so every subsequent log line
// (including errors) inherits them.
const trpcSessionLogger = ctx.logger.child({
nonBatchedRequestId: alphaid(),
});
trpcSessionLogger.info({
unverifiedTeamId: ctx.teamId,
path,
auth: ctx.metadata.auth,
auth: 'session',
source: ctx.metadata.source,
userId: ctx.user.id,
apiTokenId: null,
trpcMiddleware: 'admin',
} satisfies TrpcApiLog);
trpcSessionLogger.info({
position: 'trpcProcedure',
});
return await next({
ctx: {
...ctx,
@@ -300,12 +333,12 @@ export const adminMiddleware = t.middleware(async ({ ctx, next, path }) => {
});
export const procedureMiddleware = t.middleware(async ({ ctx, next, path }) => {
// Recreate the logger with a sub request ID to differentiate between batched requests.
// Recreate the logger with a sub request ID to differentiate between batched
// requests, as well as identifying attributes so every subsequent log line
// (including errors) inherits them.
const trpcSessionLogger = ctx.logger.child({
nonBatchedRequestId: alphaid(),
});
trpcSessionLogger.info({
unverifiedTeamId: ctx.teamId,
path,
auth: ctx.metadata.auth,
source: ctx.metadata.source,
@@ -314,6 +347,10 @@ export const procedureMiddleware = t.middleware(async ({ ctx, next, path }) => {
trpcMiddleware: 'procedure',
} satisfies TrpcApiLog);
trpcSessionLogger.info({
position: 'trpcProcedure',
});
return await next({
ctx: {
...ctx,