mirror of
https://github.com/documenso/documenso.git
synced 2026-07-25 01:15:49 +10:00
Merge branch 'main' into feat/external-2fa-codes
This commit is contained in:
@@ -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, {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user