diff --git a/package.json b/package.json index dfa4283..34fbe49 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@prisma/client": "^4.13.0", "bcrypt": "^5.1.0", "dotenv": "^16.0.3", + "envalid": "^7.3.1", "fastify": "^4.12.0", "fluent-json-schema": "^4.0.0", "jsonwebtoken": "^9.0.0", diff --git a/src/Server.ts b/src/Server.ts index 45e7b71..b160621 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -1,10 +1,10 @@ import fastify from 'fastify'; import type { FastifyCookieOptions } from '@fastify/cookie'; -import { COOKIE_SECRET, CORS_WHITE_LIST, ENVIRONMENT, loggerConfig, swaggerConfig, swaggerUIConfig } from '@configs'; +import { CORS_WHITE_LIST, envs, loggerConfig, swaggerConfig, swaggerUIConfig } from '@configs'; import { apiPlugin, authPlugin } from './plugins'; export function createServer(config: ServerConfig) { - const app = fastify({ logger: loggerConfig[ENVIRONMENT] }); + const app = fastify({ logger: loggerConfig[envs.NODE_ENV] }); global.logger = app.log; app.register(import('@fastify/sensible')); @@ -14,12 +14,12 @@ export function createServer(config: ServerConfig) { }); app.register(import('@fastify/cookie'), { - secret: COOKIE_SECRET, // for cookies signature + secret: envs.COOKIE_SECRET, // for cookies signature hook: 'onRequest' } as FastifyCookieOptions); // Swagger on production will be turned off in the future - if (ENVIRONMENT === 'development' || ENVIRONMENT === 'staging' || ENVIRONMENT === 'production') { + if (envs.NODE_ENV === 'development' || envs.NODE_ENV === 'staging' || envs.NODE_ENV === 'production') { app.register(import('@fastify/swagger'), swaggerConfig); app.register(import('@fastify/swagger-ui'), swaggerUIConfig); } diff --git a/src/configs/env.ts b/src/configs/env.ts index 22b388d..0e9617a 100644 --- a/src/configs/env.ts +++ b/src/configs/env.ts @@ -1,7 +1,16 @@ -import * as dotenv from 'dotenv'; -dotenv.config(); +import { config as configEnv } from 'dotenv'; +import { str, cleanEnv } from 'envalid'; -export const ENVIRONMENT = !process.env.NODE_ENV ? 'development' : (process.env.NODE_ENV as Environment); -export const JWT_SECRET = process.env.JWT_SECRET as string; -export const COOKIE_SECRET = process.env.COOKIE_SECRET as string; -export const CORS_WHITE_LIST = process.env.CORS_WHITE_LIST === undefined ? [] : process.env.CORS_WHITE_LIST.split(','); +configEnv(); + +export const envs = cleanEnv(process.env, { + NODE_ENV: str({ + devDefault: 'development', + choices: ['development', 'test', 'production', 'staging'] + }), + JWT_SECRET: str(), + COOKIE_SECRET: str(), + CORS_WHITE_LIST: str() +}); + +export const CORS_WHITE_LIST = envs.CORS_WHITE_LIST.split(','); diff --git a/src/constants/cookie.ts b/src/constants/cookie.ts index 5b450de..b47e56b 100644 --- a/src/constants/cookie.ts +++ b/src/constants/cookie.ts @@ -1,8 +1,8 @@ -import { ENVIRONMENT } from '@configs'; +import { envs } from '@configs'; export const cookieOptions = { signed: false, - secure: ENVIRONMENT === 'production', + secure: envs.isProduction, path: '/', httpOnly: true }; diff --git a/src/handlers/auth.handler.ts b/src/handlers/auth.handler.ts index 9678cac..6a90a6e 100644 --- a/src/handlers/auth.handler.ts +++ b/src/handlers/auth.handler.ts @@ -3,7 +3,7 @@ import { compare, hash } from 'bcrypt'; import { prisma } from '@repositories'; import { cookieOptions, DUPLICATED_EMAIL, LOGIN_FAIL, SALT_ROUNDS, USER_NOT_FOUND } from '@constants'; import jwt from 'jsonwebtoken'; -import { JWT_SECRET } from '@configs'; +import { envs } from '@configs'; import { User } from '@prisma/client'; import { AuthInputDto } from '@dtos/in'; import { AuthResultDto } from '@dtos/out'; @@ -22,7 +22,7 @@ async function login(request: FastifyRequest<{ Body: AuthInputDto }>, reply: Fas const correctPassword = await compare(request.body.password, user.password); if (!correctPassword) return reply.badRequest(LOGIN_FAIL); - const userToken = jwt.sign({ userId: user.id }, JWT_SECRET); + const userToken = jwt.sign({ userId: user.id }, envs.JWT_SECRET); reply.setCookie('token', userToken, cookieOptions); return { @@ -45,7 +45,7 @@ async function signup(request: FastifyRequest<{ Body: AuthInputDto }>, reply: Fa return reply.badRequest(DUPLICATED_EMAIL); } - const userToken = jwt.sign({ userId: user.id }, JWT_SECRET); + const userToken = jwt.sign({ userId: user.id }, envs.JWT_SECRET); reply.setCookie('token', userToken, cookieOptions); return { diff --git a/src/index.ts b/src/index.ts index 43084ae..dfa509c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,10 @@ -import { ENVIRONMENT } from '@configs/env'; +import { envs } from '@configs/env'; import { createServer } from './Server'; const PORT = 8080; // DO NOT modify, it is used to resolve port mapping when deploy. -const HOST = ENVIRONMENT === 'development' ? 'localhost' : '0.0.0.0'; +const HOST = envs.NODE_ENV === 'development' ? 'localhost' : '0.0.0.0'; // Setup and start fastify server const app = createServer({ diff --git a/src/middlewares/auth.middleware.ts b/src/middlewares/auth.middleware.ts index dc3f517..375b384 100644 --- a/src/middlewares/auth.middleware.ts +++ b/src/middlewares/auth.middleware.ts @@ -1,4 +1,4 @@ -import { JWT_SECRET } from '@configs'; +import { envs } from '@configs'; import { INVALID_TOKEN, MUST_LOGIN_FIRST } from '@constants'; import { FastifyReply, FastifyRequest } from 'fastify'; import jwt from 'jsonwebtoken'; @@ -10,7 +10,7 @@ export async function verifyToken(request: FastifyRequest, reply: FastifyReply) try { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const decodedPayload: any = jwt.verify(token, JWT_SECRET); + const decodedPayload: any = jwt.verify(token, envs.JWT_SECRET); request.headers['userId'] = decodedPayload['userId']; return; } catch (err) { diff --git a/yarn.lock b/yarn.lock index f3e1248..3e60aac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1734,6 +1734,13 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" +envalid@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/envalid/-/envalid-7.3.1.tgz#5bf6bbb4effab2d64a1991d8078b4ae38924f0d2" + integrity sha512-KL1YRwn8WcoF/Ty7t+yLLtZol01xr9ZJMTjzoGRM8NaSU+nQQjSWOQKKJhJP2P57bpdakJ9jbxqQX4fGTOicZg== + dependencies: + tslib "2.3.1" + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -4224,6 +4231,11 @@ tsconfig@^7.0.0: strip-bom "^3.0.0" strip-json-comments "^2.0.0" +tslib@2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + tslib@^1.8.1: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"