Skip to content

Commit

Permalink
User blacklist middleware (#79)
Browse files Browse the repository at this point in the history
* User blacklist middleware #75

* Get all blacklisted users

* Error responses
  • Loading branch information
joshuakojko authored Feb 26, 2025
1 parent 7179f32 commit 360e7ac
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 5 deletions.
33 changes: 30 additions & 3 deletions server/src/middlewares/auth-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import { FORBIDDEN, UNAUTHORIZED } from 'stoker/http-status-codes';
import type { Context } from '@/lib/context';
import { lucia } from '@/lib/auth';

import { db } from '@/db/db';
import { blacklist } from '@/db/schema';
import type { Blacklist } from '@/db/schema';
import { eq } from 'drizzle-orm';

export const authMiddleWare = (role: 'user' | 'member' | 'admin'): MiddlewareHandler => createMiddleware<Context>(async (c, next) => {
const sessionId = getCookie(c, lucia.sessionCookieName) ?? null;
if (!sessionId) {
Expand All @@ -18,12 +23,21 @@ export const authMiddleWare = (role: 'user' | 'member' | 'admin'): MiddlewareHan
if (!user) {
return c.json({ error: 'Unauthorized' }, UNAUTHORIZED);
}
const blacklistedUser: Blacklist | undefined = await db
.select()
.from(blacklist)
.where(eq(blacklist.userId, user.id))
.then((res) => res[0]);

if (blacklistedUser) {
return c.json({ error: 'Blacklisted', message: blacklistedUser.reason }, FORBIDDEN);
}
if (role === 'admin' && user.role !== 'admin') {
return c.json({ error: 'Forbidden' }, FORBIDDEN);
}
if (role === 'member' && user.role === 'user') {
return c.json({ error: 'Forbidden' }, FORBIDDEN);
}
if (role === 'member' && user.role === 'user') {
return c.json({ error: 'Forbidden' }, FORBIDDEN);
}

if (session && session.fresh) {
c.header('Set-Cookie', lucia.createSessionCookie(session.id).serialize(), {
Expand Down Expand Up @@ -65,3 +79,16 @@ export const forbiddenRequest = {
},
},
};

export const blacklistedRequest = {
[FORBIDDEN]: {
description: 'Blacklisted',
content: {
'application/json': {
schema: z.object({
error: z.string(),
}),
},
},
},
};
122 changes: 122 additions & 0 deletions server/src/router/v1/blacklist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { OpenAPIHono, createRoute } from '@hono/zod-openapi';
import { z } from 'zod';
import * as HttpStatusCodes from 'stoker/http-status-codes';

import type { Context } from '@/lib/context';
import { db } from '@/db/db';
import { blacklist } from '@/db/schema';
import type { NewBlacklist, Blacklist } from '@/db/schema';
import { newBlacklistSchema, userIdSchema, blacklistSchema } from '@/util/zod';
import { authMiddleWare, unauthorizedRequest, forbiddenRequest } from '@/middlewares/auth-middleware';
import { eq } from 'drizzle-orm';

const blacklistRouter = new OpenAPIHono<Context>();

blacklistRouter.openapi(
createRoute({
method: 'get',
path: '/',
tags: ['blacklist'],
summary: 'Get all blacklisted users',
middleware: [authMiddleWare('admin')],
responses: {
[HttpStatusCodes.OK]: {
content: {
'application/json': {
schema: z.object({
blacklist: z.array(blacklistSchema),
}),
},
},
description: 'List of all blacklisted users',
},
},
...unauthorizedRequest,
...forbiddenRequest,
}),
async (c) => {
const blacklistedUsers: Blacklist[] = await db
.select()
.from(blacklist);
return c.json({ blacklist: blacklistedUsers }, HttpStatusCodes.OK);
},
);

blacklistRouter.openapi(
createRoute({
method: 'post',
path: '/',
tags: ['blacklist'],
summary: 'Blacklist a user',
middleware: [authMiddleWare('admin')],
request: {
body: {
content: {
'application/json': {
schema: newBlacklistSchema,
},
},
},
},
responses: {
[HttpStatusCodes.CREATED]: {
content: {
'application/json': {
schema: z.object({
blacklist: newBlacklistSchema,
}),
},
},
description: 'User successfully blacklisted',
},
},
...unauthorizedRequest,
...forbiddenRequest,
}),
async (c) => {
const { userId, reason } = c.req.valid('json');
const newBlacklist: NewBlacklist[] = await db
.insert(blacklist)
.values({ userId, reason })
.onConflictDoNothing()
.returning();
return c.json({ blacklist: newBlacklist[0] }, HttpStatusCodes.CREATED);
},
);

blacklistRouter.openapi(
createRoute({
method: 'delete',
path: '/{userId}',
tags: ['blacklist'],
summary: 'Remove a user from blacklist',
middleware: [authMiddleWare('admin')],
request: {
params: userIdSchema,
},
responses: {
[HttpStatusCodes.OK]: {
content: {
'application/json': {
schema: z.object({
success: z.boolean(),
}),
},
},
description: 'User removed from blacklist',
},
},
...unauthorizedRequest,
...forbiddenRequest,
}),
async (c) => {
const { userId } = c.req.valid('param');
const deletedBlacklist: Blacklist[] = await db
.delete(blacklist)
.where(eq(blacklist.userId, userId))
.returning();
return c.json({ success: deletedBlacklist.length > 0 }, HttpStatusCodes.OK);
},
);

export default blacklistRouter;
2 changes: 2 additions & 0 deletions server/src/router/v1/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import majorRouter from '@/router/v1/major';
import enumRouter from '@/router/v1/enum';
import sponsorRouter from '@/router/v1/sponsor';
import officerRouter from '@/router/v1/officer';
import blacklistRouter from '@/router/v1/blacklist';
import clubRouter from '@/router/v1/club';

const v1App = new OpenAPIHono<Context>();
Expand All @@ -25,6 +26,7 @@ v1App.route('/majors', majorRouter);
v1App.route('/enums', enumRouter);
v1App.route('/sponsors', sponsorRouter);
v1App.route('/officers', officerRouter);
v1App.route('/blacklist', blacklistRouter);
v1App.route('/club', clubRouter);

export default v1App;
6 changes: 4 additions & 2 deletions server/src/util/zod.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createSelectSchema } from 'drizzle-zod';
import { educationLevelEnum, bookmarkedEvents, projects, users, events, subscribedCompanies, majors, companies, equipmentRentalType, equipmentItem, equipmentRentals, files, sponsors, officers, csFieldsEnum, clubLinks, landingQuestions, landingSpotlights } from '@/db/schema';
import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
import { educationLevelEnum, bookmarkedEvents, projects, users, events, subscribedCompanies, majors, companies, equipmentRentalType, equipmentItem, equipmentRentals, files, sponsors, officers, blacklist, csFieldsEnum, clubLinks, landingQuestions, landingSpotlights } from '@/db/schema';

import { z } from 'zod';

Expand Down Expand Up @@ -55,6 +55,8 @@ export const equipmentTypeIdSchema = z.object({
}),
});
export const officerSchema = createSelectSchema(officers);
export const newBlacklistSchema = createInsertSchema(blacklist);
export const blacklistSchema = createSelectSchema(blacklist);
export const clubLinkSchema = createSelectSchema(clubLinks);
export const landingSpotlightSchema = createSelectSchema(landingSpotlights);
export const landingQuestionSchema = createSelectSchema(landingQuestions);

0 comments on commit 360e7ac

Please sign in to comment.