feat: safer environment variables, updated packages

This commit is contained in:
Hackntosh 2024-09-28 00:05:00 +01:00
parent 2eaa2294da
commit 47b50415ea
11 changed files with 3565 additions and 2215 deletions

View file

@ -29,6 +29,7 @@ $ npm run docker:db
```
This will start the following services:
- **PostgreSQL**
- **Redis**
- **MinIO**
@ -58,11 +59,10 @@ This will start all the previous services and the back-end image.
## 🗄️ Stack
This back-end uses the following stack:
- **Docker**
- **Fastify**
- **MinIO**
- **NestJS**
- **Passport**
- **PostgreSQL**
- **Prisma**
- **Redis**

5619
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -43,6 +43,8 @@
"@nestjs/throttler": "^5.1.1",
"@prisma/client": "^5.9.1",
"bcrypt": "^5.1.1",
"dotenv": "^16.4.5",
"dotenv-expand": "^11.0.6",
"file-type": "^19.0.0",
"ioredis": "^5.3.2",
"nestjs-s3": "^2.0.1",
@ -53,7 +55,8 @@
"passport-local": "^1.0.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"sharp": "^0.33.2"
"sharp": "^0.33.2",
"tstl": "^3.0.0"
},
"devDependencies": {
"@biomejs/biome": "1.5.3",

View file

@ -8,6 +8,7 @@ import { ThrottlerStorageRedisService } from "nestjs-throttler-storage-redis";
import { ZodValidationPipe } from "nestjs-zod";
import { AuthModule } from "./auth/auth.module";
import { JwtAuthGuard } from "./auth/jwt-auth.guard";
import { Configuration } from "./configuration";
import { KweeksModule } from "./kweeks/kweeks.module";
import { UserModule } from "./users/users.module";
@ -20,20 +21,18 @@ import { UserModule } from "./users/users.module";
}),
ThrottlerModule.forRoot({
throttlers: [{ limit: 10, ttl: 60000 }],
storage: new ThrottlerStorageRedisService(
`redis://:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}/0`,
),
storage: new ThrottlerStorageRedisService(Configuration.REDIS_URL()),
}),
KweeksModule,
FastifyMulterModule,
S3Module.forRoot({
config: {
credentials: {
accessKeyId: process.env.MINIO_ROOT_USER, // CHANGE WHEN PRODUCTION TO S3
secretAccessKey: process.env.MINIO_ROOT_PASSWORD,
accessKeyId: Configuration.MINIO_ROOT_USER(),
secretAccessKey: Configuration.MINIO_ROOT_PASSWORD(),
},
region: "us-east-1",
endpoint: process.env.MINIO_ENDPOINT,
endpoint: Configuration.MINIO_ENDPOINT(),
forcePathStyle: true,
},
}),

View file

@ -1,6 +1,7 @@
import { Module } from "@nestjs/common";
import { JwtModule } from "@nestjs/jwt";
import { PassportModule } from "@nestjs/passport";
import { Configuration } from "src/configuration";
import { UserModule } from "src/users/users.module";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
@ -13,7 +14,7 @@ import { LocalStrategy } from "./local.strategy";
UserModule,
PassportModule,
JwtModule.register({
secret: process.env.JWT_ACCESS_SECRET,
secret: Configuration.JWT_ACCESS_SECRET(),
signOptions: { expiresIn: "1d" }, // TODO: add refresh tokens
}),
],

View file

@ -1,6 +1,7 @@
import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { ExtractJwt, Strategy } from "passport-jwt";
import { Configuration } from "src/configuration";
type Payload = {
displayName: string;
@ -14,7 +15,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_ACCESS_SECRET,
secretOrKey: Configuration.JWT_ACCESS_SECRET(),
});
}

14
src/configuration.ts Normal file
View file

@ -0,0 +1,14 @@
import { Environment } from "./environment";
export namespace Configuration {
export const NODE_ENV = () => Environment.env.NODE_ENV;
export const SERVER_HOST = () => Environment.env.SERVER_HOST;
export const SERVER_PORT = () => Environment.env.SERVER_PORT;
export const JWT_ACCESS_SECRET = () => Environment.env.JWT_ACCESS_SECRET;
export const REDIS_URL = () => Environment.env.REDIS_URL;
export const MINIO_ROOT_USER = () => Environment.env.MINIO_ROOT_USER;
export const MINIO_ROOT_PASSWORD = () => Environment.env.MINIO_ROOT_PASSWORD;
export const MINIO_DEFAULT_BUCKETS = () =>
Environment.env.MINIO_DEFAULT_BUCKETS;
export const MINIO_ENDPOINT = () => Environment.env.MINIO_ENDPOINT;
}

78
src/environment.ts Normal file
View file

@ -0,0 +1,78 @@
import dotenv from "dotenv";
import dotEnvExpand from "dotenv-expand";
import { Singleton } from "tstl";
import { z } from "nestjs-zod/z";
/**
* Global variables of the server.
*/
export class Environment {
public static get env(): IEnvironment {
return environments.get();
}
public static get node_env(): NodeEnv {
if (nodeEnvWrapper.value === undefined || nodeEnvWrapper.value === null) {
nodeEnvWrapper.value = environments.get().NODE_ENV;
}
return nodeEnvWrapper.value;
}
public setMode(mode: NodeEnv): void {
if (!["dev", "prod"].includes(mode)) {
throw new Error("Invalid NODE_ENV value, expected 'dev' or 'prod'");
}
nodeEnvWrapper.value = mode;
}
}
const EnvironmentSchema = z.object({
NODE_ENV: z.enum(["dev", "prod"]),
POSTGRES_HOST: z.string(),
POSTGRES_DB: z.string(),
POSTGRES_USER: z.string(),
POSTGRES_PASSWORD: z.string(),
POSTGRES_PORT: z.string().regex(/^[0-9]+$/),
DATABASE_URL: z.string(),
REDIS_HOST: z.string(),
REDIS_PORT: z.string().regex(/^[0-9]+$/),
REDIS_PASSWORD: z.string(),
REDIS_URL: z.string(),
SERVER_PORT: z.string().regex(/^[0-9]+$/),
SERVER_HOST: z.string(),
JWT_ACCESS_SECRET: z.string(),
MINIO_ROOT_USER: z.string(),
MINIO_ROOT_PASSWORD: z.string(),
MINIO_DEFAULT_BUCKETS: z.string(),
MINIO_ENDPOINT: z.string(),
});
type IEnvironment = z.infer<typeof EnvironmentSchema>;
type NodeEnv = "dev" | "prod";
interface INodeEnv {
value?: NodeEnv;
}
const nodeEnvWrapper: INodeEnv = {};
const environments = new Singleton(() => {
const env = dotenv.config();
dotEnvExpand.expand(env);
const parsedEnv = EnvironmentSchema.safeParse(process.env);
if (!parsedEnv.success) {
const errors = parsedEnv.error.format();
throw new Error(`Environment validation failed: ${JSON.stringify(errors)}`);
}
return parsedEnv.data;
});

View file

@ -1,4 +1,4 @@
import * as helmet from "@fastify/helmet";
import helmet from "@fastify/helmet";
import { NestFactory } from "@nestjs/core";
import {
FastifyAdapter,
@ -7,6 +7,7 @@ import {
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
import { patchNestJsSwagger } from "nestjs-zod";
import { AppModule } from "./app.module";
import { Configuration } from "./configuration";
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
@ -44,6 +45,6 @@ async function bootstrap() {
await app.register(helmet);
await app.listen(process.env.SERVER_PORT, process.env.SERVER_HOST);
await app.listen(Configuration.SERVER_PORT(), Configuration.SERVER_HOST);
}
bootstrap();

View file

@ -3,6 +3,7 @@ import { File } from "@nest-lab/fastify-multer";
import { Injectable, InternalServerErrorException } from "@nestjs/common";
import { InjectS3, S3 } from "nestjs-s3";
import sharp from "sharp";
import { Configuration } from "src/configuration";
@Injectable()
export class S3Service {
@ -18,7 +19,7 @@ export class S3Service {
.toBuffer();
const params: PutObjectCommandInput = {
Bucket: process.env.MINIO_DEFAULT_BUCKETS,
Bucket: Configuration.MINIO_DEFAULT_BUCKETS(),
Key: `profile_images/${userID}.webp`,
Body: compressedBuffer,
ContentType: "image/webp",
@ -29,7 +30,7 @@ export class S3Service {
const { ETag } = await this.s3.send(new PutObjectCommand(params));
if (ETag !== null) {
return `${process.env.MINIO_ENDPOINT}/${process.env.MINIO_DEFAULT_BUCKETS}/profile_images/${userID}.webp`;
return `${Configuration.MINIO_ENDPOINT}/${Configuration.MINIO_DEFAULT_BUCKETS}/profile_images/${userID}.webp`;
}
throw new InternalServerErrorException(
@ -64,13 +65,13 @@ export class S3Service {
const Key = `posts/${id}/${index}.webp`;
const params: PutObjectCommandInput = {
Bucket: process.env.MINIO_DEFAULT_BUCKETS,
Bucket: Configuration.MINIO_DEFAULT_BUCKETS(),
Key,
Body: buffer,
ContentType: "image/webp",
};
await this.s3.send(new PutObjectCommand(params));
return `${process.env.MINIO_ENDPOINT}/${process.env.MINIO_DEFAULT_BUCKETS}/${Key}`;
return `${Configuration.MINIO_ENDPOINT}/${Configuration.MINIO_DEFAULT_BUCKETS}/${Key}`;
}
}

View file

@ -2,6 +2,7 @@
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"esModuleInterop": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,