Refresh of AWS.config.credentials - amazon-web-services

I'm trying to find a proper way of refreshing the AWS.config.credentials I get from Cognito. I'm using developer authorized identities and it works fine. I get the credentials and if I perform a refresh() the AWS.config.credentials.expireTime is also updated, as expected.
The credentials expire after one hour, so I thought I could use setTimeout to refresh the credentials and configure it based on the credentials.expireTime (I calculate the number of millis).
However, it seems like I have to perform the refresh much more often. The credentials keeps timing out before their time. The setTimeout-method works just fine if I reduce the delay to a much smaller amount, but I would prefer not to overdo the refresh.
Is this true, and if so how often do I need to do this? Having it refresh every 5 minutes or so seems excessive :/
Recurring refresh
function refreshAwsCredentials() {
clearTimeout(awsRenewalTimeout);
// perform credentials renewal
AwsService.refreshAwsCredentials()
.then( function () {
awsRenewalTimeout = setInterval(
function () {
refreshAwsCredentials();
}, AWS.config.credentials.expireTime.getTime() - new Date().getTime() - 300000
);
})
.catch( function (error) {
// checks error, normally it basically logs in, then refreshes
});
}
AwsService.refreshAwsCredentials()
if ( AWS.config.credentials.needsRefresh() ) {
AWS.config.credentials.refresh( function (error) {
if (error) {
// rejects promise with error message
}
else {
// resolves promise
}
});
}

I finally found out that I had a check for auth error where I was too specific. I checked for being logged in and not that the credentials had timed out. Now I attempt a login when things fail and the credential timeout is now resolved when needed.

Related

PWA: how to refresh content every time the app is opened

I created a PWA app which sends API call to my domotic server and prints the response on the home page (e.g. outside temperature and vacuum robot status).
While all the data get refreshed at very first app opening, if I minimize the app whithout completely shutting it off I have no data refreshing at all.
I was wondering how to force a refresh every time the app gets re-opened without having to do it manually (no pull-down to refresh, no refresh-button).
Found myself the solution adding the following code in service worker:
self.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'visible') {
console.log('APP resumed');
window.location.reload();
}
});
Here is the solution that works.
You can place this code wherever you have access to the window object:
window.addEventListener("visibilitychange", function () {
console.log("Visibility changed");
if (document.visibilityState === "visible") {
console.log("APP resumed");
window.location.reload();
}
});
Consider this may affect user experience or data loss with a forced reload every time the user swipes between apps.

Firebase function connection with GCP Redis instance in the same VPC keeps on disconnecting

I am working on multiple Firebase cloud functions (all hosted in the same region) that connect with a GCP hosted Redis instance in the same region, using a VPC connector. I am using version 3.0.2 of the nodejs library for Redis. In the cloud functions' debug logs, I am seeing frequent connection reset logs, triggered for each cloud function with no fixed pattern around the timeline for the connection reset. And each time, the error captured in the error event handler is ECONNRESET. While creating the Redis instance, I have provided a retry_strategy to reconnect after 5 ms with maximum of 10 such attempts, along with the retry_unfulfilled_commands set to true, expecting that any unfulfilled command at the time of connection reset will be automatically retried (refer the code below).
const redisLib = require('redis');
const client = redisLib.createClient(REDIS_PORT, REDIS_HOST, {
enable_offline_queue: true,
retry_unfulfilled_commands: true,
retry_strategy: function(options) {
if (options.error && options.error.code === "ECONNREFUSED") {
// End reconnecting on a specific error and flush all commands with
// a individual error
return new Error("The server refused the connection");
}
if (options.attempt > REDIS_CONNECTION_RETRY_ATTEMPTS) {
// End reconnecting with built in error
console.log('Connection retry count exceeded 10');
return undefined;
}
// reconnect after 5 ms
console.log('Retrying connection after 5 ms');
return 5;
},
});
client.on('connect', () => {
console.log('Redis instance connected');
});
client.on('error', (err) => {
console.error(`Error connecting to Redis instance - ${err}`);
});
exports.getUserDataForId = (userId) => {
console.log('getUserDataForId invoked');
return new Promise((resolve, reject) => {
if(!client.connected) {
console.log('Redis instance not yet connected');
}
client.get(userId, (err, reply) => {
if(err) {
console.error(JSON.stringify(err));
reject(err);
} else {
resolve(reply);
}
});
});
}
// more such exports for different operations
Following are the questions / issues I am facing.
Why is the connection getting reset intermittently?
I have seen logs that even if the cloud function is being executed, the connection to Redis server lost resulting in failure of the command.
With retry_unfulfilled_commands set to true, I hoped it will handle the scenario as mentioned in point number 2 above, but as per debug logs, the cloud function times out in such scenario. This is what I observed in the logs in that case.
getUserDataForId invoked
Retrying connection after 5 ms
Redis instance connected
Function execution took 60002 ms, finished with status: 'timeout' --> coming from wrapper cloud function
Should I, instead of having a Redis connection instance at global level, try to have a connection created during each such Redis operation? It might have some performance issues as well as issues around number of concurrent Redis connections (since I have multiple cloud functions and all those will be creating Redis connections for each simultaneous invocation), right?
So, how to best handle it since I am facing all these issues during development itself, so not really sure if it's code related issue or some infrastructure configuration related issue.
This behavior could be caused by background activities.
"Background activity is anything that happens after your function has
terminated"
When the background activity interferes with subsequent invocations in Cloud Functions, unexpected behavior and errors that are hard to diagnose may occur. Accessing the network after a function terminates usually leads to "ECONNRESET" errors.
To troubleshoot this, make sure that there is no background activity by searching the logs for entries after the line saying that the invocation finished. Background activity can sometimes be buried deeper in the code, especially when asynchronous operations such as callbacks or timers are present. Review your code to make sure all asynchronous operations finish before you terminate the function.
Source

Cognito Refresh Token Expires prematurely

We have an app that uses AWS Cognito for authentication. The backend code (using AWS SDK for C# works fine mostly)
After the initial login, we obtain, ID, Access and Refresh TOKEN. Then every hour we try getting a new ID and ACCESS token by calling
public bool ExtendTokens(string userRefreshToken, out AdminInitiateAuthResponse output)
{
output = null;
AdminInitiateAuthRequest request = new AdminInitiateAuthRequest();
AdminInitiateAuthResponse response = new AdminInitiateAuthResponse();
try
{
request.UserPoolId = XXXXXXXXXXX;
request.ClientId = YYYYYYYYYY;
request.AuthFlow = AuthFlowType.REFRESH_TOKEN_AUTH;
request.AuthParameters.Add("REFRESH_TOKEN", userRefreshToken);
response = awsCognito_client.AdminInitiateAuth(request);
if (response != null)
{
output = response;
return true;
}
}
catch (Exception ex)
{
//log the exception and the inner exception!
}
return false;
}
on the backend side and passing them to the client. (The way this app works is that the client makes few calls every 3min to the server, and then server calls Cognito for authentication) then after 60min, renewing tokens the first time (60min after initial login) works fine! However, (after precisely 2hrs) the second time I get this error:
The remote server returned an error: (400) Bad Request.
Refresh Token has been revoked
For several internal/external users this happens right on the dot! 120min after they login using Username/Password. So it cannot be where a user signs out or we call GlobalSignOut accidentally. I have checked my code several places, I don't see where I might have goofed! I even use the same code module in another product and that one does not get kicked out at all! (That one does not make calls every now and then!)
Also, Tracking user devices is OFF. So it cannot be this answer.
Moreover, the Cognito Limitation document does not say anything about the total number of calls per account!
Other useful details: the default expiry of our refresh token is 15days. That's why I call this two hours expiry prematurely!
I am not able to reproduce this on my localhost, but it happens after deploying to IIS. I have checked the settings and the web.configs and I could not find any meaningful difference between the two that would invalidate my refresh tokens!
So, I was able to get around this (I still don't know the root cause) but the way to avoid this is to call the renew function every 45~50 minutes instead of waiting for that 1hr to pass! Doesn't make any sense but Its been 48hrs that my session is active and tokens are being renewed.

Cognito returns UnexpectedLambdaException timeout error while invoking Lambda function

The very first time I login to my application, first thing in the morning, AWS Cognito returns this error:
{
"message": "arn:aws:lambda:us-east-1:XXXXXXXXXX:function:main-devryan-users_onCognitoLogin failed with error Socket timeout while invoking Lambda function.",
"code": "UnexpectedLambdaException",
"time": "2017-08-29T13:30:01.351Z",
"requestId": "1c04c982-8cbe-11e7-b9c9-a584e55a17f8",
"statusCode": 400,
"retryable": false,
"retryDelay": 96.636396268355
}
The second time, and every time afterwards for the rest of the day, everything is fine.
When I check the logs in Cloudwatch for my main-devryan-users_onCognitoLogin function, it finished successfully in 2.3 seconds:
REPORT RequestId: 1f2d5a22-8cbe-11e7-ba74-5b21665a40c1 Duration: 2283.60 ms Billed Duration: 2300 ms Memory Size: 128 MB Max Memory Used: 51 MB
Every time afterwards, for the rest of the day, I don't see this error. My Lambda is set to timeout after 30 seconds, but I know Cognito needs a response in 5 seconds.
My lambda function is just updating the last login time in the DB. It's slow the first time because it takes about 1.8 seconds to create a connection to my RDS DB. I'm using Node JS 6 with Sequelize 3 for that part.
I'm guessing it took 2.7 seconds for Lambda to load my app into a container.
Does anybody have solution here? I'm stumped.
This seems to be a lambda cold start time issue. There are various approaches you can try to optimize this such as:
Increase the memory allocated to your functions, which also increases CPU proportionally. Because your functions are called very infrequently, the added cost of increasing memory size will be balanced by faster cold start times and thus lower billed duration.
Reduce your code size: a smaller .zip, removing unnecessary require()'s in Node.js, etc. For example, if you are including the Async library just to remove a nested callback, consider forgoing that to improve performance.
I solved this problem by creating 2 Lambdas: onLogin and actualOnLogin. The onLogin Lambda just kicks off the actualOnLogin Lambda asynchronously and returns immediately. The actualOnLogin does my database update. This way onLogin returns in <100ms and Cognito is happy.
Here's the basic code for onLogin:
let AWS = require("aws-sdk");
let lambda = new AWS.Lambda();
let params = {
FunctionName: "main-#deploy_env#-users_actualCognitoLogin", // I'm using the Serverless framework
InvocationType: "Event", // Makes it async
LogType: "None",
Payload: JSON.stringify(event)
};
lambda.invoke(params, (err, data) => {
if (err) {
console.log("ERROR: " + err, err.stack); // an error occurred
context.done(err, event);
} else {
console.log(data); // successful response
context.done(null, event);
}
});

send email when user registers - AWS Cognito federated Identities

How can i send an email/trigger a lambda function when a new user registers?
Under "edit identity pool" i only found a sync trigger.
If i understand correctly: This one is triggered every time a user syncs his data...
Is there any way to trigger a lambda function only for the "initial" sync or when a certain dataset is created for the user?
Edit:
To be more specific: I do create the user via lambdas using the JS SDK. I use developer authentication with my own oauth2 flow. I don't know how to distinguish between a user granting access e.g. via Google the first time from someone doing this the second time. The json with the access code seams the same to me... Maybe I am mistaken.
Also using the getOpenIdTokenForDeveloperIdentity call I don't know how to distinguish between an ID that is new to cognito from one cognito already knows.
Edit 2:
To be even more precise:
I am building on this project: https://github.com/laardee/serverless-authentication-boilerplate/blob/master/authentication/lib/storage/usersStorage.js
here is how i do save the User to cognito at the moment.
I do run this code for first time users as well as nth time users. My problem is that i dont know how to distinguish...
const saveCognito = (profile) => new Promise((resolve, reject) => {
if (profile) {
cognitoidentity.getOpenIdTokenForDeveloperIdentity({
IdentityPoolId: process.env.COGNITO_IDENTITY_POOL_ID,
Logins: {
// profile.userId = encrypted id of the e.g. google oauth2 id
[process.env.COGNITO_PROVIDER_NAME]: profile.userId
}
}, (err, dat) => {
if (err) {
reject(err);
} else {
var list_params = {
DatasetName: 'user-data', /* dataset name */
IdentityId: dat.IdentityId, /* cognito id */
IdentityPoolId: process.env.COGNITO_IDENTITY_POOL_ID
};
cognitosync.listRecords(list_params, function(err, data) {
if (err) {
reject(err); // an error occurred
} else {
var RecordPatches = //[Parts of the i want to write to the user]
// SyncSessionToken is returned by the cognitosync.listRecords call
list_params["SyncSessionToken"] = data.SyncSessionToken;
list_params["RecordPatches"] = RecordPatches;
cognitosync.updateRecords(list_params, function(err, update_data) {
if (err){
reject(err);
} else {
resolve();
}
});
}
});
}
});
} else {
reject('Invalid profile');
}
});
So this is something which is not currently supported in Cognito out of the box. You are correct in saying that the only built in Cognito Event that will trigger a Lambda Function is the "Sync Trigger" Event. This Sync event is fired every time that a Cognito IdentityId Synchronizes some of their data to the Cognito Sync cloud data store.
This event is unrelated to the creation of a new IdentityId by Cognito Federated Identity.
You could in theory:
Run a list-identities call on the IdentityPool, before the user logs
in.
Login the user. Check whether the IdentityId which has been given to the user is present in the list you retrieved before they logged
in. This would tell you whether or not the identity that they were
given existed before this login.
Based on this information you could
make a decision whether or not to programatically call the Lambda
Function from your application.
The setup of the above would be complex, as for security reasons you would need to maintain this service, server-side. The list-identities call requires AWS credentials to call. And I doubt you'd want to include permissions for that call in your IAM policy for unauthenticated users.
Aside from the above there is not much you can do at the moment.
In order to do this, you would need to setup a DynamoDB table (or some similar low latency datastore) where you could maintain the state of the IdentityId list, and then query this service/store whenever you login a user to compare new logins to the pre-existing list.
If this is critical to your use case I would suggest heading over to AWS Support, and create a case where you can log this as a feature request.
https://aws.amazon.com/premiumsupport/