Skip to content

Commit 71f243a

Browse files
authored
Merge pull request #222 from ohamamarachi474-del/fix/providers-and-helmet
fix(security): standardize frontend Providers export and add backend …
2 parents 911c0ea + abea989 commit 71f243a

5 files changed

Lines changed: 47 additions & 92 deletions

File tree

backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"class-validator": "^0.14.2",
4848
"dotenv": "^16.4.7",
4949
"ioredis": "^5.6.1",
50+
"helmet": "^8.0.0",
5051
"ip2location-nodejs": "^9.6.3",
5152
"joi": "^17.0.0",
5253
"multer": "^1.4.5-lts.2",

backend/src/main.ts

Lines changed: 22 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,3 @@
1-
import { Module } from "@nestjs/common"
2-
import { AppController } from "./app.controller"
3-
import { AppService } from "./app.service"
4-
import { TypeOrmModule } from "@nestjs/typeorm"
5-
import { ConfigModule, ConfigService } from "@nestjs/config"
6-
import { AuthModule } from "./auth/auth.module"
7-
import { UserInventoryModule } from "./user-inventory/user-inventory.module"
8-
import { CacheModule } from "./cache/cache.module"
9-
import appConfig from "config/app.config"
10-
import databaseConfig from "config/database.config"
11-
import { PuzzleCategoryModule } from "./puzzle-category/puzzle-category.module"
12-
import { RewardsModule } from "./rewards/rewards.module"
13-
import { PuzzleModule } from "./puzzle/puzzle.module"
14-
import { PuzzleSubmissionModule } from "./puzzle-submission/puzzle-submission.module"
15-
import { ContentModule } from "./content/content.module"
16-
import { UserReportCardModule } from "./user-report-card/user-report-card.module"
17-
import { PuzzleDependencyModule } from "./puzzle-dependency/puzzle-dependency.module"
18-
import { TimeTrialModule } from "./time-trial/time-trial.module"
19-
import { InAppNotificationsModule } from "./in-app-notifications/in-app-notifications.module"
20-
import { User } from "./auth/entities/user.entity"
21-
import { TimeTrial } from "./time-trial/time-trial.entity"
22-
import { Puzzle } from "./puzzle/puzzle.entity"
23-
import { Category } from "./puzzle-category/entities/category.entity"
24-
import { AnalyticsModule } from './analytics/analytics.module';
25-
import { RewardShopModule } from './reward-shop/reward-shop.module';
26-
import { ApiKeyModule } from './api-key/api-key.module';
271
import { NestFactory } from '@nestjs/core';
282
import { Logger, ValidationPipe } from '@nestjs/common';
293
import { ConfigService } from '@nestjs/config';
@@ -37,19 +11,6 @@ async function bootstrap(): Promise<void> {
3711
const app = await NestFactory.create(AppModule);
3812
const configService = app.get<ConfigService>(ConfigService);
3913

40-
// The frontend calls endpoints under the `/api` prefix (see
41-
// frontend/store calls to /api/login, /api/register, etc.), so the global
42-
// prefix is set on the whole Nest app. This also resolves issue #105 which
43-
// expects the /api/users/:userId/history URL shape.
44-
//
45-
// Swagger UI is excluded so /docs, its JSON sibling /docs-json, and its
46-
// nested asset routes (e.g. /docs/swagger-ui-init.js) stay at canonical
47-
// paths instead of being double-prefixed to /api. Nest treats string
48-
// entries as exact paths, so we also pass a RegExp to cover /docs/....
49-
// A single anchored regex covers `docs`, `docs-json`, and any nested
50-
// /docs/<asset> route (e.g. /docs/swagger-ui-init.js). Nest evaluates the
51-
// exclude list against the registered handler path before the global
52-
// prefix is applied.
5314
app.setGlobalPrefix('api', { exclude: [/^docs/] });
5415

5516
app.enableCors({
@@ -71,55 +32,37 @@ async function bootstrap(): Promise<void> {
7132
configService.get<boolean>('appConfig.cors.credentials') ?? true,
7233
});
7334

74-
app.use(helmet({
75-
contentSecurityPolicy: {
76-
directives: {
77-
defaultSrc: ["'self'"],
78-
scriptSrc: ["'self'", "'unsafe-eval'", "'unsafe-inline'"],
79-
styleSrc: ["'self'", "'unsafe-inline'"],
80-
imgSrc: ["'self'", "data:", "blob:", "https:"],
81-
fontSrc: ["'self'"],
82-
connectSrc: ["'self'", "https://soroban-testnet.stellar.org"],
83-
frameAncestors: ["'none'"],
84-
baseUri: ["'self'"],
85-
formAction: ["'self'"],
35+
// Security Headers via Helmet middleware
36+
app.use(
37+
helmet({
38+
contentSecurityPolicy: {
39+
directives: {
40+
defaultSrc: ["'self'"],
41+
scriptSrc: ["'self'", "'unsafe-eval'", "'unsafe-inline'"],
42+
styleSrc: ["'self'", "'unsafe-inline'"],
43+
imgSrc: ["'self'", "data:", "blob:", "https:"],
44+
fontSrc: ["'self'"],
45+
connectSrc: ["'self'", "https://soroban-testnet.stellar.org"],
46+
frameAncestors: ["'none'"],
47+
baseUri: ["'self'"],
48+
formAction: ["'self'"],
49+
},
8650
},
87-
},
88-
hsts: { maxAge: 63072000, includeSubDomains: true, preload: true },
89-
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
90-
}));
51+
hsts: { maxAge: 63072000, includeSubDomains: true, preload: true },
52+
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
53+
}),
54+
);
9155

9256
app.useGlobalPipes(
9357
new ValidationPipe({
9458
whitelist: true,
9559
transform: true,
9660
forbidNonWhitelisted: false,
9761
}),
98-
PuzzleModule,
99-
PuzzleSubmissionModule,
100-
ContentModule,
101-
UserReportCardModule,
102-
PuzzleDependencyModule,
103-
TimeTrialModule,
104-
InAppNotificationsModule,
105-
PuzzleTranslationModule,
106-
NFTClaimModule,
107-
AnalyticsModule,
108-
RewardShopModule,
109-
ApiKeyModule,
110-
UserReactionModule,
111-
MultiplayerQueueModule,
112-
// Redis-backed caching + single-flight for the read-heavy endpoints
113-
// (`/streaks/leaderboard`, `/analytics/puzzles/most-solved`) (#107).
114-
CacheModule,
115-
],
116-
controllers: [AppController],
117-
providers: [AppService],
118-
})
119-
export class AppModule {}
12062
);
12163

122-
const apiVersion = configService.get<string>('appConfig.apiVersion') ?? '1.0';
64+
const apiVersion =
65+
configService.get<string>('appConfig.apiVersion') ?? '1.0';
12366
const swaggerConfig = new DocumentBuilder()
12467
.setTitle('StellarHunts API')
12568
.setDescription('StellarHunts backend REST API documentation.')
@@ -130,10 +73,9 @@ export class AppModule {}
13073
)
13174
.build();
13275
const document = SwaggerModule.createDocument(app, swaggerConfig);
133-
// Excluded from the global prefix above, so this resolves to /docs.
13476
SwaggerModule.setup('docs', app, document);
13577

136-
const port = parseInt(process.env.PORT, 10) || 3001;
78+
const port = parseInt(process.env.PORT || '3001', 10);
13779
await app.listen(port);
13880
logger.log(`StellarHunts API listening on http://localhost:${port}`);
13981
logger.log(`Swagger UI available at http://localhost:${port}/docs`);

frontend/app/layout.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import localFont from "next/font/local";
22
import "./globals.css";
33

44
import StoreProvider from "@/store/StoreProvider";
5-
import Providers from "@/lib/queryClient";
5+
import Providers from "@/lib/Providers";
66
import Navbar from "@/components/Navbar";
77

88
const geistSans = localFont({

frontend/lib/Providers.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"use client";
2+
3+
import React, { useState } from "react";
4+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5+
import { SessionProvider } from "next-auth/react";
6+
7+
export default function Providers({ children, session }) {
8+
const [queryClient] = useState(() => new QueryClient());
9+
10+
return (
11+
<SessionProvider session={session}>
12+
<QueryClientProvider client={queryClient}>
13+
{children}
14+
</QueryClientProvider>
15+
</SessionProvider>
16+
);
17+
}

frontend/lib/queryClient.js

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
"use client";
2-
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
3-
import { useState } from "react";
42

5-
export default function Providers({ children }) {
6-
const [queryClient] = useState(() => new QueryClient());
7-
8-
return (
9-
<QueryClientProvider client={queryClient}>
10-
{children}
11-
</QueryClientProvider>
12-
);
13-
}
3+
import Providers from "./Providers";
4+
import { QueryClient } from "@tanstack/react-query";
5+
6+
export const queryClient = new QueryClient();
7+
export { Providers };
8+
export default Providers;

0 commit comments

Comments
 (0)