Cognito post authentication custom response - node.js

Is it possible to return custom data in a AWS Cognito post-authentication lambda trigger?
I have tried setting properties in event.response, but these are not propagated back to the client.
For example:
module.exports.post_auth_trigger = (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
event.response.some_custom_property = 'this is custom';
callback(null, event);
};
Using this code does not return the some_custom_property to the client after authentication. How may this be achieved?

I don't think it's possible. Check the response in RespondToAuthChallenge. There is no element that could be used for passing custom data to client.
You will have to manually fire a new request after successful authentication.

Related

Global variables values Node.js are missing on App Engine

I have a Node.js service deployed on App Engine which uses the Dialogflow fulfillment library. The scenario is this: I have an async function which retrieves the credentials using Secret manager and, with that info, calls an API that brings a url instance and a token back. This is a server-to-server authentication (OAuth), so it is the same for all users that access it. I set those values in global variables, like this:
let globalUser = "";
let globalPass = "";
...
async function credentials() {
const credentials = await secretsInstance.getCredentials();
const parsedCredentials = JSON.parse(credentials);
const user = parsedCredentials.user;
const pass = parsedCredentials.pass;
//setting the values to the global variables
globalUser = user;
globalPass = pass;
//call the authentication API - in the callback I set other global variables
await authApiInstance.authenticate(user, pass, callback);
}
After the callback function is called, I set the instance url and token to the global variables.
The token gets expired each 20 minutes, so I need to keep it updated. For that I call a setInterval function in which I call the authApiInstance.authenticate(...)
The problem here is that, when receiving a POST request coming from Dialogflow, I need to call another API that needs that url, which in this stage is empty for the first time, so it throws ECONNREFUSED. Then if I call the server other times, the variable is set.
The logs in GCP are like this:
2020-08-14 23:29:49.078 BRT
"Calling the loadQuestions API
2020-08-14 23:29:49.078 BRT
"The url is: /services/…
2020-08-14 23:29:49.091 BRT
"CATCH: Error: connect ECONNREFUSED 127.0.0.1:80"
2020-08-14 23:29:49.268 BRT
dialogflowGatewayProdxjmztxaet4d8Function execution took 764 ms, finished with status code:
200
2020-08-14 23:29:49.278 BRT
{ message_id: '39045207393', status: 200 }
2020-08-14 23:29:49.289 BRT
"Credentials ok"
2020-08-14 23:29:49.976 BRT
"Url set"
As it can be seen, the credentials and url were set after the API got called, so it didn't have a url to proceed successfully with the call.
I could call the function inside the POST, each time there is a request to guarantee that it will always exist, but the performance would be lost, especially dealing with Chatbots that must be quick.
I also tried the warmup approach, in which theoretically it would be called when deploying and changing the instance (but it could not be called, as by docs):
app.get('/_ah/warmup', (req, res) => {
credentials();
});
How could I approach this? I'm pretty new to Node.js and the server world.
Thanks
credentials(); by itself. no need to do it in express. The issue i would be race condition on the the shared credential.
crude example assuming the event loop has only these script in queue :
let say, you have 2 concurrent users A and B. A request and found the credential expire which in turn request new credential. B request before the credential return from A request, which in turn request another credential. Based on node eventloop, A then get credential_A , B will get credential B. If your third party only allow single credential then A will get an error from api call.
So the approach would be to forward the credential related task to one module, which manages the credential. background task or on request ( get token it expires on request) will face the same race problem. since node doesn't have context of thread, it is simple.
let credential = {}
let isUpdating = false;
const _updateCrediental = (newCrediential){
//map here
}
const _getCredential = async()=> {
try{
if(!updating){
updating = true;
const newCrediential = await apiCall();
updateCrediential(newCrediential);
updating = false;
return credential;
}else{
return false;
}
}catch(err){
throw err;
}
}
export.getCredential = ()=>{
if(credentialIsValid()){
return credential;
}
return __getCredential();
}
/// check the return if it promise type then waaait for it if its false then wait for certain time and check again.
An improvement to this would be using event to instead of using timeout.
I myself would prefer work with database as well as you might want to log credential generation as well. Most database promise certain kind of transaction or locking. (feel safer)

Service to service authentication in (hapi+molecular) NodeJS

I have different microservices developed in Hapi+Molecular.
I used hapi-moleculer npm module to add molecular in hapi, I am using redis as transported to communicate between services.
I can call functions of service A from service B...
what i need is to add authentication to call functions of other services.
Like if Service A calling function of Service B it needs to authenticate to prevent others from connecting to my services.
I am calling servies like this
request.broker.call('users.logout', { });
I saw a module imicros-auth for this but i didn't found it much useful is there anyother module which can do this or is there any better approach to custom code for service to service authentication.
It should be like
If service is calling its own function, then no auth required, if calling function of other service then it must be authenticated
One more thing it should not be like fetching auth from db or some kind of this which makes response of service slow, can be token based or something like this
Maybe this middleware? https://github.com/icebob/moleculer-protect-services
To use this, you should generate a JWT token with service name for all services and define a list of the permitted services. The middleware will validate the JWT.
Here is the source of the middleware:
const { MoleculerClientError } = require("moleculer").Errors;
module.exports = {
// Wrap local action handlers (legacy middleware handler)
localAction(next, action) {
// If this feature enabled
if (action.restricted) {
// Create new handler
return async function ServiceGuardMiddleware(ctx) {
// Check the service auth token in Context meta
const token = ctx.meta.$authToken;
if (!token)
throw new MoleculerClientError("Service token is missing", 401, "TOKEN_MISSING");
// Verify token & restricted services
// Tip: For better performance, you can cache the response because it won't change in runtime.
await ctx.call("guard.check", { token, services: action.restricted })
// Call the original handler
return await next(ctx);
}.bind(this);
}
// Return original handler, because feature is disabled
return next;
},
// Wrap broker.call method
call(next) {
// Create new handler
return async function(actionName, params, opts = {}) {
// Put the service auth token in the meta
if (opts.parentCtx) {
const service = opts.parentCtx.service;
const token = service.schema.authToken;
if (!opts.meta)
opts.meta = {};
opts.meta.$authToken = token;
}
// Call the original handler
return await next(actionName, params, opts);
}.bind(this);
},
};

Azure keyvault, request for multiple secrets

Im making use of the following node library azure-keyvault to get retrieve stored secrets from azure keyvault. Ive only found the client.getSecret api exposed to retrieve a secret value. Im searching for a way to retrieve multiple secret values in one call. I hav'nt found one yet. Is there a way to do this that i'm missing or its simply not supported.
const { SecretClient } = require('#azure/keyvault-secrets')
const client = new SecretClient(
`https://${KEYVAULT_NAME}.vault.azure.net`,
new DefaultAzureCredential()
)
const [secret1, secret2] = await Promise.all([
client.getSecret(`secret1`),
client.getSecret(`secret2`)
])
Here is the complete code for getting the multiple client secret at once:
var credentials = new KeyVault.KeyVaultCredentials(authenticator);
var client = new KeyVault.KeyVaultClient(credentials);
client.setSecret(vaultUri, 'mysecret', 'my password', options, function (err, secretBundle) {
// List all secrets
var parsedId = KeyVault.parseSecretIdentifier(secretBundle.id);
client.getSecrets(parsedId.vault, parsedId.name, function (err, result) {
if (err) throw err;
var loop = function (nextLink) {
if (nextLink !== null && nextLink !== undefined) {
client.getSecretsNext(nextLink, function (err, res) {
console.log(res);
loop(res.nextLink);
});
}
};
console.log(result);
loop(result.nextLink);
});
});
You can find the complete reference for azure key vault using node js below:
http://azure.github.io/azure-sdk-for-node/azure-keyvault/latest/KeyVaultClient.html#getSecrets
http://azure.github.io/azure-sdk-for-node/azure-keyvault/latest/
Hope it helps.
You can use read-azure-secrets npm package which will return all secrets to you.
E.g.
const secretClient = require('read-azure-secrets');
async function loadKeyVaultValues() {
let applicationID = '';
let applicationSecret = '';
let vaultURL = 'https://<your-key-vault-name>.vault.azure.net/';
let secrets = await secretClient.getSecrets(applicationID, applicationSecret, vaultURL);
secrets.forEach(secret => {
console.log(secret);
});
}
loadKeyVaultValues();
You can try using client.getSecrets(..) method exposed by the REST Api.
Kindly go through the following useful blog, in which all methods have been implemented.
LINK: https://www.red-gate.com/simple-talk/cloud/platform-as-a-service/using-azure-keyvault-with-node-js/
You haven't specified what information about the secret you want to fetch so I am going to assume that you are looking for the secret's value. I am also going to assume you are looking to minimize network traffic for fetching multiple secrets (either for costs or for performance).
Looking at the Azure REST API documentation while there is a route to list multiple secrets it only provides the secret identifier and metadata about the secret (attributes, tags, etc). So if you want to get the secret's value (the actual secret) you will need to make individual calls although get-secrets route can be used to find all the secrets stored in the Key Vault.
As far as the client library, #azure/keyvault-secrets maps pretty closely to the REST API it supports so it will not provide a method that fetches multiple secrets. Even if it did, it would just be a facade over multiple network calls so it would not help reduce the number of network trips.
So to answer your question - it does not look possible today unless all you want is metadata about the secret and not the secret value itself.

Find a better way to renew AWS credentials

I am using sts:assumeRole to connect to a s3 bucket of a different account.
Now, the job that I run takes a few days and along the way the credentials expire and I needed a way to renew them.
I have written the following code to handle expiry of the temporary credentials
This code is inside my downloadFile():
return new Promise((resolve, reject) => {
function responseCallback(error, data) {
if (error) {
const errorMessage = `Fail to download file from s3://${config().s3.bucket}/${path}: ${error};`;
reject(error);
} else {
Logger.info(`Successfully download file from s3://${config().s3.bucket}/${path}`);
resolve(data.Body);
}
}
const fn = this.s3Client.getObject({
Bucket: config().s3.bucket,
Key: path
}, (error, data) => this.handleTokenExpiry(error, data, fn, responseCallback));
});
And this is the handleTokenExpiry()
handleTokenExpiry(error, data, fn, callback) {
if (!error || error.code !== "ExpiredToken") return callback(error, data);
Logger.info("Token expired, creating new token");
this.s3Client = null; // null so that init() doesn't return existing s3Client
return this.init().then(fn);
}
Here init() is the method which sets this.s3Client using sts:assumeRole
and then new AWS.S3()
This works fine but I am not sure if this a clean way to do it. The strange thing is when I test it in local it takes almost two minutes for responseCallback() to be called when token is expired. Though responseCallback() gets executed immediately while the token is active.
For tasks running less than 12h, here is the solution.
When using AssumeRole, you can specify DurationSeconds argument to specify the duration of the temporary credentials returned by STS. This is 15 min minimum, up to 12h.
The role you are assuming needs to be modified to authorize the maximum duration too. See https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html#id_roles_use_view-role-max-session
In your case, with a job running for several days. I would suggest to refactor the app to run in smaller batches, each running for a few hours each.
Another alternative would be to be proactive about token expiration. If your code know the token duration and the time at which it acquired the token, I would suggest to call a method before calling a method that uses the token (such as S3's getObject). That method you check if the token are soon to expire and proactively refresh them. Pseudo code would be like
function refreshToken() {
return new Promise( (resolve, reject) => {
// XX depends how long is your S3 getObject call
if (token_acquisition_time + token_duration <= now() + xx minutes) {
// refresh token
sts.assumeRole(...).promise().then(resolve());
} else {
resolve();
}
});
}
...
refreshToken.then(s3.getObject(...));
The AWS SDK can handle refreshing the credentials for you. For example:
const credentials = new ChainableTemporaryCredentials({
params: {
RoleArn: "optional-role-arn",
RoleSessionName: `required-parameter-${Date.now()}`
}
})
const s3 = new AWS.S3({credentials})
Now AWS SDK will refresh the tokens behind the scenes without any action from caller of s3.
For more information, please see AWSK SDK Documentation. Refresh is limited to validity time of the credentials used.

USER_AUTHENTICATION_FAILED Creating Envelope

Currently I'm working with a Node.js integration for DocuSign (https://www.npmjs.com/package/docusign-esign), I made all the test with the sandbox account and worked perfectly, right now I'm trying to use a production account, the login process is fine but when I'm going to create the envelope I get a USER_AUTHENTICATION_FAILED error (even if the first login went without errors). I would like to know if someone has experienced same thing or has an idea of how can I fix this.
This is the code that I took from the docusign-esign to create the envelope:
var loginAccount = new docusign.LoginAccount();
loginAccount = loginAccounts[0];
var accountId = loginAccount.accountId;
var envelopesApi = new docusign.EnvelopesApi();
envelopesApi.createEnvelope(accountId, envDef, null, function (error, envelopeSummary, response)
The account Id is the same retrieved after the login process.
One possible cause could be that your DocuSign account is hosted on na2.docusign.net, na3.docusign.net or eu.docusign.net, while your code uses the default www.docusign.com as a base URL.
The login call will pass even if you use www, however all the subsequent API calls will fail if you are not hitting the exact base URL that corresponds to your production account. You should have received this information as part of the DocuSign Go-Live process (formerly known as API Certification). You can always get the base URL from the login call response.
For Node, here how to get the correct base URL from the login call and set it up to the API Client (lines in bold are likely what is missing in your code):
authApi.login(loginOps, function (err, loginInfo, response) {
if (err) {
return next(err);
}
if (loginInfo) {
// list of user account(s)
// note that a given user may be a member of multiple accounts
var loginAccounts = loginInfo.getLoginAccounts();
console.log('LoginInformation: ' + JSON.stringify(loginAccounts));
var loginAccount = loginAccounts[0];
var accountId = loginAccount.accountId;
var baseUrl = loginAccount.baseUrl;
var accountDomain = baseUrl.split("/v2");
apiClient.setBasePath(accountDomain[0]);
docusign.Configuration.default.setDefaultApiClient(apiClient);
next(null, loginAccount);
}
});

Resources