Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(env-vars): auth environment validation #7621

Merged
merged 9 commits into from
Oct 1, 2024
1 change: 1 addition & 0 deletions packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@opencrvs/commons": "^1.3.0",
"app-module-path": "^2.2.0",
"dotenv": "^6.1.0",
"envalid": "^8.0.0",
"fp-ts": "^2.12.3",
"hapi-pino": "^9.0.0",
"hapi-sentry": "^3.1.0",
Expand Down
6 changes: 3 additions & 3 deletions packages/auth/src/config/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
*/
import { SENTRY_DSN } from '@auth/constants'
import { env } from '@auth/environment'
import { ServerRegisterPluginObject } from '@hapi/hapi'
import { logger } from '@opencrvs/commons'
import * as Pino from 'hapi-pino'
Expand All @@ -30,13 +30,13 @@ export default function getPlugins() {
})
}

if (SENTRY_DSN) {
if (env.SENTRY_DSN) {
plugins.push({
plugin: Sentry,
options: {
client: {
environment: process.env.DOMAIN,
dsn: SENTRY_DSN
dsn: env.SENTRY_DSN
},
catchLogErrors: true
}
Expand Down
40 changes: 0 additions & 40 deletions packages/auth/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,46 +8,6 @@
*
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
*/
export const REDIS_HOST = process.env.REDIS_HOST || 'localhost'
export const AUTH_HOST = process.env.AUTH_HOST || '0.0.0.0'
export const AUTH_PORT = process.env.AUTH_PORT || 4040
export const USER_MANAGEMENT_URL =
process.env.USER_MANAGEMENT_URL || 'http://localhost:3030/'
export const METRICS_URL = process.env.METRICS_URL || 'http://localhost:1050'
export const NOTIFICATION_SERVICE_URL =
process.env.NOTIFICATION_SERVICE_URL || 'http://localhost:2020/'
export const HOSTNAME = process.env.DOMAIN || '*'
export const COUNTRY_CONFIG_URL =
process.env.COUNTRY_CONFIG_URL || 'http://localhost:3040/'
export const LOGIN_URL = process.env.LOGIN_URL || 'http://localhost:3020/'
export const CLIENT_APP_URL =
process.env.CLIENT_APP_URL || 'http://localhost:3000/'

export const CERT_PRIVATE_KEY_PATH =
(process.env.CERT_PRIVATE_KEY_PATH as string) ||
'../../.secrets/private-key.pem'
export const CERT_PUBLIC_KEY_PATH =
(process.env.CERT_PUBLIC_KEY_PATH as string) ||
'../../.secrets/public-key.pem'
export const SENTRY_DSN = process.env.SENTRY_DSN

export const PRODUCTION = process.env.NODE_ENV === 'production'
export const QA_ENV = process.env.QA_ENV || false

export const CONFIG_TOKEN_EXPIRY_SECONDS = process.env
.CONFIG_TOKEN_EXPIRY_SECONDS
? parseInt(process.env.CONFIG_TOKEN_EXPIRY_SECONDS, 10)
: 604800 // 1 week

export const CONFIG_SMS_CODE_EXPIRY_SECONDS = process.env
.CONFIG_SMS_CODE_EXPIRY_SECONDS
? parseInt(process.env.CONFIG_SMS_CODE_EXPIRY_SECONDS, 10)
: 600

export const CONFIG_SYSTEM_TOKEN_EXPIRY_SECONDS = process.env
.CONFIG_SYSTEM_TOKEN_EXPIRY_SECONDS
? parseInt(process.env.CONFIG_SYSTEM_TOKEN_EXPIRY_SECONDS, 10)
: 600

export const WEB_USER_JWT_AUDIENCES = [
naftis marked this conversation as resolved.
Show resolved Hide resolved
'opencrvs:auth-user',
Expand Down
6 changes: 3 additions & 3 deletions packages/auth/src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
*/
import * as redis from 'redis'
import { REDIS_HOST } from '@auth/constants'
import { env } from '@auth/environment'
import { promisify } from 'util'
import { logger } from '@opencrvs/commons'

Expand All @@ -29,9 +29,9 @@ export async function stop() {
}

export async function start() {
logger.info(`REDIS_HOST, ${JSON.stringify(REDIS_HOST)}`)
logger.info(`REDIS_HOST, ${JSON.stringify(env.REDIS_HOST)}`)
redisClient = redis.createClient({
host: REDIS_HOST,
host: env.REDIS_HOST,
retry_strategy: (options) => 1000
})
}
Expand Down
33 changes: 33 additions & 0 deletions packages/auth/src/environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* OpenCRVS is also distributed under the terms of the Civil Registration
* & Healthcare Disclaimer located at http://opencrvs.org/license.
*
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
*/

import { cleanEnv, str, port, url, num, bool } from 'envalid'

export const env = cleanEnv(process.env, {
REDIS_HOST: str({ devDefault: 'localhost' }),
AUTH_HOST: str({ devDefault: '0.0.0.0' }),
AUTH_PORT: port({ devDefault: 4040 }),
naftis marked this conversation as resolved.
Show resolved Hide resolved
USER_MANAGEMENT_URL: url({ devDefault: 'http://localhost:3030/' }),
METRICS_URL: url({ devDefault: 'http://localhost:1050' }),
NOTIFICATION_SERVICE_URL: url({ devDefault: 'http://localhost:2020/' }),
DOMAIN: str({ devDefault: '*' }),
COUNTRY_CONFIG_URL: url({ devDefault: 'http://localhost:3040/' }),
LOGIN_URL: url({ devDefault: 'http://localhost:3020/' }),
CLIENT_APP_URL: url({ devDefault: 'http://localhost:3000/' }),
CERT_PRIVATE_KEY_PATH: str({ devDefault: '../../.secrets/private-key.pem' }),
CERT_PUBLIC_KEY_PATH: str({ devDefault: '../../.secrets/public-key.pem' }),
SENTRY_DSN: str({ default: undefined }),
QA_ENV: bool({ default: false }),

CONFIG_TOKEN_EXPIRY_SECONDS: num({ default: 604800 }), // 1 week
CONFIG_SMS_CODE_EXPIRY_SECONDS: num({ default: 600 }), // 10 minutes
CONFIG_SYSTEM_TOKEN_EXPIRY_SECONDS: num({ default: 600 }) // 10 minutes
})
28 changes: 26 additions & 2 deletions packages/auth/src/features/authenticate/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,19 @@ describe('authenticate handler receives a request', () => {
expect(res.statusCode).toBe(403)
})
it('generates a mobile verification code and sends it to notification gateway', async () => {
server = await createServerWithEnvironment({ NODE_ENV: 'production' })
server = await createServerWithEnvironment({
NODE_ENV: 'production',
AUTH_HOST: '0.0.0.0',
AUTH_PORT: '4040',
CLIENT_APP_URL: 'http://localhost:3000/',
COUNTRY_CONFIG_URL: 'http://localhost:3040/',
DOMAIN: '*',
LOGIN_URL: 'http://localhost:3020/',
METRICS_URL: 'http://localhost:1050',
NOTIFICATION_SERVICE_URL: 'http://localhost:2020/',
REDIS_HOST: 'localhost',
USER_MANAGEMENT_URL: 'http://localhost:3030/'
naftis marked this conversation as resolved.
Show resolved Hide resolved
})

// eslint-disable-next-line @typescript-eslint/no-var-requires
const reloadedCodeService = require('../verifyCode/service')
Expand Down Expand Up @@ -102,7 +114,19 @@ describe('authenticate handler receives a request', () => {
expect(spy.mock.calls[0][3]).toBe('+345345343')
})
it('does not generate a mobile verification code for pending users', async () => {
server = await createServerWithEnvironment({ NODE_ENV: 'production' })
server = await createServerWithEnvironment({
NODE_ENV: 'production',
AUTH_HOST: '0.0.0.0',
AUTH_PORT: '4040',
CLIENT_APP_URL: 'http://localhost:3000/',
COUNTRY_CONFIG_URL: 'http://localhost:3040/',
DOMAIN: '*',
LOGIN_URL: 'http://localhost:3020/',
METRICS_URL: 'http://localhost:1050',
NOTIFICATION_SERVICE_URL: 'http://localhost:2020/',
REDIS_HOST: 'localhost',
USER_MANAGEMENT_URL: 'http://localhost:3030/'
})

// eslint-disable-next-line
const reloadedCodeService = require('../verifyCode/service')
Expand Down
29 changes: 10 additions & 19 deletions packages/auth/src/features/authenticate/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,6 @@
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
*/
import fetch from 'node-fetch'
import {
USER_MANAGEMENT_URL,
CERT_PRIVATE_KEY_PATH,
CERT_PUBLIC_KEY_PATH,
CONFIG_TOKEN_EXPIRY_SECONDS,
CONFIG_SYSTEM_TOKEN_EXPIRY_SECONDS,
PRODUCTION,
QA_ENV,
METRICS_URL
} from '@auth/constants'
import { resolve } from 'url'
import { readFileSync } from 'fs'
import { promisify } from 'util'
Expand All @@ -35,9 +25,10 @@ import { logger } from '@opencrvs/commons'
import { unauthorized } from '@hapi/boom'
import { chainW, tryCatch } from 'fp-ts/Either'
import { pipe } from 'fp-ts/function'
import { env } from '@auth/environment'

const cert = readFileSync(CERT_PRIVATE_KEY_PATH)
const publicCert = readFileSync(CERT_PUBLIC_KEY_PATH)
const cert = readFileSync(env.CERT_PRIVATE_KEY_PATH)
const publicCert = readFileSync(env.CERT_PUBLIC_KEY_PATH)

const sign = promisify<
Record<string, unknown>,
Expand Down Expand Up @@ -75,7 +66,7 @@ export async function authenticate(
username: string,
password: string
): Promise<IAuthentication> {
const url = resolve(USER_MANAGEMENT_URL, '/verifyPassword')
const url = resolve(env.USER_MANAGEMENT_URL, '/verifyPassword')

const res = await fetch(url, {
method: 'POST',
Expand All @@ -101,7 +92,7 @@ export async function authenticateSystem(
client_id: string,
client_secret: string
): Promise<ISystemAuthentication> {
const url = resolve(USER_MANAGEMENT_URL, '/verifySystem')
const url = resolve(env.USER_MANAGEMENT_URL, '/verifySystem')

const res = await fetch(url, {
method: 'POST',
Expand Down Expand Up @@ -135,8 +126,8 @@ export async function createToken(
subject: userId,
algorithm: 'RS256',
expiresIn: temporary
? CONFIG_SYSTEM_TOKEN_EXPIRY_SECONDS
: CONFIG_TOKEN_EXPIRY_SECONDS,
? env.CONFIG_SYSTEM_TOKEN_EXPIRY_SECONDS
: env.CONFIG_TOKEN_EXPIRY_SECONDS,
audience,
issuer
})
Expand Down Expand Up @@ -173,7 +164,7 @@ export async function generateAndSendVerificationCode(
mobile?: string,
email?: string
) {
const isDemoUser = scope.indexOf('demo') > -1 || QA_ENV
const isDemoUser = scope.indexOf('demo') > -1 || env.QA_ENV
logger.info(
`isDemoUser,
${JSON.stringify({
Expand All @@ -187,7 +178,7 @@ export async function generateAndSendVerificationCode(
} else {
verificationCode = await generateVerificationCode(nonce)
}
if (!PRODUCTION || QA_ENV) {
if (!env.isProd || env.QA_ENV) {
logger.info(
`Sending a verification to,
${JSON.stringify({
Expand Down Expand Up @@ -247,7 +238,7 @@ export async function postUserActionToMetrics(
userAgent: string,
practitionerId?: string
) {
const url = resolve(METRICS_URL, '/audit/events')
const url = resolve(env.METRICS_URL, '/audit/events')
const body = { action: action, practitionerId }
const authentication = 'Bearer ' + token

Expand Down
8 changes: 3 additions & 5 deletions packages/auth/src/features/invalidateToken/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
*/
import { setex } from '@auth/database'
import {
INVALID_TOKEN_NAMESPACE,
CONFIG_TOKEN_EXPIRY_SECONDS
} from '@auth/constants'
import { INVALID_TOKEN_NAMESPACE } from '@auth/constants'
import { env } from '@auth/environment'

export async function invalidateToken(token: string) {
return setex(
`${INVALID_TOKEN_NAMESPACE}:${token}`,
CONFIG_TOKEN_EXPIRY_SECONDS,
env.CONFIG_TOKEN_EXPIRY_SECONDS,
'INVALID'
)
}
14 changes: 13 additions & 1 deletion packages/auth/src/features/refresh/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,19 @@ describe('authenticate handler receives a request', () => {
let server: any

beforeEach(async () => {
server = await createServerWithEnvironment({ NODE_ENV: 'production' })
server = await createServerWithEnvironment({
NODE_ENV: 'production',
AUTH_HOST: '0.0.0.0',
AUTH_PORT: '4040',
CLIENT_APP_URL: 'http://localhost:3000/',
COUNTRY_CONFIG_URL: 'http://localhost:3040/',
DOMAIN: '*',
LOGIN_URL: 'http://localhost:3020/',
METRICS_URL: 'http://localhost:1050',
NOTIFICATION_SERVICE_URL: 'http://localhost:2020/',
REDIS_HOST: 'localhost',
USER_MANAGEMENT_URL: 'http://localhost:3030/'
})
})

describe('refresh expiring token', () => {
Expand Down
14 changes: 13 additions & 1 deletion packages/auth/src/features/resend/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,19 @@ describe('resend handler receives a request', () => {

describe('resend notification service says nonce is valid, generates a mobile verification code and sends it to notification gateway', () => {
it('returns a nonce to the client', async () => {
server = await createServerWithEnvironment({ NODE_ENV: 'production' })
server = await createServerWithEnvironment({
NODE_ENV: 'production',
AUTH_HOST: '0.0.0.0',
AUTH_PORT: '4040',
CLIENT_APP_URL: 'http://localhost:3000/',
COUNTRY_CONFIG_URL: 'http://localhost:3040/',
DOMAIN: '*',
LOGIN_URL: 'http://localhost:3020/',
METRICS_URL: 'http://localhost:1050',
NOTIFICATION_SERVICE_URL: 'http://localhost:2020/',
REDIS_HOST: 'localhost',
USER_MANAGEMENT_URL: 'http://localhost:3030/'
})
// eslint-disable-next-line
const codeService = require('../verifyCode/service')
// eslint-disable-next-line
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
*/
import fetch from 'node-fetch'
import { USER_MANAGEMENT_URL } from '@auth/constants'
import { env } from '@auth/environment'
import { resolve } from 'url'

export async function changePassword(
Expand All @@ -18,7 +18,7 @@ export async function changePassword(
remoteAddress: string,
userAgent: string
) {
const url = resolve(USER_MANAGEMENT_URL, '/changePassword')
const url = resolve(env.USER_MANAGEMENT_URL, '/changePassword')

const res = await fetch(url, {
method: 'POST',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
deleteRetrievalStepInformation
} from '@auth/features/retrievalSteps/verifyUser/service'
import { logger } from '@opencrvs/commons'
import { PRODUCTION } from '@auth/constants'
import { env } from '@auth/environment'
import { postUserActionToMetrics } from '@auth/features/authenticate/service'

interface IPayload {
Expand All @@ -47,7 +47,7 @@ export default async function sendUserNameHandler(
request.headers['x-real-ip'] || request.info.remoteAddress
const userAgent =
request.headers['x-real-user-agent'] || request.headers['user-agent']
if (!PRODUCTION || isDemoUser) {
if (!env.isProd || isDemoUser) {
logger.info(
`Sending a verification SMS,
${JSON.stringify({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
*/
import fetch from 'node-fetch'
import { NOTIFICATION_SERVICE_URL, JWT_ISSUER } from '@auth/constants'
import { JWT_ISSUER } from '@auth/constants'
import { env } from '@auth/environment'
import { resolve } from 'url'
import { IUserName, createToken } from '@auth/features/authenticate/service'

Expand All @@ -19,7 +20,7 @@ export async function sendUserName(
mobile?: string,
email?: string
) {
const url = resolve(NOTIFICATION_SERVICE_URL, '/retrieveUserName')
const url = resolve(env.NOTIFICATION_SERVICE_URL, '/retrieveUserName')
const res = await fetch(url, {
method: 'POST',
body: JSON.stringify({ msisdn: mobile, email, username, userFullName }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
*/
import fetch from 'node-fetch'
import { USER_MANAGEMENT_URL } from '@auth/constants'
import { env } from '@auth/environment'
import { resolve } from 'url'

export interface IVerifySecurityAnswerResponse {
Expand All @@ -22,7 +22,7 @@ export async function verifySecurityAnswer(
questionKey: string,
answer: string
): Promise<IVerifySecurityAnswerResponse> {
const url = resolve(USER_MANAGEMENT_URL, '/verifySecurityAnswer')
const url = resolve(env.USER_MANAGEMENT_URL, '/verifySecurityAnswer')

const res = await fetch(url, {
method: 'POST',
Expand Down
Loading
Loading