From e78c6e312dc4a7b3fbf8ff40f55cede74839a1fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20T=C3=B6lle?= Date: Sun, 3 May 2020 03:47:24 +0200 Subject: [PATCH] feat(api): validate configuration --- docker-compose.yml | 4 +- package-lock.json | 49 +++++++++++++++++++ package.json | 2 + src/app.module.ts | 8 +-- src/auth/spotify.strategy.ts | 6 +-- src/config/config.module.ts | 40 +++++++++++++++ .../spotify/spotify-api/spotify-api.module.ts | 6 ++- .../spotify-auth/spotify-auth.module.ts | 6 ++- 8 files changed, 109 insertions(+), 12 deletions(-) create mode 100644 src/config/config.module.ts diff --git a/docker-compose.yml b/docker-compose.yml index 58559e0..2c76687 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,9 +16,11 @@ services: target: build-api command: ["npm", "run", "start:dev"] environment: + DB_HOST: db DB_USERNAME: listory DB_PASSWORD: listory - DB_HOST: db + DB_DATABASE: listory + APP_URL: "http://localhost:3000" env_file: .env volumes: - ./src:/app/src diff --git a/package-lock.json b/package-lock.json index ba7d23b..012728e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -484,6 +484,49 @@ } } }, + "@hapi/address": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.0.1.tgz", + "integrity": "sha512-0oEP5UiyV4f3d6cBL8F3Z5S7iWSX39Knnl0lY8i+6gfmmIBj44JCBNtcMgwyS+5v7j3VYavNay0NFHDS+UGQcw==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@hapi/formula": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", + "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" + }, + "@hapi/hoek": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", + "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==" + }, + "@hapi/joi": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz", + "integrity": "sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg==", + "requires": { + "@hapi/address": "^4.0.1", + "@hapi/formula": "^2.0.0", + "@hapi/hoek": "^9.0.0", + "@hapi/pinpoint": "^2.0.0", + "@hapi/topo": "^5.0.0" + } + }, + "@hapi/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" + }, + "@hapi/topo": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", + "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, "@istanbuljs/load-nyc-config": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz", @@ -1523,6 +1566,12 @@ "@types/node": "*" } }, + "@types/hapi__joi": { + "version": "16.0.12", + "resolved": "https://registry.npmjs.org/@types/hapi__joi/-/hapi__joi-16.0.12.tgz", + "integrity": "sha512-xJYifuz59jXdWY5JMS15uvA3ycS3nQYOGqoIIE0+fwQ0qI3/4CxBc6RHsOTp6wk9M0NWEdpcTl02lOQOKMifbQ==", + "dev": true + }, "@types/istanbul-lib-coverage": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", diff --git a/package.json b/package.json index 429588f..d7b1643 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "test:e2e": "jest --config ./apps/listory/test/jest-e2e.json" }, "dependencies": { + "@hapi/joi": "^17.1.1", "@nestjs/common": "^7.0.9", "@nestjs/config": "^0.4.0", "@nestjs/core": "^7.0.9", @@ -48,6 +49,7 @@ "@nestjs/schematics": "^7.0.0", "@nestjs/testing": "^7.0.9", "@types/express": "^4.17.6", + "@types/hapi__joi": "^16.0.12", "@types/jest": "^25.2.1", "@types/node": "^13.13.4", "@types/passport-jwt": "^3.0.3", diff --git a/src/app.module.ts b/src/app.module.ts index 042c873..d62e205 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,5 +1,4 @@ import { Module } from "@nestjs/common"; -import { ConfigModule } from "@nestjs/config"; import { ScheduleModule } from "@nestjs/schedule"; import { ServeStaticModule } from "@nestjs/serve-static"; import { join } from "path"; @@ -10,12 +9,14 @@ import { LoggerModule } from "./logger/logger.module"; import { MusicLibraryModule } from "./music-library/music-library.module"; import { SourcesModule } from "./sources/sources.module"; import { UsersModule } from "./users/users.module"; +import { ConfigModule } from "./config/config.module"; @Module({ imports: [ - ConfigModule.forRoot({ isGlobal: true }), - ScheduleModule.forRoot(), + LoggerModule, + ConfigModule, DatabaseModule, + ScheduleModule.forRoot(), ServeStaticModule.forRoot({ rootPath: join(__dirname, "..", "static"), exclude: ["/api*"], @@ -25,7 +26,6 @@ import { UsersModule } from "./users/users.module"; SourcesModule, MusicLibraryModule, ListensModule, - LoggerModule, ], }) export class AppModule {} diff --git a/src/auth/spotify.strategy.ts b/src/auth/spotify.strategy.ts index 19754b6..0a47485 100644 --- a/src/auth/spotify.strategy.ts +++ b/src/auth/spotify.strategy.ts @@ -13,9 +13,9 @@ export class SpotifyStrategy extends PassportStrategy(Strategy) { super({ clientID: config.get("SPOTIFY_CLIENT_ID"), clientSecret: config.get("SPOTIFY_CLIENT_SECRET"), - callbackURL: `${ - config.get("BASE_DOMAIN") || "http://localhost:3000" - }/api/v1/auth/spotify/callback`, + callbackURL: `${config.get( + "APP_URL" + )}/api/v1/auth/spotify/callback`, scope: [ "user-read-private", "user-read-email", diff --git a/src/config/config.module.ts b/src/config/config.module.ts new file mode 100644 index 0000000..7dcbe6e --- /dev/null +++ b/src/config/config.module.ts @@ -0,0 +1,40 @@ +import * as Joi from "@hapi/joi"; +import { Module } from "@nestjs/common"; +import { + ConfigModule as NestConfigModule, + ConfigService, +} from "@nestjs/config"; + +@Module({ + imports: [ + NestConfigModule.forRoot({ + isGlobal: true, + validationSchema: Joi.object({ + // Application + NODE_ENV: Joi.string().valid("dev", "production").default("dev"), + PORT: Joi.number().default(3000), + APP_URL: Joi.string().default("http://localhost:3000"), + + // JWT + JWT_SECRET: Joi.string().required(), + JWT_EXPIRATION_TIME: Joi.string().default("1d"), + + // Spotify + SPOTIFY_CLIENT_ID: Joi.string().required(), + SPOTIFY_CLIENT_SECRET: Joi.string().required(), + SPOTIFY_FETCH_INTERVAL_MS: Joi.number().default(5 * 60 * 1000), + SPOTIFY_WEB_API_URL: Joi.string().default("https://api.spotify.com/"), + SPOTIFY_AUTH_API_URL: Joi.string().default( + "https://accounts.spotify.com/" + ), + + // DB + DB_HOST: Joi.string().required(), + DB_USERNAME: Joi.string().required(), + DB_PASSWORD: Joi.string().required(), + DB_DATABASE: Joi.string().required(), + }), + }), + ], +}) +export class ConfigModule {} diff --git a/src/sources/spotify/spotify-api/spotify-api.module.ts b/src/sources/spotify/spotify-api/spotify-api.module.ts index 38080a0..2f853ea 100644 --- a/src/sources/spotify/spotify-api/spotify-api.module.ts +++ b/src/sources/spotify/spotify-api/spotify-api.module.ts @@ -1,13 +1,15 @@ import { HttpModule, Module } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; import { SpotifyApiService } from "./spotify-api.service"; @Module({ imports: [ HttpModule.registerAsync({ - useFactory: () => ({ + useFactory: (config: ConfigService) => ({ timeout: 5000, - baseURL: "https://api.spotify.com/", + baseURL: config.get("SPOTIFY_WEB_API_URL"), }), + inject: [ConfigService], }), ], providers: [SpotifyApiService], diff --git a/src/sources/spotify/spotify-auth/spotify-auth.module.ts b/src/sources/spotify/spotify-auth/spotify-auth.module.ts index 7aabcb8..a3e89f6 100644 --- a/src/sources/spotify/spotify-auth/spotify-auth.module.ts +++ b/src/sources/spotify/spotify-auth/spotify-auth.module.ts @@ -1,13 +1,15 @@ import { HttpModule, Module } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; import { SpotifyAuthService } from "./spotify-auth.service"; @Module({ imports: [ HttpModule.registerAsync({ - useFactory: () => ({ + useFactory: (config: ConfigService) => ({ timeout: 5000, - baseURL: "https://accounts.spotify.com/", + baseURL: config.get("SPOTIFY_AUTH_API_URL"), }), + inject: [ConfigService], }), ], providers: [SpotifyAuthService],