diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cdcfe04d36..84d7e0f475 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,7 +59,7 @@ jobs: redis: image: redis:4.0.14 ports: - - 6379:6379 + - 6380:6379 steps: - uses: actions/checkout@v1 diff --git a/docker-compose.yml b/docker-compose.yml index 77fed323d3..4b12a85dee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,9 +39,4 @@ services: redis: image: "redis:4.0.14" ports: - - "6379:6379" - - adminer: - image: adminer - ports: - - 8080:8080 \ No newline at end of file + - "6380:6379" \ No newline at end of file diff --git a/docs/authentication-and-access-control/session-tokens.md b/docs/authentication-and-access-control/session-tokens.md index 35eaccb438..e13b88e1a8 100644 --- a/docs/authentication-and-access-control/session-tokens.md +++ b/docs/authentication-and-access-control/session-tokens.md @@ -478,18 +478,15 @@ This store uses the default TypeORM connection which is usually specified in `or npm install @foal/redis ``` -In order to use this store, you must provide the redis URI in either: -- a configuration file - - *Example with config/default.yml* - ```yaml - redis: - uri: 'redis://localhost:6379' - ``` -- or in a `.env` file or in an environment variable: - ``` - REDIS_URI=redis://localhost:6379 - ``` +In order to use this store, you must provide the redis URI in either a configuration file + +*Example with config/default.yml* +```yaml +settings: + redis: + uri: 'redis://localhost:6379' +``` + ### MongoDBStore @@ -497,21 +494,14 @@ In order to use this store, you must provide the redis URI in either: npm install @foal/mongodb ``` -This store saves your session states in a MongoDB database (using the collection `sessions`). In order to use it, you must provide the MongoDB URI in either: - -- a configuration file - - *Example with config/default.yml* - ```yaml - mongodb: - uri: 'mongodb://localhost:27017' - ``` -- or in a `.env` file or in an environment variable: - ``` - MONGODB_URI=mongodb://localhost:27017 - ``` - +This store saves your session states in a MongoDB database (using the collection `sessions`). In order to use it, you must provide the MongoDB URI a configuration file: +*Example with config/default.yml* +```yaml +settings: + mongodb: + uri: 'mongodb://localhost:27017' +``` ### Custom Store diff --git a/packages/acceptance-tests/src/authentication/jwt.cookie.spec.ts b/packages/acceptance-tests/src/authentication/jwt.cookie.spec.ts index 48cbed582f..1b945b2dce 100644 --- a/packages/acceptance-tests/src/authentication/jwt.cookie.spec.ts +++ b/packages/acceptance-tests/src/authentication/jwt.cookie.spec.ts @@ -99,7 +99,7 @@ describe('[Authentication|JWT|cookie|no redirection] Users', () => { async logout() { return new HttpResponseNoContent() .setCookie( - Config.get('settings.jwt.cookieName', 'string', 'auth'), + Config.get('settings.jwt.cookie.name', 'string', 'auth'), '', { ...cookieOptions, maxAge: 0 } ); @@ -123,7 +123,7 @@ describe('[Authentication|JWT|cookie|no redirection] Users', () => { }); return new HttpResponseNoContent() .setCookie( - Config.get('settings.jwt.cookieName', 'string', 'auth'), + Config.get('settings.jwt.cookie.name', 'string', 'auth'), token, { ...cookieOptions, maxAge: 3600 } ); @@ -138,7 +138,8 @@ describe('[Authentication|JWT|cookie|no redirection] Users', () => { } before(async () => { - process.env.SETTINGS_JWT_SECRET = 'session-secret'; + Config.set('settings.jwt.secret', 'session-secret'); + await createConnection({ database: 'e2e_db.sqlite', dropSchema: true, @@ -154,7 +155,8 @@ describe('[Authentication|JWT|cookie|no redirection] Users', () => { after(async () => { await getConnection().close(); - delete process.env.SETTINGS_JWT_SECRET; + + Config.remove('settings.jwt.secret'); }); it('cannot access protected routes if they are not logged in.', () => { diff --git a/packages/acceptance-tests/src/authentication/jwt.token.spec.ts b/packages/acceptance-tests/src/authentication/jwt.token.spec.ts index 64a6e315bd..38934e324e 100644 --- a/packages/acceptance-tests/src/authentication/jwt.token.spec.ts +++ b/packages/acceptance-tests/src/authentication/jwt.token.spec.ts @@ -11,9 +11,9 @@ import * as request from 'supertest'; // FoalTS import { - Context, controller, createApp, - Get, hashPassword, HttpResponseOK, - HttpResponseUnauthorized, Post, ValidateBody, verifyPassword + Config, Context, controller, + createApp, Get, hashPassword, + HttpResponseOK, HttpResponseUnauthorized, Post, ValidateBody, verifyPassword } from '@foal/core'; import { getSecretOrPrivateKey, JWTRequired } from '@foal/jwt'; import { fetchUser } from '@foal/typeorm'; @@ -120,7 +120,8 @@ describe('[Authentication|JWT|no cookie|no redirection] Users', () => { } before(async () => { - process.env.SETTINGS_JWT_SECRET = 'session-secret'; + Config.set('settings.jwt.secret', 'session-secret'); + await createConnection({ database: 'e2e_db.sqlite', dropSchema: true, @@ -136,7 +137,8 @@ describe('[Authentication|JWT|no cookie|no redirection] Users', () => { after(async () => { await getConnection().close(); - delete process.env.SETTINGS_JWT_SECRET; + + Config.remove('settings.jwt.secret'); }); it('cannot access protected routes if they are not logged in.', () => { diff --git a/packages/acceptance-tests/src/examples/security/csrf/regular-web-app.stateful.spec.ts b/packages/acceptance-tests/src/examples/security/csrf/regular-web-app.stateful.spec.ts index b122a54a76..3fbb167b9c 100644 --- a/packages/acceptance-tests/src/examples/security/csrf/regular-web-app.stateful.spec.ts +++ b/packages/acceptance-tests/src/examples/security/csrf/regular-web-app.stateful.spec.ts @@ -7,6 +7,7 @@ import * as request from 'supertest'; // FoalTS import { + Config, Context, controller, createApp, @@ -123,8 +124,8 @@ describe('Feature: Stateful CSRF protection in a Regular Web App', () => { let csrfToken: string; before(async () => { - process.env.SETTINGS_SESSION_CSRF_ENABLED = 'true'; - process.env.SETTINGS_SESSION_CSRF_COOKIE_NAME = csrfCookieName; + Config.set('settings.session.csrf.enabled', true); + Config.set('settings.session.csrf.cookie.name', csrfCookieName); connection = await createTestConnection([ User, DatabaseSession ]); @@ -135,8 +136,8 @@ describe('Feature: Stateful CSRF protection in a Regular Web App', () => { }); after(async () => { - delete process.env.SETTINGS_SESSION_CSRF_ENABLED; - delete process.env.SETTINGS_SESSION_CSRF_COOKIE_NAME; + Config.remove('settings.session.csrf.enabled'); + Config.remove('settings.session.csrf.cookie.name'); await connection.close(); }); diff --git a/packages/acceptance-tests/src/examples/security/csrf/spa.stateful.spec.ts b/packages/acceptance-tests/src/examples/security/csrf/spa.stateful.spec.ts index 9d386a5da1..dfe0aa4db5 100644 --- a/packages/acceptance-tests/src/examples/security/csrf/spa.stateful.spec.ts +++ b/packages/acceptance-tests/src/examples/security/csrf/spa.stateful.spec.ts @@ -7,6 +7,7 @@ import * as request from 'supertest'; // FoalTS import { + Config, Context, controller, createApp, @@ -91,8 +92,8 @@ describe('Feature: Stateful CSRF protection in a Single-Page Application', () => let csrfToken: string; before(async () => { - process.env.SETTINGS_SESSION_CSRF_ENABLED = 'true'; - process.env.SETTINGS_SESSION_CSRF_COOKIE_NAME = csrfCookieName; + Config.set('settings.session.csrf.enabled', true); + Config.set('settings.session.csrf.cookie.name', csrfCookieName); connection = await createTestConnection([ User, DatabaseSession ]); @@ -103,8 +104,8 @@ describe('Feature: Stateful CSRF protection in a Single-Page Application', () => }); after(async () => { - delete process.env.SETTINGS_SESSION_CSRF_ENABLED; - delete process.env.SETTINGS_SESSION_CSRF_COOKIE_NAME; + Config.remove('settings.session.csrf.enabled'); + Config.remove('settings.session.csrf.cookie.name'); await connection.close(); }); diff --git a/packages/acceptance-tests/src/examples/security/csrf/spa.stateless.spec.ts b/packages/acceptance-tests/src/examples/security/csrf/spa.stateless.spec.ts index 06b6808747..cb0e036af0 100644 --- a/packages/acceptance-tests/src/examples/security/csrf/spa.stateless.spec.ts +++ b/packages/acceptance-tests/src/examples/security/csrf/spa.stateless.spec.ts @@ -8,6 +8,7 @@ import * as request from 'supertest'; // FoalTS import { + Config, Context, controller, createApp, @@ -91,9 +92,9 @@ describe('Feature: Stateless CSRF protection in a Single-Page Application', () = let csrfToken: string; before(async () => { - process.env.SETTINGS_JWT_SECRET = 'jwtSecret'; - process.env.SETTINGS_JWT_CSRF_ENABLED = 'true'; - process.env.SETTINGS_JWT_CSRF_COOKIE_NAME = csrfCookieName; + Config.set('settings.jwt.secret', 'jwtSecret'); + Config.set('settings.jwt.csrf.enabled', true); + Config.set('settings.jwt.csrf.cookie.name', csrfCookieName); connection = await createTestConnection([ User ]); @@ -104,9 +105,9 @@ describe('Feature: Stateless CSRF protection in a Single-Page Application', () = }); after(async () => { - delete process.env.SETTINGS_JWT_SECRET; - delete process.env.SETTINGS_JWT_CSRF_ENABLED; - delete process.env.SETTINGS_JWT_CSRF_COOKIE_NAME; + Config.remove('settings.jwt.secret'); + Config.remove('settings.jwt.csrf.enabled'); + Config.remove('settings.jwt.csrf.cookie.name'); await connection.close(); }); diff --git a/packages/acceptance-tests/src/session.config.spec.ts b/packages/acceptance-tests/src/session.config.spec.ts index ce2363677e..19036ed4ed 100644 --- a/packages/acceptance-tests/src/session.config.spec.ts +++ b/packages/acceptance-tests/src/session.config.spec.ts @@ -1,4 +1,5 @@ import { + Config, Context, createApp, createSession, @@ -21,14 +22,14 @@ describe('The session store', () => { beforeEach(() => { // Use ".." to remove the "build/" directory. - process.env.SETTINGS_SESSION_STORE = './../node_modules/@foal/redis'; - process.env.SETTINGS_SESSION_SECRET = 'secret'; + Config.set('settings.session.store', './../node_modules/@foal/redis'); + Config.set('settings.session.secret', 'secret'); serviceManager = new ServiceManager(); }); afterEach(() => { - delete process.env.SETTINGS_SESSION_STORE; - delete process.env.SETTINGS_SESSION_SECRET; + Config.remove('settings.session.store'); + Config.remove('settings.session.secret'); // Hack to close the redis connection in this test. (serviceManager as any).map.forEach((value: any) => { if (value.service.close) { diff --git a/packages/acceptance-tests/src/typeorm.mongodb-store.spec.ts b/packages/acceptance-tests/src/typeorm.mongodb-store.spec.ts index 251ac0f0f1..52c63c92e8 100644 --- a/packages/acceptance-tests/src/typeorm.mongodb-store.spec.ts +++ b/packages/acceptance-tests/src/typeorm.mongodb-store.spec.ts @@ -3,6 +3,7 @@ import { strictEqual } from 'assert'; // 3p import { + Config, Context, createAndInitApp, createSession, @@ -136,7 +137,8 @@ describe('[Sample] TypeORM & MongoDB Store', async () => { let connection: Connection; before(async () => { - process.env.MONGODB_URI = 'mongodb://localhost:27017/e2e_db'; + Config.set('settings.mongodb.uri', 'mongodb://localhost:27017/e2e_db'); + connection = await createConnection({ database: 'e2e_db', dropSchema: true, @@ -160,7 +162,8 @@ describe('[Sample] TypeORM & MongoDB Store', async () => { }); after(async () => { - delete process.env.MONGODB_URI; + Config.remove('settings.mongodb.uri'); + return Promise.all([ connection.close(), app.foal.services.get(MongoDBStore).close(), diff --git a/packages/acceptance-tests/src/typeorm.redis-store.spec.ts b/packages/acceptance-tests/src/typeorm.redis-store.spec.ts index f6f32bcca5..b7e0d5647d 100644 --- a/packages/acceptance-tests/src/typeorm.redis-store.spec.ts +++ b/packages/acceptance-tests/src/typeorm.redis-store.spec.ts @@ -3,6 +3,7 @@ import { strictEqual } from 'assert'; // 3p import { + Config, Context, createAndInitApp, createSession, @@ -134,7 +135,8 @@ describe('[Sample] MongoDB & Redis Store', async () => { let connection: Connection; before(async () => { - process.env.MONGODB_URI = 'mongodb://localhost:27017/e2e_db'; + Config.set('settings.mongodb.uri', 'mongodb://localhost:27017/e2e_db'); + connection = await createConnection({ database: 'e2e_db', dropSchema: true, @@ -165,7 +167,8 @@ describe('[Sample] MongoDB & Redis Store', async () => { }); after(() => { - delete process.env.MONGODB_URI; + Config.remove('settings.mongodb.uri'); + return Promise.all([ connection.close(), app.foal.services.get(RedisStore).close(), diff --git a/packages/aws-s3/src/s3-disk.service.spec.ts b/packages/aws-s3/src/s3-disk.service.spec.ts index add057624a..442dc27a06 100644 --- a/packages/aws-s3/src/s3-disk.service.spec.ts +++ b/packages/aws-s3/src/s3-disk.service.spec.ts @@ -67,20 +67,21 @@ describe('S3Disk', () => { })); beforeEach(() => { - process.env.SETTINGS_DISK_S3_BUCKET = bucketName; + Config.set('settings.disk.s3.bucket', bucketName); disk = createService(S3Disk); }); afterEach(async () => { - delete process.env.SETTINGS_DISK_S3_BUCKET; - delete process.env.SETTINGS_AWS_ENDPOINT; + Config.remove('settings.disk.s3.bucket'); + Config.remove('settings.aws.endpoint'); await rmObjectsIfExist(s3); }); it('should accept a S3 custom endpoint in the config.', async () => { // This test assumes that the "delete" method tries at least to connect to AWS. - process.env.SETTINGS_AWS_ENDPOINT = 'foobar'; + Config.set('settings.aws.endpoint', 'foobar'); + try { await disk.delete('foo/test.txt'); throw new Error('An error should have been thrown.'); @@ -95,7 +96,8 @@ describe('S3Disk', () => { describe('has a "write" method that', () => { it('should throw an Error if no bucket is specified in the config.', async () => { - delete process.env.SETTINGS_DISK_S3_BUCKET; + Config.remove('settings.disk.s3.bucket'); + try { await disk.write('foo', Buffer.from('hello', 'utf8')); throw new Error('An error should have been thrown.'); @@ -160,7 +162,8 @@ describe('S3Disk', () => { describe('has a "read" method that', () => { it('should throw an Error if no bucket is specified in the config.', async () => { - delete process.env.SETTINGS_DISK_S3_BUCKET; + Config.remove('settings.disk.s3.bucket'); + try { await disk.read('foo', 'buffer'); throw new Error('An error should have been thrown.'); @@ -247,7 +250,8 @@ describe('S3Disk', () => { describe('has a "readSize" method that', () => { it('should throw an Error if no bucket is specified in the config.', async () => { - delete process.env.SETTINGS_DISK_S3_BUCKET; + Config.remove('settings.disk.s3.bucket'); + try { await disk.readSize('foo'); throw new Error('An error should have been thrown.'); @@ -288,7 +292,8 @@ describe('S3Disk', () => { describe('has a "delete" method that', () => { it('should throw an Error if no bucket is specified in the config.', async () => { - delete process.env.SETTINGS_DISK_S3_BUCKET; + Config.remove('settings.disk.s3.bucket'); + try { await disk.delete('foo'); throw new Error('An error should have been thrown.'); diff --git a/packages/core/src/common/utils/get-ajv-instance.spec.ts b/packages/core/src/common/utils/get-ajv-instance.spec.ts index 42ef536858..24cb01c063 100644 --- a/packages/core/src/common/utils/get-ajv-instance.spec.ts +++ b/packages/core/src/common/utils/get-ajv-instance.spec.ts @@ -1,5 +1,5 @@ import { deepStrictEqual, strictEqual } from 'assert'; -import { ConfigTypeError } from '../../core'; +import { Config, ConfigTypeError } from '../../core'; import { _instanceWrapper, getAjvInstance } from './get-ajv-instance'; describe('getAjvInstance', () => { @@ -45,11 +45,11 @@ describe('getAjvInstance', () => { beforeEach(() => { delete _instanceWrapper.instance; - process.env.SETTINGS_AJV_COERCE_TYPES = 'false'; - process.env.SETTINGS_AJV_REMOVE_ADDITIONAL = 'false'; - process.env.SETTINGS_AJV_USE_DEFAULTS = 'false'; - process.env.SETTINGS_AJV_NULLABLE = 'true'; - process.env.SETTINGS_AJV_ALL_ERRORS = 'true'; + Config.set('settings.ajv.coerceTypes', false); + Config.set('settings.ajv.removeAdditional', false); + Config.set('settings.ajv.useDefaults', false); + Config.set('settings.ajv.nullable', true); + Config.set('settings.ajv.allErrors', true); }); it('should accept custom configuration from the Config.', () => { @@ -119,7 +119,7 @@ describe('getAjvInstance', () => { }); it('should throw a ConfigTypeError when the value of `settings.ajv.coerceTypes` has an invalid type.', () => { - process.env.SETTINGS_AJV_COERCE_TYPES = 'hello'; + Config.set('settings.ajv.coerceTypes', 'hello'); try { getAjvInstance().validate({}, {}); @@ -137,7 +137,7 @@ describe('getAjvInstance', () => { }); it('should throw a ConfigTypeError when the value of `settings.ajv.nullable` has an invalid type.', () => { - process.env.SETTINGS_AJV_NULLABLE = 'hello'; + Config.set('settings.ajv.nullable', 'hello'); try { getAjvInstance().validate({}, {}); @@ -155,7 +155,7 @@ describe('getAjvInstance', () => { }); it('should throw a ConfigTypeError when the value of `settings.ajv.allErrors` has an invalid type.', () => { - process.env.SETTINGS_AJV_ALL_ERRORS = 'hello'; + Config.set('settings.ajv.allErrors', 'hello'); try { getAjvInstance().validate({}, {}); @@ -174,11 +174,11 @@ describe('getAjvInstance', () => { after(() => { delete _instanceWrapper.instance; - delete process.env.SETTINGS_AJV_COERCE_TYPES; - delete process.env.SETTINGS_AJV_REMOVE_ADDITIONAL; - delete process.env.SETTINGS_AJV_USE_DEFAULTS; - delete process.env.SETTINGS_AJV_NULLABLE; - delete process.env.SETTINGS_AJV_ALL_ERRORS; + Config.remove('settings.ajv.coerceTypes'); + Config.remove('settings.ajv.removeAdditional'); + Config.remove('settings.ajv.useDefaults'); + Config.remove('settings.ajv.nullable'); + Config.remove('settings.ajv.allErrors'); }); }); diff --git a/packages/core/src/common/utils/render-error.util.spec.ts b/packages/core/src/common/utils/render-error.util.spec.ts index 7c0dfffe11..f401dbac3e 100644 --- a/packages/core/src/common/utils/render-error.util.spec.ts +++ b/packages/core/src/common/utils/render-error.util.spec.ts @@ -2,7 +2,7 @@ import { strictEqual } from 'assert'; // FoalTS -import { Context, isHttpResponseInternalServerError } from '../../core'; +import { Config, Context, isHttpResponseInternalServerError } from '../../core'; import { renderError } from './render-error.util'; describe('renderError', () => { @@ -11,7 +11,7 @@ describe('renderError', () => { before(() => ctx = new Context({})); - afterEach(() => delete process.env.SETTINGS_DEBUG); + afterEach(() => Config.remove('settings.debug')); const default500page = '<html><head><title>INTERNAL SERVER ERROR</title></head><body>' + '<h1>500 - INTERNAL SERVER ERROR</h1></body></html>'; @@ -29,7 +29,7 @@ describe('renderError', () => { it('should return a response which body is the default html 500 page with no stack' + ' if debug is false.', async () => { - process.env.SETTINGS_DEBUG = 'false'; + Config.set('settings.debug', false); const response = await renderError(new Error(), ctx); strictEqual(response.body, default500page); @@ -37,7 +37,8 @@ describe('renderError', () => { it('should return a response which body is the debug html 500 page with a stack' + ' if debug is true.', async () => { - process.env.SETTINGS_DEBUG = 'true'; + Config.set('settings.debug', true); + const err = new Error('This is an error'); const response = await renderError(err, ctx); diff --git a/packages/core/src/common/utils/render.util.spec.ts b/packages/core/src/common/utils/render.util.spec.ts index f4acb1746c..c75cb1d03b 100644 --- a/packages/core/src/common/utils/render.util.spec.ts +++ b/packages/core/src/common/utils/render.util.spec.ts @@ -4,7 +4,7 @@ import { existsSync, mkdirSync, rmdirSync, unlinkSync, writeFileSync } from 'fs' import { join } from 'path'; // FoalTS -import { HttpResponseOK } from '../../core'; +import { Config, HttpResponseOK } from '../../core'; import { render, renderToString } from './render.util'; const ejsTemplate = 'Hello <%= name %>! How are you?'; @@ -94,10 +94,11 @@ describe('render', () => { describe('given the configuration key "settings.templateEngine" is defined', () => { - afterEach(() => delete process.env.SETTINGS_TEMPLATE_ENGINE); + afterEach(() => Config.remove('settings.templateEngine')); it('should throw an Error if the given template engine is not compatible with Foal.', async () => { - process.env.SETTINGS_TEMPLATE_ENGINE = 'rimraf'; // A random package + Config.set('settings.templateEngine', 'rimraf'); // A random package + try { await render('./templates/template.default.html', {}, __dirname); throw new Error('An error should have been thrown'); @@ -110,7 +111,8 @@ describe('render', () => { }); it('should render the template with the given template engine (renderToString).', async () => { - process.env.SETTINGS_TEMPLATE_ENGINE = '@foal/ejs'; + Config.set('settings.templateEngine', '@foal/ejs'); + const name = 'Foobar'; const expected = `Hello ${name}! How are you?`; const actual = await render('./templates/template.ejs.html', { name }, __dirname); @@ -119,7 +121,8 @@ describe('render', () => { }); it('should render the template with the given template engine (Express).', async () => { - process.env.SETTINGS_TEMPLATE_ENGINE = 'ejs'; + Config.set('settings.templateEngine', 'ejs'); + const name = 'Foobar'; const expected = `Hello ${name}! How are you?`; const actual = await render('./templates/template.ejs.html', { name }, __dirname); @@ -128,7 +131,8 @@ describe('render', () => { }); it('should render the template with the given template engine (Express: twig).', async () => { - process.env.SETTINGS_TEMPLATE_ENGINE = 'twig'; + Config.set('settings.templateEngine', 'twig'); + const users = [ { name: 'John' }, { name: 'Mary'} ]; const expected = ' John Mary '; const actual = await render('./templates/template.twig.html', { users }, __dirname); @@ -137,7 +141,8 @@ describe('render', () => { }); it('should throw errors returned by the given template engine (renderToString).', async () => { - process.env.SETTINGS_TEMPLATE_ENGINE = '@foal/ejs'; + Config.set('settings.templateEngine', '@foal/ejs'); + try { await render('./templates/template.ejs.html', {}, __dirname); throw new Error('An error should have been thrown'); @@ -147,7 +152,8 @@ describe('render', () => { }); it('should throw errors returned by the given template engine (Express).', async () => { - process.env.SETTINGS_TEMPLATE_ENGINE = 'ejs'; + Config.set('settings.templateEngine', 'ejs'); + try { await render('./templates/template.ejs.html', {}, __dirname); throw new Error('An error should have been thrown'); diff --git a/packages/core/src/core/config/config.spec.ts b/packages/core/src/core/config/config.spec.ts index 7f14d2c11e..8beb6fc2df 100644 --- a/packages/core/src/core/config/config.spec.ts +++ b/packages/core/src/core/config/config.spec.ts @@ -95,6 +95,16 @@ describe('Config', () => { strictEqual(Config.get('a.b.c'), 2); }); + context('given the static method "remove" has been called after', () => { + + beforeEach(() => Config.remove('a.b.c')); + + it('should not return the configuration value.', () => { + strictEqual(Config.get('a.b.c'), undefined); + }); + + }); + }); function testConfigFile(path: string, fileContent: string, nodeEnv?: string): void { diff --git a/packages/core/src/core/config/config.ts b/packages/core/src/core/config/config.ts index 749a4c9090..1c6c757126 100644 --- a/packages/core/src/core/config/config.ts +++ b/packages/core/src/core/config/config.ts @@ -134,6 +134,10 @@ export class Config { this.testConfig.set(key, value); } + static remove(key: string): void { + this.testConfig.delete(key); + } + private static yaml: any; private static config: { [key: string ]: any } | null = null; private static testConfig: Map<string, string|number|boolean> = new Map(); diff --git a/packages/core/src/core/hooks.spec.ts b/packages/core/src/core/hooks.spec.ts index 3c157fd752..a1c4e29f1b 100644 --- a/packages/core/src/core/hooks.spec.ts +++ b/packages/core/src/core/hooks.spec.ts @@ -5,6 +5,7 @@ import { deepStrictEqual, strictEqual } from 'assert'; import 'reflect-metadata'; // FoalTS +import { Config } from './config'; import { getHookFunction, getHookFunctions, Hook, HookFunction, MergeHooks } from './hooks'; import { ApiUseTag, getApiUsedTags } from './openapi'; @@ -13,7 +14,7 @@ describe('Hook', () => { const hook1: HookFunction = () => { return; }; const hook2: HookFunction = () => undefined; - afterEach(() => delete process.env.SETTINGS_OPENAPI_USE_HOOKS); + afterEach(() => Config.remove('settings.openapi.useHooks')); it('should add the hook to the metadata hooks on the method class.', () => { class Foobar { @@ -54,7 +55,8 @@ describe('Hook', () => { it( 'should apply the OpenAPI decorators if options.openapi is undefined and settings.openapi.useHooks is true.', () => { - process.env.SETTINGS_OPENAPI_USE_HOOKS = 'true'; + Config.set('settings.openapi.useHooks', true); + class Foobar { @Hook(hook1, [ ApiUseTag('tag1'), @@ -71,7 +73,8 @@ describe('Hook', () => { it( 'should NOT apply the OpenAPI decorators if options.openapi is undefined and settings.openapi.useHooks is false.', () => { - process.env.SETTINGS_OPENAPI_USE_HOOKS = 'false'; + Config.set('settings.openapi.useHooks', false); + class Foobar { @Hook(hook1, [ ApiUseTag('tag1'), @@ -104,7 +107,8 @@ describe('Hook', () => { it( 'should apply the OpenAPI decorators if options.openapi is true and settings.openapi.useHooks is true.', () => { - process.env.SETTINGS_OPENAPI_USE_HOOKS = 'true'; + Config.set('settings.openapi.useHooks', true); + class Foobar { @Hook(hook1, [ ApiUseTag('tag1'), @@ -121,7 +125,8 @@ describe('Hook', () => { it( 'should apply the OpenAPI decorators if options.openapi is true and settings.openapi.useHooks is false.', () => { - process.env.SETTINGS_OPENAPI_USE_HOOKS = 'false'; + Config.set('settings.openapi.useHooks', false); + class Foobar { @Hook(hook1, [ ApiUseTag('tag1'), @@ -154,7 +159,8 @@ describe('Hook', () => { it( 'should NOT apply the OpenAPI decorators if options.openapi is false and settings.openapi.useHooks is true.', () => { - process.env.SETTINGS_OPENAPI_USE_HOOKS = 'true'; + Config.set('settings.openapi.useHooks', true); + class Foobar { @Hook(hook1, [ ApiUseTag('tag1'), @@ -171,7 +177,8 @@ describe('Hook', () => { it( 'should NOT apply the OpenAPI decorators if options.openapi is false and settings.openapi.useHooks is false.', () => { - process.env.SETTINGS_OPENAPI_USE_HOOKS = 'false'; + Config.set('settings.openapi.useHooks', false); + class Foobar { @Hook(hook1, [ ApiUseTag('tag1'), diff --git a/packages/core/src/core/openapi/openapi.service.spec.ts b/packages/core/src/core/openapi/openapi.service.spec.ts index fab078c990..c44b09867e 100644 --- a/packages/core/src/core/openapi/openapi.service.spec.ts +++ b/packages/core/src/core/openapi/openapi.service.spec.ts @@ -2,6 +2,7 @@ import { deepStrictEqual, strictEqual, throws } from 'assert'; // FoalTS +import { Config } from '../config'; import { createService } from '../service-manager'; import { IApiComponents, IOpenAPI } from './interfaces'; import { OpenApi } from './openapi.service'; @@ -11,11 +12,11 @@ describe('OpenApi', () => { let service: OpenApi; beforeEach(() => { - process.env.SETTINGS_OPENAPI_ENABLED = 'true'; + Config.set('settings.openapi.enabled', true); service = createService(OpenApi); }); - afterEach(() => delete process.env.SETTINGS_OPENAPI_ENABLED); + afterEach(() => Config.remove('settings.openapi.enabled')); describe('has a "addDocument" method that', () => { diff --git a/packages/core/src/core/routes/make-controller-routes.spec.ts b/packages/core/src/core/routes/make-controller-routes.spec.ts index 1734c59a9d..b13ce02289 100644 --- a/packages/core/src/core/routes/make-controller-routes.spec.ts +++ b/packages/core/src/core/routes/make-controller-routes.spec.ts @@ -3,6 +3,7 @@ import { deepStrictEqual, notDeepStrictEqual, notStrictEqual, ok, strictEqual, t // FoalTS import { controller } from '../../common/utils/controller.util'; +import { Config } from '../config'; import { Hook, HookFunction } from '../hooks'; import { Context, Get, HttpResponseOK, Post } from '../http'; import { @@ -337,9 +338,9 @@ describe('makeControllerRoutes', () => { }; const openApi = services.get(OpenApi); - beforeEach(() => process.env.SETTINGS_OPENAPI_ENABLED = 'true'); + beforeEach(() => Config.set('settings.openapi.enabled', true)); - afterEach(() => delete process.env.SETTINGS_OPENAPI_ENABLED); + afterEach(() => Config.remove('settings.openapi.enabled')); it('but not the other controllers.', () => { @ApiInfo(infoMetadata) diff --git a/packages/core/src/core/service-manager.spec.ts b/packages/core/src/core/service-manager.spec.ts index 00f134497e..d21874352e 100644 --- a/packages/core/src/core/service-manager.spec.ts +++ b/packages/core/src/core/service-manager.spec.ts @@ -7,7 +7,7 @@ import { ConcreteSessionStore } from '@foal/internal-test'; // FoalTS import { existsSync, mkdirSync, rmdirSync, unlinkSync, writeFileSync } from 'fs'; import { join } from 'path'; -import { ConfigNotFoundError } from './config'; +import { Config, ConfigNotFoundError } from './config'; import { createService, dependency, Dependency, IDependency, ServiceManager } from './service-manager'; describe('dependency', () => { @@ -508,7 +508,7 @@ describe('ServiceManager', () => { describe('and if it has a static property "concreteClassConfigPath"', () => { - beforeEach(() => delete process.env.SETTINGS_TOTO); + beforeEach(() => Config.remove('settings.toto')); it('should throw an Error if Service.concreteClassConfigPath is not a string.', () => { abstract class Foobar { @@ -525,7 +525,7 @@ describe('ServiceManager', () => { }); it('should throw an Error if Service.concreteClassName is not defined.', () => { - process.env.SETTINGS_TOTO = '@foal/internal-test'; + Config.set('settings.toto', '@foal/internal-test'); abstract class Foobar { static concreteClassConfigPath = 'settings.toto'; @@ -540,7 +540,7 @@ describe('ServiceManager', () => { }); it('should throw an Error if Service.concreteClassName is not a string.', () => { - process.env.SETTINGS_TOTO = '@foal/internal-test'; + Config.set('settings.toto', '@foal/internal-test'); abstract class Foobar { static concreteClassConfigPath = 'settings.toto'; @@ -558,7 +558,7 @@ describe('ServiceManager', () => { describe('when the concrete class path is a package name (does not start by "./")', () => { it('should throw an Error if the package is not installed.', () => { - process.env.SETTINGS_TOTO = 'uninstalledPackage'; + Config.set('settings.toto', 'uninstalledPackage'); abstract class Foobar { static concreteClassConfigPath = 'settings.toto'; @@ -574,7 +574,7 @@ describe('ServiceManager', () => { }); it('should throw an Error if the specified concrete class is not found in the package.', () => { - process.env.SETTINGS_TOTO = '@foal/internal-test'; + Config.set('settings.toto', '@foal/internal-test'); abstract class Foobar { static concreteClassConfigPath = 'settings.toto'; @@ -591,7 +591,7 @@ describe('ServiceManager', () => { }); it('should throw an Error if the specified concrete class is actually not a class.', () => { - process.env.SETTINGS_TOTO = '@foal/internal-test'; + Config.set('settings.toto', '@foal/internal-test'); abstract class Foobar { static concreteClassConfigPath = 'settings.toto'; @@ -607,7 +607,7 @@ describe('ServiceManager', () => { }); it('should return the concrete class instance.', () => { - process.env.SETTINGS_TOTO = '@foal/internal-test'; + Config.set('settings.toto', '@foal/internal-test'); abstract class Foobar { static concreteClassConfigPath = 'settings.toto'; @@ -745,7 +745,7 @@ describe('ServiceManager', () => { describe('when the concrete class path is "local"', () => { - beforeEach(() => process.env.SETTINGS_TOTO = 'local'); + beforeEach(() => Config.set('settings.toto', 'local')); it('should throw an error if Service.defaultConcreteClassPath is not defined.', () => { abstract class Foobar { @@ -789,7 +789,7 @@ describe('ServiceManager', () => { }); it('should throw an Error if the file does not exist.', () => { - process.env.SETTINGS_TOTO = './foo'; + Config.set('settings.toto', './foo'); abstract class Foobar { static concreteClassConfigPath = 'settings.toto'; @@ -805,7 +805,7 @@ describe('ServiceManager', () => { }); it('should throw an Error if the specified concrete class is not found in the package.', () => { - process.env.SETTINGS_TOTO = './service-manager.test2'; + Config.set('settings.toto', './service-manager.test2'); abstract class Foobar { static concreteClassConfigPath = 'settings.toto'; @@ -822,7 +822,7 @@ describe('ServiceManager', () => { }); it('should throw an Error if the specified concrete class is actually not a class.', () => { - process.env.SETTINGS_TOTO = './service-manager.test2'; + Config.set('settings.toto', './service-manager.test2'); abstract class Foobar { static concreteClassConfigPath = 'settings.toto'; @@ -839,7 +839,7 @@ describe('ServiceManager', () => { }); it('should return the concrete class instance.', () => { - process.env.SETTINGS_TOTO = './service-manager.test2'; + Config.set('settings.toto', './service-manager.test2'); abstract class Foobar { static concreteClassConfigPath = 'settings.toto'; diff --git a/packages/core/src/express/create-app.spec.ts b/packages/core/src/express/create-app.spec.ts index 881d3b6815..78ee62159b 100644 --- a/packages/core/src/express/create-app.spec.ts +++ b/packages/core/src/express/create-app.spec.ts @@ -9,7 +9,7 @@ import * as request from 'supertest'; // FoalTS import { existsSync, mkdirSync, rmdirSync, unlinkSync, writeFileSync } from 'fs'; import { - Context, Delete, dependency, Get, Head, HttpResponseOK, OpenApi, Options, Patch, Post, Put, ServiceManager + Config, Context, Delete, dependency, Get, Head, HttpResponseOK, OpenApi, Options, Patch, Post, Put, ServiceManager } from '../core'; import { createAndInitApp, createApp, OPENAPI_SERVICE_ID } from './create-app'; @@ -20,12 +20,12 @@ describe('createApp', () => { mkdirSync('test-public'); } writeFileSync('test-public/hello-world.html', '<h1>Hello world!</h1>', 'utf8'); - process.env.SETTINGS_STATIC_PATH = 'test-public'; + Config.set('settings.staticPath', 'test-public'); }); after(() => { - delete process.env.SETTINGS_STATIC_PATH; - delete process.env.SETTINGS_DEBUG; + Config.remove('settings.staticPath'); + Config.remove('settings.debug'); if (existsSync('test-public/hello-world.html')) { unlinkSync('test-public/hello-world.html'); } @@ -35,9 +35,9 @@ describe('createApp', () => { }); afterEach(() => { - delete process.env.SETTINGS_STATIC_PATH_PREFIX; - delete process.env.SETTING_DEBUG; - delete process.env.SETTINGS_BODY_PARSER_LIMIT; + Config.remove('settings.staticPathPrefix'); + Config.remove('settings.debug'); + Config.remove('settings.bodyParser.limit'); }); it('should include security headers in HTTP responses.', async () => { @@ -87,7 +87,7 @@ describe('createApp', () => { }); it('should support custom path prefix when serving static files.', async () => { - process.env.SETTINGS_STATIC_PATH_PREFIX = '/prefix'; + Config.set('settings.staticPathPrefix', '/prefix'); const app = createApp(class { }); await request(app) @@ -231,7 +231,7 @@ describe('createApp', () => { }); it('should accept higher or lower request body size if this is specified in the configuration.', async () => { - process.env.SETTINGS_BODY_PARSER_LIMIT = '10'; + Config.set('settings.bodyParser.limit', 10); class MyController { @Post('/foo') @@ -315,7 +315,7 @@ describe('createApp', () => { }); it('should use the optional postMiddlewares if they are given (in good time).', () => { - process.env.SETTINGS_DEBUG = 'true'; + Config.set('settings.debug', true); class AppController { @Get('/a') diff --git a/packages/core/src/express/handle-errors.spec.ts b/packages/core/src/express/handle-errors.spec.ts index 53bffb658e..351e73ff86 100644 --- a/packages/core/src/express/handle-errors.spec.ts +++ b/packages/core/src/express/handle-errors.spec.ts @@ -6,7 +6,7 @@ import * as express from 'express'; import * as request from 'supertest'; // FoalTS -import { Context, HttpResponseOK } from '../core'; +import { Config, Context, HttpResponseOK } from '../core'; import { CreateAppOptions } from './create-app'; import { handleErrors } from './handle-errors'; @@ -14,7 +14,7 @@ describe('handleErrors', () => { describe('should return an error-handling middleware which', () => { - afterEach(() => delete process.env.SETTINGS_LOG_ERRORS); + afterEach(() => Config.remove('settings.logErrors')); it('should ignore Express client errors and forward them to the new error-handling middleware.', () => { const app = express() @@ -49,7 +49,8 @@ describe('handleErrors', () => { }); it('should not log the error if the value of the configuration key settings.logErrors is false.', async () => { - process.env.SETTINGS_LOG_ERRORS = 'false'; + Config.set('settings.logErrors', false); + let str = null; const logFn = (msg: string) => str = msg; const err = new Error(); diff --git a/packages/core/src/sessions/remove-session-cookie.spec.ts b/packages/core/src/sessions/remove-session-cookie.spec.ts index 49eb5bc0d5..6f24ee237f 100644 --- a/packages/core/src/sessions/remove-session-cookie.spec.ts +++ b/packages/core/src/sessions/remove-session-cookie.spec.ts @@ -1,5 +1,5 @@ import { strictEqual } from 'assert'; -import { HttpResponse, HttpResponseOK } from '../core'; +import { Config, HttpResponse, HttpResponseOK } from '../core'; import { SESSION_DEFAULT_COOKIE_HTTP_ONLY, SESSION_DEFAULT_COOKIE_NAME, @@ -63,22 +63,22 @@ describe('removeSessionCookie', () => { const cookieName = SESSION_DEFAULT_COOKIE_NAME + '2'; beforeEach(() => { - process.env.SETTINGS_SESSION_COOKIE_NAME = cookieName; - process.env.SETTINGS_SESSION_COOKIE_DOMAIN = 'example.com'; - process.env.SETTINGS_SESSION_COOKIE_HTTP_ONLY = 'false'; - process.env.SETTINGS_SESSION_COOKIE_PATH = '/foo'; - process.env.SETTINGS_SESSION_COOKIE_SAME_SITE = 'strict'; - process.env.SETTINGS_SESSION_COOKIE_SECURE = 'true'; + Config.set('settings.session.cookie.name', cookieName); + Config.set('settings.session.cookie.domain', 'example.com'); + Config.set('settings.session.cookie.httpOnly', false); + Config.set('settings.session.cookie.path', '/foo'); + Config.set('settings.session.cookie.sameSite', 'strict'); + Config.set('settings.session.cookie.secure', true); removeSessionCookie(response); }); afterEach(() => { - delete process.env.SETTINGS_SESSION_COOKIE_NAME; - delete process.env.SETTINGS_SESSION_COOKIE_DOMAIN; - delete process.env.SETTINGS_SESSION_COOKIE_HTTP_ONLY; - delete process.env.SETTINGS_SESSION_COOKIE_PATH; - delete process.env.SETTINGS_SESSION_COOKIE_SAME_SITE; - delete process.env.SETTINGS_SESSION_COOKIE_SECURE; + Config.remove('settings.session.cookie.name'); + Config.remove('settings.session.cookie.domain'); + Config.remove('settings.session.cookie.httpOnly'); + Config.remove('settings.session.cookie.path'); + Config.remove('settings.session.cookie.sameSite'); + Config.remove('settings.session.cookie.secure'); }); it('with the proper default name and value.', () => { @@ -122,9 +122,9 @@ describe('removeSessionCookie', () => { context('given the CSRF protection is enabled in the config', () => { - beforeEach(() => process.env.SETTINGS_SESSION_CSRF_ENABLED = 'true'); + beforeEach(() => Config.set('settings.session.csrf.enabled', true)); - afterEach(() => delete process.env.SETTINGS_SESSION_CSRF_ENABLED); + afterEach(() => Config.remove('settings.session.csrf.enabled')); describe('should set a session cookie in the response', () => { @@ -142,13 +142,11 @@ describe('removeSessionCookie', () => { context('given configuration options are provided', () => { beforeEach(() => { - process.env.SETTINGS_SESSION_COOKIE_SAME_SITE = 'strict'; + Config.set('settings.session.cookie.sameSite', 'strict'); removeSessionCookie(response); }); - afterEach(() => { - delete process.env.SETTINGS_SESSION_COOKIE_SAME_SITE; - }); + afterEach(() => Config.remove('settings.session.cookie.sameSite')); it('with the proper default "sameSite" directive.', () => { const { options } = response.getCookie(SESSION_DEFAULT_COOKIE_NAME); @@ -207,22 +205,22 @@ describe('removeSessionCookie', () => { const csrfCookieName = SESSION_DEFAULT_CSRF_COOKIE_NAME + '2'; beforeEach(() => { - process.env.SETTINGS_SESSION_CSRF_COOKIE_NAME = csrfCookieName; - process.env.SETTINGS_SESSION_COOKIE_DOMAIN = 'example.com'; - process.env.SETTINGS_SESSION_COOKIE_HTTP_ONLY = 'true'; - process.env.SETTINGS_SESSION_COOKIE_PATH = '/foo'; - process.env.SETTINGS_SESSION_COOKIE_SAME_SITE = 'strict'; - process.env.SETTINGS_SESSION_COOKIE_SECURE = 'true'; + Config.set('settings.session.csrf.cookie.name', csrfCookieName); + Config.set('settings.session.cookie.domain', 'example.com'); + Config.set('settings.session.cookie.httpOnly', true); + Config.set('settings.session.cookie.path', '/foo'); + Config.set('settings.session.cookie.sameSite', 'strict'); + Config.set('settings.session.cookie.secure', 'true'); removeSessionCookie(response); }); afterEach(() => { - delete process.env.SETTINGS_SESSION_CSRF_COOKIE_NAME; - delete process.env.SETTINGS_SESSION_COOKIE_DOMAIN; - delete process.env.SETTINGS_SESSION_COOKIE_HTTP_ONLY; - delete process.env.SETTINGS_SESSION_COOKIE_PATH; - delete process.env.SETTINGS_SESSION_COOKIE_SAME_SITE; - delete process.env.SETTINGS_SESSION_COOKIE_SECURE; + Config.remove('settings.session.csrf.cookie.name'); + Config.remove('settings.session.cookie.domain'); + Config.remove('settings.session.cookie.httpOnly'); + Config.remove('settings.session.cookie.path'); + Config.remove('settings.session.cookie.sameSite'); + Config.remove('settings.session.cookie.secure'); }); it('with the proper default name and value.', () => { diff --git a/packages/core/src/sessions/session.spec.ts b/packages/core/src/sessions/session.spec.ts index 36603c2dbd..5fd7eaf921 100644 --- a/packages/core/src/sessions/session.spec.ts +++ b/packages/core/src/sessions/session.spec.ts @@ -2,7 +2,7 @@ import { deepStrictEqual, notStrictEqual, rejects, strictEqual } from 'assert'; // FoalTS -import { ConfigTypeError, createService } from '../core'; +import { Config, ConfigTypeError, createService } from '../core'; import { SESSION_DEFAULT_ABSOLUTE_TIMEOUT, SESSION_DEFAULT_INACTIVITY_TIMEOUT } from './constants'; import { Session } from './session'; import { SessionState } from './session-state.interface'; @@ -86,8 +86,8 @@ describe('Session', () => { describe('has a "isExpired" property that', () => { afterEach(() => { - delete process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_INACTIVITY; - delete process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_ABSOLUTE; + Config.remove('settings.session.expirationTimeouts.inactivity'); + Config.remove('settings.session.expirationTimeouts.absolute'); }); it('should return false is the session has not expired.', () => { @@ -131,7 +131,7 @@ describe('Session', () => { it('should return true is the session has expired (inactivity, custom timeout).', () => { const timeout = Math.floor(SESSION_DEFAULT_INACTIVITY_TIMEOUT / 2); - process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_INACTIVITY = timeout.toString(); + Config.set('settings.session.expirationTimeouts.inactivity', timeout); const session = new Session( store, @@ -147,7 +147,7 @@ describe('Session', () => { it('should return true is the session has expired (absolute, custom timeout).', () => { const timeout = Math.floor(SESSION_DEFAULT_ABSOLUTE_TIMEOUT / 2); - process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_ABSOLUTE = timeout.toString(); + Config.set('settings.session.expirationTimeouts.absolute', timeout); const session = new Session( store, @@ -166,8 +166,8 @@ describe('Session', () => { describe('has an "expirationTime" property that', () => { afterEach(() => { - delete process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_INACTIVITY; - delete process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_ABSOLUTE; + Config.get('settings.session.expirationTimeouts.inactivity'); + Config.get('settings.session.expirationTimeouts.absolute'); }); describe('should return the session expiration time in seconds', () => { @@ -191,7 +191,7 @@ describe('Session', () => { it('when updatedAt + timeout < createdAt + timeout (custom timeout).', () => { const timeout = Math.floor(SESSION_DEFAULT_INACTIVITY_TIMEOUT / 2); - process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_INACTIVITY = timeout.toString(); + Config.set('settings.session.expirationTimeouts.inactivity', timeout); const state: SessionState = { ...createState(), @@ -224,7 +224,7 @@ describe('Session', () => { it('when createdAt + timeout < updatedAt + timeout (custom timeout).', () => { const timeout = Math.floor(SESSION_DEFAULT_ABSOLUTE_TIMEOUT / 2); - process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_ABSOLUTE = timeout.toString(); + Config.set('settings.session.expirationTimeouts.absolute', timeout); const state: SessionState = { ...createState(), @@ -538,7 +538,7 @@ describe('Session', () => { describe('providing an idle timeout', () => { - afterEach(() => delete process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_INACTIVITY); + afterEach(() => Config.remove('settings.session.expirationTimeouts.inactivity')); it('from the framework default values.', async () => { await session.commit(); @@ -547,7 +547,7 @@ describe('Session', () => { }); it('from the configuration.', async () => { - process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_INACTIVITY = '1'; + Config.set('settings.session.expirationTimeouts.inactivity', 1); await session.commit(); // tslint:disable-next-line @@ -555,7 +555,7 @@ describe('Session', () => { }); it('and should throw an error if the configuration value is not a number.', async () => { - process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_INACTIVITY = 'a'; + Config.set('settings.session.expirationTimeouts.inactivity', 'a'); await rejects( () => session.commit(), @@ -564,7 +564,7 @@ describe('Session', () => { }); it('and should throw an error if the configuration value is negative.', async () => { - process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_INACTIVITY = '-1'; + Config.set('settings.session.expirationTimeouts.inactivity', -1); await rejects( () => session.commit(), @@ -780,13 +780,14 @@ describe('Session', () => { }); afterEach(() => { - delete process.env.SETTINGS_SESSION_GARBAGE_COLLECTOR_PERIODICITY; - delete process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_INACTIVITY; - delete process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_ABSOLUTE; + Config.remove('settings.session.garbageCollection.periodicity'); + Config.remove('settings.session.expirationTimeouts.inactivity'); + Config.remove('settings.session.expirationTimeouts.absolute'); }); it('with a frequency defined in the configuration (periodicity = 1).', async () => { - process.env.SETTINGS_SESSION_GARBAGE_COLLECTOR_PERIODICITY = '1'; + Config.set('settings.session.garbageCollector.periodicity', 1); + await session.commit(); deepStrictEqual(store.cleanUpExpiredSessionsCalledWith, { @@ -796,14 +797,15 @@ describe('Session', () => { }); it('with a frequency defined in the configuration (periodicity = 1 000 000 000).', async () => { - process.env.SETTINGS_SESSION_GARBAGE_COLLECTOR_PERIODICITY = '1000000000'; + Config.set('settings.session.garbageCollector.periodicity', 1000000000); + await session.commit(); strictEqual(store.cleanUpExpiredSessionsCalledWith, undefined); }); it('and should throw an error if the periodicity provided in the configuration is not a number.', async () => { - process.env.SETTINGS_SESSION_GARBAGE_COLLECTOR_PERIODICITY = 'a'; + Config.set('settings.session.garbageCollector.periodicity', 'a'); await rejects( () => session.commit(), @@ -812,10 +814,10 @@ describe('Session', () => { }); it('with idle and absolute timeouts defined in the configuration.', async () => { - process.env.SETTINGS_SESSION_GARBAGE_COLLECTOR_PERIODICITY = '1'; + Config.set('settings.session.garbageCollector.periodicity', 1); - process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_INACTIVITY = '15'; - process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_ABSOLUTE = '30'; + Config.set('settings.session.expirationTimeouts.inactivity', 15); + Config.set('settings.session.expirationTimeouts.absolute', 30); await session.commit(); @@ -828,7 +830,7 @@ describe('Session', () => { it( 'and should throw an error if the inactivity timeout provided in the configuration is not a number.', async () => { - process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_INACTIVITY = 'a'; + Config.set('settings.session.expirationTimeouts.inactivity', 'a'); await rejects( () => session.commit(), @@ -838,7 +840,7 @@ describe('Session', () => { ); it('and should throw an error the inactivity timeout provided in the configuration is negative.', async () => { - process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_INACTIVITY = '-1'; + Config.set('settings.session.expirationTimeouts.inactivity', -1); await rejects( () => session.commit(), @@ -849,7 +851,7 @@ describe('Session', () => { it( 'and should throw an error if the absolute timeout provided in the configuration is not a number.', async () => { - process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_ABSOLUTE = 'a'; + Config.set('settings.session.expirationTimeouts.absolute', 'a'); await rejects( () => session.commit(), @@ -859,7 +861,7 @@ describe('Session', () => { ); it('and should throw an error the inactivity timeout provided in the configuration is negative.', async () => { - process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_ABSOLUTE = '-1'; + Config.set('settings.session.expirationTimeouts.absolute', -1); await rejects( () => session.commit(), @@ -871,8 +873,8 @@ describe('Session', () => { 'and should throw an error if the absolute timeout provided in the configuration is lower ' + 'than the inactivity timeout.', async () => { - process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_INACTIVTY = '2'; - process.env.SETTINGS_SESSION_EXPIRATION_TIMEOUTS_ABSOLUTE = '1'; + Config.set('settings.session.expirationTimeouts.inactivity', 2); + Config.set('settings.session.expirationTimeouts.absolute', 1); await rejects( () => session.commit(), diff --git a/packages/core/src/sessions/set-session-cookie.spec.ts b/packages/core/src/sessions/set-session-cookie.spec.ts index 5dc0481539..5eb3377555 100644 --- a/packages/core/src/sessions/set-session-cookie.spec.ts +++ b/packages/core/src/sessions/set-session-cookie.spec.ts @@ -1,5 +1,5 @@ import { deepStrictEqual, strictEqual } from 'assert'; -import { HttpResponse, HttpResponseOK } from '../core'; +import { Config, HttpResponse, HttpResponseOK } from '../core'; import { SESSION_DEFAULT_COOKIE_HTTP_ONLY, SESSION_DEFAULT_COOKIE_NAME, @@ -85,22 +85,22 @@ describe('setSessionCookie', () => { const cookieName = SESSION_DEFAULT_COOKIE_NAME + '2'; beforeEach(() => { - process.env.SETTINGS_SESSION_COOKIE_NAME = cookieName; - process.env.SETTINGS_SESSION_COOKIE_DOMAIN = 'example.com'; - process.env.SETTINGS_SESSION_COOKIE_HTTP_ONLY = 'false'; - process.env.SETTINGS_SESSION_COOKIE_PATH = '/foo'; - process.env.SETTINGS_SESSION_COOKIE_SAME_SITE = 'strict'; - process.env.SETTINGS_SESSION_COOKIE_SECURE = 'true'; + Config.set('settings.session.cookie.name', cookieName); + Config.set('settings.session.cookie.domain', 'example.com'); + Config.set('settings.session.cookie.httpOnly', false); + Config.set('settings.session.cookie.path', '/foo'); + Config.set('settings.session.cookie.sameSite', 'strict'); + Config.set('settings.session.cookie.secure', true); setSessionCookie(response, session); }); afterEach(() => { - delete process.env.SETTINGS_SESSION_COOKIE_NAME; - delete process.env.SETTINGS_SESSION_COOKIE_DOMAIN; - delete process.env.SETTINGS_SESSION_COOKIE_HTTP_ONLY; - delete process.env.SETTINGS_SESSION_COOKIE_PATH; - delete process.env.SETTINGS_SESSION_COOKIE_SAME_SITE; - delete process.env.SETTINGS_SESSION_COOKIE_SECURE; + Config.remove('settings.session.cookie.name'); + Config.remove('settings.session.cookie.domain'); + Config.remove('settings.session.cookie.httpOnly'); + Config.remove('settings.session.cookie.path'); + Config.remove('settings.session.cookie.sameSite'); + Config.remove('settings.session.cookie.secure'); }); it('with the proper default name and value.', () => { @@ -144,9 +144,9 @@ describe('setSessionCookie', () => { context('given the CSRF protection is enabled in the config', () => { - beforeEach(() => process.env.SETTINGS_SESSION_CSRF_ENABLED = 'true'); + beforeEach(() => Config.set('settings.session.csrf.enabled', true)); - afterEach(() => delete process.env.SETTINGS_SESSION_CSRF_ENABLED); + afterEach(() => Config.remove('settings.session.csrf.enabled')); describe('should set a session cookie in the response', () => { @@ -164,12 +164,12 @@ describe('setSessionCookie', () => { context('given configuration options are provided', () => { beforeEach(() => { - process.env.SETTINGS_SESSION_COOKIE_SAME_SITE = 'strict'; + Config.set('settings.session.cookie.sameSite', 'strict'); setSessionCookie(response, session); }); afterEach(() => { - delete process.env.SETTINGS_SESSION_COOKIE_SAME_SITE; + Config.remove('settings.session.cookie.sameSite') }); it('with the proper default "sameSite" directive.', () => { @@ -229,22 +229,22 @@ describe('setSessionCookie', () => { const csrfCookieName = SESSION_DEFAULT_CSRF_COOKIE_NAME + '2'; beforeEach(() => { - process.env.SETTINGS_SESSION_CSRF_COOKIE_NAME = csrfCookieName; - process.env.SETTINGS_SESSION_COOKIE_DOMAIN = 'example.com'; - process.env.SETTINGS_SESSION_COOKIE_HTTP_ONLY = 'true'; - process.env.SETTINGS_SESSION_COOKIE_PATH = '/foo'; - process.env.SETTINGS_SESSION_COOKIE_SAME_SITE = 'strict'; - process.env.SETTINGS_SESSION_COOKIE_SECURE = 'true'; + Config.set('settings.session.csrf.cookie.name', csrfCookieName); + Config.set('settings.session.cookie.domain', 'example.com'); + Config.set('settings.session.cookie.httpOnly', true); + Config.set('settings.session.cookie.path', '/foo'); + Config.set('settings.session.cookie.sameSite', 'strict'); + Config.set('settings.session.cookie.secure', true); setSessionCookie(response, session); }); afterEach(() => { - delete process.env.SETTINGS_SESSION_CSRF_COOKIE_NAME; - delete process.env.SETTINGS_SESSION_COOKIE_DOMAIN; - delete process.env.SETTINGS_SESSION_COOKIE_HTTP_ONLY; - delete process.env.SETTINGS_SESSION_COOKIE_PATH; - delete process.env.SETTINGS_SESSION_COOKIE_SAME_SITE; - delete process.env.SETTINGS_SESSION_COOKIE_SECURE; + Config.remove('settings.session.csrf.cookie.name'); + Config.remove('settings.session.cookie.domain'); + Config.remove('settings.session.cookie.httpOnly'); + Config.remove('settings.session.cookie.path'); + Config.remove('settings.session.cookie.sameSite'); + Config.remove('settings.session.cookie.secure'); }); it('with the proper default name and value.', () => { diff --git a/packages/core/src/sessions/token.hook.spec.ts b/packages/core/src/sessions/token.hook.spec.ts index 9a83311f5a..4dda165e87 100644 --- a/packages/core/src/sessions/token.hook.spec.ts +++ b/packages/core/src/sessions/token.hook.spec.ts @@ -3,6 +3,7 @@ import { deepStrictEqual, doesNotReject, rejects, strictEqual } from 'assert'; // FoalTS import { + Config, ConfigNotFoundError, Context, getApiComponents, @@ -118,8 +119,8 @@ export function testSuite(Token: typeof TokenRequired|typeof TokenOptional, requ }); afterEach(() => { - delete process.env.SETTINGS_SESSION_COOKIE_NAME; - delete process.env.SETTINGS_SESSION_STORE; + Config.remove('settings.session.cookie.name'); + Config.remove('settings.session.store'); }); context('given no session store class is provided as option', () => { @@ -134,7 +135,7 @@ export function testSuite(Token: typeof TokenRequired|typeof TokenOptional, requ }); it('should use the session store package provided in settings.session.store.', () => { - process.env.SETTINGS_SESSION_STORE = '@foal/internal-test'; + Config.set('settings.session.store', '@foal/internal-test'); return doesNotReject(() => hook(ctx, services)); }); @@ -352,10 +353,10 @@ export function testSuite(Token: typeof TokenRequired|typeof TokenOptional, requ context('given settings.session.csrf.enabled is true', () => { beforeEach(() => { - process.env.SETTINGS_SESSION_CSRF_ENABLED = 'true'; + Config.set('settings.session.csrf.enabled', true); }); - afterEach(() => delete process.env.SETTINGS_SESSION_CSRF_ENABLED); + afterEach(() => Config.remove('settings.session.csrf.enabled')); context('given options.cookie is false or not defined', () => { @@ -498,7 +499,7 @@ export function testSuite(Token: typeof TokenRequired|typeof TokenOptional, requ describe('should set Context.session', () => { - afterEach(() => delete process.env.SETTINGS_SESSION_COOKIE_NAME); + afterEach(() => Config.remove('settings.session.cookie.name')); it('with the session.', async () => { ctx = createContext({ Authorization: `Bearer ${anonymousSessionID}`}); @@ -516,7 +517,7 @@ export function testSuite(Token: typeof TokenRequired|typeof TokenOptional, requ // This test might be put in a better place. it('with the session (custom cookie name).', async () => { - process.env.SETTINGS_SESSION_COOKIE_NAME = 'auth2'; + Config.set('settings.session.cookie.name', 'auth2'); ctx = createContext({}, { auth2: anonymousSessionID }); hook = getHookFunction(Token({ store: Store, cookie: true })); @@ -905,7 +906,7 @@ export function testSuite(Token: typeof TokenRequired|typeof TokenOptional, requ describe('should define an API specification', () => { - afterEach(() => delete process.env.SETTINGS_SESSION_CSRF_ENABLED); + afterEach(() => Config.remove('settings.session.csrf.enabled')); it('unless options.openapi is false.', () => { @Token({ openapi: false }) @@ -934,7 +935,8 @@ export function testSuite(Token: typeof TokenRequired|typeof TokenOptional, requ }); it('with the proper security scheme (cookie) (cookie name different).', () => { - process.env.SETTINGS_SESSION_COOKIE_NAME = 'auth2'; + Config.set('settings.session.cookie.name', 'auth2'); + @Token({ cookie: true }) class Foobar {} @@ -1005,7 +1007,8 @@ export function testSuite(Token: typeof TokenRequired|typeof TokenOptional, requ }); it('with the proper API responses (no cookie & csrf protection).', () => { - process.env.SETTINGS_SESSION_CSRF_ENABLED = 'true'; + Config.set('settings.session.csrf.enabled', true); + testResponses({ cookie: false }); }); @@ -1014,7 +1017,8 @@ export function testSuite(Token: typeof TokenRequired|typeof TokenOptional, requ }); it('with the proper API responses (cookie & csrf protection).', () => { - process.env.SETTINGS_SESSION_CSRF_ENABLED = 'true'; + Config.set('settings.session.csrf.enabled', true); + @Token({ cookie: true }) class Foobar {} @@ -1056,7 +1060,8 @@ export function testSuite(Token: typeof TokenRequired|typeof TokenOptional, requ }); it('with the proper API responses (no cookie & csrf protection).', () => { - process.env.SETTINGS_SESSION_CSRF_ENABLED = 'true'; + Config.set('settings.session.csrf.enabled', true); + testResponses({ cookie: false }); }); @@ -1065,7 +1070,8 @@ export function testSuite(Token: typeof TokenRequired|typeof TokenOptional, requ }); it('with the proper API responses (cookie & csrf protection).', () => { - process.env.SETTINGS_SESSION_CSRF_ENABLED = 'true'; + Config.set('settings.session.csrf.enabled', true); + @Token({ cookie: true }) class Foobar {} diff --git a/packages/graphql/src/acceptance-test.spec.ts b/packages/graphql/src/acceptance-test.spec.ts index aed5407cf5..07ce18c573 100644 --- a/packages/graphql/src/acceptance-test.spec.ts +++ b/packages/graphql/src/acceptance-test.spec.ts @@ -20,7 +20,7 @@ import { existsSync, mkdirSync, rmdirSync, unlinkSync, writeFileSync } from 'fs' import { get, Server } from 'http'; // 3p -import { controller, createApp, dependency } from '@foal/core'; +import { Config, controller, createApp, dependency } from '@foal/core'; import { InMemoryCache } from 'apollo-cache-inmemory'; import { ApolloClient } from 'apollo-client'; import { HttpLink } from 'apollo-link-http'; @@ -57,7 +57,7 @@ describe('[Acceptance test] GraphQLController', () => { if (server) { server.close(); } - delete process.env.SETTINGS_DEBUG; + Config.remove('settings.debug'); }); describe('should support common GraphQL clients such as', () => { @@ -166,7 +166,7 @@ describe('[Acceptance test] GraphQLController', () => { }); it('should include in the documentation an example on how to handle errors in resolvers.', async () => { - process.env.SETTINGS_DEBUG = 'false'; + Config.set('settings.debug', false); class Resolvers { @FormatError() diff --git a/packages/graphql/src/mask-and-log-error.spec.ts b/packages/graphql/src/mask-and-log-error.spec.ts index 8cd953ec4d..93a0347dd5 100644 --- a/packages/graphql/src/mask-and-log-error.spec.ts +++ b/packages/graphql/src/mask-and-log-error.spec.ts @@ -1,15 +1,19 @@ // std import { strictEqual } from 'assert'; +// 3p +import { Config } from '@foal/core'; + // FoalTS import { maskAndLogError } from './mask-and-log-error'; describe('maskAndLogError', () => { - afterEach(() => delete process.env.SETTINGS_DEBUG); + afterEach(() => Config.remove('settings.debug')); it('should return the error if the config key settings.debug is true.', () => { - process.env.SETTINGS_DEBUG = 'true'; + Config.set('settings.debug', true); + const err = new Error('This is an error (that should be shown in the logs)'); const result = maskAndLogError(err); diff --git a/packages/jwt/src/get-secret-or-private-key.util.spec.ts b/packages/jwt/src/get-secret-or-private-key.util.spec.ts index db51a456bb..7d123420c6 100644 --- a/packages/jwt/src/get-secret-or-private-key.util.spec.ts +++ b/packages/jwt/src/get-secret-or-private-key.util.spec.ts @@ -1,4 +1,4 @@ -import { ConfigTypeError } from '@foal/core'; +import { Config, ConfigTypeError } from '@foal/core'; import { deepStrictEqual, strictEqual, throws } from 'assert'; import { getSecretOrPrivateKey } from './get-secret-or-private-key.util'; @@ -8,12 +8,13 @@ describe('getSecretOrPrivateKey', () => { const secret = 'secretX'; - beforeEach(() => process.env.SETTINGS_JWT_SECRET = secret); + beforeEach(() => Config.set('settings.jwt.secret', secret)); - afterEach(() => delete process.env.SETTINGS_JWT_SECRET); + afterEach(() => Config.remove('settings.jwt.secret')); + + it('should throw a ConfigTypeError if the value is not a string.', () => { + Config.set('settings.jwt.secret', 2); - xit('should throw a ConfigTypeError if the value is not a string.', () => { - process.env.SETTINGS_JWT_SECRET = 2 as any; return throws( () => getSecretOrPrivateKey(), new ConfigTypeError('settings.jwt.secret', 'string', 'number'), @@ -33,12 +34,13 @@ describe('getSecretOrPrivateKey', () => { const encoding = 'base64'; - beforeEach(() => process.env.SETTINGS_JWT_SECRET_ENCODING = 'base64'); + beforeEach(() => Config.set('settings.jwt.secretEncoding', 'base64')); + + afterEach(() => Config.remove('settings.jwt.secretEncoding')); - afterEach(() => delete process.env.SETTINGS_JWT_SECRET_ENCODING); + it('should throw a ConfigTypeError if the value is not a string.', () => { + Config.set('settings.jwt.secretEncoding', 2); - xit('should throw a ConfigTypeError if the value is not a string.', () => { - process.env.SETTINGS_JWT_SECRET_ENCODING = 2 as any; return throws( () => getSecretOrPrivateKey(), new ConfigTypeError('settings.jwt.secretEncoding', 'string', 'number'), @@ -62,12 +64,13 @@ describe('getSecretOrPrivateKey', () => { const privateKey = 'privateKeyX'; - beforeEach(() => process.env.SETTINGS_JWT_PRIVATE_KEY = privateKey); + beforeEach(() => Config.set('settings.jwt.privateKey', privateKey)); + + afterEach(() => Config.remove('settings.jwt.privateKey')); - afterEach(() => delete process.env.SETTINGS_JWT_PRIVATE_KEY); + it('should throw a ConfigTypeError if the value is not a string.', () => { + Config.set('settings.jwt.privateKey', 2); - xit('should throw a ConfigTypeError if the value is not a string.', () => { - process.env.SETTINGS_JWT_PRIVATE_KEY = 2 as any; return throws( () => getSecretOrPrivateKey(), new ConfigTypeError('settings.jwt.privateKey', 'string', 'number'), diff --git a/packages/jwt/src/get-secret-or-public-key.util.spec.ts b/packages/jwt/src/get-secret-or-public-key.util.spec.ts index 9df9274d55..b158a7d966 100644 --- a/packages/jwt/src/get-secret-or-public-key.util.spec.ts +++ b/packages/jwt/src/get-secret-or-public-key.util.spec.ts @@ -1,4 +1,4 @@ -import { ConfigTypeError } from '@foal/core'; +import { Config, ConfigTypeError } from '@foal/core'; import { deepStrictEqual, strictEqual, throws } from 'assert'; import { getSecretOrPublicKey } from './get-secret-or-public-key.util'; @@ -8,12 +8,13 @@ describe('getSecretOrPublicKey', () => { const secret = 'secretX'; - beforeEach(() => process.env.SETTINGS_JWT_SECRET = secret); + beforeEach(() => Config.set('settings.jwt.secret', secret)); - afterEach(() => delete process.env.SETTINGS_JWT_SECRET); + afterEach(() => Config.remove('settings.jwt.secret')); + + it('should throw a ConfigTypeError if the value is not a string.', () => { + Config.set('settings.jwt.secret', 2); - xit('should throw a ConfigTypeError if the value is not a string.', () => { - process.env.SETTINGS_JWT_SECRET = 2 as any; return throws( () => getSecretOrPublicKey(), new ConfigTypeError('settings.jwt.secret', 'string', 'number'), @@ -33,12 +34,13 @@ describe('getSecretOrPublicKey', () => { const encoding = 'base64'; - beforeEach(() => process.env.SETTINGS_JWT_SECRET_ENCODING = 'base64'); + beforeEach(() => Config.set('settings.jwt.secretEncoding', 'base64')); + + afterEach(() => Config.remove('settings.jwt.secretEncoding')); - afterEach(() => delete process.env.SETTINGS_JWT_SECRET_ENCODING); + it('should throw a ConfigTypeError if the value is not a string.', () => { + Config.set('settings.jwt.secretEncoding', 2); - xit('should throw a ConfigTypeError if the value is not a string.', () => { - process.env.SETTINGS_JWT_SECRET_ENCODING = 2 as any; return throws( () => getSecretOrPublicKey(), new ConfigTypeError('settings.jwt.secretEncoding', 'string', 'number'), @@ -62,12 +64,13 @@ describe('getSecretOrPublicKey', () => { const publicKey = 'publicKeyX'; - beforeEach(() => process.env.SETTINGS_JWT_PUBLIC_KEY = publicKey); + beforeEach(() => Config.set('settings.jwt.publicKey', publicKey)); + + afterEach(() => Config.remove('settings.jwt.publicKey')); - afterEach(() => delete process.env.SETTINGS_JWT_PUBLIC_KEY); + it('should throw a ConfigTypeError if the value is not a string.', () => { + Config.set('settings.jwt.publicKey', 2); - xit('should throw a ConfigTypeError if the value is not a string.', () => { - process.env.SETTINGS_JWT_PUBLIC_KEY = 2 as any; return throws( () => getSecretOrPublicKey(), new ConfigTypeError('settings.jwt.publicKey', 'string', 'number'), diff --git a/packages/jwt/src/jwt-optional.hook.ts b/packages/jwt/src/jwt-optional.hook.ts index 3bc88fb645..23b8140188 100644 --- a/packages/jwt/src/jwt-optional.hook.ts +++ b/packages/jwt/src/jwt-optional.hook.ts @@ -23,7 +23,7 @@ import { JWT, JWTOptions } from './jwt.hook'; * @param {(token: string) => boolean|Promise<boolean>} [options.blacklist] - A function that takes a token * and returns true or false. If the returned value is true, then the hook returns a 401 error. * @param {boolean} [options.cookie] - If true, the hook expects the token to be sent in a cookie - * named `auth`. You can change the cookie name with the key `settings.jwt.cookieName` in the configuration. + * named `auth`. You can change the cookie name with the key `settings.jwt.cookie.name` in the configuration. * @param {VerifyOptions} [verifyOptions={}] - Options of the `jsonwebtoken` package. * @returns {HookDecorator} The hook. */ diff --git a/packages/jwt/src/jwt-required.hook.ts b/packages/jwt/src/jwt-required.hook.ts index db0325fa72..6319fd1fe0 100644 --- a/packages/jwt/src/jwt-required.hook.ts +++ b/packages/jwt/src/jwt-required.hook.ts @@ -23,7 +23,7 @@ import { JWT, JWTOptions } from './jwt.hook'; * @param {(token: string) => boolean|Promise<boolean>} [options.blacklist] - A function that takes a token * and returns true or false. If the returned value is true, then the hook returns a 401 error. * @param {boolean} [options.cookie] - If true, the hook expects the token to be sent in a cookie - * named `auth`. You can change the cookie name with the key `settings.jwt.cookieName` in the configuration. + * named `auth`. You can change the cookie name with the key `settings.jwt.cookie.name` in the configuration. * @param {VerifyOptions} [verifyOptions={}] - Options of the `jsonwebtoken` package. * @returns {HookDecorator} The hook. */ diff --git a/packages/jwt/src/jwt.hook.spec.ts b/packages/jwt/src/jwt.hook.spec.ts index 2a9d85479f..778459d235 100644 --- a/packages/jwt/src/jwt.hook.spec.ts +++ b/packages/jwt/src/jwt.hook.spec.ts @@ -3,6 +3,7 @@ import { deepStrictEqual, notStrictEqual, rejects, strictEqual } from 'assert'; // 3p import { + Config, Context, getApiComponents, getApiParameters, @@ -115,13 +116,13 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: hook = getHookFunction(JWT({ user: fetchUser })); services = new ServiceManager(); - process.env.SETTINGS_JWT_SECRET = secret; + Config.set('settings.jwt.secret', secret); }); afterEach(() => { - delete process.env.SETTINGS_JWT_SECRET; - delete process.env.SETTINGS_JWT_PUBLIC_KEY; - delete process.env.SETTINGS_JWT_COOKIE_NAME; + Config.remove('settings.jwt.secret'); + Config.remove('settings.jwt.publicKey'); + Config.remove('settings.jwt.cookie.name'); }); describe('should validate the request and', () => { @@ -340,7 +341,7 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: describe('should verify the token and', () => { - afterEach(() => delete process.env.SETTINGS_JWT_SECRET_ENCODING); + afterEach(() => Config.remove('settings.jwt.secretEncoding')); it('should return an HttpResponseUnauthorized object if the signature is invalid.', async () => { const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' @@ -365,7 +366,7 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: it('should throw an error if no secret or public key is set in the Config and options.secretOrPublicKey is' + ' not defined.', async () => { // Remove the secret. - delete process.env.SETTINGS_JWT_SECRET; + Config.remove('settings.jwt.secret'); const token = sign({}, secret); ctx = createContext({ Authorization: `Bearer ${token}` }); @@ -403,7 +404,7 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: + ' (different secret encoding).', async () => { const hook = getHookFunction(JWT()); - process.env.SETTINGS_JWT_SECRET_ENCODING = 'base64'; + Config.set('settings.jwt.secretEncoding', 'base64'); const token = sign({}, Buffer.from(secret, 'base64')); ctx = createContext({ Authorization: `Bearer ${token}` }); @@ -486,10 +487,11 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: sub = 'subX'; token = sign({ foo: 'bar' }, secret, { subject: sub }); csrfToken = sign({ foo2: 'bar' }, secret, { subject: sub }); - process.env.SETTINGS_JWT_CSRF_ENABLED = 'true'; + + Config.set('settings.jwt.csrf.enabled', true); }); - afterEach(() => delete process.env.SETTINGS_JWT_CSRF_ENABLED); + afterEach(() => Config.remove('settings.jwt.csrf.enabled')); context('given options.cookie is false or not defined', () => { @@ -686,9 +688,9 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: const csrfCookieName = JWT_DEFAULT_CSRF_COOKIE_NAME + '2'; - beforeEach(() => process.env.SETTINGS_JWT_CSRF_COOKIE_NAME = csrfCookieName); + beforeEach(() => Config.set('settings.jwt.csrf.cookie.name', csrfCookieName)); - afterEach(() => delete process.env.SETTINGS_JWT_CSRF_COOKIE_NAME); + afterEach(() => Config.remove('settings.jwt.csrf.cookie.name')); testCsrkToken((requestCsrfToken, cookieCsrfToken) => createContext( { 'X-XSRF-Token': requestCsrfToken }, @@ -743,7 +745,8 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: }); it('with the decoded payload (header & secret from options.secretOrPublicKey).', async () => { - delete process.env.SETTINGS_JWT_SECRET; + Config.remove('settings.jwt.secret'); + const secretOrPublicKey = async (header: any, payload: any) => { deepStrictEqual(header, { alg: 'HS256', @@ -764,8 +767,8 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: }); it('with the decoded payload (header & public key).', async () => { - delete process.env.SETTINGS_JWT_SECRET; - process.env.SETTINGS_JWT_PUBLIC_KEY = publicKey; + Config.remove('settings.jwt.secret'); + Config.set('settings.jwt.publicKey', publicKey); const hook = getHookFunction(JWT()); @@ -791,7 +794,8 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: }); it('with the decoded payload (cookie with a custom name & secret).', async () => { - process.env.SETTINGS_JWT_COOKIE_NAME = 'xxx'; + Config.set('settings.jwt.cookie.name', 'xxx'); + const hook = getHookFunction(JWT({ cookie: true })); const jwt = sign({ foo: 'bar' }, secret, {}); @@ -864,8 +868,8 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: const csrfCookieName = JWT_DEFAULT_CSRF_COOKIE_NAME + '2'; afterEach(() => { - delete process.env.SETTINGS_JWT_CSRF_ENABLED; - delete process.env.SETTINGS_JWT_CSRF_COOKIE_NAME; + Config.remove('settings.jwt.csrf.enabled'); + Config.remove('settings.jwt.csrf.cookie.name'); }); it('unless options.openapi is false.', () => { @@ -895,7 +899,8 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: }); it('with the proper security scheme (cookie name different).', () => { - process.env.SETTINGS_JWT_COOKIE_NAME = 'auth2'; + Config.set('settings.jwt.cookie.name', 'auth2'); + @JWT({ cookie: true }) class Foobar {} @@ -967,7 +972,8 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: }); it('with the proper API responses (no cookie & csrf protection).', () => { - process.env.SETTINGS_JWT_CSRF_ENABLED = 'true'; + Config.set('settings.jwt.csrf.enabled', true); + testResponses({ cookie: false }); }); @@ -976,7 +982,8 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: }); it('with the proper API responses (cookie & csrf protection).', () => { - process.env.SETTINGS_JWT_CSRF_ENABLED = 'true'; + Config.set('settings.jwt.csrf.enabled', true); + @JWT({ cookie: true }) class Foobar {} @@ -1018,7 +1025,8 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: }); it('with the proper API responses (no cookie & csrf protection).', () => { - process.env.SETTINGS_JWT_CSRF_ENABLED = 'true'; + Config.set('settings.jwt.csrf.enabled', true); + testResponses({ cookie: false }); }); @@ -1027,7 +1035,8 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: }); it('with the proper API responses (cookie & csrf protection).', () => { - process.env.SETTINGS_JWT_CSRF_ENABLED = 'true'; + Config.set('settings.jwt.csrf.enabled', true); + @JWT({ cookie: true }) class Foobar {} @@ -1051,7 +1060,8 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: }); it('with the proper API parameters (no cookie & csrf protection).', () => { - process.env.SETTINGS_JWT_CSRF_ENABLED = 'true'; + Config.set('settings.jwt.csrf.enabled', true); + testParameters({ cookie: false }); }); @@ -1060,7 +1070,8 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: }); it('with the proper API parameters (cookie & csrf protection).', () => { - process.env.SETTINGS_JWT_CSRF_ENABLED = 'true'; + Config.set('settings.jwt.csrf.enabled', true); + @JWT({ cookie: true }) class Foobar {} @@ -1073,8 +1084,8 @@ export function testSuite(JWT: typeof JWTOptional|typeof JWTRequired, required: }); it('with the proper API parameters (cookie & csrf protection & custom name).', () => { - process.env.SETTINGS_JWT_CSRF_ENABLED = 'true'; - process.env.SETTINGS_JWT_CSRF_COOKIE_NAME = csrfCookieName; + Config.set('settings.jwt.csrf.enabled', true); + Config.set('settings.jwt.csrf.cookie.name', csrfCookieName); @JWT({ cookie: true }) class Foobar {} diff --git a/packages/jwt/src/jwt.hook.ts b/packages/jwt/src/jwt.hook.ts index 8d8d3c8b04..24561e49f8 100644 --- a/packages/jwt/src/jwt.hook.ts +++ b/packages/jwt/src/jwt.hook.ts @@ -73,7 +73,7 @@ export function JWT(required: boolean, options: JWTOptions, verifyOptions: Verif async function hook(ctx: Context) { let token: string; if (options.cookie) { - const cookieName = Config.get('settings.jwt.cookieName', 'string', JWT_DEFAULT_COOKIE_NAME); + const cookieName = Config.get('settings.jwt.cookie.name', 'string', JWT_DEFAULT_COOKIE_NAME); const content = ctx.request.cookies[cookieName] as string|undefined; if (!content) { @@ -210,7 +210,7 @@ export function JWT(required: boolean, options: JWTOptions, verifyOptions: Verif if (options.cookie) { const securityScheme: IApiSecurityScheme = { in: 'cookie', - name: Config.get('settings.jwt.cookieName', 'string', JWT_DEFAULT_COOKIE_NAME), + name: Config.get('settings.jwt.cookie.name', 'string', JWT_DEFAULT_COOKIE_NAME), type: 'apiKey', }; openapi.push(ApiDefineSecurityScheme('cookieAuth', securityScheme)); diff --git a/packages/jwt/src/remove-auth-cookie.spec.ts b/packages/jwt/src/remove-auth-cookie.spec.ts index 315a6128a1..7f03fc7bd8 100644 --- a/packages/jwt/src/remove-auth-cookie.spec.ts +++ b/packages/jwt/src/remove-auth-cookie.spec.ts @@ -2,7 +2,7 @@ import { strictEqual } from 'assert'; // 3p -import { HttpResponse, HttpResponseOK } from '@foal/core'; +import { Config, HttpResponse, HttpResponseOK } from '@foal/core'; // FoalTS import { @@ -67,22 +67,22 @@ describe('removeAuthCookie', () => { const cookieName = JWT_DEFAULT_COOKIE_NAME + '2'; beforeEach(() => { - process.env.SETTINGS_JWT_COOKIE_NAME = cookieName; - process.env.SETTINGS_JWT_COOKIE_DOMAIN = 'example.com'; - process.env.SETTINGS_JWT_COOKIE_HTTP_ONLY = 'false'; - process.env.SETTINGS_JWT_COOKIE_PATH = '/foo'; - process.env.SETTINGS_JWT_COOKIE_SAME_SITE = 'strict'; - process.env.SETTINGS_JWT_COOKIE_SECURE = 'true'; + Config.set('settings.jwt.cookie.name', cookieName); + Config.set('settings.jwt.cookie.domain', 'example.com'); + Config.set('settings.jwt.cookie.httpOnly', false); + Config.set('settings.jwt.cookie.path', '/foo'); + Config.set('settings.jwt.cookie.sameSite', 'strict'); + Config.set('settings.jwt.cookie.secure', true); removeAuthCookie(response); }); afterEach(() => { - delete process.env.SETTINGS_JWT_COOKIE_NAME; - delete process.env.SETTINGS_JWT_COOKIE_DOMAIN; - delete process.env.SETTINGS_JWT_COOKIE_HTTP_ONLY; - delete process.env.SETTINGS_JWT_COOKIE_PATH; - delete process.env.SETTINGS_JWT_COOKIE_SAME_SITE; - delete process.env.SETTINGS_JWT_COOKIE_SECURE; + Config.remove('settings.jwt.cookie.name'); + Config.remove('settings.jwt.cookie.domain'); + Config.remove('settings.jwt.cookie.httpOnly'); + Config.remove('settings.jwt.cookie.path'); + Config.remove('settings.jwt.cookie.sameSite'); + Config.remove('settings.jwt.cookie.secure'); }); it('with the proper default name and value.', () => { @@ -126,9 +126,9 @@ describe('removeAuthCookie', () => { context('given the CSRF protection is enabled in the config', () => { - beforeEach(() => process.env.SETTINGS_JWT_CSRF_ENABLED = 'true'); + beforeEach(() => Config.set('settings.jwt.csrf.enabled', true)); - afterEach(() => delete process.env.SETTINGS_JWT_CSRF_ENABLED); + afterEach(() => Config.remove('settings.jwt.csrf.enabled')); describe('should set an auth cookie in the response', () => { @@ -146,12 +146,12 @@ describe('removeAuthCookie', () => { context('given configuration options are provided', () => { beforeEach(() => { - process.env.SETTINGS_JWT_COOKIE_SAME_SITE = 'strict'; + Config.set('settings.jwt.cookie.sameSite', 'strict'); removeAuthCookie(response); }); afterEach(() => { - delete process.env.SETTINGS_JWT_COOKIE_SAME_SITE; + Config.remove('settings.jwt.cookie.sameSite'); }); it('with the proper default "sameSite" directive.', () => { @@ -211,22 +211,22 @@ describe('removeAuthCookie', () => { const csrfCookieName = JWT_DEFAULT_CSRF_COOKIE_NAME + '2'; beforeEach(() => { - process.env.SETTINGS_JWT_CSRF_COOKIE_NAME = csrfCookieName; - process.env.SETTINGS_JWT_COOKIE_DOMAIN = 'example.com'; - process.env.SETTINGS_JWT_COOKIE_HTTP_ONLY = 'true'; - process.env.SETTINGS_JWT_COOKIE_PATH = '/foo'; - process.env.SETTINGS_JWT_COOKIE_SAME_SITE = 'strict'; - process.env.SETTINGS_JWT_COOKIE_SECURE = 'true'; + Config.set('settings.jwt.csrf.cookie.name', csrfCookieName); + Config.set('settings.jwt.cookie.domain', 'example.com'); + Config.set('settings.jwt.cookie.httpOnly', true); + Config.set('settings.jwt.cookie.path', '/foo'); + Config.set('settings.jwt.cookie.sameSite', 'strict'); + Config.set('settings.jwt.cookie.secure', true); removeAuthCookie(response); }); afterEach(() => { - delete process.env.SETTINGS_JWT_CSRF_COOKIE_NAME; - delete process.env.SETTINGS_JWT_COOKIE_DOMAIN; - delete process.env.SETTINGS_JWT_COOKIE_HTTP_ONLY; - delete process.env.SETTINGS_JWT_COOKIE_PATH; - delete process.env.SETTINGS_JWT_COOKIE_SAME_SITE; - delete process.env.SETTINGS_JWT_COOKIE_SECURE; + Config.remove('settings.jwt.csrf.cookie.name'); + Config.remove('settings.jwt.cookie.domain'); + Config.remove('settings.jwt.cookie.httpOnly'); + Config.remove('settings.jwt.cookie.path'); + Config.remove('settings.jwt.cookie.sameSite'); + Config.remove('settings.jwt.cookie.secure'); }); it('with the proper default name and value.', () => { diff --git a/packages/jwt/src/set-auth-cookie.spec.ts b/packages/jwt/src/set-auth-cookie.spec.ts index 670285d82f..e7eeef95f3 100644 --- a/packages/jwt/src/set-auth-cookie.spec.ts +++ b/packages/jwt/src/set-auth-cookie.spec.ts @@ -2,7 +2,7 @@ import { deepStrictEqual, notStrictEqual, rejects, strictEqual } from 'assert'; // 3p -import { HttpResponse, HttpResponseOK } from '@foal/core'; +import { Config, HttpResponse, HttpResponseOK } from '@foal/core'; import { decode, sign, verify } from 'jsonwebtoken'; // FoalTS @@ -134,22 +134,22 @@ describe('setAuthCookie', () => { const cookieName = JWT_DEFAULT_COOKIE_NAME + '2'; beforeEach(async () => { - process.env.SETTINGS_JWT_COOKIE_NAME = cookieName; - process.env.SETTINGS_JWT_COOKIE_DOMAIN = 'example.com'; - process.env.SETTINGS_JWT_COOKIE_HTTP_ONLY = 'false'; - process.env.SETTINGS_JWT_COOKIE_PATH = '/foo'; - process.env.SETTINGS_JWT_COOKIE_SAME_SITE = 'strict'; - process.env.SETTINGS_JWT_COOKIE_SECURE = 'true'; + Config.set('settings.jwt.cookie.name', cookieName); + Config.set('settings.jwt.cookie.domain', 'example.com'); + Config.set('settings.jwt.cookie.httpOnly', false); + Config.set('settings.jwt.cookie.path', '/foo'); + Config.set('settings.jwt.cookie.sameSite', 'strict'); + Config.set('settings.jwt.cookie.secure', true); await setAuthCookie(response, token); }); afterEach(() => { - delete process.env.SETTINGS_JWT_COOKIE_NAME; - delete process.env.SETTINGS_JWT_COOKIE_DOMAIN; - delete process.env.SETTINGS_JWT_COOKIE_HTTP_ONLY; - delete process.env.SETTINGS_JWT_COOKIE_PATH; - delete process.env.SETTINGS_JWT_COOKIE_SAME_SITE; - delete process.env.SETTINGS_JWT_COOKIE_SECURE; + Config.remove('settings.jwt.cookie.name'); + Config.remove('settings.jwt.cookie.domain'); + Config.remove('settings.jwt.cookie.httpOnly'); + Config.remove('settings.jwt.cookie.path'); + Config.remove('settings.jwt.cookie.sameSite'); + Config.remove('settings.jwt.cookie.secure'); }); it('with the proper default name and value.', () => { @@ -198,14 +198,14 @@ describe('setAuthCookie', () => { context('given the CSRF protection is enabled in the config', () => { beforeEach(() => { - process.env.SETTINGS_JWT_CSRF_ENABLED = 'true'; - process.env.SETTINGS_JWT_PRIVATE_KEY = privateKey; + Config.set('settings.jwt.csrf.enabled', true); + Config.set('settings.jwt.privateKey', privateKey); }); afterEach(() => { - delete process.env.SETTINGS_JWT_CSRF_ENABLED; - delete process.env.SETTINGS_JWT_PRIVATE_KEY; - delete process.env.SETTINGS_JWT_SECRET; + Config.remove('settings.jwt.csrf.enabled'); + Config.remove('settings.jwt.privateKey'); + Config.remove('settings.jwt.secret'); }); describe('should set an auth cookie in the response', () => { @@ -224,12 +224,12 @@ describe('setAuthCookie', () => { context('given configuration options are provided', () => { beforeEach(async () => { - process.env.SETTINGS_JWT_COOKIE_SAME_SITE = 'strict'; + Config.set('settings.jwt.cookie.sameSite', 'strict'); await setAuthCookie(response, token); }); afterEach(() => { - delete process.env.SETTINGS_JWT_COOKIE_SAME_SITE; + Config.remove('settings.jwt.cookie.sameSite'); }); it('with the proper default "sameSite" directive.', () => { @@ -293,8 +293,8 @@ describe('setAuthCookie', () => { }); it('should use the algorithm of the given JWT.', async () => { - delete process.env.SETTINGS_JWT_PRIVATE_KEY; - process.env.SETTINGS_JWT_SECRET = secret; + Config.remove('settings.jwt.privateKey'); + Config.set('settings.jwt.secret', secret); await setAuthCookie(response, tokenSignedWithSecret); @@ -372,22 +372,22 @@ describe('setAuthCookie', () => { const csrfCookieName = JWT_DEFAULT_CSRF_COOKIE_NAME + '2'; beforeEach(async () => { - process.env.SETTINGS_JWT_CSRF_COOKIE_NAME = csrfCookieName; - process.env.SETTINGS_JWT_COOKIE_DOMAIN = 'example.com'; - process.env.SETTINGS_JWT_COOKIE_HTTP_ONLY = 'true'; - process.env.SETTINGS_JWT_COOKIE_PATH = '/foo'; - process.env.SETTINGS_JWT_COOKIE_SAME_SITE = 'strict'; - process.env.SETTINGS_JWT_COOKIE_SECURE = 'true'; + Config.set('settings.jwt.csrf.cookie.name', csrfCookieName); + Config.set('settings.jwt.cookie.domain', 'example.com'); + Config.set('settings.jwt.cookie.httpOnly', true); + Config.set('settings.jwt.cookie.path', '/foo'); + Config.set('settings.jwt.cookie.sameSite', 'strict'); + Config.set('settings.jwt.cookie.secure', true); await setAuthCookie(response, token); }); afterEach(() => { - delete process.env.SETTINGS_JWT_CSRF_COOKIE_NAME; - delete process.env.SETTINGS_JWT_COOKIE_DOMAIN; - delete process.env.SETTINGS_JWT_COOKIE_HTTP_ONLY; - delete process.env.SETTINGS_JWT_COOKIE_PATH; - delete process.env.SETTINGS_JWT_COOKIE_SAME_SITE; - delete process.env.SETTINGS_JWT_COOKIE_SECURE; + Config.remove('settings.jwt.csrf.cookie.name'); + Config.remove('settings.jwt.cookie.domain'); + Config.remove('settings.jwt.cookie.httpOnly'); + Config.remove('settings.jwt.cookie.path'); + Config.remove('settings.jwt.cookie.sameSite'); + Config.remove('settings.jwt.cookie.secure'); }); describe('with the proper default name and value', () => { diff --git a/packages/mongodb/src/mongodb-store.service.spec.ts b/packages/mongodb/src/mongodb-store.service.spec.ts index 685b871d9a..3daa66a1e0 100644 --- a/packages/mongodb/src/mongodb-store.service.spec.ts +++ b/packages/mongodb/src/mongodb-store.service.spec.ts @@ -2,7 +2,7 @@ import { deepStrictEqual, doesNotReject, rejects, strictEqual } from 'assert'; // 3p -import { ConfigNotFoundError, createService, createSession, SessionAlreadyExists, SessionState } from '@foal/core'; +import { Config, ConfigNotFoundError, createService, createSession, SessionAlreadyExists, SessionState } from '@foal/core'; import { MongoClient } from 'mongodb'; // FoalTS @@ -40,19 +40,19 @@ describe('MongoDBStore', () => { } before(async () => { - process.env.MONGODB_URI = MONGODB_URI; - - mongoDBClient = await MongoClient.connect(MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true }); - try { - await mongoDBClient.db().collection(COLLECTION_NAME).dropIndexes(); - } catch (error) { - if (!(error.message.includes('ns not found'))) { - throw error; - } + Config.set('settings.mongodb.uri', MONGODB_URI); + + mongoDBClient = await MongoClient.connect(MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true }); + try { + await mongoDBClient.db().collection(COLLECTION_NAME).dropIndexes(); + } catch (error) { + if (!(error.message.includes('ns not found'))) { + throw error; } - store = createService(MongoDBStore); - await store.boot(); - }); + } + store = createService(MongoDBStore); + await store.boot(); + }); beforeEach(async () => { state = createState(); @@ -65,7 +65,7 @@ describe('MongoDBStore', () => { }); after(() => { - delete process.env.MONGODB_URI; + Config.remove('settings.mongodb.uri'); return Promise.all([ mongoDBClient.close(), @@ -103,11 +103,12 @@ describe('MongoDBStore', () => { describe('has a "boot" method that', () => { it('should throw a ConfigNotFoundError if no MongoDB URI is provided.', () => { - delete process.env.MONGODB_URI; + Config.remove('settings.mongodb.uri'); + return rejects( () => createService(MongoDBStore).boot(), new ConfigNotFoundError( - 'mongodb.uri', + 'settings.mongodb.uri', 'You must provide the URI of your database when using MongoDBStore.', ) ); diff --git a/packages/mongodb/src/mongodb-store.service.ts b/packages/mongodb/src/mongodb-store.service.ts index 5e760d6575..aea64efed4 100644 --- a/packages/mongodb/src/mongodb-store.service.ts +++ b/packages/mongodb/src/mongodb-store.service.ts @@ -20,7 +20,7 @@ export class MongoDBStore extends SessionStore { async boot() { const mongoDBURI = Config.getOrThrow( - 'mongodb.uri', + 'settings.mongodb.uri', 'string', 'You must provide the URI of your database when using MongoDBStore.' ); diff --git a/packages/redis/src/redis-store.service.spec.ts b/packages/redis/src/redis-store.service.spec.ts index 96657e36e3..04850a4a81 100644 --- a/packages/redis/src/redis-store.service.spec.ts +++ b/packages/redis/src/redis-store.service.spec.ts @@ -1,5 +1,5 @@ // 3p -import { createService, createSession, SessionAlreadyExists, SessionState } from '@foal/core'; +import { Config, createService, createSession, SessionAlreadyExists, SessionState } from '@foal/core'; import { createClient } from 'redis'; // FoalTS @@ -8,7 +8,7 @@ import { RedisStore } from './redis-store.service'; describe('RedisStore', () => { - const REDIS_URI = 'redis://localhost:6379'; + const REDIS_URI = 'redis://localhost:6380'; const COLLECTION_NAME = 'sessions'; let store: RedisStore; @@ -36,7 +36,7 @@ describe('RedisStore', () => { } before(async () => { - process.env.REDIS_URI = REDIS_URI; + Config.set('settings.redis.uri', REDIS_URI); redisClient = createClient(REDIS_URI); store = createService(RedisStore); @@ -50,7 +50,7 @@ describe('RedisStore', () => { }); after(() => { - delete process.env.REDIS_URI; + Config.remove('settings.redis.uri'); return Promise.all([ redisClient.end(true), diff --git a/packages/redis/src/redis-store.service.ts b/packages/redis/src/redis-store.service.ts index d48d4710fc..b4230517a0 100644 --- a/packages/redis/src/redis-store.service.ts +++ b/packages/redis/src/redis-store.service.ts @@ -13,7 +13,7 @@ export class RedisStore extends SessionStore { private redisClient: any; boot() { - const redisURI = Config.get('redis.uri', 'string'); + const redisURI = Config.get('settings.redis.uri', 'string'); this.redisClient = createClient(redisURI); } diff --git a/packages/social/src/abstract-provider.service.spec.ts b/packages/social/src/abstract-provider.service.spec.ts index e6f8305cab..9919c23160 100644 --- a/packages/social/src/abstract-provider.service.spec.ts +++ b/packages/social/src/abstract-provider.service.spec.ts @@ -5,6 +5,7 @@ import { URLSearchParams } from 'url'; // 3p import { + Config, Context, createApp, createService, @@ -138,18 +139,19 @@ describe('AbstractProvider', () => { const redirectUri = 'https://example.com/callback'; beforeEach(() => { - process.env.SETTINGS_SOCIAL_EXAMPLE_CLIENT_ID = clientId; - process.env.SETTINGS_SOCIAL_EXAMPLE_CLIENT_SECRET = clientSecret; - process.env.SETTINGS_SOCIAL_EXAMPLE_REDIRECT_URI = redirectUri; + Config.set('settings.social.example.clientId', clientId); + Config.set('settings.social.example.clientSecret', clientSecret); + Config.set('settings.social.example.redirectUri', redirectUri); + Config.set('settings.loggerFormat', 'none'); provider = createService(ConcreteProvider); }); afterEach(() => { - delete process.env.SETTINGS_SOCIAL_EXAMPLE_CLIENT_ID; - delete process.env.SETTINGS_SOCIAL_EXAMPLE_CLIENT_SECRET; - delete process.env.SETTINGS_SOCIAL_EXAMPLE_REDIRECT_URI; - delete process.env.SETTINGS_SOCIAL_COOKIE_SECURE; + Config.remove('settings.social.example.clientId'); + Config.remove('settings.social.example.clientSecret'); + Config.remove('settings.social.example.redirectUri'); + Config.remove('settings.social.cookie.secure'); }); describe('has a "redirect" method that', () => { @@ -245,7 +247,7 @@ describe('AbstractProvider', () => { }); it('with a generated state in a cookie whose secure option is defined with the config.', async () => { - process.env.SETTINGS_SOCIAL_COOKIE_SECURE = 'true'; + Config.set('settings.social.cookie.secure', true); const response = await provider.redirect(); const { options } = response.getCookie(STATE_COOKIE_NAME); diff --git a/packages/social/src/facebook-provider.service.spec.ts b/packages/social/src/facebook-provider.service.spec.ts index 8f010aee42..9118ca65fb 100644 --- a/packages/social/src/facebook-provider.service.spec.ts +++ b/packages/social/src/facebook-provider.service.spec.ts @@ -3,7 +3,7 @@ import { deepStrictEqual, strictEqual } from 'assert'; import { Server } from 'http'; // 3p -import { Context, createApp, createService, Get, HttpResponseBadRequest, HttpResponseOK } from '@foal/core'; +import { Config, Context, createApp, createService, Get, HttpResponseBadRequest, HttpResponseOK } from '@foal/core'; // FoalTS import { SocialTokens } from './abstract-provider.service'; @@ -23,9 +23,11 @@ describe('FacebookProvider', () => { beforeEach(() => { provider = createService(FacebookProvider2); + Config.set('settings.loggerFormat', 'none'); }); afterEach(() => { + Config.remove('settings.loggerFormat'); if (server) { server.close(); } diff --git a/packages/social/src/github-provider.service.spec.ts b/packages/social/src/github-provider.service.spec.ts index b4337a0f28..cb48092dde 100644 --- a/packages/social/src/github-provider.service.spec.ts +++ b/packages/social/src/github-provider.service.spec.ts @@ -3,7 +3,7 @@ import { deepStrictEqual, strictEqual } from 'assert'; import { Server } from 'http'; // 3p -import { Context, createApp, createService, Get, HttpResponseBadRequest, HttpResponseOK } from '@foal/core'; +import { Config, Context, createApp, createService, Get, HttpResponseBadRequest, HttpResponseOK } from '@foal/core'; // FoalTS import { SocialTokens } from './abstract-provider.service'; @@ -23,9 +23,11 @@ describe('GithubProvider', () => { beforeEach(() => { provider = createService(GithubProvider2); + Config.set('settings.loggerFormat', 'none'); }); afterEach(() => { + Config.remove('settings.loggerFormat'); if (server) { server.close(); } diff --git a/packages/social/src/linkedin-provider.service.spec.ts b/packages/social/src/linkedin-provider.service.spec.ts index 7ea3de08ea..9da762aa41 100644 --- a/packages/social/src/linkedin-provider.service.spec.ts +++ b/packages/social/src/linkedin-provider.service.spec.ts @@ -3,7 +3,7 @@ import { deepStrictEqual, strictEqual } from 'assert'; import { Server } from 'http'; // 3p -import { Context, createApp, createService, Get, HttpResponseBadRequest, HttpResponseOK } from '@foal/core'; +import { Config, Context, createApp, createService, Get, HttpResponseBadRequest, HttpResponseOK } from '@foal/core'; import { SocialTokens } from './abstract-provider.service'; import { LinkedInProvider } from './linkedin-provider.service'; import { UserInfoError } from './user-info.error'; @@ -21,9 +21,11 @@ describe('LinkedInProvider', () => { beforeEach(() => { provider = createService(LinkedInProvider2); + Config.set('settings.loggerFormat', 'none'); }); afterEach(() => { + Config.remove('settings.loggerFormat'); if (server) { server.close(); } diff --git a/packages/storage/src/local-disk.service.spec.ts b/packages/storage/src/local-disk.service.spec.ts index 467a315324..71c9bf4b55 100644 --- a/packages/storage/src/local-disk.service.spec.ts +++ b/packages/storage/src/local-disk.service.spec.ts @@ -5,7 +5,7 @@ import { join } from 'path'; import { Readable } from 'stream'; // 3p -import { ConfigNotFoundError, createService } from '@foal/core'; +import { Config, ConfigNotFoundError, createService } from '@foal/core'; // FoalTS import { FileDoesNotExist } from './abstract-disk.service'; @@ -44,7 +44,7 @@ describe('LocalDisk', () => { let disk: LocalDisk; beforeEach(() => { - process.env.SETTINGS_DISK_LOCAL_DIRECTORY = 'uploaded'; + Config.set('settings.disk.local.directory', 'uploaded'); if (!existsSync('uploaded')) { mkdirSync('uploaded'); } @@ -56,14 +56,15 @@ describe('LocalDisk', () => { }); afterEach(() => { - delete process.env.SETTINGS_DISK_LOCAL_DIRECTORY; + Config.remove('settings.disk.local.directory'); rmDirAndFilesIfExist('uploaded'); }); describe('has a "write" method that', () => { it('should throw an ConfigNotFoundError if no directory is specified in the config.', async () => { - delete process.env.SETTINGS_DISK_LOCAL_DIRECTORY; + Config.remove('settings.disk.local.directory'); + try { await disk.write('foo', Buffer.from('hello', 'utf8')); throw new Error('An error should have been thrown.'); @@ -118,7 +119,8 @@ describe('LocalDisk', () => { describe('has a "read" method that', () => { it('should throw an ConfigNotFoundError if no directory is specified in the config.', async () => { - delete process.env.SETTINGS_DISK_LOCAL_DIRECTORY; + Config.remove('settings.disk.local.directory'); + try { await disk.read('foo', 'buffer'); throw new Error('An error should have been thrown.'); @@ -193,7 +195,8 @@ describe('LocalDisk', () => { describe('has a "readSize" method that', () => { it('should throw an ConfigNotFoundError if no directory is specified in the config.', async () => { - delete process.env.SETTINGS_DISK_LOCAL_DIRECTORY; + Config.remove('settings.disk.local.directory'); + try { await disk.readSize('foo'); throw new Error('An error should have been thrown.'); @@ -231,7 +234,8 @@ describe('LocalDisk', () => { describe('has a "delete" method that', () => { it('should throw an ConfigNotFoundError if no directory is specified in the config.', async () => { - delete process.env.SETTINGS_DISK_LOCAL_DIRECTORY; + Config.remove('settings.disk.local.directory'); + try { await disk.delete('foo'); throw new Error('An error should have been thrown.'); diff --git a/packages/storage/src/validate-multipart-form-data-body.hook.spec.ts b/packages/storage/src/validate-multipart-form-data-body.hook.spec.ts index ba365124ba..41ec117cf3 100644 --- a/packages/storage/src/validate-multipart-form-data-body.hook.spec.ts +++ b/packages/storage/src/validate-multipart-form-data-body.hook.spec.ts @@ -5,7 +5,7 @@ import { join } from 'path'; // 3p import { - Context, createApp, createService, getApiRequestBody, HttpResponseOK, IApiRequestBody, Post + Config, Context, createApp, createService, getApiRequestBody, HttpResponseOK, IApiRequestBody, Post } from '@foal/core'; import * as request from 'supertest'; @@ -17,13 +17,13 @@ import { MultipartFormDataSchema, ValidateMultipartFormDataBody } from './valida describe('ValidateMultipartFormDataBody', () => { beforeEach(() => { - process.env.SETTINGS_LOGGER_FORMAT = 'none'; - process.env.SETTINGS_DISK_DRIVER = 'local'; + Config.set('settings.loggerFormat', 'none'); + Config.set('settings.disk.driver', 'local'); }); afterEach(() => { - delete process.env.SETTINGS_LOGGER_FORMAT; - delete process.env.SETTINGS_DISK_DRIVER; + Config.remove('settings.loggerFormat'); + Config.remove('settings.disk.driver'); }); // Note: Unfortunatly, in order to have a multipart request object, @@ -100,14 +100,14 @@ describe('ValidateMultipartFormDataBody', () => { describe('when the fields are not validated against the given schema', () => { beforeEach(() => { - process.env.SETTINGS_DISK_LOCAL_DIRECTORY = 'uploaded'; + Config.set('settings.disk.local.directory', 'uploaded'); mkdirSync('uploaded'); mkdirSync('uploaded/images'); }); afterEach(() => { - delete process.env.SETTINGS_DISK_LOCAL_DIRECTORY; + Config.remove('settings.disk.local.directory'); const contents = readdirSync('uploaded/images'); for (const content of contents) { @@ -202,18 +202,16 @@ describe('ValidateMultipartFormDataBody', () => { describe('when the max file size has been reached', () => { beforeEach(() => { - process.env.SETTINGS_MULTIPART_REQUESTS_FILE_SIZE_LIMIT = '200000'; - - process.env.SETTINGS_DISK_LOCAL_DIRECTORY = 'uploaded'; + Config.set('settings.multipartRequests.fileSizeLimit', 200000); + Config.set('settings.disk.local.directory', 'uploaded'); mkdirSync('uploaded'); mkdirSync('uploaded/images'); }); afterEach(() => { - delete process.env.SETTINGS_MULTIPART_REQUESTS_FILE_SIZE_LIMIT; - - delete process.env.SETTINGS_DISK_LOCAL_DIRECTORY; + Config.remove('settings.multipartRequests.fileSizeLimit'); + Config.remove('settings.disk.local.directory'); const contents = readdirSync('uploaded/images'); for (const content of contents) { @@ -266,18 +264,16 @@ describe('ValidateMultipartFormDataBody', () => { describe('when the max number of files has been reached', () => { beforeEach(() => { - process.env.SETTINGS_MULTIPART_REQUESTS_FILE_NUMBER_LIMIT = '1'; - - process.env.SETTINGS_DISK_LOCAL_DIRECTORY = 'uploaded'; + Config.set('settings.multipartRequests.fileNumberLimit', 1); + Config.set('settings.disk.local.directory', 'uploaded'); mkdirSync('uploaded'); mkdirSync('uploaded/images'); }); afterEach(() => { - delete process.env.SETTINGS_MULTIPART_REQUESTS_FILE_NUMBER_LIMIT; - - delete process.env.SETTINGS_DISK_LOCAL_DIRECTORY; + Config.remove('settings.multipartRequests.fileNumberLimit'); + Config.remove('settings.disk.local.directory'); const contents = readdirSync('uploaded/images'); for (const content of contents) { @@ -400,14 +396,14 @@ describe('ValidateMultipartFormDataBody', () => { describe('when a file is not uploaded but it is required', () => { beforeEach(() => { - process.env.SETTINGS_DISK_LOCAL_DIRECTORY = 'uploaded'; + Config.set('settings.disk.local.directory', 'uploaded'); mkdirSync('uploaded'); mkdirSync('uploaded/images'); }); afterEach(() => { - delete process.env.SETTINGS_DISK_LOCAL_DIRECTORY; + Config.remove('settings.disk.local.directory'); const contents = readdirSync('uploaded/images'); for (const content of contents) { @@ -584,8 +580,8 @@ describe('ValidateMultipartFormDataBody', () => { let disk: Disk; beforeEach(() => { - process.env.SETTINGS_DISK_LOCAL_DIRECTORY = 'uploaded'; - process.env.SETTINGS_LOG_ERRORS = 'false'; + Config.set('settings.disk.local.directory', 'uploaded'); + Config.set('settings.logErrors', false); mkdirSync('uploaded'); mkdirSync('uploaded/images'); @@ -594,8 +590,8 @@ describe('ValidateMultipartFormDataBody', () => { }); afterEach(() => { - delete process.env.SETTINGS_DISK_LOCAL_DIRECTORY; - delete process.env.SETTINGS_LOG_ERRORS; + Config.remove('settings.disk.local.directory'); + Config.remove('settings.logErrors'); const contents = readdirSync('uploaded/images'); for (const content of contents) { @@ -606,7 +602,7 @@ describe('ValidateMultipartFormDataBody', () => { }); it('should not kill the process if Disk.write throws an error.', async () => { - process.env.SETTINGS_DISK_DRIVER = '@foal/internal-test'; + Config.set('settings.disk.driver', '@foal/internal-test'); const actual: { body: any } = { body: null }; const app = createAppWithHook({ @@ -623,7 +619,7 @@ describe('ValidateMultipartFormDataBody', () => { }); it('should not kill the process if Disk.write rejects an error.', async () => { - delete process.env.SETTINGS_DISK_LOCAL_DIRECTORY; + Config.remove('settings.disk.local.directory'); const actual: { body: any } = { body: null }; const app = createAppWithHook({