apps/api: check and pass env variables

This commit is contained in:
vchikalkin 2023-10-16 12:15:27 +03:00
parent 27ad1e96dd
commit e6ff4ab199
12 changed files with 55 additions and 27 deletions

View File

@ -1,4 +0,0 @@
SECRET=secret
TOKEN_TTL=3600
CACHE_TTL=3600
COOKIE_TOKEN_NAME=token

View File

@ -1,18 +1,19 @@
# The web Dockerfile is copy-pasted into our main docs at /docs/handbook/deploying-with-docker. # 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. # 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. # 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 add --no-cache libc6-compat
RUN apk update RUN apk update
# Set working directory # Set working directory
WORKDIR /app WORKDIR /app
RUN yarn global add turbo RUN yarn global add turbo
RUN yarn global add dotenv-cli
COPY . . COPY . .
RUN turbo prune --scope=api --docker RUN turbo prune --scope=api --docker
# Add lockfile and package.json's of isolated subworkspace # 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 add --no-cache libc6-compat
RUN apk update RUN apk update
WORKDIR /app WORKDIR /app
@ -26,9 +27,9 @@ RUN yarn install
# Build the project and its dependencies # Build the project and its dependencies
COPY --from=builder /app/out/full/ . COPY --from=builder /app/out/full/ .
COPY turbo.json turbo.json 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 WORKDIR /app
# Don't run production as root # Don't run production as root

View File

@ -37,7 +37,8 @@
"ldap-authentication": "2.3.1", "ldap-authentication": "2.3.1",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"rxjs": "^7.8.1" "rxjs": "^7.8.1",
"zod": "^3.22.4"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/schematics": "^10.0.2", "@nestjs/schematics": "^10.0.2",

View File

@ -1,6 +1,7 @@
import { AppController } from './app.controller'; import { AppController } from './app.controller';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module'; import { AuthModule } from './auth/auth.module';
import { env } from './config/env';
import { LdapModule } from './ldap/ldap.module'; import { LdapModule } from './ldap/ldap.module';
import { UsersModule } from './users/users.module'; import { UsersModule } from './users/users.module';
import { Global, Module } from '@nestjs/common'; import { Global, Module } from '@nestjs/common';
@ -16,9 +17,9 @@ import { JwtModule } from '@nestjs/jwt';
isGlobal: true, isGlobal: true,
}), }),
JwtModule.register({ JwtModule.register({
secret: process.env.SECRET, secret: env.SECRET,
signOptions: { signOptions: {
expiresIn: process.env.TOKEN_TTL, expiresIn: env.TOKEN_TTL,
}, },
}), }),
AuthModule, AuthModule,

View File

@ -6,13 +6,14 @@ import { COOKIE_TOKEN_NAME } from './lib/constants';
import { Credentials } from './types/request'; import { Credentials } from './types/request';
import { Body, Controller, Get, HttpException, HttpStatus, Post, Req, Res } from '@nestjs/common'; import { Body, Controller, Get, HttpException, HttpStatus, Post, Req, Res } from '@nestjs/common';
import { FastifyReply, FastifyRequest } from 'fastify'; import { FastifyReply, FastifyRequest } from 'fastify';
import { env } from 'src/config/env';
@Controller() @Controller()
export class AuthController { export class AuthController {
cookieOptions: { maxAge: number; path: string }; cookieOptions: { maxAge: number; path: string };
constructor(private readonly authService: AuthService) { constructor(private readonly authService: AuthService) {
this.cookieOptions = { this.cookieOptions = {
maxAge: Number.parseInt(process.env.TOKEN_TTL, 10), maxAge: env.TOKEN_TTL,
path: '/', path: '/',
}; };
} }

View File

@ -3,6 +3,7 @@ import { UsersCache } from '../users/users.cache';
import type { DecodedToken, TokenPayload } from './types/jwt'; import type { DecodedToken, TokenPayload } from './types/jwt';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import { env } from 'src/config/env';
@Injectable() @Injectable()
export class AuthService { export class AuthService {
@ -19,7 +20,7 @@ export class AuthService {
await this.usersCache.addUser(username, user); await this.usersCache.addUser(username, user);
const payload: TokenPayload = { const payload: TokenPayload = {
domain: process.env.LDAP_DOMAIN, domain: env.LDAP_DOMAIN,
username, username,
}; };

View File

@ -0,0 +1,3 @@
import envSchema from './schema/env';
export const env = envSchema.parse(process.env);

View File

@ -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;

View File

@ -1,3 +1,4 @@
import { env } from '../config/env';
import type { User } from '../types/user'; import type { User } from '../types/user';
import type { LdapUser } from './types/user'; import type { LdapUser } from './types/user';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
@ -6,17 +7,17 @@ import { authenticate } from 'ldap-authentication';
@Injectable() @Injectable()
export class LdapService { export class LdapService {
async authenticate(login: string, password?: string) { public async authenticate(login: string, password?: string) {
const options: AuthenticationOptions = { const options: AuthenticationOptions = {
adminDn: process.env.LDAP_BIND_DN, adminDn: env.LDAP_BIND_DN,
adminPassword: process.env.LDAP_BIND_CREDENTIALS, adminPassword: env.LDAP_BIND_CREDENTIALS,
ldapOpts: { ldapOpts: {
url: process.env.LDAP_URL, url: env.LDAP_URL,
}, },
userPassword: password, userPassword: password,
userSearchBase: process.env.LDAP_BASE, userSearchBase: env.LDAP_BASE,
username: login, username: login,
usernameAttribute: process.env.LDAP_ATTRIBUTE, usernameAttribute: env.LDAP_ATTRIBUTE,
verifyUserExists: password === undefined, verifyUserExists: password === undefined,
}; };
@ -31,8 +32,8 @@ export class LdapService {
const user: User = { const user: User = {
department, department,
displayName, displayName,
domain: process.env.LDAP_DOMAIN, domain: env.LDAP_DOMAIN,
domainName: `${process.env.LDAP_DOMAIN}\\${username}`, domainName: `${env.LDAP_DOMAIN}\\${username}`,
mail, mail,
position: title, position: title,
username, username,

View File

@ -1,5 +1,6 @@
/* eslint-disable unicorn/prefer-top-level-await */ /* eslint-disable unicorn/prefer-top-level-await */
import { AppModule } from './app.module'; import { AppModule } from './app.module';
import { env } from './config/env';
import { fastifyCookie } from '@fastify/cookie'; import { fastifyCookie } from '@fastify/cookie';
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import type { NestFastifyApplication } from '@nestjs/platform-fastify'; import type { NestFastifyApplication } from '@nestjs/platform-fastify';
@ -14,10 +15,10 @@ async function bootstrap() {
); );
await app.register(fastifyCookie, { 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(); bootstrap();

View File

@ -6,16 +6,17 @@ import { CacheModule } from '@nestjs/cache-manager';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import * as redisStore from 'cache-manager-ioredis'; import * as redisStore from 'cache-manager-ioredis';
import type { RedisOptions } from 'ioredis'; import type { RedisOptions } from 'ioredis';
import { env } from 'src/config/env';
@Module({ @Module({
controllers: [UsersController], controllers: [UsersController],
exports: [UsersCache], exports: [UsersCache],
imports: [ imports: [
CacheModule.register<RedisOptions>({ CacheModule.register<RedisOptions>({
host: process.env.REDIS_HOST, host: env.REDIS_HOST,
port: Number.parseInt(process.env.REDIS_PORT, 10) || 6379, port: env.REDIS_PORT,
store: redisStore, store: redisStore,
ttl: Number.parseInt(process.env.CACHE_TTL, 10), ttl: env.CACHE_TTL,
}), }),
LdapModule, LdapModule,
], ],

View File

@ -6,7 +6,7 @@
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"target": "es2017", "target": "ES2021",
"sourceMap": true, "sourceMap": true,
"outDir": "./dist", "outDir": "./dist",
"baseUrl": "./", "baseUrl": "./",