How can we pass parameters to an already built Vue JS app? - javascript

We have a Vue app that connects to a Web Service and get's some data. The web service URL is different, depending on the location we install the app on.
I first thought of using .env files, but later I realized these files get injected into the minified .js files.
Having this in my main.js was very convenient in the case of .env files:
Vue.prototype.ApiBaseUrl = process.env.VUE_APP_APIBASEURL
Vue.prototype.PrintDocsFolder = process.env.VUE_APP_PRINTDOCSFOLDER
Vue.prototype.TicketPrintWSocket = process.env.VUE_APP_TICKETPRINTWSOCKET
The app is already built. I don't want to build the app for each of the hundred locations we have to deploy to. I'm not sure about the "official" approach for this.
Is there any out of the box solution in Vue that can allow this configuration? Basically we need to have a file in the root folder of the built app, and read values for our Vue.prototype.VARIABLES.
We are using vue-cli 3.

Like others have said, you can do this at runtime via a network request. If you don't know where you're deploying to until you're live, you'll need to do something like this.
Alternatively, you can do this at an infrastructure and networking level. Sometimes for A/B testing systems, that's how it's done.
Alternatively, you can do this at build time. I've done both... for static assets like images, sometimes you cannot do this at runtime and you need to replace the public urls at build time. For the network request approach, fetching a static json file with the mappings you host is a definite possibility.
You were very close with the idea of using .env files.
Build-time approach with Vue CLI
In Vue CLI, you get to use the Webpack's DefinePlugin for free by specifying variables in your .env files prefixed as such: VUE_APP_THE_API_URL and then using it like process.env.VUE_APP_THE_API_URL. Docs.
Usage
In your source, use process.env.VUE_APP_THE_API_URL and .env files. Reference your API url in your source code as process.env.VUE_APP_THE_API_URL and then you should use the .env files, like you were planning to, to switch between the dev-only value and a production-only value.
The production-only value is gonna be fake and very unique so that when you find + replace it, it will be distinct.
Find + Replace the fake API_URL you built with
After you're done building your application for production, you're going to loop over a mapping file (json, js, whatever) which contains the API urls you're going to deploy to.
You'll use the filesystem and find + replace to replicate your app as many times as you need before doing a deploy via S3, fastly, etc. You can do this in a bash script or using node + execa or node + fs.
Why you might need to do this
I had to do this at build time because modifying certain assets is not possible at runtime due to optimizations done by webpack loaders where they hard-code things like the public path so it is faster. With hundreds of API/CDN urls, it would be extremely inefficient to rebuild the application over and over.
How Vue CLI does it (if you don't wanna do VUE_APP_*)
Vue CLI is ontop of webpack and this is kind of an advanced use case, so you'll want to set configureWebpack inside of vue.config.js and point that to a require('webpack.config.js') file. You want to do this in Webpack or just within your build process (bash, node, gulp, whatever).
Vue CLI 3 is tied to the major webpack version. Right now that's Webpack 4. I'm going to give you the Webpack 4 answer for your problem, but I think they're changing the plugin name in Webpack v5.
Define Plugin
The plugin you want is the DefinePlugin. Just do the above steps, but you'd be manually setting { plugins: [ new DefinePlugin() ] } with the options you want. You'd do this if you didn't wanna do VUE_APP_* as the prefix to your env variables.

This can be done simply by making an XMLHttpRequest to a URL (e.g., to a local JSON file), and reading the data contents.
For instance, you could use fetch (supported by 95% browsers) to request the config from <projectroot>/config.json (which can be unique to each deployment location), and then set the global properties with the result:
// main.js
fetch('config.json')
.then(res => res.json())
.catch(error => {
// ignore any errors
console.warn(error)
return {}
})
.then(config => {
Vue.prototype.ApiBaseUrl = config.ApiBaseUrl
Vue.prototype.PrintDocsFolder = config.PrintDocsFolder
Vue.prototype.TicketPrintWSocket = config.TicketPrintWSocket
// initialize app here
new Vue(...)
})
demo

If there are literally 100's of locations where the app is supposed to be deployed with different APIs/params for each location AND YOU DON'T WANT TO LEAVE ANY TRACE OF THE WHOLE DATA apart from the variables which are necessary for proper functioning of the app, I would have personally stored all the different params in one central database and create one single common API which is able to decide which params to feed to which particular deployment. So that at initial app load, the app would just have to make 1 extra API to call to get the correct params. (provided there is some unique identifier for each deployment).
For example, if the unique identifier is the domain name over which the app is served.
You can store the params like this in the database:
+-------------------+----------------------------+-----------------+--------------------+--+
| domainName | ApiBaseUrl | PrintDocsFolder | TicketPrintWSocket | |
+-------------------+----------------------------+-----------------+--------------------+--+
| example.com | http://api-base-url-1.com/ | print-doc-1 | ticket-print-1 | |
+-------------------+----------------------------+-----------------+--------------------+--+
| secondExample.com | http://api-base-url-2.com/ | print-doc-2 | ticket-print-2 | |
+-------------------+----------------------------+-----------------+--------------------+--+
| thirdExample.com | http://api-base-url-3.com/ | print-doc-3 | ticket-print-3 | |
+-------------------+----------------------------+-----------------+--------------------+--+
then at app load, you can make a axios (Promise based HTTP client) call and pass the current domain name as a param like this:
const details = await axios.get('/common-api-with-all-the-details', {
params: {
domainName: location.hostname
});
This common API should match the domain with db and fetch the correct record accordingly.
Advantages:
You never need to rebuild the app or config the environment variables
individually.
You will always be in control over which params to feed
to which particular deployment.
You can CHANGE/UPDATE the params on
the FLY.
Your whole data store is not public.
Disadvantages:
Requires one extra server setup.
1 extra API call at app's initial load.
Other approaches:
You can avoid the use of database (if your dataset is not too large) by storing all your details in an array. Then on every COMMON API call, you can match the domain name against the array (lodash can help), thereby increasing response time, lowering complexity and avoiding a database setup completely.
You can use server-less architecture to avoid setting up a completely new server to host your COMMON API, Firebase Cloud functions or AWS Lambda have generous free tiers to cover a decent amount of traffic.

Related

How to secure API Keys in Environment Variables in a Vue CLI 4 and Electron project

I'm trying to develop a desktop app which would need to make a few private API calls, authenticated using some secret keys.
The keys are created for me by external IT service providers outside of my organisation - they are responsible for the security so there are a few constraints:
They said even though they have already taken steps on their end to secure the API and there are mitigation strategies in place even if a breach happens, but still they would like to make sure that I treat the keys with a security-conscious mindset and take whatever steps possible on my end as well to make sure they remain secured.
I'm not allowed to just create random middleware / gateway on a private server or serverless platform to perform the API calls on my app's behalf as these calls may contain business data.
I have done some research and from what I can find, the general recommendation is to set up a ".env" file in the project folder and use environment variables in that file to store the API keys.
But upon reading the Vue CLI documentation I found the following:
WARNING
Do not store any secrets (such as private API keys) in your app!
Environment variables are embedded into the build, meaning anyone can
view them by inspecting your app's files.
So, given the constraints, is there a way to store these keys securely in a Vue CLI 4 + Electron Desktop app project?
Thanks.
In general, especially if you have a lot of environment variables, it would be better practice to store environment variables in a dot env file (.env), however, it's possible that this file could be leaked when you package your electron app. So, in this case it would be better to store your environment variables from the terminal/command line. To do so follow this guide (https://www.electronjs.org/docs/api/environment-variables).
Keep in mind anything that requires the API key/private information try to keep it on the backend, i.e., the electron process and send the results to the Vue front end.
Here's an example of how you could implement this:
On windows from CMD:
set SOME_SECRET="a cool secret"
On POSIX:
$ export SOME_SECRET="a cool secret"
Main process:
// Other electron logic
const { ipcMain } = require("electron");
// Listen for an event sent from the client to do something with the secret
ipcMain.on("doSomethingOnTheBackend", (event, data) => {
API.post("https://example.com/some/api/endpoint", {token: process.env.SOME_SECRET, data});
});
Client side:
const { ipcRenderer } = require("electron");
ipcRenderer.send("doSomethingOnTheBackend", {username: "test", password: "some password"});
Also note, to use the ipcRenderer on the client side nodeIntegration needs to be enabled.
Here are some more resources to help you get started:
https://www.electronjs.org/docs/api/ipc-renderer
https://www.electronjs.org/docs/api/ipc-main

how to use variables in base url and compose config when making the request in Vue.js?

What I want to achieve - for vue to serve a foo1.bar.com where "foo" is a name of the tenant in multitenant project. So base API that vue uses becomes foo2.bar.com/api when foo2.bar.com is accessed and foo3.bar.com/api when foo3.bar.com is accessed.
Context: this is a suggested way to achieve coherence with existing multitenant backend API, URLs of which look like t1.site.com/api and t2.site.com/api .
It was suggested on reddit as a response to this question:
I have almost finished my first decoupled (frond and back ends are
separate) project - the back end is written with django + rest
framework and implements multitenancy (means my api endpoints look
like tenant1.sitename.com/api/endpoint and
tenant2.sitename.com/api/endpoint) . While I was developing, I assumed
that there shouldn't be a problem consuming my api since the front end
is the same for all tenants, so django could just consume same vue
front end no matter which tenant.. ant then it struck me - actually
it's vue consuming django api, not other way around.. and vue doesn't
know which tenant is selected..So now I'm very close to a deadline and
lost.
My main.ts looks like this
axios.defaults.baseURL = 'http://tenant1.mysite.local:8000/api/';axios.defaults.withCredentials
= true;
and works... while I need the first tenant's data....
I'm not entirely sure that variable is supposed to be used in baseUrl, or that typescript is supposed to be used, but as I said, my current setup has baseurl in main.ts .
To reiterate:
I have one back-end serving api for different tenants like t1.foo.com/api and t2.foo.com/api and one front-end that currently only sends requests to only one baseurl defined in settings, for example t1.foo.com/api ; It then serves it on t1.foo.com/home . Problem is, if I would to go to t2.foo.com/home , it would still send requests to t1.foo.com/api .
I know neither how to make different (t1,t2,t3) urls accessible nor how to make it send requests to matching api. I want to acieve my frontent sending the api request to t1.foo.com/api when i go to to t1.foo.com/home and t2.foo.com/api when I go to t2.foo.com/home .
I asked a similar question before and I got this full detailed answer
if I understand you correctly I think the best solution for is to set this in vue.config.js file:
publicPath: './'
which sets the url of all request to the backend to the relative url of the served html file (including static files like css, js...).
For example if you access you application with this url t1.mysite.com/index.html - all request will be sent to t1.mysite.com/..../....
you can read more about publicPath in vue.js docs

Node process.env variables empty

I'm building my first Express app, which needs to interact with an API, using an API key that ideally remains secure.
So I wanted to follow a basic pattern of keeping the key (and any future environment variables), in a .gitignored .env file in the root directory.
To not reinvent the wheel, I used this package, and set my env variables like so, in my app.coffee file (the root file of the application):
env = require('node-env-file')
env __dirname + '/.env'
console.log process.env.MY_API_KEY
That console.log prints out the right key to the server logs. The problem arises later:
If I try to access that same variable in one of the JS files loaded later on by my app, process.env is an empty object, so the API key is undefined. This doesn't appear to be a problem with the above package, because if I define the variable in the CL (API_KEY=whatever npm start), the behavior is the same -- it console logs correctly from app.coffee but is unavailable later.
Some information on how the files in which the key is unavailable are being loaded:
The app is running React, which I write to a few .jsx files in public/javascripts/src, and which are compiled by gulp into public/javascripts/build/*.js.
I'm trying to access the key in a .js file in public/javascripts/ which is required by one of the .jsx files.
In that required .js file, process.env returns an empty object. When I try to access process.env in the .jsx files, I'm actually told that process itself is undefined.
Any ideas what's going on here? I'm new to Express/React, and unclear where this process object, which I thought was global and defined on npm start is defined, and what's happening to all the env info in it.
Thanks! Please let me know if any other information would be helpful, orif anyone has any suggestions for how better to handle private env info in my situation.
EDIT:
I tried the suggestions below, and created a separate endpoint internally, which hits the external API and then returns a response. I've strung things up correctly, so that this responds correctly:
router.get '/images', (req, res, next) ->
res.json({ some: 'json' });
but this (which uses a separate class to make a request to an external API), throws an error:
router.get '/images', (req, res, next) ->
new Images('nature').fetch (images) ->
res.json({ some: 'json' })
Essentially, it looks like the asynchrony of the response from the external API (and not even the data itself, which I ignored), is creating a problem. How do I hit this external endpoint and then respond to the internal request with the incoming data?
Back-end vs Front-end
It seems like you are trying to access back-end data from a front-end location, in a wrong way.
The great power of Node.js is having JavaScript in the front and in the back, but it is quite confusing in the beginning to understand on which side each script is executed.
In an Express project, all Javascript files that are sent to the front-end, those that will directly interact with the client's page, are located in public/javascripts/. Generally you will have some AJAX functions in some of those files to exchange data and communicate with the back-end.
These back-end files are located everywhere else : in the root directory, in routes/, and all the other folders you create. Those files are pretty much all connected to your Node instance, and therefore can communicate with each other using global objects like process for example.
Your script in public/javascripts/, that is executed on the client's computer, is trying to directly access a variable located on the server running your Node instance : that's why your code doesn't work. If you wish to access data from the back-end, you must use AJAX calls in the front-end.
Server <---(AJAX only)--- Client
------ ------
app.js public/javascripts/script.js
routes.js
...
That being said, you wanted to keep your API key private, which will not happen if you send it to every client who's on that specific page. What you should do is make the call from the back-end, using the xhr module for example, and then delivering the data to front-end, without the secret API key.
I hope I was clear, Node is quite confusing at first but very soon you will get over these little mistakes !
All .jsx is, is some code, what matters is where the code is being executed. process.env is a variable that is accessible inside the Node.js runtime. When your .jsx code gets transpiled down to .js and served to the browser, the process.env variable will no longer exist. If you're making an API call inside the browser, the API key will be fundamentally available to the client. If you want to secure the key, you have to have your Node.js server expose an API route, which your React app will hit. That Node.js server will then make the call to the external service using the API key. Because that call is being made by the server, process.env will be available, and will remain hidden from the client. You can then forward the result of the API call back to the user.

Singe Page Application External Configurations (Not On NodeJS)

I'm looking for either a reference or an answer to what I think is a very common problem that people who are current implementing JavaScript MVC frameworks (such as Angular, Ember or Backbone) would come across.
I am looking for a way or common pattern to externalize application properties that are accessible in the JS realm. Something that would allow the javascript to load server side properties such as endpoints, salts, etc. that are external to the application root. The issue that I'm coming across is that browsers do not typically have access to the file systems because it is a security concerns.
Therefore, what is the recommended approach for loading properties that are configurable outside of a deployable artifact if such a thing exists?
If not, what is currently being used or is in practice that is considered the recommended approach for this types of problem?
I am looking for a cross compatible answer (Google Chrome is awesome, I agree).
Data Driven Local Storage Pattern
Just came up with that!!
The idea is to load the configuration properties based on a naming over convention configuration where all properties are derived from the targeted hostname. That is, the hostname will derive a trusted endpoint and that endpoint will load the corresponding properties to the application. These application properties will contain information that is relative at runtime. The runtime information will be supplied to the integration parts which then communicate via property iteration on the bootstrapping start up.
To keep it simple, we'll just use two properties here:
This implementation is Ember JS specific but the general idea should be portable
I am currently narrowing the scope of this question to a specific technological perspective, that is Ember JS with the following remedy that is working properly for me and hope it will help any of you out there dealing with the same issue.
Ember.Application.initializer implementation in start up
initialize: function (container, application) {
var origin = window.location.origin;
var host = window.location.hostname;
var port = window.location.port;
var configurationEndPoint = '';
//local mode
if(host === 'localhost'){
//standalone using api stub on NODEJS
if(port === '8000'){
configurationEndPoint = '/api/local';
}//standalone UI app integrating with back end application on same machine, different port
else{
configurationEndPoint = '/services/env';
}
origin += configurationEndPoint;
}else{
throw Error('Unsupported Environment!!');
}
//load the configuration from a trusted resource and store it in local storage on start up
$.get(origin,
function( data ) {
//load all configurations as key value pairs and store in localStorage for access.
configuration = data.configuration;
for(var config in configuration){
debugger;
var objectProperty = localStorage + '.' + config.toString()
objectProperty = configuration[config];
}
}
);
}
Configurable Adapter
export default DS.RESTAdapter.extend({
host: localStorage.host,
namespace: localStorage.namespace
});
No later than yesterday morning i was tackling the same issue.
Basically, you have two options:
Use localStorage/indexedDB or any other client-side persistent storage. (But you have to put config there somehow).
Render your main template (the one that gets rendered always) with a hidden where you put config JSON.
Then in your app init code you get this config and use it. Plain and simple in theory, but lets get down to nasty practice (for second option).
First, client should get config before application loads. It is not easy sometimes. e.g. user should be logged in to see config. In my case i check if i can provide config on the first request, and if not redirect user to login page. This leads us to second limitation. Once you are ready to provide config, you have to reboot app completely so that configuration code run again (at least in Angular it is necessary, as you cannot access providers after the app bootstraps).
Another constraint, the second option is useless if you use static html and cannot change it somehow on server before sending to the client.
May be a better option would be to combine both variants. This should solve some problems for returning users, but first interaction will not be very pleasant anyway. I have not tried this yet.

Making collections available on the client without meteor startup

When i define my meteor collections on the server and try to access them in the client without being in any of the meteor provided methods rendered, events, created, helpers ... I always get an error that says Meteor collection not defined if i try to redefine the method in the client, i get the Meteor collection already exists. I am able to get around this by referencing my custom made collections in a Meteor.startup() function. How can i reference the collection i defined on the server in the client. In the meteor docs there able to create two instances of Meteor.Collection() and subscribe even before declaring.
// okay to subscribe (and possibly receive data) before declaring
// the client collection that will hold it. assume "allplayers"
// publishes data from **server's "players" collection.**
Meteor.subscribe("allplayers");
...
// client queues incoming players records until ...
...
Players = new Meteor.Collection("players");
You can place Players = new Meteor.Collection("players"); at the top of your file without it being in Meteor.startup. Make sure its defined before you initiate Meteor.subscribe
e.g your file could be:
Players = new Meteor.Collection("players");
MyCollection2 = new Meteor.Collection("MyCollection2");
Meteor.subscribe("allplayers");
Meteor.subscribe("mycollection2");
..rest of stuff
Something a bit cleaner might be to create a file in your project's root directory containing this so that its used on both the client and the server without you having to redefine them for each e.g a collection.js in your project root could contain
Players = new Meteor.Collection("players");
MyCollection2 = new Meteor.Collection("MyCollection2");
if(Meteor.isClient) {
Meteor.subscribe("allplayers");
Meteor.subscribe("mycollection2");
}
so now you don't have to define Players or MyCollection2 on your /server or /client anymore. The way meteor loads files will ensure that this is defined before your other regular files. This probably works best if you've arranged your files in the /client,/server and /public format as used on the other meteor examples (parties & todo)
Edit: as BenjaminRH suggests, putting your file in /lib/collections.js assures it will be loaded even before other files in your root project dir.

Categories

Resources