wip
This commit is contained in:
@@ -50,7 +50,6 @@ export default function AddDashboard() {
|
|||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
name,
|
name,
|
||||||
projectId,
|
projectId,
|
||||||
organizationSlug,
|
|
||||||
});
|
});
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -114,7 +114,6 @@ export default function SaveReport({ report }: SaveReportProps) {
|
|||||||
dashboardMutation.mutate({
|
dashboardMutation.mutate({
|
||||||
projectId,
|
projectId,
|
||||||
name: value,
|
name: value,
|
||||||
organizationSlug,
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { db, getProjectById } from '@openpanel/db';
|
import { db, getProjectById } from '@openpanel/db';
|
||||||
import { cacheable } from '@openpanel/redis';
|
import { cacheable } from '@openpanel/redis';
|
||||||
|
|
||||||
export const getProjectAccessCached = cacheable(getProjectAccess, 60 * 60);
|
export const getProjectAccessCached = cacheable(getProjectAccess, 60 * 5);
|
||||||
export async function getProjectAccess({
|
export async function getProjectAccess({
|
||||||
userId,
|
userId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -16,14 +16,26 @@ export async function getProjectAccess({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const member = await db.member.findFirst({
|
const [projectAccess, member] = await Promise.all([
|
||||||
where: {
|
db.projectAccess.findMany({
|
||||||
organizationId: project.organizationSlug,
|
where: {
|
||||||
userId,
|
userId,
|
||||||
},
|
organizationId: project.organizationSlug,
|
||||||
});
|
},
|
||||||
|
}),
|
||||||
|
db.member.findFirst({
|
||||||
|
where: {
|
||||||
|
organizationId: project.organizationSlug,
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
return member;
|
if (projectAccess.length === 0 && member) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectAccess.find((item) => item.projectId === projectId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -31,7 +43,7 @@ export async function getProjectAccess({
|
|||||||
|
|
||||||
export const getOrganizationAccessCached = cacheable(
|
export const getOrganizationAccessCached = cacheable(
|
||||||
getOrganizationAccess,
|
getOrganizationAccess,
|
||||||
60 * 60
|
60 * 5
|
||||||
);
|
);
|
||||||
export async function getOrganizationAccess({
|
export async function getOrganizationAccess({
|
||||||
userId,
|
userId,
|
||||||
@@ -47,3 +59,35 @@ export async function getOrganizationAccess({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getClientAccessCached = cacheable(getClientAccess, 60 * 5);
|
||||||
|
export async function getClientAccess({
|
||||||
|
userId,
|
||||||
|
clientId,
|
||||||
|
}: {
|
||||||
|
userId: string;
|
||||||
|
clientId: string;
|
||||||
|
}) {
|
||||||
|
const client = await db.client.findFirst({
|
||||||
|
where: {
|
||||||
|
id: clientId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.projectId) {
|
||||||
|
return getProjectAccess({ userId, projectId: client.projectId });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.organizationId) {
|
||||||
|
return getOrganizationAccess({
|
||||||
|
userId,
|
||||||
|
organizationId: client.organizationId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,3 +5,9 @@ export const TRPCAccessError = (message: string) =>
|
|||||||
code: 'UNAUTHORIZED',
|
code: 'UNAUTHORIZED',
|
||||||
message,
|
message,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const TRPCNotFoundError = (message: string) =>
|
||||||
|
new TRPCError({
|
||||||
|
code: 'NOT_FOUND',
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { hashPassword, stripTrailingSlash } from '@openpanel/common';
|
|||||||
import type { Prisma } from '@openpanel/db';
|
import type { Prisma } from '@openpanel/db';
|
||||||
import { db } from '@openpanel/db';
|
import { db } from '@openpanel/db';
|
||||||
|
|
||||||
|
import { getClientAccess } from '../access';
|
||||||
|
import { TRPCAccessError } from '../errors';
|
||||||
import { createTRPCRouter, protectedProcedure } from '../trpc';
|
import { createTRPCRouter, protectedProcedure } from '../trpc';
|
||||||
|
|
||||||
export const clientRouter = createTRPCRouter({
|
export const clientRouter = createTRPCRouter({
|
||||||
@@ -17,7 +19,16 @@ export const clientRouter = createTRPCRouter({
|
|||||||
crossDomain: z.boolean().optional(),
|
crossDomain: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const access = await getClientAccess({
|
||||||
|
userId: ctx.session.userId,
|
||||||
|
clientId: input.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this client');
|
||||||
|
}
|
||||||
|
|
||||||
return db.client.update({
|
return db.client.update({
|
||||||
where: {
|
where: {
|
||||||
id: input.id,
|
id: input.id,
|
||||||
@@ -66,7 +77,16 @@ export const clientRouter = createTRPCRouter({
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const access = await getClientAccess({
|
||||||
|
userId: ctx.session.userId,
|
||||||
|
clientId: input.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this client');
|
||||||
|
}
|
||||||
|
|
||||||
await db.client.delete({
|
await db.client.delete({
|
||||||
where: {
|
where: {
|
||||||
id: input.id,
|
id: input.id,
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
import { PrismaError } from 'prisma-error-enum';
|
import { PrismaError } from 'prisma-error-enum';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { db, getDashboardsByProjectId, getId } from '@openpanel/db';
|
import {
|
||||||
|
db,
|
||||||
|
getDashboardsByProjectId,
|
||||||
|
getId,
|
||||||
|
getProjectById,
|
||||||
|
} from '@openpanel/db';
|
||||||
import type { Prisma } from '@openpanel/db';
|
import type { Prisma } from '@openpanel/db';
|
||||||
|
|
||||||
|
import { getProjectAccess } from '../access';
|
||||||
|
import { TRPCAccessError, TRPCNotFoundError } from '../errors';
|
||||||
import { createTRPCRouter, protectedProcedure } from '../trpc';
|
import { createTRPCRouter, protectedProcedure } from '../trpc';
|
||||||
|
|
||||||
export const dashboardRouter = createTRPCRouter({
|
export const dashboardRouter = createTRPCRouter({
|
||||||
@@ -13,7 +20,7 @@ export const dashboardRouter = createTRPCRouter({
|
|||||||
projectId: z.string(),
|
projectId: z.string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ input }) => {
|
.query(({ input }) => {
|
||||||
return getDashboardsByProjectId(input.projectId);
|
return getDashboardsByProjectId(input.projectId);
|
||||||
}),
|
}),
|
||||||
create: protectedProcedure
|
create: protectedProcedure
|
||||||
@@ -21,17 +28,31 @@ export const dashboardRouter = createTRPCRouter({
|
|||||||
z.object({
|
z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
projectId: z.string(),
|
projectId: z.string(),
|
||||||
organizationSlug: z.string(),
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(async ({ input: { organizationSlug, projectId, name } }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const access = await getProjectAccess({
|
||||||
|
projectId: input.projectId,
|
||||||
|
userId: ctx.session.userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this project');
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = await getProjectById(input.projectId);
|
||||||
|
|
||||||
|
if (!project) {
|
||||||
|
throw TRPCNotFoundError('Project not found');
|
||||||
|
}
|
||||||
|
|
||||||
return db.dashboard.create({
|
return db.dashboard.create({
|
||||||
data: {
|
data: {
|
||||||
id: await getId('dashboard', name),
|
id: await getId('dashboard', input.name),
|
||||||
projectId: projectId,
|
projectId: input.projectId,
|
||||||
organizationSlug: organizationSlug,
|
organizationSlug: project.organizationId!,
|
||||||
organizationId: organizationSlug,
|
organizationId: project.organizationId,
|
||||||
name,
|
name: input.name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
@@ -42,7 +63,22 @@ export const dashboardRouter = createTRPCRouter({
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const dashboard = await db.dashboard.findUniqueOrThrow({
|
||||||
|
where: {
|
||||||
|
id: input.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const access = await getProjectAccess({
|
||||||
|
projectId: dashboard.projectId,
|
||||||
|
userId: ctx.session.userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
return db.dashboard.update({
|
return db.dashboard.update({
|
||||||
where: {
|
where: {
|
||||||
id: input.id,
|
id: input.id,
|
||||||
@@ -59,18 +95,33 @@ export const dashboardRouter = createTRPCRouter({
|
|||||||
forceDelete: z.boolean().optional(),
|
forceDelete: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(async ({ input: { id, forceDelete } }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const dashboard = await db.dashboard.findUniqueOrThrow({
|
||||||
|
where: {
|
||||||
|
id: input.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const access = await getProjectAccess({
|
||||||
|
projectId: dashboard.projectId,
|
||||||
|
userId: ctx.session.userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (forceDelete) {
|
if (input.forceDelete) {
|
||||||
await db.report.deleteMany({
|
await db.report.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
dashboardId: id,
|
dashboardId: input.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await db.dashboard.delete({
|
await db.dashboard.delete({
|
||||||
where: {
|
where: {
|
||||||
id,
|
id: input.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -27,18 +27,20 @@ export const eventRouter = createTRPCRouter({
|
|||||||
conversion: z.boolean().optional(),
|
conversion: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(({ input: { projectId, name, icon, color, conversion } }) => {
|
.mutation(
|
||||||
return db.eventMeta.upsert({
|
async ({ input: { projectId, name, icon, color, conversion } }) => {
|
||||||
where: {
|
return db.eventMeta.upsert({
|
||||||
name_projectId: {
|
where: {
|
||||||
name,
|
name_projectId: {
|
||||||
projectId,
|
name,
|
||||||
|
projectId,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
create: { projectId, name, icon, color, conversion },
|
||||||
create: { projectId, name, icon, color, conversion },
|
update: { icon, color, conversion },
|
||||||
update: { icon, color, conversion },
|
});
|
||||||
});
|
}
|
||||||
}),
|
),
|
||||||
|
|
||||||
byId: protectedProcedure
|
byId: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
@@ -78,8 +80,10 @@ export const eventRouter = createTRPCRouter({
|
|||||||
profile: z.boolean().optional(),
|
profile: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ input }) => getEventList(input)),
|
.query(async ({ input }) => {
|
||||||
conversions: publicProcedure
|
return getEventList(input);
|
||||||
|
}),
|
||||||
|
conversions: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
projectId: z.string(),
|
projectId: z.string(),
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { z } from 'zod';
|
|||||||
import { db } from '@openpanel/db';
|
import { db } from '@openpanel/db';
|
||||||
import { zInviteUser } from '@openpanel/validation';
|
import { zInviteUser } from '@openpanel/validation';
|
||||||
|
|
||||||
|
import { getOrganizationAccess, getOrganizationAccessCached } from '../access';
|
||||||
|
import { TRPCAccessError } from '../errors';
|
||||||
import { createTRPCRouter, protectedProcedure } from '../trpc';
|
import { createTRPCRouter, protectedProcedure } from '../trpc';
|
||||||
|
|
||||||
export const organizationRouter = createTRPCRouter({
|
export const organizationRouter = createTRPCRouter({
|
||||||
@@ -15,7 +17,16 @@ export const organizationRouter = createTRPCRouter({
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const access = await getOrganizationAccess({
|
||||||
|
userId: ctx.session.userId,
|
||||||
|
organizationId: input.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this project');
|
||||||
|
}
|
||||||
|
|
||||||
return db.organization.update({
|
return db.organization.update({
|
||||||
where: {
|
where: {
|
||||||
id: input.id,
|
id: input.id,
|
||||||
@@ -29,6 +40,15 @@ export const organizationRouter = createTRPCRouter({
|
|||||||
inviteUser: protectedProcedure
|
inviteUser: protectedProcedure
|
||||||
.input(zInviteUser)
|
.input(zInviteUser)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const access = await getOrganizationAccess({
|
||||||
|
userId: ctx.session.userId,
|
||||||
|
organizationId: input.organizationSlug,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this project');
|
||||||
|
}
|
||||||
|
|
||||||
const email = input.email.toLowerCase();
|
const email = input.email.toLowerCase();
|
||||||
const userExists = await db.user.findFirst({
|
const userExists = await db.user.findFirst({
|
||||||
where: {
|
where: {
|
||||||
@@ -68,12 +88,22 @@ export const organizationRouter = createTRPCRouter({
|
|||||||
memberId: z.string(),
|
memberId: z.string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const member = await db.member.findUniqueOrThrow({
|
const member = await db.member.findUniqueOrThrow({
|
||||||
where: {
|
where: {
|
||||||
id: input.memberId,
|
id: input.memberId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const access = await getOrganizationAccess({
|
||||||
|
userId: ctx.session.userId,
|
||||||
|
organizationId: member.organizationId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this project');
|
||||||
|
}
|
||||||
|
|
||||||
const invitationId = pathOr<string | undefined>(
|
const invitationId = pathOr<string | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
['meta', 'invitationId'],
|
['meta', 'invitationId'],
|
||||||
@@ -107,6 +137,15 @@ export const organizationRouter = createTRPCRouter({
|
|||||||
throw new Error('You cannot remove yourself from the organization');
|
throw new Error('You cannot remove yourself from the organization');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const access = await getOrganizationAccess({
|
||||||
|
userId: ctx.session.userId,
|
||||||
|
organizationId: input.organizationId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this project');
|
||||||
|
}
|
||||||
|
|
||||||
await db.$transaction([
|
await db.$transaction([
|
||||||
db.member.deleteMany({
|
db.member.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
@@ -131,7 +170,16 @@ export const organizationRouter = createTRPCRouter({
|
|||||||
access: z.array(z.string()),
|
access: z.array(z.string()),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const access = await getOrganizationAccess({
|
||||||
|
userId: ctx.session.userId,
|
||||||
|
organizationId: input.organizationSlug,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this project');
|
||||||
|
}
|
||||||
|
|
||||||
return db.$transaction([
|
return db.$transaction([
|
||||||
db.projectAccess.deleteMany({
|
db.projectAccess.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
import { db, getId, getProjectsByOrganizationSlug } from '@openpanel/db';
|
import { db, getId, getProjectsByOrganizationSlug } from '@openpanel/db';
|
||||||
|
|
||||||
|
import { getProjectAccess } from '../access';
|
||||||
|
import { TRPCAccessError } from '../errors';
|
||||||
import { createTRPCRouter, protectedProcedure } from '../trpc';
|
import { createTRPCRouter, protectedProcedure } from '../trpc';
|
||||||
|
|
||||||
export const projectRouter = createTRPCRouter({
|
export const projectRouter = createTRPCRouter({
|
||||||
@@ -23,7 +25,16 @@ export const projectRouter = createTRPCRouter({
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const access = await getProjectAccess({
|
||||||
|
userId: ctx.session.userId,
|
||||||
|
projectId: input.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this project');
|
||||||
|
}
|
||||||
|
|
||||||
return db.project.update({
|
return db.project.update({
|
||||||
where: {
|
where: {
|
||||||
id: input.id,
|
id: input.id,
|
||||||
@@ -56,7 +67,16 @@ export const projectRouter = createTRPCRouter({
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const access = await getProjectAccess({
|
||||||
|
userId: ctx.session.userId,
|
||||||
|
projectId: input.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this project');
|
||||||
|
}
|
||||||
|
|
||||||
await db.project.delete({
|
await db.project.delete({
|
||||||
where: {
|
where: {
|
||||||
id: input.id,
|
id: input.id,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { z } from 'zod';
|
|||||||
import { db, getReferences } from '@openpanel/db';
|
import { db, getReferences } from '@openpanel/db';
|
||||||
import { zCreateReference, zRange } from '@openpanel/validation';
|
import { zCreateReference, zRange } from '@openpanel/validation';
|
||||||
|
|
||||||
|
import { getProjectAccess } from '../access';
|
||||||
|
import { TRPCAccessError } from '../errors';
|
||||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from '../trpc';
|
import { createTRPCRouter, protectedProcedure, publicProcedure } from '../trpc';
|
||||||
import { getChartStartEndDate } from './chart.helpers';
|
import { getChartStartEndDate } from './chart.helpers';
|
||||||
|
|
||||||
@@ -23,7 +25,22 @@ export const referenceRouter = createTRPCRouter({
|
|||||||
),
|
),
|
||||||
delete: protectedProcedure
|
delete: protectedProcedure
|
||||||
.input(z.object({ id: z.string() }))
|
.input(z.object({ id: z.string() }))
|
||||||
.mutation(async ({ input: { id } }) => {
|
.mutation(async ({ input: { id }, ctx }) => {
|
||||||
|
const reference = await db.reference.findUniqueOrThrow({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const access = await getProjectAccess({
|
||||||
|
userId: ctx.session.userId,
|
||||||
|
projectId: reference.projectId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this project');
|
||||||
|
}
|
||||||
|
|
||||||
return db.reference.delete({
|
return db.reference.delete({
|
||||||
where: {
|
where: {
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { z } from 'zod';
|
|||||||
import { db } from '@openpanel/db';
|
import { db } from '@openpanel/db';
|
||||||
import { zReportInput } from '@openpanel/validation';
|
import { zReportInput } from '@openpanel/validation';
|
||||||
|
|
||||||
|
import { getProjectAccess } from '../access';
|
||||||
|
import { TRPCAccessError } from '../errors';
|
||||||
import { createTRPCRouter, protectedProcedure } from '../trpc';
|
import { createTRPCRouter, protectedProcedure } from '../trpc';
|
||||||
|
|
||||||
export const reportRouter = createTRPCRouter({
|
export const reportRouter = createTRPCRouter({
|
||||||
@@ -13,12 +15,22 @@ export const reportRouter = createTRPCRouter({
|
|||||||
dashboardId: z.string(),
|
dashboardId: z.string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(async ({ input: { report, dashboardId } }) => {
|
.mutation(async ({ input: { report, dashboardId }, ctx }) => {
|
||||||
const dashboard = await db.dashboard.findUniqueOrThrow({
|
const dashboard = await db.dashboard.findUniqueOrThrow({
|
||||||
where: {
|
where: {
|
||||||
id: dashboardId,
|
id: dashboardId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const access = await getProjectAccess({
|
||||||
|
userId: ctx.session.userId,
|
||||||
|
projectId: dashboard.projectId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this project');
|
||||||
|
}
|
||||||
|
|
||||||
return db.report.create({
|
return db.report.create({
|
||||||
data: {
|
data: {
|
||||||
projectId: dashboard.projectId,
|
projectId: dashboard.projectId,
|
||||||
@@ -42,7 +54,22 @@ export const reportRouter = createTRPCRouter({
|
|||||||
report: zReportInput.omit({ projectId: true }),
|
report: zReportInput.omit({ projectId: true }),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(({ input: { report, reportId } }) => {
|
.mutation(async ({ input: { report, reportId }, ctx }) => {
|
||||||
|
const dbReport = await db.report.findUniqueOrThrow({
|
||||||
|
where: {
|
||||||
|
id: reportId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const access = await getProjectAccess({
|
||||||
|
userId: ctx.session.userId,
|
||||||
|
projectId: dbReport.projectId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this project');
|
||||||
|
}
|
||||||
|
|
||||||
return db.report.update({
|
return db.report.update({
|
||||||
where: {
|
where: {
|
||||||
id: reportId,
|
id: reportId,
|
||||||
@@ -66,7 +93,22 @@ export const reportRouter = createTRPCRouter({
|
|||||||
reportId: z.string(),
|
reportId: z.string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(({ input: { reportId } }) => {
|
.mutation(async ({ input: { reportId }, ctx }) => {
|
||||||
|
const report = await db.report.findUniqueOrThrow({
|
||||||
|
where: {
|
||||||
|
id: reportId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const access = await getProjectAccess({
|
||||||
|
userId: ctx.session.userId,
|
||||||
|
projectId: report.projectId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this project');
|
||||||
|
}
|
||||||
|
|
||||||
return db.report.delete({
|
return db.report.delete({
|
||||||
where: {
|
where: {
|
||||||
id: reportId,
|
id: reportId,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const uid = new ShortUniqueId({ length: 6 });
|
|||||||
export const shareRouter = createTRPCRouter({
|
export const shareRouter = createTRPCRouter({
|
||||||
shareOverview: protectedProcedure
|
shareOverview: protectedProcedure
|
||||||
.input(zShareOverview)
|
.input(zShareOverview)
|
||||||
.mutation(({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return db.shareOverview.upsert({
|
return db.shareOverview.upsert({
|
||||||
where: {
|
where: {
|
||||||
projectId: input.projectId,
|
projectId: input.projectId,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { has } from 'ramda';
|
|||||||
import superjson from 'superjson';
|
import superjson from 'superjson';
|
||||||
import { ZodError } from 'zod';
|
import { ZodError } from 'zod';
|
||||||
|
|
||||||
import { getProjectAccessCached } from './access';
|
import { getOrganizationAccessCached, getProjectAccessCached } from './access';
|
||||||
import { TRPCAccessError } from './errors';
|
import { TRPCAccessError } from './errors';
|
||||||
|
|
||||||
export function createContext({ req, res }: CreateFastifyContextOptions) {
|
export function createContext({ req, res }: CreateFastifyContextOptions) {
|
||||||
@@ -45,7 +45,7 @@ const t = initTRPC.context<Context>().create({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const enforceUserIsAuthed = t.middleware(async ({ ctx, next, input }) => {
|
const enforceUserIsAuthed = t.middleware(async ({ ctx, next }) => {
|
||||||
if (!ctx.session?.userId) {
|
if (!ctx.session?.userId) {
|
||||||
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Not authenticated' });
|
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Not authenticated' });
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ const enforceUserIsAuthed = t.middleware(async ({ ctx, next, input }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Only used on protected routes
|
// Only used on protected routes
|
||||||
const enforceProjectAccess = t.middleware(async ({ ctx, next, rawInput }) => {
|
const enforceAccess = t.middleware(async ({ ctx, next, rawInput }) => {
|
||||||
if (has('projectId', rawInput)) {
|
if (has('projectId', rawInput)) {
|
||||||
const access = await getProjectAccessCached({
|
const access = await getProjectAccessCached({
|
||||||
userId: ctx.session.userId!,
|
userId: ctx.session.userId!,
|
||||||
@@ -78,6 +78,28 @@ const enforceProjectAccess = t.middleware(async ({ ctx, next, rawInput }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (has('organizationId', rawInput)) {
|
||||||
|
const access = await getOrganizationAccessCached({
|
||||||
|
userId: ctx.session.userId!,
|
||||||
|
organizationId: rawInput.organizationId as string,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this organization');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has('organizationSlug', rawInput)) {
|
||||||
|
const access = await getOrganizationAccessCached({
|
||||||
|
userId: ctx.session.userId!,
|
||||||
|
organizationId: rawInput.organizationSlug as string,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!access) {
|
||||||
|
throw TRPCAccessError('You do not have access to this organization');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -86,4 +108,4 @@ export const createTRPCRouter = t.router;
|
|||||||
export const publicProcedure = t.procedure;
|
export const publicProcedure = t.procedure;
|
||||||
export const protectedProcedure = t.procedure
|
export const protectedProcedure = t.procedure
|
||||||
.use(enforceUserIsAuthed)
|
.use(enforceUserIsAuthed)
|
||||||
.use(enforceProjectAccess);
|
.use(enforceAccess);
|
||||||
|
|||||||
Reference in New Issue
Block a user