Two .env environments with different passwords - node.js

In my app, I am using two API's that require different credentials. I am storing each in a .env file and read them using process.env. Both .env and .config files are in different directories.
The first config.js:
const dotenv = require('dotenv');
const cfg = {};
dotenv.config({path: '.env'});
cfg.port = process.env.PORT;
cfg.apiKey = process.env.apiKey;
cfg.authDomain = process.env.authDomain;
cfg.databaseURL = process.env.databaseURL;
cfg.projectId = process.env.projectId;
cfg.storageBucket = process.env.storageBucket;
cfg.messagingSenderId = process.env.messagingSenderId;
module.exports = cfg;
The second config.js
const dotenv = require('dotenv');
const cfg = {};
dotenv.config({path: '.env'});
cfg.port = process.env.PORT;
cfg.accountSid = process.env.TWILIO_ACCOUNT_SID;
cfg.authToken = process.env.TWILIO_AUTH_TOKEN;
cfg.twimlAppSid = process.env.TWILIO_TWIML_APP_SID;
cfg.callerId = process.env.TWILIO_CALLER_ID;
module.exports = cfg;
I configured both .env files the same way. But apparently the second config.js is not able to read the credentials such as: TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, etc. Which led me to believe that for the second .env file I have to configure differently than the first one.
How do I load the two sets of credentials into one environment? Or do I have to load them into different environments?
Thanks for your time.

For your first question, yes, you can load both set of credentials in one environment as the keys are different for each one, you just need to import both config.js in your 'main' code.
If what you need to do is to use two set of credentials in the same api, you could both credentials in the same JSON as follow:
{
firstSet: {
TWILIO_ACCOUNT_SID: 'value',
TWILIO_AUTH_TOKEN: 'value',
TWILIO_TWIML_APP_SID: 'value',
TWILIO_CALLER_ID: 'value'
},
secondSet: {
TWILIO_ACCOUNT_SID: 'value2',
TWILIO_AUTH_TOKEN: 'value2',
TWILIO_TWIML_APP_SID: 'value2',
TWILIO_CALLER_ID: 'value2'
}
}
Defined your two different credentials, you could define the logic to use one or another or both credentials in your config.js, depending on your needs, and export that to your application where you could pick the credential from the config to use in the api.

Related

Ensuring Azure keyvault secrets are loaded to config (node-config) at application startup

I have a NodeJS application that uses Node-Config (https://www.npmjs.com/package/config) to load application configurations. What I'm trying to do is to load secrets from Azure Keyvault to the config during startup, and ensure these are available before required (e.g. connecting to databases etc).
I have no problem connecting to and retrieving values from the Keyvault, but I am struggling with the non-blocking nature of JS. The application startup process is continuing before the config values have completed loaded (asynchronously) to the config.
One strategy could be to delay application launch to await the keyvault secrets loading How to await in the main during start up in node?
Another would be to not load them in Config but instead modify code where-ever secrets are used to load these asynchronously via promises
It seems like this will be a common problem, so I am hoping someone here can provide examples or a design pattern of the best way of ensuring remote keyvault secrets are loaded during startup.
Thanks in advance for suggestions.
Rod
I have now successfully resolved this question.
A key point to note is setting process.env['ALLOW_CONFIG_MUTATIONS']=true;
Configs are immutable by default (they can't be changed after initial setting). Since async is going to resolve these later, it's critical that you adjust this setting. Otherwise you will see asynchronous configs obtaining correct values from the keystore, but when you check with config.get they will not have been set. This really should be added to the documentation at https://github.com/node-config/node-config/wiki/Asynchronous-Configurations
My solution: first, let's create a module for the Azure keystore client - azure-keyvault.mjs :
import { DefaultAzureCredential } from '#azure/identity';
import { SecretClient } from '#azure/keyvault-secrets';
// https://learn.microsoft.com/en-us/azure/developer/javascript/how-to/with-web-app/use-secret-environment-variables
if (
!process.env.AZURE_TENANT_ID ||
!process.env.AZURE_CLIENT_ID ||
!process.env.AZURE_CLIENT_SECRET ||
!process.env.KEY_VAULT_NAME
) {
throw Error('azure-keyvault - required environment vars not configured');
}
const credential = new DefaultAzureCredential();
// Build the URL to reach your key vault
const url = `https://${process.env.KEY_VAULT_NAME}.vault.azure.net`;
// Create client to connect to service
const client = new SecretClient(url, credential);
export default client;
In the config (using #node-config) files:
process.env['ALLOW_CONFIG_MUTATIONS']=true;
const asyncConfig = require('config/async').asyncConfig;
const defer = require('config/defer').deferConfig;
const debug = require('debug')('app:config:default');
// example usage debug(`\`CASSANDRA_HOSTS\` environment variable is ${databaseHosts}`);
async function getSecret(secretName) {
const client = await (await (import('../azure/azure-keyvault.mjs'))).default;
const secret = await client.getSecret(secretName);
// dev: debug(`Get Async config: ${secretName} : ${secret.value}`);
return secret.value
}
module.exports = {
//note: defer just calculates this config at the end of config generation
isProduction: defer(cfg => cfg.env === 'production'),
database: {
// use asyncConfig to obtain promise for secret
username: asyncConfig(getSecret('DATABASE-USERNAME')),
password: asyncConfig(getSecret('DATABASE-PASSWORD'))
},
...
}
Finally modify application startup to resolve the async conferences BEFORE config.get is called
server.js
const { resolveAsyncConfigs } = require('config/async');
const config = require('config');
const P = require('bluebird');
...
function initServer() {
return resolveAsyncConfigs(config).then(() => {
// if you want to confirm the async configs have loaded
// try outputting one of them to the console at this point
console.log('db username: ' + config.get("database.username"));
// now proceed with any operations that will require configs
const client = require('./init/database.js');
// continue with bootstrapping (whatever you code is)
// in our case let's proceed once the db is ready
return client.promiseToBeReady().then(function () {
return new P.Promise(_pBootstrap);
});
});
}
I hope this helps others wishing to use config/async with remote keystores such as Azure. Comments or improvements on above welcome.
~ Rod

Saving to two different Firestore databases with dialogflow

I'm making an actions on google project that will require adding data to two different Cloud Firestore. For some reason when I trigger the intent, it will only save to the original Cloud Firestore, but not the new one.
For simplicity, I'm going to refer to the original Cloud Firestore as "DB1" and the new one/ second one as "DB2"
Here's what I had tried:
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const admin = require('firebase-admin');
const {google} = require('googleapis');
const {
<Basically All of the libraries / AOG functions>
} = require('actions-on-google');
const defaultAppConfig = {"<FIREBASE CREDENTIALS FOR DB1 >"}
const OtherAppConfig = {"<FIREBASE CREDENTIALS FOR DB2>"}
const defaultApp = admin.initializeApp(defaultAppConfig); // DB1
const otherApp = admin.initializeApp(OtherappConfig, 'Other'); // DB2
const db = admin.firestore(functions.config(defaultApp).firebase); //DB1
const ab = admin.firestore(functions.config(otherApp).firebase); // DB2
const app = dialogflow({
debug: true,
clientId: '<DIALOGFLOW CREDENTIALS>'
});
app.intent('DB2 Write', (conv) =>{
conv.ask('Okay I made the write to DB2');
var data = {
name: 'This is a Test write'
};
var setDoc = ab.collection('Test').doc('Write').set(data);
});
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);
Sorry if some parts are unnecessary, I wanted to include as much information as I could (I might be missing something that someone else sees).
To sum up what I thought would happen, I thought when I triggered the intent 'DB2 Write' that it would write 'This is a Test Write' to DB2, however it just keeps writing the message/data to DB1.
How do I get this working so it will write to my second Cloud Firestore or "DB2" when this intent is triggered?
Thanks for the help!
Note: If it makes a difference, I'm using the dialogflow inline editor for this code.
____________________________________________________________________________
Update: Here is what I have tried/ updated and it still writes to DB1
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
const otherAdmin = require('firebase-admin');
otherAdmin.initializeApp({
credential: otherAdmin.credential.cert(OtherAppConfig)
},'Other');
const ab = otherAdmin.firestore();
and as well:
admin.initializeApp(defaultAppConfig);
var otherApp = admin.initializeApp(OtherAppConfig, 'other');
console.log(admin.app().name); // '[DEFAULT]'
console.log(otherApp.name); // 'other'
// Use the shorthand notation to retrieve the default app's services
var db = admin.firestore(functions.config().firebase);
// Use the otherApp variable to retrieve the other app's services
var ab = otherApp.firestore(functions.config().firebase);
I'd like to note, the credentials I'm using for "OtherAppConfig" and "defaultAppConfig" were taken from the Firebase private key. ie: firebase console > project overview > service accounts > generate private key. Could this be the problem?
I think the problem is thus:
A Dialogflow project and a Firebase project are the same under the hood. This cool, as your Firebase Functions will know intuitively connect with Dialogflow and your database without a lot of manual configuration.
However, if you have two databases from different Cloud Projects, you will need to do some additional configurations to connect securely. I'm not sure what your AppConfigs contain, but they may not be sufficiently setup. As such, the Firebase setup may be pulling the default (current project) app and database when you're grabbing the functions config.
You may want to, for your second project, download the service key. Then you can load it as a file or directly as JSON in your startup routine.
This snippet below should work the way you want.
// Setup 1st db, this project's db
const admin = require('firebase-admin');
admin.initializeApp(); // Initialize with default params as we get it by default
const db = admin.firestore();
// Setup 2nd db
const otherAdmin = require('firebase-admin'); // We can import twice
const myOtherServiceKey = { ... }
otherAdmin.initializeApp({
credential: otherAdmin.credential.cert(myOtherServiceKey)
});
const ab = otherAdmin.firestore(); // This should be the DB of our second project

How authenticate with gcloud credentials an Dialogflow API

I have a Node JS app that make requests to a Dialogflow agent. I actually use a temporally token based request, but how can i change this to do it through google service credentials? (https://cloud.google.com/docs/authentication/getting-started). I have a credencial created (with billing added), and the service_account json file.
I would like to use the Dialogflow package in node (https://www.npmjs.com/package/dialogflow) but i don't underestand how to use it with the json file.
const projectId = 'ENTER_PROJECT_ID_HERE';
const sessionId = 'quickstart-session-id';
const query = 'hello';
const languageCode = 'en-US';
// Instantiate a DialogFlow client.
const dialogflow = require('dialogflow');
const sessionClient = new dialogflow.SessionsClient();
// Define session path
const sessionPath = sessionClient.sessionPath(projectId, sessionId);
The example of the package use Project ID and Session ID, but not with a json file like the example of the google services (or using big query like How to authenticate with gcloud big query using a json credentials file?). Anyway, where can i get this project and session id?
Please, if someone can help me or guide how to do this in a better way?. Thanks
First you have to create a service account and download a .JSON format file of credentials on your local system.
Now, there are three ways to use that credentials for authentication/authorisation in dialogflow library.
Method 1
Create a environment variable GOOGLE_APPLICATION_CREDENTIALS and it's value should be the absolute path of that JSON credentials file.By this method, google library will implicitly loads the file and use that credentials for authentication. We don't need to do anything inside our code relating to this credentials file.
export GOOGLE_APPLICATION_CREDENTIALS="<absolute-path-of-json-file>" # for UNIX,LINUX
# then run your code, google library will pick credentials file and loads it automatically
Method 2
Assume, you know the absolute path of your JSON file and put that as value in below snippet of credentials_file_path variable.
// You can find your project ID in your Dialogflow agent settings
const projectId = '<project-id-here>';
const sessionId = '<put-chat-session-id-here>';
// const sessionid = 'fa2d5904-a751-40e0-a878-d622fa8d65d9'
const query = 'hi';
const languageCode = 'en-US';
const credentials_file_path = '<absolute-file-path-of-JSON-file>';
// Instantiate a DialogFlow client.
const dialogflow = require('dialogflow');
const sessionClient = new dialogflow.SessionsClient({
projectId,
keyFilename: credentials_file_path,
});
Method 3
You can note down the project_id, client_email and private_key from the JSON, use them in your code for authentication explicitly.
// You can find your project ID in your Dialogflow agent settings
const projectId = '<project-id-here>';
const sessionId = '<put-chat-session-id-here>';
// const sessionid = 'fa2d5904-a751-40e0-a878-d622fa8d65d9'
const query = 'hi';
const languageCode = 'en-US';
const credentials = {
client_email: '<client-email-here>',
private_key:
'<private-key-here>',
};
// Instantiate a DialogFlow client.
const dialogflow = require('dialogflow');
const sessionClient = new dialogflow.SessionsClient({
projectId,
credentials,
});
Here is how you can do it with a service account code sample is in kotlin and definitely can be translated into the node.js sdk
val credentialsProvider = FixedCredentialsProvider.create(ServiceAccountCredentials
.fromStream(Classes.getResourceAsStream([YOUR JSON CONFIG FILE GOES HERE])))
val sessionsSettings = SessionsSettings.newBuilder().setCredentialsProvider(credentialsProvider).build()
sessionsClient = SessionsClient.create(sessionsSettings)
You can get the service account from Dialogflow settings click on the service account links and then create a json config file there in ur cloud console.

Change connection used on runtime

I hope you can help me (us). I'm working on a API project which have two databases :
Production DB : api.myapp.fr
Testing DB : test.api.myapp.fr
Theses two databases are writable by the user.
When a user call our API, he can set the authorization header whichever he needs. For example :
Authorization: s_0
Will perform operations on api.myapp.fr and
Authorization: s_t_0
Will perform operations on test.api.myapp.fr .
My question is : How can I do that with sails ?
Actually, I have a policie which check if the user is using a production key or a testing key, and I override the default models with the one for testings purposes, like this :
if (!is_production) {
req.session.isProd = false;
req.session.logs.environment = "test";
User = UserTest;
Payment = PaymentTest;
PayzenStatus = PayzenStatusTest;
Transaction = TransactionTest;
Card = CardTest;
Doc = DocTest;
}
But you can see the problem if a user makes a test request and then a production request, the models are still the tests ones...
I use my models in services and policies, therefor I can't do
req.models = {};
// If not in production, use the test models
if (!is_production) {
req.session.isProd = false;
req.session.logs.environment = "test";
req.models.User = UserTest;
req.models.Payment = PaymentTest;
req.models.PayzenStatus = PayzenStatusTest;
req.models.Transaction = TransactionTest;
req.models.Card = CardTest;
req.models.Doc = DocTest;
}
// Otherwise use the production models
else {
req.models.User = User;
req.models.Payment = Payment;
req.models.PayzenStatus = PayzenStatus;
req.models.Transaction = Transaction;
req.models.Card = Card;
req.models.Doc = Doc;
}
If you have any idea on how ton achieve this (whatever the way, we can still perform deep changes in our code), I would be really happy to ear it.
Thanks
Two different ways of doing this.
First you could set an environment variable on your production host and check that environment variable to see if you are running in prod. If you are running in prod then use the URI to the production database.
Secodonly, which is probably the better way of doing this creating a config.js file that allows you to read environment variables. What I do for all my apps is set environment variables for connection info to databases and API keys. When running locally/testing I have some defaults in my app but when they environment variables are set they are read and used. So set the environment variable in production to point to your production databases.
The config.js file Im posting below contains references to VCAP, which assumes you are running on Cloud Foundry.
config.js
var VCAP_SERVICES = process.env["VCAP_SERVICES"],
vcapServices;
if (VCAP_SERVICES) {
vcapServices = JSON.parse(VCAP_SERVICES);
}
function getEnv(propName, defaultValue) {
if (process.env[propName]) {
return process.env[propName];
} else {
return defaultValue;
}
}
module.exports = function() {
return {
getEnv : getEnv,
couchDbURL: function() {
// Default to a local couch installation for development
if (VCAP_SERVICES) {
return vcapServices["cloudantNoSQLDB"][0].credentials.url;
}
else {
return "http://localhost:5984";
}
},
couchDbName: function() {
return getEnv("COUCHDB_NAME", "mydb");
}
};
};
app.js
var config = require("./config")();
console.log(config.couchDbURL());

Read environment neutral Configuration Value from NodeJS

I have an env. neutral configuration which is EnableEmailCheck. If the EnableEmailCheck= true then the email validation will takes place and if it is set to false application will not do the email validation.
I need to understand how to do something like that in nodejs
What format the configuration file should be ? I think I can keep the key value pair it in .json file
How can I read this configuration value from nodejs
If the value of the configuration file is changed is it required to restart the nodejs app server to reflect the changes
This configuration value will get changed if there is an issue in the emailvalidation functionality. Then the value will be switched off; in this case it will be set to false by and application engineer.
I would save it in json format and add a watch on it:
config.json content:
{
"env":
{
"EnableEmailCheck": true;
}
}
Loading config.json file at start and when it changes:
var config = null;
var fs = require('fs');
function loadConfig(fileName )
{
var data = fs.readFileSync( fileName );
return JSON.parse( data );
}
function useConfig( )
{
if( config.env.EnableEmailCheck )
{
// do your stuff;
}
}
//load it at start
config = loadConfig("config.json");
useConfig();
//add watch for changes
fs.watchFile("config.json", function ()
{
config = loadConfig("config.json");
useConfig();
});

Resources