diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4ad06c47c78a0b947a1e1145b6b35a3d3f2cc330..b863f59919abbd5f1be5802a8478258108a89133 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -15,6 +15,9 @@ build_development: script: - export TAG=dev - export MIDDLEWARE_LEGACY_SERVICE_BIND_PORT=3004 + - export REDIS_SENTINEL_PORT=26379 + - export REDIS_SLAVE_PORT=6380 + - export REDIS_MASTER_PORT=6379 - docker-compose build - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker-compose push @@ -28,6 +31,9 @@ build_release: script: - export TAG=$(echo $CI_COMMIT_TAG | sed 's/v//g') - export MIDDLEWARE_LEGACY_SERVICE_BIND_PORT=3004 + - export REDIS_SENTINEL_PORT=26379 + - export REDIS_SLAVE_PORT=6380 + - export REDIS_MASTER_PORT=6379 - docker-compose build - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker-compose push @@ -39,6 +45,9 @@ deploy_development: script: - export TAG=dev - export MIDDLEWARE_LEGACY_SERVICE_BIND_PORT=3004 + - export REDIS_SENTINEL_PORT=26379 + - export REDIS_SLAVE_PORT=6380 + - export REDIS_MASTER_PORT=6379 - export SERVICE_EMAIL_URL=http://vm-refonte-data-2:9000/email - export FRONT_END_URL=https://data-reloaded-dev.alpha.grandlyon.com/fr - export API_KEY=$DEV_API_KEY @@ -56,6 +65,9 @@ deploy_staging: script: - export TAG=staging - export MIDDLEWARE_LEGACY_SERVICE_BIND_PORT=3104 + - export REDIS_SENTINEL_PORT=26479 + - export REDIS_SLAVE_PORT=6480 + - export REDIS_MASTER_PORT=6479 - export SERVICE_EMAIL_URL=http://vm-refonte-data-2:9100/email - export FRONT_END_URL=https://data-reloaded-rec.alpha.grandlyon.com/fr - export API_KEY=$REC_API_KEY diff --git a/docker-compose.yml b/docker-compose.yml index be4435a52e14371a748f229d4afd0b63e0f06f99..9f99f1b150b94121c12cffca153d6656f915e9f7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,22 +18,45 @@ services: - API_KEY=${API_KEY} - ACCESS_TOKEN_COOKIE_KEY=${ACCESS_TOKEN_COOKIE_KEY} - IMAGE_HOST=https://minio.alpha.grandlyon.com/email-template-assets - - REDIS_HOST=redis - - REDIS_PORT=6379 + - REDIS_SENTINEL_HOST=redis-sentinel-1 + - REDIS_SENTINEL_PORT=26379 + - REDIS_GROUP_NAME=mymaster restart: unless-stopped depends_on: - - redis - - redis: - container_name: middleware-legacy-aut-redis-${TAG} - image: redis:5.0.0-alpine - command: ["redis-server", "--appendonly", "yes"] - hostname: redis + - redis-sentinel-1 + + redis-master: + image: 'bitnami/redis:latest' + environment: + - ALLOW_EMPTY_PASSWORD=yes + ports: + - '${REDIS_MASTER_PORT}:6379' + volumes: + - redis-master:/bitnami/redis/data + + redis-sentinel-1: + image: 'bitnami/redis-sentinel:latest' + environment: + - REDIS_MASTER_HOST=redis-master + - REDIS_MASTER_SET=mymaster + ports: + - '${REDIS_SENTINEL_PORT}:26379' + volumes: + - redis-sentinel-1:/bitnami/redis/data + + redis-slave-1: + image: 'bitnami/redis:latest' + command: redis-server --bind 0.0.0.0 --slaveof redis-master 6379 --dir /bitnami/redis/data + environment: + - ALLOW_EMPTY_PASSWORD=yes + ports: + - '${REDIS_SLAVE_PORT}:6379' volumes: - - redis-data:/data - restart: unless-stopped + - redis-slave-1:/bitnami/redis/data volumes: - redis-data: + redis-master: + redis-sentinel-1: + redis-slave-1: diff --git a/package-lock.json b/package-lock.json index de64ce471d57c499db00454524a7e403462edffb..158aa6c932e6a0a9c2c59b3c34f00e371bbe625d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "legacy-auth-middleware", - "version": "3.0.0", + "version": "3.2.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2423,6 +2423,11 @@ "wrap-ansi": "^2.0.0" } }, + "cluster-key-slot": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.0.12.tgz", + "integrity": "sha512-21O0kGmvED5OJ7ZTdqQ5lQQ+sjuez33R+d35jZKLwqUb5mqcPHUsxOSzj61+LHVtxGZd1kShbQM3MjB/gBJkVg==" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -2954,6 +2959,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "denque": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", + "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -3986,8 +3996,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -4008,14 +4017,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4030,20 +4037,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -4160,8 +4164,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -4173,7 +4176,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4188,7 +4190,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4196,14 +4197,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -4222,7 +4221,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -4303,8 +4301,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -4316,7 +4313,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -4402,8 +4398,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -4439,7 +4434,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4459,7 +4453,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4503,14 +4496,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -5554,6 +5545,37 @@ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" }, + "ioredis": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.10.0.tgz", + "integrity": "sha512-bAdt/sKdOvUyKhjLJ8HKFmO6ZQ+OHHmfFgWn9X/ecsp1lJNnOtmh/Xl2+AdKwUdSkl/Rrw1CKOkR8+Kv8tRinQ==", + "requires": { + "cluster-key-slot": "^1.0.6", + "debug": "^3.1.0", + "denque": "^1.1.0", + "lodash.defaults": "^4.2.0", + "lodash.flatten": "^4.4.0", + "redis-commands": "1.5.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.0.1" + }, + "dependencies": { + "redis-commands": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.5.0.tgz", + "integrity": "sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==" + }, + "redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", + "requires": { + "redis-errors": "^1.0.0" + } + } + } + }, "ipaddr.js": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", @@ -6679,6 +6701,16 @@ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -8788,6 +8820,11 @@ "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.4.0.tgz", "integrity": "sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw==" }, + "redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=" + }, "redis-parser": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", @@ -9821,6 +9858,11 @@ "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", "dev": true }, + "standard-as-callback": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.0.1.tgz", + "integrity": "sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg==" + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -12536,4 +12578,4 @@ "dev": true } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index c770a7f91ec6760be88d4f7e3e66c0650ad1f5dc..6caca95ac61cf52b75c353d1145ba0c801c8ec96 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "legacy-auth-middleware", - "version": "3.2.1", + "version": "3.2.2", "description": "", "author": "", "license": "MIT", @@ -25,16 +25,15 @@ "@nestjs/core": "^5.1.0", "@nestjs/swagger": "^3.0.2", "@nestjs/terminus": "^5.6.0", - "bluebird": "^3.5.3", "class-transformer": "^0.2.0", "class-validator": "^0.9.1", "cookie-parser": "^1.4.4", "dotenv": "^6.1.0", + "ioredis": "^4.10.0", "jsonwebtoken": "^8.4.0", "moment": "^2.24.0", "moment-timezone": "^0.5.23", "node-rsa": "^1.0.3", - "redis": "^2.8.0", "reflect-metadata": "^0.1.12", "request": "^2.88.0", "request-promise-native": "^1.0.5", diff --git a/src/configuration/config.service.ts b/src/configuration/config.service.ts index 2b0f608f18f192bb311022c6fde644dc03f28aab..b5d7cec6048b5934e94355a4123e0de4dfb34033 100644 --- a/src/configuration/config.service.ts +++ b/src/configuration/config.service.ts @@ -1,8 +1,8 @@ - -import * as dotenv from 'dotenv'; import { Config } from './config'; -import * as fs from 'fs'; import * as NodeRSA from 'node-rsa'; +import { Redis } from '../helpers/redis.helper'; +import { Logger, InternalServerErrorException } from '@nestjs/common'; + export class ConfigService { private _config = Config; @@ -17,22 +17,55 @@ export class ConfigService { this._config.apiKey = process.env.API_KEY; this._config.accessTokenCookieKey = process.env.ACCESS_TOKEN_COOKIE_KEY; this._config.imageHost = process.env.IMAGE_HOST; - this._config.redis.host = process.env.REDIS_HOST; - this._config.redis.port = process.env.REDIS_PORT; + this._config.redis.sentinelHost = process.env.REDIS_SENTINEL_HOST; + this._config.redis.sentinelPort = process.env.REDIS_SENTINEL_PORT; + this._config.redis.groupName = process.env.REDIS_GROUP_NAME; this._config.frontEnd.validateAccountUri = process.env.VALIDATE_ACCOUNT_URI; this._config.frontEnd.passwordResetUri = process.env.PASSWORD_RESET_URI; - - this.initilizePublicPrivateKeys(); } get config() { return this._config; } - initilizePublicPrivateKeys() { - const key = new NodeRSA({ b: 2048 }); + async getPublicKey() { + if (!this._config.publicKey) { + await this.initilizePublicPrivateKeys(); + } + return this._config.publicKey; + } + + async getPrivateKey() { + if (!this._config.privateKey) { + await this.initilizePublicPrivateKeys(); + } + return this._config.privateKey; + } + + async initilizePublicPrivateKeys() { + try { + const redis = new Redis(this.config.redis.sentinelPort, this.config.redis.sentinelHost, this.config.redis.groupName); + let keys: any = await redis.getValueByKey('encryptionKeys'); - this._config.publicKey = key.exportKey('public'); - this._config.privateKey = key.exportKey('private'); + if (keys) { + Logger.log(`Existing encryption keys found`, `initilizePublicPrivateKeys`); + keys = JSON.parse(keys) as { publicKey: string; privateKey: string; }; + this._config.publicKey = keys.publicKey; + this._config.privateKey = keys.privateKey; + } else { + const key = new NodeRSA({ b: 2048 }); + this._config.publicKey = key.exportKey('public'); + this._config.privateKey = key.exportKey('private'); + const encryptionKeys = JSON.stringify({ + publicKey: this._config.publicKey, + privateKey: this._config.privateKey, + }); + await redis.setKeyValue('encryptionKeys', encryptionKeys); + Logger.log(`Encryption keys generated`, `initilizePublicPrivateKeys`); + } + } catch (error) { + Logger.error('Failed to initialize Public/Private keys', `${error}`, `${this.initilizePublicPrivateKeys.name}`); + throw new InternalServerErrorException('Failed to initialize Public/Private keys'); + } } } \ No newline at end of file diff --git a/src/configuration/config.ts b/src/configuration/config.ts index e411ad023f2df0064b59ccdf0e2cc812028b6557..988ea02a1be22b099e85e1a5258611eca04aecb7 100644 --- a/src/configuration/config.ts +++ b/src/configuration/config.ts @@ -14,8 +14,9 @@ export const Config = { }, resetPasswordSessionTtl: 86400, // 24 hours redis: { - port: null, - host: '', + sentinelPort: null, + sentinelHost: '', + groupName: '', }, imageHost: '', apiKey: '', diff --git a/src/health/redis.healthIndicator.ts b/src/health/redis.healthIndicator.ts index 3f7293b8787686f226788b6d1341a9abf61404d0..bdf6308c44a4b7d2bef9e482c6615ce3b2c1640b 100644 --- a/src/health/redis.healthIndicator.ts +++ b/src/health/redis.healthIndicator.ts @@ -1,16 +1,17 @@ import { HealthIndicator } from '@nestjs/terminus/dist/health-indicators/abstract/health-indicator'; import { HealthIndicatorResult } from '@nestjs/terminus'; -import * as redis from 'redis'; -import * as bluebird from 'bluebird'; +import * as IORedis from 'ioredis'; import { HealthCheckError } from '@godaddy/terminus'; -bluebird.promisifyAll(redis); export class RedisHealthIndicator extends HealthIndicator { - async checkRedisConnection(redisConf: { host: string, port: number }): Promise<HealthIndicatorResult> { + async checkRedisConnection(redisConf: { sentinelHost: string, sentinelPort: number, groupName: string; }): Promise<HealthIndicatorResult> { - const client = redis.createClient({ - host: redisConf.host, port: redisConf.port, + const client = new IORedis({ + sentinels: [ + { host: redisConf.sentinelHost, port: redisConf.sentinelPort }, + ], + name: redisConf.groupName, }); await new Promise((resolve, reject) => { @@ -18,7 +19,7 @@ export class RedisHealthIndicator extends HealthIndicator { client.on('error', reject); setTimeout(reject, 2000); }).catch((err) => { - client.quit(); + client.disconnect(); throw new HealthCheckError('Redis connection failed', { Redis: { err, diff --git a/src/legacy/encryptionHelpers.ts b/src/helpers/encryptionHelpers.ts similarity index 99% rename from src/legacy/encryptionHelpers.ts rename to src/helpers/encryptionHelpers.ts index 23d76db206414de087a6217f3629235831834277..704c61d087dbce163323c4c96d9697c3113a1826 100644 --- a/src/legacy/encryptionHelpers.ts +++ b/src/helpers/encryptionHelpers.ts @@ -15,4 +15,4 @@ export const decrypt = (encrypted: string, privateKey) => { } else { throw new BadRequestException(); } -}; \ No newline at end of file +}; diff --git a/src/helpers/redis.helper.ts b/src/helpers/redis.helper.ts new file mode 100644 index 0000000000000000000000000000000000000000..66175858bf2775e662ea2f4a0f129653e0a1eaca --- /dev/null +++ b/src/helpers/redis.helper.ts @@ -0,0 +1,99 @@ +import { InternalServerErrorException, Logger } from '@nestjs/common'; +import { handleError } from '../legacy/errorHandlingHelper'; +import * as IORedis from 'ioredis'; + +export class Redis { + + constructor( + private redisSentinelPort: number, + private redisSentinelHost: string, + private redisGroupName: string, + ) { } + + connect() { + Logger.log(`Entering function`, `Redis.connect`); + const client = new IORedis({ + sentinels: [ + { host: this.redisSentinelHost, port: this.redisSentinelPort }, + ], + name: this.redisGroupName, + }); + + client.on('error', (error) => { + Logger.error('Redis client error.', `${error}`, `Redis.connect`); + client.disconnect(); + }); + + return client; + } + + async getValueByKey(key: string): Promise<string> { + Logger.log(`Entering function with params ${key}`, `Redis.getValueByKey`); + let client; + + try { + client = this.connect(); + + const res = await client.get(key).catch((error) => { + client.disconnect(); + Logger.error('Couldn\'t get redis value.', `${error}`, `Redis.getValueByKey`); + throw new InternalServerErrorException({ error, message: 'Couldn\'t get redis value.' }); + }); + Logger.log(`Value found`, `Redis.getValueByKey`); + client.disconnect(); + return res; + } catch (error) { + handleError(error, new InternalServerErrorException('Couldn\'t get value from redis.')); + } + } + + async setKeyValue(key: string, value: string, ttl?: number): Promise<any> { + Logger.log(`Entering function with key ${key} and ttl: ${ttl}`, `Redis.setKeyValue`); + let client; + try { + client = this.connect(); + let res; + + if (ttl) { + // Set key value with expiration time in seconds + res = await client.set(key, value, 'EX', ttl).catch((error) => { + client.disconnect(); + Logger.error('Couldn\'t set redis key/value (with ttl).', `${error}`, `Redis.setKeyValue`); + throw new InternalServerErrorException({ error, message: 'Couldn\'t set redis key/value.' }); + }); + } else { + // Set key value with expiration time in seconds + res = await client.set(key, value).catch((error) => { + client.disconnect(); + Logger.error('Couldn\'t set redis key/value (without ttl).', `${error}`, `Redis.setKeyValue`); + throw new InternalServerErrorException({ error, message: 'Couldn\'t set redis key/value.' }); + }); + } + + Logger.log(`Done setting key/value pair`, `Redis.setKeyValue`); + client.disconnect(); + return res; + } catch (error) { + handleError(error, new InternalServerErrorException('Couldn\'t set key/value in redis.')); + } + } + + async deleteKeyValue(key: string): Promise<any> { + Logger.log(`Entering function with params ${key}`, `Redis.deleteKeyValue`); + let client; + try { + client = this.connect(); + // Set key value with expiration time in seconds + const res = await client.del(key).catch((error) => { + client.disconnect(); + Logger.error('Couldn\'t delete redis key/value.', `${error}`, `Redis.deleteKeyValue`); + throw new InternalServerErrorException({ error, message: 'Couldn\'t delete redis key/value.' }); + }); + Logger.log(`Removing key/pair, result is: ${res}`, `Redis.deleteKeyValue`); + client.disconnect(); + return res; + } catch (error) { + handleError(error, new InternalServerErrorException('Couldn\'t remove key/value in redis.')); + } + } +} \ No newline at end of file diff --git a/src/legacy/legacy.service.ts b/src/legacy/legacy.service.ts index 6af18137c49c46e9e18fb1ada657cc753ada24eb..7412c41976ae26c89ba816cb90e26122abf4cb1e 100644 --- a/src/legacy/legacy.service.ts +++ b/src/legacy/legacy.service.ts @@ -9,36 +9,36 @@ import { } from './legacy.model'; import * as request from 'request-promise-native'; import { ConfigService } from '../configuration/config.service'; -import { decrypt } from './encryptionHelpers'; +import { decrypt } from '../helpers/encryptionHelpers'; import { buildDataAccessDeletionAdminEmail, buildDataAccessDeletionUserEmail } from '../email-templates/data-access-deletion'; import { buildDataAccessRequestAdminEmail, buildDataAccessRequestUserEmail } from '../email-templates/data-access-request'; import { buildRenewDataAccessRequestAdminEmail, buildRenewDataAccessRequestUserEmail } from '../email-templates/data-access-renew'; import { handleError } from './errorHandlingHelper'; -import * as bluebird from 'bluebird'; -import * as redis from 'redis'; -bluebird.promisifyAll(redis); import * as uuid4 from 'uuid/v4'; import { buildAccountValidationEmail } from '../email-templates/account-creation'; import { buildResetPasswordEmail } from '../email-templates/reset-password'; import moment = require('moment-timezone'); +import { Redis } from '../helpers/redis.helper'; moment.tz.setDefault('Europe/Paris'); @Injectable() export class LegacyService { conf: any; private logger: Logger; + private redis: Redis; constructor( private configService: ConfigService, ) { this.conf = this.configService.config; this.logger = new Logger(LegacyService.name); + this.redis = new Redis(this.conf.redis.sentinelPort, this.conf.redis.sentinelHost, this.conf.redis.groupName); } async getUser(loginForm: LoginForm): Promise<UserInfoWithEcryptedPassword> { this.logger.log('Entering function', `${LegacyService.name} - ${this.getUser.name}`); try { - const decryptedPassword = decrypt(loginForm.password, this.conf.privateKey); + const decryptedPassword = decrypt(loginForm.password, await this.configService.getPrivateKey()); let res = await request.post(`${this.conf.legacyAuthServiceUrl}/get_user/`).form( { username: loginForm.username, @@ -69,7 +69,7 @@ export class LegacyService { async getUserInfo(username: string, encryptedPassword: string): Promise<UserInfo> { this.logger.log('Entering function', `${LegacyService.name} - ${this.getUserInfo.name}`); try { - const password = decrypt(encryptedPassword, this.conf.privateKey); + const password = decrypt(encryptedPassword, await this.configService.getPrivateKey()); let res = await request.post(`${this.conf.legacyAuthServiceUrl}/get_user/`).form( { username, @@ -100,7 +100,7 @@ export class LegacyService { async updateUserPassword(username: string, updatePasswordForm: UpdatePasswordForm): Promise<void> { this.logger.log('Entering function', `${LegacyService.name} - ${this.updateUserPassword.name}`); try { - const decryptedOldPassword = decrypt(updatePasswordForm.oldPassword, this.conf.privateKey); + const decryptedOldPassword = decrypt(updatePasswordForm.oldPassword, await this.configService.getPrivateKey()); let res = await request.post(`${this.conf.legacyAuthServiceUrl}/get_user/`).form( { @@ -114,7 +114,7 @@ export class LegacyService { res = JSON.parse(res); if (res.server_response && res.server_response === 'Success') { - const decryptedNewPassword = decrypt(updatePasswordForm.newPassword, this.conf.privateKey); + const decryptedNewPassword = decrypt(updatePasswordForm.newPassword, await this.configService.getPrivateKey()); res = await request.post(`${this.conf.legacyAuthServiceUrl}/update_user_password/`).form({ username, password: decryptedNewPassword, @@ -153,14 +153,14 @@ export class LegacyService { async createUser(form: UserCreationForm): Promise<void> { this.logger.log('Entering function', `${LegacyService.name} - ${this.createUser.name}`); try { - // form.password = decrypt(form.password, this.conf.privateKey); + // form.password = decrypt(form.password,await this.configService.getPrivateKey()); const legacyForm = new LegacyUserCreationForm(form); // Generate a unique random token const accountCreationToken = uuid4(); // Set a redis key/value, this allow to open a 24h session with the token as key that will let us identify the email associated to the token - await this.setRedisKeyValue(accountCreationToken, JSON.stringify(legacyForm), this.conf.accountCreationTokenTTL); + await this.redis.setKeyValue(accountCreationToken, JSON.stringify(legacyForm), this.conf.accountCreationTokenTTL); // Building account validation url const accountValidationUrl = `${this.conf.frontEnd.url}/${this.conf.frontEnd.validateAccountUri}?token=${accountCreationToken}`; @@ -192,10 +192,10 @@ export class LegacyService { try { - const redisValue = await this.getRedisValueByKey(token); + const redisValue = await this.redis.getValueByKey(token); const userInfo = JSON.parse(redisValue) as LegacyUserCreationForm; - userInfo.password = decrypt(userInfo.password, this.conf.privateKey); + userInfo.password = decrypt(userInfo.password, await this.configService.getPrivateKey()); if (userInfo) { Logger.log(`User account validation for : ${userInfo.email}`, `${LegacyService.name} - ${this.validateUserAccount.name}`); @@ -211,7 +211,7 @@ export class LegacyService { Logger.log(`User account created.`, `${LegacyService.name} - ${this.validateUserAccount.name}`); // Deleting the key from redis when the account has successfully been created - this.deleteRedisKey(token); + this.redis.deleteKeyValue(token); return; } else { this.logger.error('Couldn\'t create user.', `${res}`, `${LegacyService.name} - ${this.validateUserAccount.name}`); @@ -239,7 +239,7 @@ export class LegacyService { async updateUserInfo(token: JWTToken, form: UserUpdateForm): Promise<void> { this.logger.log('Entering function', `${LegacyService.name} - ${this.updateUserInfo.name}`); try { - const legacyForm = new LegacyUserUpdateForm(form, token.username, decrypt(token.authzKey, this.conf.privateKey)); + const legacyForm = new LegacyUserUpdateForm(form, token.username, decrypt(token.authzKey, await this.configService.getPrivateKey())); let res = await request.post(`${this.conf.legacyAuthServiceUrl}/update_user/`).form(legacyForm).catch((error) => { this.logger.error('Couldn\'t update user info.', `${error}`, `${LegacyService.name} - ${this.updateUserInfo.name}`); @@ -274,11 +274,11 @@ export class LegacyService { this.logger.log(`Entering function with token ${form.token}`, `${LegacyService.name} - ${this.resetPassword.name}`); try { - const username = await this.getRedisValueByKey(form.token); + const username = await this.redis.getValueByKey(form.token); if (username) { - const decryptedPassword = decrypt(form.password, this.conf.privateKey); + const decryptedPassword = decrypt(form.password, await this.configService.getPrivateKey()); let res = await request.post(`${this.conf.legacyAuthServiceUrl}/update_user_password/`).form({ username, @@ -294,7 +294,7 @@ export class LegacyService { if (res && res.server_response && res.server_response === 'Success') { // Remove the token - await this.deleteRedisKey(form.token); + await this.redis.deleteKeyValue(form.token); return; } else { this.logger.error('Couldn\'t update user password.', `${res.message}`, `${LegacyService.name} - ${this.resetPassword.name}`); @@ -313,7 +313,7 @@ export class LegacyService { this.logger.log(`Entering function with token ${token}`, `${LegacyService.name} - ${this.isPasswordResetTokenValid.name}`); try { - const email = await this.getRedisValueByKey(token); + const email = await this.redis.getValueByKey(token); Logger.log(`Password reset token is valid for email: ${email}`, `${LegacyService.name} - ${this.isPasswordResetTokenValid.name}`); @@ -331,7 +331,7 @@ export class LegacyService { const resetPasswordToken = uuid4(); // Set a redis key/value, this allow to open a 24h session with the token as key that will let us identify the email associated to the token - await this.setRedisKeyValue(resetPasswordToken, form.email, this.conf.resetPasswordSessionTtl); + await this.redis.setKeyValue(resetPasswordToken, form.email, this.conf.resetPasswordSessionTtl); // Generate the content of the mail const resetPasswordUrl = `${this.conf.frontEnd.url}/${this.conf.frontEnd.passwordResetUri}?token=${resetPasswordToken}`; @@ -388,7 +388,7 @@ export class LegacyService { this.logger.log('Entering function', `${LegacyService.name} - ${this.getUserResources.name}`); try { // Decrypt the password - password = decrypt(password, this.conf.privateKey); + password = decrypt(password, await this.configService.getPrivateKey()); if (!password) { throw new UnauthorizedException('Invalid user credentials.'); @@ -445,7 +445,11 @@ export class LegacyService { return restrictedServices; } else { - this.logger.error('Couldn\'t get the different restricted services.', `${rawRestrictedService}`, `${LegacyService.name} - ${this.getRestrictedAccessDatasets.name}`); + this.logger.error( + 'Couldn\'t get the different restricted services.', + `${rawRestrictedService}`, + `${LegacyService.name} - ${this.getRestrictedAccessDatasets.name}`, + ); throw new InternalServerErrorException('Couldn\'t get the different restricted services.'); } } catch (err) { @@ -461,7 +465,7 @@ export class LegacyService { this.logger.log('Entering function', `${LegacyService.name} - ${this.addUserResource.name}`); try { // Decrypt the password - const password = decrypt(token.authzKey, this.conf.privateKey); + const password = decrypt(token.authzKey, await this.configService.getPrivateKey()); if (!password) { throw new UnauthorizedException('Invalid user credentials.'); @@ -484,7 +488,7 @@ export class LegacyService { username: token.username, service_id: accessRequest.id, modes: accessRequest.servicesId.toString(), - } + }, ).catch((error) => { this.logger.error('Couldn\'t request access to the resource.', `${error}`, `${LegacyService.name} - ${this.addUserResource.name}`); throw new InternalServerErrorException({ error, message: 'Couldn\'t request access to the resource.' }); @@ -563,7 +567,7 @@ export class LegacyService { this.logger.log('Entering function', `${LegacyService.name} - ${this.renewUserResource.name}`); try { // Decrypt the password - const password = decrypt(token.authzKey, this.conf.privateKey); + const password = decrypt(token.authzKey, await this.configService.getPrivateKey()); if (!password) { this.logger.warn('Invalid user credentials.', `${LegacyService.name} - ${this.renewUserResource.name}`); @@ -666,7 +670,7 @@ export class LegacyService { this.logger.log('Entering function', `${LegacyService.name} - ${this.deleteUserResource.name}`); try { // Decrypt the password - const password = decrypt(token.authzKey, this.conf.privateKey); + const password = decrypt(token.authzKey, await this.configService.getPrivateKey()); if (!password) { this.logger.warn('Invalid user credentials.', `${LegacyService.name} - ${this.deleteUserResource.name}`); @@ -767,7 +771,7 @@ export class LegacyService { this.logger.log('Entering function', `${LegacyService.name} - ${this.deleteUserAccount.name}`); try { // Decrypt the password - const password = decrypt(token.authzKey, this.conf.privateKey); + const password = decrypt(token.authzKey, await this.configService.getPrivateKey()); if (!password) { this.logger.error('Invalid user credentials.', null, `${LegacyService.name} - ${this.deleteUserAccount.name}`); throw new UnauthorizedException('Invalid user credentials.'); @@ -801,91 +805,17 @@ export class LegacyService { } } - getPublicKey() { + async getPublicKey() { this.logger.log(`Entering function`, `${LegacyService.name} - ${this.getPublicKey.name}`); - if (this.conf.publicKey) { - return { publicKey: this.conf.publicKey }; + const pubKey = await this.configService.getPublicKey(); + if (pubKey) { + return { publicKey: pubKey }; } else { this.logger.error('Couldn\'t get the public key.', null, `${LegacyService.name} - ${this.getPublicKey.name}`); throw new InternalServerErrorException('Couldn\'t get the public key.'); } } - /**** Redis methods ****/ - - private async setRedisKeyValue(key: string, value: string, ttl: number): Promise<any> { - this.logger.log(`Entering function with params ${key} ${value} ${ttl}`, `${LegacyService.name} - ${this.setRedisKeyValue.name}`); - let client; - try { - client = this.connectToRedis(); - - // Set key value with expiration time in seconds - const res = await client.setAsync(key, value, 'EX', ttl).catch((error) => { - this.logger.error('Couldn\'t set redis key/value.', `${error}`, `${LegacyService.name} - ${this.setRedisKeyValue.name}`); - throw new InternalServerErrorException({ error, message: 'Couldn\'t set redis key/value.' }); - }); - - Logger.log(`Setting key/pair, result is: ${res}`, `${LegacyService.name} - ${this.setRedisKeyValue.name}`); - client.quit(); - return res; - } catch (error) { - client.quit(); - handleError(error, new InternalServerErrorException('Couldn\'t set key/value in redis.')); - } - } - - private async deleteRedisKey(key: string): Promise<any> { - this.logger.log(`Entering function with params ${key}`, `${LegacyService.name} - ${this.deleteRedisKey.name}`); - let client; - try { - client = this.connectToRedis(); - // Set key value with expiration time in seconds - const res = await client.delAsync(key).catch((error) => { - this.logger.error('Couldn\'t delete redis key/value.', `${error}`, `${LegacyService.name} - ${this.deleteRedisKey.name}`); - throw new InternalServerErrorException({ error, message: 'Couldn\'t delete redis key/value.' }); - }); - Logger.log(`Removing key/pair, result is: ${res}`, `${LegacyService.name} - ${this.deleteRedisKey.name}`); - client.quit(); - return res; - } catch (error) { - client.quit(); - handleError(error, new InternalServerErrorException('Couldn\'t remove key/value in redis.')); - } - } - - private async getRedisValueByKey(key: string): Promise<string> { - this.logger.log(`Entering function with params ${key}`, `${LegacyService.name} - ${this.getRedisValueByKey.name}`); - let client; - - try { - client = this.connectToRedis(); - - const res = await client.getAsync(key).catch((error) => { - this.logger.error('Couldn\'t get redis value.', `${error}`, `${LegacyService.name} - ${this.getRedisValueByKey.name}`); - throw new InternalServerErrorException({ error, message: 'Couldn\'t get redis value.' }); - }); - Logger.log(`Value found: ${res}`, `${LegacyService.name} - ${this.getRedisValueByKey.name}`); - client.quit(); - return res; - } catch (error) { - client.quit(); - handleError(error, new InternalServerErrorException('Couldn\'t get value from redis.')); - } - } - - private connectToRedis() { - this.logger.log(`Entering function`, `${LegacyService.name} - ${this.connectToRedis.name}`); - - const client = redis.createClient({ host: this.conf.redis.host, port: this.conf.redis.port }); - - client.on('error', (error) => { - this.logger.error('Redis client error.', `${error}`, `${LegacyService.name} - ${this.connectToRedis.name}`); - client.quit(); - }); - - return client; - } - private async sendEmail(emailBody: Email) { try { this.logger.log(`Entering function`, `${LegacyService.name} - ${this.sendEmail.name}`); diff --git a/template.env b/template.env index 0e0dea673e43dc507679a16c8118bfa521e21426..14c7851a414db79f78c062c806497a492fa2494e 100644 --- a/template.env +++ b/template.env @@ -9,5 +9,8 @@ FRONT_END_URL=<web app url> API_KEY=<api key of the of the service (generated in kong)> ACCESS_TOKEN_COOKIE_KEY=<cookie key where the access token will be stored> IMAGE_HOST=<host of the images present in the emails body> -REDIS_HOST=<redis host> -REDIS_PORT=<redis port> \ No newline at end of file +REDIS_SENTINEL_HOST=<redis sentinel host> +REDIS_SENTINEL_PORT=<redis sentinel port> +REDIS_MASTER_PORT=<redis master port> +REDIS_SLAVE_PORT=<redis slave port> +REDIS_GROUP_NAME=<group name containing a master, and one or more slaves> \ No newline at end of file