From e6ff4ab199f472e124719667d7f008e0f0ad3ff7 Mon Sep 17 00:00:00 2001 From: vchikalkin Date: Mon, 16 Oct 2023 12:15:27 +0300 Subject: [PATCH] apps/api: check and pass env variables --- apps/api/.env | 4 ---- apps/api/Dockerfile | 9 +++++---- apps/api/package.json | 3 ++- apps/api/src/app.module.ts | 5 +++-- apps/api/src/auth/auth.controller.ts | 3 ++- apps/api/src/auth/auth.service.ts | 3 ++- apps/api/src/config/env.ts | 3 +++ apps/api/src/config/schema/env.ts | 21 +++++++++++++++++++++ apps/api/src/ldap/ldap.service.ts | 17 +++++++++-------- apps/api/src/main.ts | 5 +++-- apps/api/src/users/users.module.ts | 7 ++++--- apps/api/tsconfig.json | 2 +- 12 files changed, 55 insertions(+), 27 deletions(-) delete mode 100644 apps/api/.env create mode 100644 apps/api/src/config/env.ts create mode 100644 apps/api/src/config/schema/env.ts diff --git a/apps/api/.env b/apps/api/.env deleted file mode 100644 index 287f286..0000000 --- a/apps/api/.env +++ /dev/null @@ -1,4 +0,0 @@ -SECRET=secret -TOKEN_TTL=3600 -CACHE_TTL=3600 -COOKIE_TOKEN_NAME=token \ No newline at end of file diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile index 946ade2..f2c8321 100644 --- a/apps/api/Dockerfile +++ b/apps/api/Dockerfile @@ -1,18 +1,19 @@ # The web Dockerfile is copy-pasted into our main docs at /docs/handbook/deploying-with-docker. # Make sure you update this Dockerfile, the Dockerfile in the web workspace and copy that over to Dockerfile in the docs. -FROM node:16-alpine AS builder +FROM node:alpine AS builder # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. RUN apk add --no-cache libc6-compat RUN apk update # Set working directory WORKDIR /app RUN yarn global add turbo +RUN yarn global add dotenv-cli COPY . . RUN turbo prune --scope=api --docker # Add lockfile and package.json's of isolated subworkspace -FROM node:16-alpine AS installer +FROM node:alpine AS installer RUN apk add --no-cache libc6-compat RUN apk update WORKDIR /app @@ -26,9 +27,9 @@ RUN yarn install # Build the project and its dependencies COPY --from=builder /app/out/full/ . COPY turbo.json turbo.json -RUN yarn turbo run build --filter=api... +RUN yarn dotenv -e .env turbo run build --filter=api... -FROM node:16-alpine AS runner +FROM node:alpine AS runner WORKDIR /app # Don't run production as root diff --git a/apps/api/package.json b/apps/api/package.json index 31ae8a8..2374fc4 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -37,7 +37,8 @@ "ldap-authentication": "2.3.1", "reflect-metadata": "^0.1.13", "rimraf": "^5.0.5", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "zod": "^3.22.4" }, "devDependencies": { "@nestjs/schematics": "^10.0.2", diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index 266af32..68d44e7 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -1,6 +1,7 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; import { AuthModule } from './auth/auth.module'; +import { env } from './config/env'; import { LdapModule } from './ldap/ldap.module'; import { UsersModule } from './users/users.module'; import { Global, Module } from '@nestjs/common'; @@ -16,9 +17,9 @@ import { JwtModule } from '@nestjs/jwt'; isGlobal: true, }), JwtModule.register({ - secret: process.env.SECRET, + secret: env.SECRET, signOptions: { - expiresIn: process.env.TOKEN_TTL, + expiresIn: env.TOKEN_TTL, }, }), AuthModule, diff --git a/apps/api/src/auth/auth.controller.ts b/apps/api/src/auth/auth.controller.ts index 0ed2848..ee0177a 100644 --- a/apps/api/src/auth/auth.controller.ts +++ b/apps/api/src/auth/auth.controller.ts @@ -6,13 +6,14 @@ import { COOKIE_TOKEN_NAME } from './lib/constants'; import { Credentials } from './types/request'; import { Body, Controller, Get, HttpException, HttpStatus, Post, Req, Res } from '@nestjs/common'; import { FastifyReply, FastifyRequest } from 'fastify'; +import { env } from 'src/config/env'; @Controller() export class AuthController { cookieOptions: { maxAge: number; path: string }; constructor(private readonly authService: AuthService) { this.cookieOptions = { - maxAge: Number.parseInt(process.env.TOKEN_TTL, 10), + maxAge: env.TOKEN_TTL, path: '/', }; } diff --git a/apps/api/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts index d981ac8..5d09216 100644 --- a/apps/api/src/auth/auth.service.ts +++ b/apps/api/src/auth/auth.service.ts @@ -3,6 +3,7 @@ import { UsersCache } from '../users/users.cache'; import type { DecodedToken, TokenPayload } from './types/jwt'; import { Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; +import { env } from 'src/config/env'; @Injectable() export class AuthService { @@ -19,7 +20,7 @@ export class AuthService { await this.usersCache.addUser(username, user); const payload: TokenPayload = { - domain: process.env.LDAP_DOMAIN, + domain: env.LDAP_DOMAIN, username, }; diff --git a/apps/api/src/config/env.ts b/apps/api/src/config/env.ts new file mode 100644 index 0000000..6dfb5a2 --- /dev/null +++ b/apps/api/src/config/env.ts @@ -0,0 +1,3 @@ +import envSchema from './schema/env'; + +export const env = envSchema.parse(process.env); diff --git a/apps/api/src/config/schema/env.ts b/apps/api/src/config/schema/env.ts new file mode 100644 index 0000000..4ca7a71 --- /dev/null +++ b/apps/api/src/config/schema/env.ts @@ -0,0 +1,21 @@ +import { z } from 'zod'; + +const envSchema = z.object({ + API_PORT: z.number().optional().default(3001), + CACHE_TTL: z.string().transform((val) => Number.parseInt(val, 10)), + LDAP_ATTRIBUTE: z.string(), + LDAP_BASE: z.string(), + LDAP_BIND_CREDENTIALS: z.string(), + LDAP_BIND_DN: z.string(), + LDAP_DOMAIN: z.string(), + LDAP_URL: z.string().url(), + REDIS_HOST: z.string(), + REDIS_PORT: z + .string() + .transform((val) => Number.parseInt(val, 10)) + .default('6379'), + SECRET: z.string(), + TOKEN_TTL: z.string().transform((val) => Number.parseInt(val, 10)), +}); + +export default envSchema; diff --git a/apps/api/src/ldap/ldap.service.ts b/apps/api/src/ldap/ldap.service.ts index 059aace..1eaab34 100644 --- a/apps/api/src/ldap/ldap.service.ts +++ b/apps/api/src/ldap/ldap.service.ts @@ -1,3 +1,4 @@ +import { env } from '../config/env'; import type { User } from '../types/user'; import type { LdapUser } from './types/user'; import { Injectable } from '@nestjs/common'; @@ -6,17 +7,17 @@ import { authenticate } from 'ldap-authentication'; @Injectable() export class LdapService { - async authenticate(login: string, password?: string) { + public async authenticate(login: string, password?: string) { const options: AuthenticationOptions = { - adminDn: process.env.LDAP_BIND_DN, - adminPassword: process.env.LDAP_BIND_CREDENTIALS, + adminDn: env.LDAP_BIND_DN, + adminPassword: env.LDAP_BIND_CREDENTIALS, ldapOpts: { - url: process.env.LDAP_URL, + url: env.LDAP_URL, }, userPassword: password, - userSearchBase: process.env.LDAP_BASE, + userSearchBase: env.LDAP_BASE, username: login, - usernameAttribute: process.env.LDAP_ATTRIBUTE, + usernameAttribute: env.LDAP_ATTRIBUTE, verifyUserExists: password === undefined, }; @@ -31,8 +32,8 @@ export class LdapService { const user: User = { department, displayName, - domain: process.env.LDAP_DOMAIN, - domainName: `${process.env.LDAP_DOMAIN}\\${username}`, + domain: env.LDAP_DOMAIN, + domainName: `${env.LDAP_DOMAIN}\\${username}`, mail, position: title, username, diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 9b98573..e072ba3 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -1,5 +1,6 @@ /* eslint-disable unicorn/prefer-top-level-await */ import { AppModule } from './app.module'; +import { env } from './config/env'; import { fastifyCookie } from '@fastify/cookie'; import { NestFactory } from '@nestjs/core'; import type { NestFastifyApplication } from '@nestjs/platform-fastify'; @@ -14,10 +15,10 @@ async function bootstrap() { ); await app.register(fastifyCookie, { - secret: process.env.SECRET, + secret: env.SECRET, }); - await app.listen(process.env.API_PORT || 3001, '0.0.0.0'); + await app.listen(env.API_PORT, '0.0.0.0'); } bootstrap(); diff --git a/apps/api/src/users/users.module.ts b/apps/api/src/users/users.module.ts index d7b176a..e733d17 100644 --- a/apps/api/src/users/users.module.ts +++ b/apps/api/src/users/users.module.ts @@ -6,16 +6,17 @@ import { CacheModule } from '@nestjs/cache-manager'; import { Module } from '@nestjs/common'; import * as redisStore from 'cache-manager-ioredis'; import type { RedisOptions } from 'ioredis'; +import { env } from 'src/config/env'; @Module({ controllers: [UsersController], exports: [UsersCache], imports: [ CacheModule.register({ - host: process.env.REDIS_HOST, - port: Number.parseInt(process.env.REDIS_PORT, 10) || 6379, + host: env.REDIS_HOST, + port: env.REDIS_PORT, store: redisStore, - ttl: Number.parseInt(process.env.CACHE_TTL, 10), + ttl: env.CACHE_TTL, }), LdapModule, ], diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json index adb614c..95f5641 100644 --- a/apps/api/tsconfig.json +++ b/apps/api/tsconfig.json @@ -6,7 +6,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, - "target": "es2017", + "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./",