Skip to content

Commit ced6a13

Browse files
inteeenottherealalanturingzakkiyyatdevdeen213rmsb-art
authored
[B2/B3/B4a] Remove any-casts, add export download rate limit (#832)
* feat: analytics date range validation, avatar filename regex guard, e2e tests * feat: inject ConfigService into password.utils, move getPasswordHistoryLimit into AuthService, add tests * feat: export file 24h expiry with admin revoke, audit logging, reactivate error handling * fix: remove (user as any) casts across controllers, add rate limit on export download * fix: update test file to use ConfigService mock instead of process.env * fix: pass ipAddress/userAgent to preflightChecks, cap date ranges instead of throw --------- Co-authored-by: nottherealalanturing <nottherealalanturing@users.noreply.github.com> Co-authored-by: zakkiyyat <zakkiyyat@users.noreply.github.com> Co-authored-by: devdeen213 <devdeen213@users.noreply.github.com> Co-authored-by: inteee <inteee@users.noreply.github.com> Co-authored-by: rmsb-art <ramatshuaibu@gmail.com> Co-authored-by: nanaf6203-bit <nanaf6203@gmail.com>
1 parent e65ce5b commit ced6a13

4 files changed

Lines changed: 25 additions & 15 deletions

File tree

src/favorites/favorites.controller.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// @ts-nocheck
2-
31
import {
42
Controller,
53
Delete,

src/transactions/transaction-notes.service.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// @ts-nocheck
2-
31
import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
42
import { PrismaService } from '../database/prisma.service';
53
import { CreateNoteDto } from './dto/transaction-note.dto';
@@ -12,7 +10,7 @@ export class TransactionNotesService {
1210
const tx = await this.prisma.transaction.findUnique({ where: { id: transactionId } });
1311
if (!tx) throw new NotFoundException('Transaction not found');
1412

15-
return (this.prisma as any).transactionNote.create({
13+
return this.prisma.transactionNote.create({
1614
data: {
1715
transactionId,
1816
authorId,
@@ -31,15 +29,12 @@ export class TransactionNotesService {
3129

3230
const where: any = { transactionId };
3331
if (!isPrivileged && !isParty) {
34-
// Non-party/non-admin can only see public notes authored by themselves
3532
where.isPublic = true;
3633
} else if (!isPrivileged) {
37-
// Transaction parties see public notes and their own private notes
3834
where.OR = [{ isPublic: true }, { authorId: viewerId }];
3935
}
40-
// Admins/agents see all notes
4136

42-
return (this.prisma as any).transactionNote.findMany({
37+
return this.prisma.transactionNote.findMany({
4338
where,
4439
orderBy: { createdAt: 'asc' },
4540
include: {
@@ -49,14 +44,14 @@ export class TransactionNotesService {
4944
}
5045

5146
async remove(noteId: string, requesterId: string, requesterRole: string) {
52-
const note = await (this.prisma as any).transactionNote.findUnique({ where: { id: noteId } });
47+
const note = await this.prisma.transactionNote.findUnique({ where: { id: noteId } });
5348
if (!note) throw new NotFoundException('Note not found');
5449

5550
const isPrivileged = requesterRole === 'ADMIN';
5651
if (note.authorId !== requesterId && !isPrivileged) {
5752
throw new ForbiddenException('Only the author or admin can delete this note');
5853
}
5954

60-
return (this.prisma as any).transactionNote.delete({ where: { id: noteId } });
55+
return this.prisma.transactionNote.delete({ where: { id: noteId } });
6156
}
6257
}

src/transactions/transactions.controller.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// @ts-nocheck
2-
31
import {
42
Controller,
53
Delete,

src/users/users.controller.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// @ts-nocheck
2-
31
import {
42
Body,
53
Controller,
@@ -44,6 +42,10 @@ const UNAUTHORIZED_ACTION_MESSAGE = 'You are not authorized to perform this acti
4442

4543
@Controller('users')
4644
export class UsersController {
45+
private readonly downloadRateLimitMap = new Map<string, { count: number; resetAt: number }>();
46+
private static readonly DOWNLOAD_LIMIT = 10;
47+
private static readonly DOWNLOAD_WINDOW_MS = 60 * 60 * 1000;
48+
4749
constructor(
4850
private readonly usersService: UsersService,
4951
private readonly activityLogService: ActivityLogService,
@@ -167,6 +169,23 @@ export class UsersController {
167169
@Res() res: Response,
168170
@CurrentUser() user: AuthUserPayload,
169171
) {
172+
const now = Date.now();
173+
const entry = this.downloadRateLimitMap.get(user.sub);
174+
if (entry && now < entry.resetAt) {
175+
if (entry.count >= UsersController.DOWNLOAD_LIMIT) {
176+
throw new HttpException(
177+
'Too many export downloads. Please try again later.',
178+
HttpStatus.TOO_MANY_REQUESTS,
179+
);
180+
}
181+
entry.count++;
182+
} else {
183+
this.downloadRateLimitMap.set(user.sub, {
184+
count: 1,
185+
resetAt: now + UsersController.DOWNLOAD_WINDOW_MS,
186+
});
187+
}
188+
170189
const filepath = path.join(process.cwd(), 'exports', filename);
171190

172191
if (!fs.existsSync(filepath)) {

0 commit comments

Comments
 (0)