Skip to content

Commit 9df0481

Browse files
committed
auth guard
1 parent ddd3d9c commit 9df0481

File tree

10 files changed

+193
-35
lines changed

10 files changed

+193
-35
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,4 @@ dist
161161
generated/*
162162
db.sqlite
163163
public/js
164+
widget/index.html
Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
1-
import { NextApiRequest, NextApiResponse } from "next";
2-
import { CommentService } from "../../../../service/comment.service";
1+
import { NextApiRequest, NextApiResponse } from 'next'
2+
import { AuthService } from '../../../../service/auth.service'
3+
import { CommentService } from '../../../../service/comment.service'
34

4-
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
5+
export default async function handler(
6+
req: NextApiRequest,
7+
res: NextApiResponse,
8+
) {
59
const commentService = new CommentService(req)
10+
const authService = new AuthService(req, res)
611

712
if (req.method === 'POST') {
8-
await commentService.approve(req.query.commentId as string)
13+
const commentId = req.query.commentId as string
14+
15+
// only project owner can approve
16+
const project = await commentService.getProject(commentId)
17+
if (!(await authService.projectOwnerGuard(project))) {
18+
return
19+
}
20+
21+
await commentService.approve(commentId)
922
res.json({
10-
message: 'success'
23+
message: 'success',
1124
})
1225
}
13-
}
26+
}
Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
1-
import { NextApiRequest, NextApiResponse } from "next";
2-
import { CommentService } from "../../../../service/comment.service";
1+
import { NextApiRequest, NextApiResponse } from 'next'
2+
import { AuthService } from '../../../../service/auth.service'
3+
import { CommentService } from '../../../../service/comment.service'
34

4-
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
5+
export default async function handler(
6+
req: NextApiRequest,
7+
res: NextApiResponse,
8+
) {
59
const commentService = new CommentService(req)
10+
const authService = new AuthService(req, res)
11+
612
if (req.method === 'POST') {
713
const body = req.body as {
814
content: string
915
}
10-
const created = await commentService.addCommentAsModerator(req.query.commentId as string, body.content)
16+
const commentId = req.query.commentId as string
17+
18+
const project = await commentService.getProject(commentId)
19+
if (!(await authService.projectOwnerGuard(project))) {
20+
return
21+
}
22+
const created = await commentService.addCommentAsModerator(
23+
commentId,
24+
body.content,
25+
)
1126
res.json({
12-
data: created
27+
data: created,
1328
})
1429
}
15-
}
30+
}

pages/api/project/[projectId]/comments.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { Project } from '.prisma/client'
12
import { NextApiRequest, NextApiResponse } from 'next'
3+
import { AuthService } from '../../../../service/auth.service'
24
import { CommentService } from '../../../../service/comment.service'
35
import { ProjectService } from '../../../../service/project.service'
46

@@ -8,18 +10,32 @@ export default async function handler(
810
) {
911
const projectService = new ProjectService(req)
1012
const commentService = new CommentService(req)
13+
const authService = new AuthService(req, res)
1114
if (req.method === 'GET') {
1215
const { projectId, page } = req.query as {
1316
projectId: string
1417
page: string
1518
}
19+
20+
// only owner can get comments
21+
const project = await projectService.get(projectId, {
22+
select: {
23+
ownerId: true
24+
}
25+
}) as Pick<Project, "ownerId">
26+
27+
if (!await authService.projectOwnerGuard(project)) {
28+
return
29+
}
30+
1631
const comments = await commentService.getComments(projectId, {
1732
parentId: null,
1833
page: Number(page),
1934
onlyOwn: true,
2035
select: {
2136
by_nickname: true,
2237
by_email: true,
38+
approved: true
2339
},
2440
})
2541
res.json({
Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,64 @@
1-
import { NextApiRequest, NextApiResponse } from "next";
1+
import { NextApiRequest, NextApiResponse } from 'next'
22

3-
import formidable from "formidable";
4-
import { DataService } from "../../../../../service/data.service";
5-
import * as fs from "fs";
3+
import formidable from 'formidable'
4+
import { DataService } from '../../../../../service/data.service'
5+
import * as fs from 'fs'
6+
import { AuthService } from '../../../../../service/auth.service'
7+
import { ProjectService } from '../../../../../service/project.service'
8+
import { Project } from '@prisma/client'
69

710
export const config = {
811
api: {
912
bodyParser: false,
1013
},
11-
};
14+
}
1215

1316
export default async function handler(
1417
req: NextApiRequest,
15-
res: NextApiResponse
18+
res: NextApiResponse,
1619
) {
17-
if (req.method === "POST") {
18-
const form = new formidable.IncomingForm();
20+
const authService = new AuthService(req, res)
21+
const projectService = new ProjectService(req)
22+
23+
if (req.method === 'POST') {
24+
const form = new formidable.IncomingForm()
1925

20-
const dataService = new DataService();
26+
const dataService = new DataService()
2127

2228
const { projectId } = req.query as {
23-
projectId: string;
24-
};
29+
projectId: string
30+
}
31+
32+
// only owner can import
33+
const project = (await projectService.get(projectId, {
34+
select: {
35+
ownerId: true,
36+
},
37+
})) as Pick<Project, 'ownerId'>
38+
39+
if (!(await authService.projectOwnerGuard(project))) {
40+
return
41+
}
2542

2643
form.parse(req, async (err, fields, files) => {
2744
if (err) {
2845
res.status(503)
2946
res.json({
30-
message: err.message
47+
message: err.message,
3148
})
3249
return
3350
}
3451

3552
const imported = await dataService.importFromDisqus(
3653
projectId,
37-
fs.readFileSync(files.file.path, { encoding: "utf-8" })
38-
);
54+
fs.readFileSync(files.file.path, { encoding: 'utf-8' }),
55+
)
3956
res.json({
4057
data: {
4158
pageCount: imported.threads.length,
42-
commentCount: imported.posts.length
59+
commentCount: imported.posts.length,
4360
},
44-
});
45-
});
61+
})
62+
})
4663
}
4764
}

pages/dashboard/project/[projectId].tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Button, Center, Container, Divider, Flex, FormControl, Heading, HStack, Input, Link, Spacer, Spinner, StackDivider, Tab, TabList, TabPanel, TabPanels, Tabs, Tag, Text, Textarea, toast, useToast, VStack } from '@chakra-ui/react'
22
import { Comment, Page, Project } from '@prisma/client'
3-
import { signIn } from 'next-auth/client'
3+
import { session, signIn } from 'next-auth/client'
44
import { useRouter } from 'next/router'
55
import React from 'react'
66
import { useMutation, useQuery } from 'react-query'
@@ -170,8 +170,14 @@ function ProjectPage(props: {
170170
session: UserSession
171171
}) {
172172

173+
React.useEffect(() => {
174+
if (!props.session) {
175+
signIn()
176+
}
177+
}, [!props.session])
178+
173179
if (!props.session) {
174-
signIn()
180+
return <div>Redirecting to signin..</div>
175181
}
176182

177183
const [page, setPage] = React.useState(1)
@@ -306,13 +312,25 @@ function Settings(props: {
306312

307313
export async function getServerSideProps(ctx) {
308314
const projectService = new ProjectService(ctx.req)
315+
const session = await getSession(ctx.req)
309316
const project = await projectService.get(ctx.query.projectId)
317+
318+
if (session && (project.ownerId !== session.uid)) {
319+
return {
320+
redirect: {
321+
destination: '/forbidden',
322+
permanent: false
323+
}
324+
}
325+
}
326+
310327
return {
311328
props: {
312329
session: await getSession(ctx.req),
313330
project: {
314331
id: project.id,
315-
title: project.title
332+
title: project.title,
333+
ownerId: project.ownerId
316334
}
317335
}
318336
}

pages/forbidden.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
function ForbiddenPage() {
2+
return (
3+
<>
4+
Forbidden
5+
</>
6+
)
7+
}
8+
9+
export async function getServerSideProps(ctx) {
10+
return {
11+
props: {
12+
13+
}
14+
}
15+
}
16+
17+
export default ForbiddenPage

service/auth.service.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Project } from '.prisma/client'
2+
import { RequestScopeService } from '.'
3+
4+
export class AuthService extends RequestScopeService {
5+
constructor(req, private res) {
6+
super(req)
7+
}
8+
9+
async authGuard() {
10+
const session = await this.getSession()
11+
if (!session) {
12+
this.res.status(403).json({
13+
message: 'Sign in required',
14+
})
15+
return null
16+
}
17+
return session
18+
}
19+
20+
async projectOwnerGuard(project: Pick<Project, 'ownerId'>) {
21+
const session = await this.authGuard()
22+
23+
if (!session) {
24+
return null
25+
}
26+
27+
if (project.ownerId !== session.uid) {
28+
this.res.status(403).json({
29+
message: 'Permission denied',
30+
})
31+
return null
32+
} else {
33+
return true
34+
}
35+
}
36+
}

service/comment.service.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,28 @@ export class CommentService extends RequestScopeService {
9494
return allComments as any[]
9595
}
9696

97+
async getProject(commentId: string) {
98+
const res = await prisma.comment.findUnique({
99+
where: {
100+
id: commentId
101+
},
102+
select: {
103+
page: {
104+
select: {
105+
project: {
106+
select: {
107+
id: true,
108+
ownerId: true
109+
}
110+
}
111+
}
112+
}
113+
}
114+
})
115+
116+
return res.page.project
117+
}
118+
97119
async addComment(
98120
projectId: string,
99121
pageSlug: string,

service/project.service.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { User } from "@prisma/client";
1+
import { Prisma, User } from "@prisma/client";
22
import { RequestScopeService } from ".";
33
import { prisma } from "../utils.server";
44

@@ -20,11 +20,14 @@ export class ProjectService extends RequestScopeService {
2020
return created
2121
}
2222

23-
async get(projectId: string) {
23+
async get(projectId: string, options?: {
24+
select?: Prisma.ProjectSelect
25+
}) {
2426
const project = await prisma.project.findUnique({
2527
where: {
2628
id: projectId
27-
}
29+
},
30+
select: options?.select
2831
})
2932

3033
return project

0 commit comments

Comments
 (0)