Skip to content

Commit a4ce54a

Browse files
committed
feat: add new middleware to prevent site interaction from locked-out users
1 parent a65d918 commit a4ce54a

5 files changed

Lines changed: 93 additions & 44 deletions

File tree

backend/src/middleware/isAdmin.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
import { type TokenPayload } from 'treasure-trove-shared';
22
import { User } from '../db/models/user.ts';
3-
import { requireAuth } from './jwt.ts';
4-
import type { Request, Response, NextFunction } from 'express';
5-
6-
// Extend Express Request type to include `auth`
7-
export interface AuthenticatedRequest extends Request {
8-
auth?: TokenPayload;
9-
}
3+
import type { Response, NextFunction } from 'express';
4+
import type { AuthenticatedRequest } from './types.ts';
5+
import { userFullAuth } from './userAuth.ts';
106

117
export async function checkAdmin(
128
auth: TokenPayload | undefined,
@@ -25,7 +21,7 @@ export async function checkAdmin(
2521

2622
export const isAdmin = [
2723
// no redundant code...admin is a layer on top of JWT auth
28-
requireAuth,
24+
userFullAuth,
2925
async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
3026
try {
3127
const valid = await checkAdmin(req.auth);

backend/src/middleware/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { TokenPayload } from 'treasure-trove-shared';
2+
import type { Request } from 'express';
3+
4+
// Extend Express Request type to include `auth`
5+
export interface AuthenticatedRequest extends Request {
6+
auth?: TokenPayload;
7+
}

backend/src/middleware/userAuth.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { Response, NextFunction } from 'express';
2+
import { User } from '../db/models/user.ts';
3+
import { type TokenPayload } from 'treasure-trove-shared';
4+
import { requireAuth } from './jwt.ts';
5+
import type { AuthenticatedRequest } from './types.ts';
6+
7+
async function checkIsLocked(auth: TokenPayload | undefined): Promise<number> {
8+
// First verify token has proper parameters.
9+
if (!auth) return 404;
10+
if (auth!.sub === undefined) return 403;
11+
12+
// If it does, make sure the user is not locked out of the platform.
13+
const user = await User.findById(auth!.sub);
14+
if (!user) return 404;
15+
if (user.locked) return 403;
16+
17+
return 200;
18+
}
19+
20+
export const userFullAuth = [
21+
requireAuth,
22+
async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
23+
try {
24+
const valid = await checkIsLocked(req.auth);
25+
switch (valid) {
26+
case 404:
27+
return res.status(404).json({ error: 'User not found' });
28+
case 403:
29+
return res
30+
.status(403)
31+
.json({ error: 'Access denied: user is locked out' });
32+
case 200:
33+
next();
34+
}
35+
} catch (err) {
36+
console.error('User-lock check failed:', err);
37+
return res.status(500).json({ error: 'Internal server error' });
38+
}
39+
},
40+
];

backend/src/routes/auctions.ts

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import express from 'express';
22
import type { Request, Response } from 'express';
3-
import { requireAuth } from '../middleware/jwt.ts';
43
import AuctionsService from '../services/auctions.ts';
54
import {
65
createAuctionSchema,
76
createBidSchema,
87
updateAuctionSchema,
98
} from 'treasure-trove-shared';
109
import BidsService from '../services/bids.ts';
10+
import { userFullAuth } from 'src/middleware/userAuth.ts';
1111

1212
const auctionsRouter = express.Router();
1313

1414
// GET information about all auctions.
15-
auctionsRouter.get('/', requireAuth, async (_req: Request, res: Response) => {
15+
auctionsRouter.get('/', userFullAuth, async (_req: Request, res: Response) => {
1616
try {
1717
const auctions = await AuctionsService.getAllAuctions();
1818
res.status(200).json(auctions);
@@ -23,18 +23,22 @@ auctionsRouter.get('/', requireAuth, async (_req: Request, res: Response) => {
2323
});
2424

2525
// GET information about the auction with the given ID.
26-
auctionsRouter.get('/:id', requireAuth, async (req: Request, res: Response) => {
27-
try {
28-
const auctionInfo = await AuctionsService.getAuctionById(req.params.id);
29-
return res.status(200).json(auctionInfo);
30-
} catch (error) {
31-
console.error('Error fetching auction info:', error);
32-
return res.status(404).json({ error: 'Auction not found' });
33-
}
34-
});
26+
auctionsRouter.get(
27+
'/:id',
28+
userFullAuth,
29+
async (req: Request, res: Response) => {
30+
try {
31+
const auctionInfo = await AuctionsService.getAuctionById(req.params.id);
32+
return res.status(200).json(auctionInfo);
33+
} catch (error) {
34+
console.error('Error fetching auction info:', error);
35+
return res.status(404).json({ error: 'Auction not found' });
36+
}
37+
},
38+
);
3539

3640
// POST endpoint to create a new auction.
37-
auctionsRouter.post('/', requireAuth, async (req: Request, res: Response) => {
41+
auctionsRouter.post('/', userFullAuth, async (req: Request, res: Response) => {
3842
try {
3943
const validatedBody = createAuctionSchema.validateSync(req.body);
4044
const auction = await AuctionsService.createAuction(validatedBody);
@@ -49,25 +53,29 @@ auctionsRouter.post('/', requireAuth, async (req: Request, res: Response) => {
4953
});
5054

5155
// PUT endpoint to update information about an existing auction.
52-
auctionsRouter.put('/:id', requireAuth, async (req: Request, res: Response) => {
53-
try {
54-
const validatedBody = updateAuctionSchema.validateSync(req.body);
55-
const auctionInfo = await AuctionsService.updateAuction(
56-
req.params.id,
57-
validatedBody,
58-
);
59-
return res.status(200).json(auctionInfo);
60-
} catch (error) {
61-
console.error('Error updating auction:', error);
62-
// Use 400 when there is a bad request for some reason.
63-
return res.status(400).json({ error: 'failed to update auction ' });
64-
}
65-
});
56+
auctionsRouter.put(
57+
'/:id',
58+
userFullAuth,
59+
async (req: Request, res: Response) => {
60+
try {
61+
const validatedBody = updateAuctionSchema.validateSync(req.body);
62+
const auctionInfo = await AuctionsService.updateAuction(
63+
req.params.id,
64+
validatedBody,
65+
);
66+
return res.status(200).json(auctionInfo);
67+
} catch (error) {
68+
console.error('Error updating auction:', error);
69+
// Use 400 when there is a bad request for some reason.
70+
return res.status(400).json({ error: 'failed to update auction ' });
71+
}
72+
},
73+
);
6674

6775
// GET all the bids associated with a given auction.
6876
auctionsRouter.get(
6977
'/:id/bids',
70-
requireAuth,
78+
userFullAuth,
7179
async (req: Request, res: Response) => {
7280
try {
7381
const bidsInfo = await BidsService.getAuctionBids(req.params.id);
@@ -82,7 +90,7 @@ auctionsRouter.get(
8290
// POST endpoint to create a new bid for the given auction.
8391
auctionsRouter.post(
8492
'/:id/bids',
85-
requireAuth,
93+
userFullAuth,
8694
async (req: Request, res: Response) => {
8795
try {
8896
const validatedBody = createBidSchema.validateSync(req.body);
@@ -101,7 +109,7 @@ auctionsRouter.post(
101109
// DELETE an auction.
102110
auctionsRouter.delete(
103111
'/:id',
104-
requireAuth,
112+
userFullAuth,
105113
async (req: Request, res: Response) => {
106114
try {
107115
await AuctionsService.deleteAuction(req.params.id);

backend/src/routes/users.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
import express from 'express';
22
import type { Request, Response } from 'express';
33
import UsersService from '../services/users.ts';
4-
import { requireAuth } from '../middleware/jwt.ts';
54
import {
65
fullUserInfoSchema,
76
regularUserInfoSchema,
87
userCredentialsSchema,
98
} from 'treasure-trove-shared';
10-
import {
11-
type AuthenticatedRequest,
12-
checkAdmin,
13-
} from '../middleware/isAdmin.ts';
9+
import { checkAdmin } from '../middleware/isAdmin.ts';
10+
import { userFullAuth } from '../middleware/userAuth.ts';
11+
import { type AuthenticatedRequest } from '../middleware/types.ts';
1412

1513
const usersRouter = express.Router();
1614

@@ -59,7 +57,7 @@ usersRouter.post('/signup', async (req: Request, res: Response) => {
5957
// Admin-only fields like lock status will only be returned with admin authentication.
6058
usersRouter.get(
6159
'/:id',
62-
requireAuth,
60+
userFullAuth,
6361
async (req: AuthenticatedRequest, res: Response) => {
6462
try {
6563
// Check if the user's token has admin privileges.
@@ -88,7 +86,7 @@ usersRouter.get(
8886
// Changing fields like lock status requires admin authentication.
8987
usersRouter.put(
9088
'/:id',
91-
requireAuth,
89+
userFullAuth,
9290
async (req: AuthenticatedRequest, res: Response) => {
9391
try {
9492
// Check if the user's token has admin privileges.

0 commit comments

Comments
 (0)