Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@
"Bash(find /home/ljtwp/Desktop/drips/niff -type f -name *.sol -o -name *.rs -o -name *.ts -o -name *.tsx -o -name *.js -o -name *.jsx)",
"Bash(cargo test:*)",
"Bash(cargo fmt:*)",
"Bash(cargo clippy:*)"
"Bash(cargo clippy:*)",
"Bash(npm install:*)",
"Bash(node -e \"const { rpc } = require\\(''''@stellar/stellar-sdk''''\\); console.log\\(Object.keys\\(rpc\\)\\);\")",
"Bash(grep -r ThemeProvider /home/ljtwp/Desktop/drips/niff/frontend/src/ --include=*.tsx --include=*.ts -l)",
"Bash(grep -n \"toast\\\\|useToast\" /home/ljtwp/Desktop/drips/niff/frontend/src/app/policy/[id]/claim/page.tsx)",
"Bash(git -C /home/ljtwp/Desktop/drips/niff add frontend/package-lock.json)",
"Bash(git -C /home/ljtwp/Desktop/drips/niff status --short)",
"Bash(npm audit:*)",
"Bash(echo \"EXIT:$?\")"
]
}
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ build/
*.tsbuildinfo
pnpm-lock.yaml
package-lock.json
!frontend/package-lock.json

# Env / secrets
.env
Expand Down
63 changes: 53 additions & 10 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion backend/specs/claim-rate-limiting/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ The implementation uses Redis for O(1) counter operations, NestJS guards for req
- Log initialization status
- _Requirements: 6.1, 6.2, 6.3_

- [x] 12. Add OpenAPI documentation
- [ ] 12. Add OpenAPI documentation
- [x] 12.1 Document rate limit responses
- Add @ApiResponse decorators for 429 status to claim endpoints
- Document RateLimitException response schema
Expand Down
14 changes: 7 additions & 7 deletions backend/src/__tests__/cors.property.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ describe("Feature: cors-helmet-security-headers, Property 2", () => {
// Should echo the exact origin string (not true, not false)
return (
calledWith !== null &&
(calledWith as any).err === null &&
(calledWith as any).allow === origin
(calledWith as { err: unknown; allow: unknown }).err === null &&
(calledWith as { err: unknown; allow: unknown }).allow === origin
);
},
),
Expand Down Expand Up @@ -141,8 +141,8 @@ describe("Feature: cors-helmet-security-headers, Property 3", () => {
// Should call cb with an Error and false
return (
calledWith !== null &&
(calledWith as any).err instanceof Error &&
(calledWith as any).allow === false
(calledWith as { err: unknown; allow: unknown }).err instanceof Error &&
(calledWith as { err: unknown; allow: unknown }).allow === false
);
},
),
Expand All @@ -159,7 +159,7 @@ describe("Feature: cors-helmet-security-headers, Property 4", () => {
* Validates: Requirements 2.4
*/
fc.assert(
fc.property(fc.constant(null), (_) => {
fc.property(fc.constant(null), () => {
return CORS_CONFIG.credentials === true;
}),
{ numRuns: 100 },
Expand All @@ -175,7 +175,7 @@ describe("Feature: cors-helmet-security-headers, Property 5", () => {
* Validates: Requirements 2.6, 2.7
*/
fc.assert(
fc.property(fc.constant(null), (_) => {
fc.property(fc.constant(null), () => {
const requiredHeaders = [
"Authorization",
"Content-Type",
Expand Down Expand Up @@ -214,7 +214,7 @@ describe("Feature: cors-helmet-security-headers, Property 6", () => {
fc.assert(
fc.property(
fc.array(fc.string({ minLength: 1 }), { minLength: 1 }),
(allowlist) => {
() => {
return (
CORS_CONFIG.maxAge === 86400 &&
CORS_CONFIG.optionsSuccessStatus === 204 &&
Expand Down
2 changes: 1 addition & 1 deletion backend/src/__tests__/cors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function buildTestApp(frontendOrigins: string[], adminOrigins: string[] = []) {
origin: (origin, cb) => {
if (!origin) return cb(null, true);
const allowed = [...frontendOrigins, ...adminOrigins];
if (allowed.includes(origin)) return cb(null, origin as any);
if (allowed.includes(origin)) return cb(null, origin);
return cb(new Error("Not allowed by CORS"));
},
credentials: true,
Expand Down
2 changes: 1 addition & 1 deletion backend/src/admin/admin.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export class AdminController {
@HttpCode(HttpStatus.ACCEPTED)
@ApiOperation({ summary: 'Submit a privacy request (anonymize or delete off-chain data)' })
async submitPrivacyRequest(@Body() dto: PrivacyRequestDto, @Req() req: Request) {
const actor = (req.user as any)?.walletAddress ?? 'unknown';
const actor = (req.user as { walletAddress?: string })?.walletAddress ?? 'unknown';
return this.privacyService.handleRequest({
subjectWalletAddress: dto.subjectWalletAddress,
requestType: dto.requestType,
Expand Down
4 changes: 4 additions & 0 deletions backend/src/cache/redis.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export class RedisService implements OnModuleDestroy {
await this.client.quit();
}

getClient(): Redis {
return this.client;
}

/**
* Get cached value
*/
Expand Down
1 change: 1 addition & 0 deletions backend/src/claims/claims.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { SubmitTransactionDto } from './dto/submit-transaction.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { WalletAddress } from '../auth/decorators/wallet-address.decorator';
import { RateLimitGuard } from '../rate-limit/rate-limit.guard';
import { MAX_LIMIT, DEFAULT_LIMIT } from '../helpers/pagination';

@ApiTags('claims')
@Controller('claims')
Expand Down
3 changes: 3 additions & 0 deletions backend/src/claims/claims.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Prisma } from '@prisma/client';
import { SorobanService } from '../rpc/soroban.service';
import { PrismaService } from '../prisma/prisma.service';
import { RedisService } from '../cache/redis.service';
import { SanitizationService } from './sanitization.service';
import {
ClaimDetailResponseDto,
ClaimMetadataDto,
Expand Down
10 changes: 5 additions & 5 deletions backend/src/claims/dto/build-claim-transaction.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,37 +30,37 @@ export class BuildClaimTransactionDto {
@Matches(/^G[A-Z2-7]{55}$/, {
message: 'holder must be a valid Stellar public key (G...)',
})
holder: string;
holder!: string;

@ApiProperty({
description: 'The ID of the policy to claim against.',
example: 1,
})
@IsInt()
@IsPositive()
policyId: number;
policyId!: number;

@ApiProperty({
description: 'Claim amount in stroops as an integer string.',
example: '500000000',
})
@IsString()
@Validate(PositiveIntStringConstraint)
amount: string;
amount!: string;

@ApiProperty({
description: 'Narrative description of the claim.',
example: 'Water damage in the kitchen due to pipe burst.',
})
@IsString()
@MaxLength(1000)
details: string;
details!: string;

@ApiProperty({
description: 'List of IPFS URLs (or CIDs) for evidence images.',
example: ['https://ipfs.io/ipfs/Qm...'],
})
@IsArray()
@IsString({ each: true })
imageUrls: string[];
imageUrls!: string[];
}
4 changes: 2 additions & 2 deletions backend/src/claims/dto/submit-transaction.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ export class SubmitTransactionDto {
})
@IsString()
@IsNotEmpty()
transactionXdr: string;
transactionXdr!: string;

@ApiProperty({
description: 'Policy ID for rate limiting (format: holderAddress:policyId)',
example: 'GABC...123:1',
})
@IsString()
@IsNotEmpty()
policyId: string;
policyId!: string;
}
35 changes: 35 additions & 0 deletions backend/src/helpers/pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@
import { createHmac } from 'crypto';
import { BadRequestException } from '@nestjs/common';

export class CursorError extends BadRequestException {
constructor(message: string) {
super(message);
this.name = 'CursorError';
}
}

// ---------------------------------------------------------------------------
// Configuration
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -215,6 +222,34 @@ export function buildKeysetWhere(after?: string): KeysetWhere | undefined {
};
}

/**
* Paginates an in-memory array using opaque offset-encoded cursors.
* Compatible with the PageParams / CursorPageResult contract used by list endpoints.
*/
export function paginate<T>(items: T[], params: PageParams): CursorPageResult<T> {
const limit = clampLimit(params.limit);
const total = items.length;

let startIndex = 0;
if (params.after) {
const decoded = Buffer.from(params.after, 'base64url').toString('utf8');
const offset = Number(decoded);
if (!Number.isInteger(offset) || offset < 0) {
throw new CursorError(`Invalid cursor: "${params.after}"`);
}
startIndex = offset;
}

const data = items.slice(startIndex, startIndex + limit);
const nextOffset = startIndex + data.length;
const next_cursor =
data.length === limit && nextOffset < total
? Buffer.from(String(nextOffset), 'utf8').toString('base64url')
: null;

return { data, next_cursor, total };
}

/**
* Builds the next_cursor from the last item in a page result.
* Returns `null` when the page is the last one.
Expand Down
3 changes: 2 additions & 1 deletion backend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { HttpExceptionFilter } from "./common/filters/http-exception.filter";
import helmet from "helmet";
import { ConfigService } from "@nestjs/config";
import { LoggerMiddleware } from "./common/middleware/logger.middleware";
import type { Request, Response, NextFunction } from "express";

export function parseOrigins(raw: string): string[] {
return raw
Expand Down Expand Up @@ -40,7 +41,7 @@ async function bootstrap() {
}),
);
// Permissions-Policy — helmet 7 does not include a built-in helper
app.use((_req: any, res: any, next: any) => {
app.use((_req: Request, res: Response, next: NextFunction) => {
res.setHeader(
"Permissions-Policy",
"camera=(), microphone=(), geolocation=()",
Expand Down
6 changes: 0 additions & 6 deletions backend/src/maintenance/privacy.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@ import { AuditService } from '../admin/audit.service';

export type PrivacyRequestType = 'ANONYMIZE' | 'DELETE';

const ANON = {
email: '[redacted]',
fullName: '[redacted]',
phone: '[redacted]',
} as const;

@Injectable()
export class PrivacyService {
private readonly logger = new Logger(PrivacyService.name);
Expand Down
Loading
Loading