Lambda function timing out after 10 seconds - node.js

Code:
const knex = require('knex')({
client: 'mysql',
connection: {
host: process.env.database_host,
user: process.env.database_user,
password: process.env.database_pass,
database: process.env.database_db,
charset: 'utf8'
}
});
const bcrypt = require('bcrypt');
const bookshelf = require('bookshelf')(knex);
const User = bookshelf.Model.extend({
tableName: 'users'
});
const checkValues = (values) => {
// todo: add data validation
return true;
};
exports.test = (database) => {
// todo: add tests
};
exports.handler = (event, context, callback) => {
let salt = bcrypt.genSaltSync();
let values = {
first_name: event.firstname,
last_name: event.lastname,
username: event.username,
date_of_birth: event.birthday,
password: bcrypt.hashSync(event.password, salt),
password_salt: salt
};
if (!checkValues(values)) {
callback(null, {
success: false,
error: {
id: 2,
details: 'data validation error'
}
});
context.done(null, "User not created");
return;
}
try {
new User({
'first_name': values.first_name,
'last_name': values.last_name,
'username': values.username,
'date_of_birth': values.date_of_birth,
'password': values.password,
'password_salt': values.password_salt
}).save();
callback(null, {
success: true
});
context.done(null, "User created");
} catch (err) {
console.log(err);
callback(null, {
success: false,
error: {
id: 1,
details: 'error inserting user into database'
}
});
context.done(null, "User not created");
}
};
I am trying to make a basic sign up api endpoint using AWS API Gateway and Lambda functions, however every time I post the information to the api gateway I get the error
{
"errorMessage": "2017-09-07T08:38:50.174Z f2368466-93a7-11e7-b4bc-01142a109ede Task timed out after 10.00 seconds"
}
I have tried using different database libraries but I seem to always be hitting the same problem. The database connection works I know this because the user does infact get added to the users table in the database and the password is successfully hashed..
I have also tried using asynchronous bcrypt but it doesn't make any difference to the result, it still does it but says it times out.
Lambda doesn't seem to be terminating properly, something keeps the process still running and I can't figure out what, any ideas?

i had the similar issue using API gateway invoking my lambda.
The default timeout for API gateway is 30 seconds. If your response is not ready within in 30 seconds, you will be timed out though your lambda would still run!
So may be try to get the response back within 30 seconds. If not have one lambda being invoked from the API and give the response back immediately and let the first lambda invoke your second lambda and that will run upto max time which is 5 mins.
Thanks

Related

Server-side user verification with AWS Cognito user pool via MFA verification code (without password)

On the server side using NodeJS + NestJS, TS: 4.7.4, "aws-sdk": "^2.1138.0".
Trying to send a request to AWS Cognito, to obtain a verification code on mobile phone.
It's far away from achieving SMS quota.
An example of my method from the service:
async sendVerificationCode(phoneNumber: string) {
const params = {
AuthFlow: 'USER_SRP_AUTH',
ClientId: process.env.AWS_COGNITO_CLIENT_ID,
// UserPoolId: process.env.AWS_COGNITO_USER_POOL,
AuthParameters: {
USERNAME: phoneNumber,
SRP_A: generateSRPA(),
},
};
console.debug('=========== params: ', params);
try {
const result = await this.cognitoIdentityServiceProvider
.initiateAuth(params)
.promise();
console.log('=========== result: ', result);
return result;
} catch (error) {
if (error instanceof Error) {
console.debug('=========== Error: ', error.message);
throw error;
}
}
}
example of generation SRP_A:
const N_HEX ='EEAF0AB9ADB38DD69C33F80AFA...';
export function generateSRPA() {
const random = randomBytes(32);
const randomHex = random.toString('hex');
const srpA = createHash('sha256').update(randomHex).digest('hex');
return createHash('sha256').update(srpA).update(N_HEX).digest('hex');
}
Now requests are successfully sending to AWS and getting response:
=========== result: {
ChallengeName: 'PASSWORD_VERIFIER',
ChallengeParameters: {
SALT: '4e9b...',
SECRET_BLOCK: '4x1k...',
SRP_B: '161d...',
USERNAME: 'b1d9...',
USER_ID_FOR_SRP: 'b1d9...'
}
}
But I'm not receiving verification code on my phone.
In the same time with the same user pool and same mobile phone all the flow works fine on mobile app which is connected to Cognito.

When I try to run a Post User Registration script in Auth0 it gives "Error! API Error. Please contact support if the problem persists" error

I am trying to use Auth0's actions for post user registration. When I try to test it via UI, it gives me an error like "Error! API Error. Please contact support if the problem persists" and in the console it only writes "Error: {}". The script I wrote for this action looks something like this:
const https = require('https');
const jsonwebtoken = require('jsonwebtoken');
/**
* #param {Event} event - Details about registration event.
*/
exports.onExecutePostUserRegistration = async (event) => {
const TOKEN_SIGNING_KEY = event.secrets.signingKey
const TOKEN_EXPIRATION_IN_SECONDS = 10
const payload = {
email: event.user.email,
name: event.user.given_name,
surname: event.user.family_name
};
const token = jsonwebtoken.sign(payload, TOKEN_SIGNING_KEY,
{ subject: "postUserRegistration",
expiresIn: TOKEN_EXPIRATION_IN_SECONDS });
console.log("Starting post user registration operations for user with email: ", payload.email);
return new Promise((resolve, reject) => {
const request = https.request(url, options,
(res) => {
if (res.statusCode === 200) {
resolve({statusCode: res.statusCode, headers: res.headers})
} else {
reject({
headers: res.headers,
statusCode: res.statusCode
})
}
res.on('error', reject);
});
request.on("error", function (e) {
console.error(e);
reject({message: {e}});
});
request.on("timeout", function () {
reject({message: "request timeout"});
})
request.end();
});
}
Can you help me about what exactly causes this problem?
In order to understand this problem I tried to assign the Promise to a variable and then see what it returned. The funny part was that it was "pending". It couldn't be "pending" in any way, because it everything was true it would be "resolved", else "rejected" and if there is a timeout/error from the request it would be "rejected".
It turns out that Auth0 has some limitations for actions and the endpoint called was stuck to our firewall which caused a timeout in Auth0 before an HTTPS timeout. As the request was broken halfway, it stayed as "pending".

Nodemailer with E-Mail Templating working locally but not in AWS Lambda environment

I am developing a small webapp that has user accounts. So I want to send E-Mails regarding their registration and E-Mail Address Confirmation.
I am using the Serverless Framework with Express and Node.js as well as Nodemailer with email-templates.
here is the mailer.js function to send a confirmation email:
function sendConfirmation (name, address) {
const transporter = nodemailer.createTransport({
host: 'smtp.strato.de',
port: 465,
secure: true,
auth: {
user: process.env.STRATO_USER,
pass: process.env.STRATO_PASSWORD,
}
});
const email = new Email({
transport: transporter,
send: true,
preview: false,
});
console.log("Sending Email");
email.send({
template: 'confirmation',
message: {
from: 'company',
to: address,
},
locals: {
fname: name,
}
}).then(() => console.log('email has been sent!'));
}
module.exports = {
sendRegister,
sendConfirmation
};
And here is the code of the route. I look for the user in the database and update him to be confirmed. Then I try to send the email.
router.post("/confirmation", async (req, res) => {
userId = req.body.userId;
let userUpdated = false;
let updatedUser;
updatedUser = await User.findByIdAndUpdate({_id: userId}, {"confirmed": true}, function (err, result) {
if(err){
res.send(err)
}
else{
userUpdated = true;
}
});
if (userUpdated) {
await sendConfirmation(updatedUser.name, updatedUser.email);
res.json({error: null, data: "User confirmed"});
}
});
If I test this with app.listen on localhost I receive the E-Mail without any problems. As soon as I deploy it to AWS with Serverless, the user gets updated in the DB, but the E-Mail is not send. It seems that Lambda does not wait until the promise of the email.send() is there? I also tried to explicitly allow the Lambda function to communicate with port 465 on outbound traffic. No luck.
In the logs of the Lambda function no error is shown, but also no confirmation of the sent email.
Has anyone an idea what I am missing? Thank you very much!
EDIT 1:
I just found out, that if I use await sleep(2000) with an according function
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
right after calling the sendConfirmation function and it works without problems. It seems, that the sendConfirmation function will not wait for the promise of email.send. Any idea how to solve this? I am fairly new to promises.
Thanks in advance!

What incorrect Metadata-Flavor header at GoogleAuth means?

I´m trying to write to firestore from a onCall firebase function
functions.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
console.log('initialing functions at ' , new Date().toString())
exports.getLinks = functions.runWith({ timeoutSeconds: 540 }).https.onCall( (data,context) => {
console.log('starting to get links ' , new Date().toString())
console.log('data' , data.query, data.limit, data.country, data.uid)
console.log('context auth', context.auth, 'context.auth.uid', context.auth.uid)
// is there anything like admin.setCredentials(context.auth) necessary here?
const queries = admin.firestore().collection('queries');
let uid = data.uid
console.log('uid', uid);
console.log('queries ref', queries)
//probably when trying to write here is not being allowed
queries.doc(uid).set({LinksArrayLength: 'starting'})
.then( r => console.log('writing to firestore 1 result', r))
.catch( err => console.error('writing to firestore 2 error', err))
The console output is like this
starting to get links Fri May 31 2019 19:01:10 GMT-0300 (GMT-03:00)
data sells anywhere 2 com fwfwqe
context auth {
uid: 'f23oij2ioafOIJOeofiwjOIJ',
token: {
iss: 'https://securetoken.google.com/was98oinr-fa4c9',
aud: 'was234r-f32c9',
auth_time: 1559327744,
user_id: 'f23oij2ioafOIJOeofiwjOIJ',
sub: 'f23oij2ioafOIJOeofiwjOIJ',
iat: 1559338208,
exp: 1559341808,
email: 'awef3h#gmail.com',
email_verified: false,
firebase: { identities: [Object], sign_in_provider: 'password' },
uid: 'f23oij2ioafOIJOeofiwjOIJ'
}
} context.auth.uid f23oij2ioafOIJOeofiwjOIJ
uid f1EMxzwjJlTaH3u7RAYsySx0MZV2
queries ref CollectionReference {
_firestore: Firestore {
_settings: {
projectId: 'xxx',
firebaseVersion: '7.0.0',
libName: 'gccl',
libVersion: '1.3.0 fire/7.0.0'
},
and then the not allowed write request to firestore ?
writing to firestore 2 error Error: Unexpected error determining execution environment: Invalid response from metadata service: incorrect Metadata-Flavor header.
> at GoogleAuth.<anonymous> (H:\nprojetos\whats_app_sender\firebase_sender\vue_sender\wa_sender\functions\node_modules\google-auth-library\build\src\auth\googleauth.js:164:23)
> at Generator.throw (<anonymous>)
> at rejected (H:\nprojetos\whats_app_sender\firebase_sender\vue_sender\wa_sender\functions\node_modules\google-auth-library\build\src\auth\googleauth.js:20:65)
> at processTicksAndRejections (internal/process/task_queues.js:89:5)
How could I ensure that the request.auth.uid is being sent to the firestore write request?
Firestore rules
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;//: if request.auth.uid != null;
// even when commented and allowing all requests still giving the error //message
}
}
}
Even when fully allowed is continues to give the error.
I´m trying to write to firestore to update the client side when something is being written to the the collections('queries') ... so that the client gets notified of the function progress...
Is there is a better way of doing that also?
On the client side the code goes like this
client-side
fireApp.firestore().collection('queries').doc(this.getUser.uid).onSnapshot(snap => {
debugger
console.log('snap', snap)
snap.exists ?
snap.docChanges().forEach(async change => {
if (change.type === "modified") {
_vue.updating = true // the function is in progress
Solved
It just required propper initialization
const credential = require('./xxxxx.json')
admin.initializeApp({credential: admin.credential.cert(credential),
databaseURL: "https://xxx.xxx.firebaseio.com"
});
this link (https://www.youtube.com/watch?v=Z87OZtIYC_0)
explains how to initialize it properly

node.js request stuck in middleware function (authentication)?

I am building a node.js server to handle logins and actions in my iOS app. One of the functions I wrote is checkAuth, which is middleware for most of my requests to check if a user is authenticated and has permission to do what he wants to do.
Now I am facing a problem where sometimes, but not always, the middleware function (checkAuth) is stuck. I receive logs from this function in my console, but nothing from the request (which should happen after authentication is successful).
This is the function I currently wrote. It is not optimized yet as I am testing everything, but it should do what I want it to do (and it does, most of the time):
const saltRounds = process.env.JWT_ROUNDS
const secret = process.env.JWT_SECRET
const checkRefreshTime = 10 // set to 10 seconds for testing, will increase later
function checkAuth(req, res, next) {
var token = req.headers['x-access-token']
jwt.verify(token, secret, (error, decoded) => {
console.log("checking auth")
if(error) {
console.log(error)
res.json({ errorCode: 406 })
} else {
const decoded = jwt.verify(token, secret)
var checkTime = (Date.now() / 1000) - checkRefreshTime;
if (decoded.iat < checkTime) {
console.log("DEC:", decoded)
const userID = decoded.userID
const queryString = "SELECT userRefreshToken, userName, userDisplayName, userProfilePicURL, userDOB, userGender FROM users WHERE userID = ? LIMIT 1"
pool.getConnection(function(error, connection) {
if(error) {
console.log(error)
res.json({ errorCode: 500 })
}
connection.query(queryString, [userID], (error, selectRows, fields) => {
if(error) {
console.log(error)
res.json({ errorCode: 500 })
}
if(selectRows.length > 0) {
if(selectRows[0].userRefreshToken == decoded.userRefreshToken) {
var userAge = moment().diff(selectRows[0].userDOB, 'years');
const payload = {
userID: userID,
userName: selectRows[0].userName,
userDisplayName: selectRows[0].userDisplayName,
userProfilePicURL: selectRows[0].userProfilePicURL,
userRefreshToken: selectRows[0].userRefreshToken,
userAge: userAge,
userGender: selectRows[0].userGender
}
var newToken = jwt.sign(payload, secret, { expiresIn: '21d' });
console.log("new token sent ", newToken)
res.locals.authToken = newToken
console.log("moving to next")
return next()
} else {
console.log("wrong refresh token")
res.json({ errorCode: 405, authResult: false })
}
} else {
console.log("0 results found!")
res.json({ errorCode: 503, authResult: false })
}
connection.release()
})
})
} else {
console.log("moving to next 2")
return next()
}
}
})
}
It probably isn't the most beautiful code you have ever seen. That's not my issue at this moment - I will optimize at a later time. Right now I am concerned about the fact that sometimes the function is stuck after the second check. The last output I then receive is "DEC: " followed by the decoded token in my console (line 16).
Other useful information: I run my server on an Ubuntu 18.04 server from DigitalOcean and use forever to keep it running:
forever start --minUptime 1000 --spinSleepTime 1000 server.js
Anybody who knows why this is happening?
EDIT: as per comment, the definition of pool
var pool = mysql.createPool({
connectionLimit: 100,
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_BASE,
ssl : {
ca : fs.readFileSync('***********'),
key : fs.readFileSync('*********'),
cert : fs.readFileSync('********'),
},
charset : 'utf8mb4',
dateStrings: true
})
I don't see where pool is defined anywhere. That could be throwing an error in the server logs.
Put a console log in the connection function to check that it actually connected to mySQL since that is the next function in the chain.

Resources