Skip to content

Commit

Permalink
Merge pull request civitai#1146 from civitai/fix/sensitive-endpoints
Browse files Browse the repository at this point in the history
Adds check to prevent calling sensitive endpoints if user doesn't have required permissions
  • Loading branch information
manuelurenah authored Apr 19, 2024
2 parents b4f81ca + 14f70f7 commit 68891b8
Show file tree
Hide file tree
Showing 13 changed files with 48 additions and 63 deletions.
2 changes: 1 addition & 1 deletion src/components/Post/Edit/EditPostImages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { VotableTags } from '~/components/VotableTags/VotableTags';
import { POST_IMAGE_LIMIT } from '~/server/common/constants';
import { ImageIngestionStatus } from '@prisma/client';
import { ImageDropzone } from '~/components/Image/ImageDropzone/ImageDropzone';
import { useEditPostContext, ImageUpload, ImageBlocked } from './EditPostProvider';
import { useEditPostContext, type ImageUpload, type ImageBlocked } from './EditPostProvider';
import { orchestratorMediaTransmitter } from '~/store/post-image-transmitter.store';
import { IMAGE_MIME_TYPE, MEDIA_TYPE, VIDEO_MIME_TYPE } from '~/server/common/mime-types';
import { useCurrentUser } from '~/hooks/useCurrentUser';
Expand Down
11 changes: 10 additions & 1 deletion src/server/controllers/home-block.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,19 @@ export const deleteUserHomeBlockHandler = async ({
input: GetByIdInput;
ctx: DeepNonNullable<Context>;
}) => {
const { id: userId, isModerator } = ctx.user;

try {
await deleteHomeBlockById({
const homeBlock = await getHomeBlockById({ ...input });
if (!homeBlock) throw throwNotFoundError(`No home block with id ${input.id}`);

if (!isModerator && homeBlock.userId !== userId) throw throwAuthorizationError();

const deleted = await deleteHomeBlockById({
input: { ...input, userId: ctx.user.id, isModerator: ctx.user.isModerator },
});

return deleted;
} catch (error) {
if (error instanceof TRPCError) throw error;
else throw throwDbError(error);
Expand Down
23 changes: 2 additions & 21 deletions src/server/routers/bounty.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
guardedProcedure,
isFlagProtected,
middleware,
moderatorProcedure,
protectedProcedure,
publicProcedure,
router,
Expand Down Expand Up @@ -51,25 +52,6 @@ const isOwnerOrModerator = middleware(async ({ ctx, next, input = {} }) => {
});
});

const isModerator = middleware(async ({ ctx, next, input = {} }) => {
if (!ctx.user) throw throwAuthorizationError();

const { id } = input as { id: number };

const isModerator = ctx?.user?.isModerator;

if (!isModerator && !!id) {
throw throwAuthorizationError();
}

return next({
ctx: {
// infers the `user` as non-nullable
user: ctx.user,
},
});
});

export const bountyRouter = router({
getInfinite: publicProcedure
.input(getInfiniteBountySchema)
Expand Down Expand Up @@ -110,9 +92,8 @@ export const bountyRouter = router({
.input(addBenefactorUnitAmountInputSchema)
.use(isFlagProtected('bounties'))
.mutation(addBenefactorUnitAmountHandler),
refund: protectedProcedure
refund: moderatorProcedure
.input(getByIdSchema)
.use(isFlagProtected('bounties'))
.use(isModerator)
.mutation(refundBountyHandler),
});
5 changes: 4 additions & 1 deletion src/server/routers/collection.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ export const collectionRouter = router({
.input(getByIdSchema)
.use(isFlagProtected('collections'))
.query(getCollectionByIdHandler),
upsert: guardedProcedure.input(upsertCollectionInput).mutation(upsertCollectionHandler),
upsert: guardedProcedure
.input(upsertCollectionInput)
.use(isOwnerOrModerator)
.mutation(upsertCollectionHandler),
updateCoverImage: guardedProcedure
.input(updateCollectionCoverImageInput)
.mutation(updateCollectionCoverImageHandler),
Expand Down
8 changes: 6 additions & 2 deletions src/server/routers/comment.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { toggleReactionInput } from '~/server/schema/comment.schema';
import {
guardedProcedure,
middleware,
moderatorProcedure,
protectedProcedure,
publicProcedure,
router,
Expand Down Expand Up @@ -111,10 +112,13 @@ export const commentRouter = router({
input: { entityType: 'commentOld', entityId: input.id, reaction: input.reaction },
})
),
toggleHide: protectedProcedure.input(getByIdSchema).mutation(toggleHideCommentHandler),
toggleHide: protectedProcedure
.input(getByIdSchema)
.use(isOwnerOrModerator)
.mutation(toggleHideCommentHandler),
toggleLock: protectedProcedure
.input(getByIdSchema)
.use(isOwnerOrModerator)
.mutation(toggleLockHandler),
setTosViolation: protectedProcedure.input(getByIdSchema).mutation(setTosViolationHandler),
setTosViolation: moderatorProcedure.input(getByIdSchema).mutation(setTosViolationHandler),
});
13 changes: 2 additions & 11 deletions src/server/routers/commentv2.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
publicProcedure,
protectedProcedure,
guardedProcedure,
moderatorProcedure,
} from '~/server/trpc';
import { dbRead } from '~/server/db/client';
import { throwAuthorizationError } from '~/server/utils/errorHandling';
Expand Down Expand Up @@ -47,15 +48,6 @@ const isOwnerOrModerator = middleware(async ({ ctx, next, input = {} }) => {
});
});

const isModerator = middleware(async ({ ctx, next }) => {
if (!ctx.user?.isModerator) throw throwAuthorizationError();
return next({
ctx: {
user: ctx.user,
},
});
});

export const commentv2Router = router({
getInfinite: publicProcedure.input(getCommentsV2Schema).query(getInfiniteCommentsV2Handler),
getCount: publicProcedure.input(commentConnectorSchema).query(getCommentCountV2Handler),
Expand All @@ -72,9 +64,8 @@ export const commentv2Router = router({
getThreadDetails: publicProcedure
.input(commentConnectorSchema)
.query(getCommentsThreadDetailsHandler),
toggleLockThread: protectedProcedure
toggleLockThread: moderatorProcedure
.input(commentConnectorSchema)
.use(isModerator)
.mutation(toggleLockThreadDetailsHandler),
toggleHide: protectedProcedure.input(toggleHideCommentSchema).mutation(toggleHideCommentHandler),
});
3 changes: 2 additions & 1 deletion src/server/routers/generation.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
import {
guardedProcedure,
isFlagProtected,
moderatorProcedure,
protectedProcedure,
publicProcedure,
router,
Expand Down Expand Up @@ -94,7 +95,7 @@ export const generationRouter = router({
.use(edgeCacheIt({ ttl: CacheTTL.sm }))
.query(() => getUnstableResources()),
getUnavailableResources: publicProcedure.query(() => getUnavailableResources()),
toggleUnavailableResource: protectedProcedure
toggleUnavailableResource: moderatorProcedure
.input(getByIdSchema)
.mutation(({ input, ctx }) =>
toggleUnavailableResource({ ...input, isModerator: ctx.user.isModerator })
Expand Down
7 changes: 4 additions & 3 deletions src/server/routers/image.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const imageRouter = router({
create: protectedProcedure
.input(createImageSchema)
.mutation(({ input, ctx }) => createImage({ ...input, userId: ctx.user.id })),
createArticleCoverImage: protectedProcedure
createArticleCoverImage: moderatorProcedure
.input(createImageSchema.extend({ userId: z.number() }))
.mutation(({ input }) => createArticleCoverImage({ ...input })),
ingestArticleImages: protectedProcedure
Expand Down Expand Up @@ -114,9 +114,9 @@ export const imageRouter = router({
})
)
.query(getImageResourcesHandler),
removeResource: protectedProcedure
removeResource: moderatorProcedure
.input(getByIdSchema)
.mutation(({ input, ctx }) => removeImageResource({ ...input, user: ctx.user })),
.mutation(({ input }) => removeImageResource(input)),
rescan: moderatorProcedure.input(getByIdSchema).mutation(({ input }) => ingestImageById(input)),
getEntitiesCoverImage: publicProcedure
.input(getEntitiesCoverImage)
Expand All @@ -134,6 +134,7 @@ export const imageRouter = router({
.mutation(({ input, ctx }) => reportCsamImages({ ...input, user: ctx.user, ip: ctx.ip })),
updateImageNsfwLevel: protectedProcedure
.input(updateImageNsfwLevelSchema)
.use(isOwnerOrModerator)
.mutation(({ input, ctx }) => updateImageNsfwLevel({ ...input, user: ctx.user })),
getImageRatingRequests: moderatorProcedure
.input(imageRatingReviewInput)
Expand Down
13 changes: 9 additions & 4 deletions src/server/routers/model-version.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ const isOwnerOrModerator = middleware(async ({ ctx, input, next }) => {
const { id: userId } = ctx.user;
const { id } = input as { id: number };

const modelId = (await getVersionById({ id, select: { modelId: true } }))?.modelId ?? 0;
const ownerId = (await getModel({ id: modelId, select: { userId: true } }))?.userId ?? -1;
if (id) {
const modelId = (await getVersionById({ id, select: { modelId: true } }))?.modelId ?? 0;
const ownerId = (await getModel({ id: modelId, select: { userId: true } }))?.userId ?? -1;

if (userId !== ownerId) throw throwAuthorizationError();
if (userId !== ownerId) throw throwAuthorizationError();
}

return next({
ctx: {
Expand All @@ -73,7 +75,10 @@ export const modelVersionRouter = router({
.input(getByIdSchema)
.use(isFlagProtected('earlyAccessModel'))
.mutation(toggleNotifyEarlyAccessHandler),
upsert: guardedProcedure.input(modelVersionUpsertSchema2).mutation(upsertModelVersionHandler),
upsert: guardedProcedure
.input(modelVersionUpsertSchema2)
.use(isOwnerOrModerator)
.mutation(upsertModelVersionHandler),
delete: protectedProcedure
.input(getByIdSchema)
.use(isOwnerOrModerator)
Expand Down
2 changes: 1 addition & 1 deletion src/server/routers/model.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export const modelRouter = router({
getModelDetailsForReview: publicProcedure
.input(getByIdSchema)
.query(getModelDetailsForReviewHandler),
restore: protectedProcedure.input(getByIdSchema).mutation(restoreModelHandler),
restore: moderatorProcedure.input(getByIdSchema).mutation(restoreModelHandler),
getDownloadCommand: protectedProcedure.input(getDownloadSchema).query(getDownloadCommandHandler),
reorderVersions: protectedProcedure
.input(reorderModelVersionsSchema)
Expand Down
18 changes: 5 additions & 13 deletions src/server/routers/report.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,21 @@ import {
setReportStatusHandler,
updateReportHandler,
} from '~/server/controllers/report.controller';
import { isModerator } from '~/server/routers/base.router';
import {
bulkUpdateReportStatusSchema,
createReportInputSchema,
getReportsSchema,
setReportStatusSchema,
updateReportSchema,
} from '~/server/schema/report.schema';
import { guardedProcedure, protectedProcedure, router } from '~/server/trpc';
import { guardedProcedure, moderatorProcedure, router } from '~/server/trpc';

export const reportRouter = router({
create: guardedProcedure.input(createReportInputSchema).mutation(createReportHandler),
getAll: protectedProcedure.input(getReportsSchema).use(isModerator).query(getReportsHandler),
update: protectedProcedure
.input(updateReportSchema)
.use(isModerator)
.mutation(updateReportHandler),
setStatus: protectedProcedure
.input(setReportStatusSchema)
.use(isModerator)
.mutation(setReportStatusHandler),
bulkUpdateStatus: protectedProcedure
getAll: moderatorProcedure.input(getReportsSchema).query(getReportsHandler),
update: moderatorProcedure.input(updateReportSchema).mutation(updateReportHandler),
setStatus: moderatorProcedure.input(setReportStatusSchema).mutation(setReportStatusHandler),
bulkUpdateStatus: moderatorProcedure
.input(bulkUpdateReportStatusSchema)
.use(isModerator)
.mutation(bulkUpdateReportStatusHandler),
});
2 changes: 1 addition & 1 deletion src/server/services/article.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,7 @@ export const deleteArticleById = async ({
if (!isOwner) throw throwAuthorizationError(`You cannot perform this action`);

const deleted = await dbWrite.$transaction(async (tx) => {
const article = await tx.article.delete({ where: { id }, select: { coverId: true } });
const article = await tx.article.delete({ where: { id, userId }, select: { coverId: true } });

await tx.file.deleteMany({ where: { entityId: id, entityType: 'Article' } });

Expand Down
4 changes: 1 addition & 3 deletions src/server/services/image.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1643,9 +1643,7 @@ export const getImagesForPosts = async ({
}));
};

export const removeImageResource = async ({ id, user }: GetByIdInput & { user?: SessionUser }) => {
if (!user?.isModerator) throw throwAuthorizationError();

export const removeImageResource = async ({ id }: GetByIdInput) => {
try {
const resource = await dbWrite.imageResource.delete({
where: { id },
Expand Down

0 comments on commit 68891b8

Please sign in to comment.