AWS Cognito: Custom Challenge with Retry - amazon-web-services

I am using Custom Challenge for MFA because i wanted to use Twilio instead of AMAZON SNS. i have successfully implemented it. It works fine but
When a user enters wrong OTP code. The user session is expired. means that he has to again provide phone number and request a OTP again. Whereas i want it to retry for atleast 3 time. before he need to request another OTP.
My Response verify trigger is as simple as below, is there something that we can do.
(event, context, callback) => {
if (event.request.privateChallengeParameters.answer == event.request.challengeAnswer) {
event.response.answerCorrect = true;
} else {
event.response.answerCorrect = false;
}
callback(null, event);
}

I acheived this by adding the answer as a variable into challengeMetaData - which so far as I can see is not returned to the client but is available on subsequent calls, I also have a variable named attempts to track how many times the user has entered an incorrect value.My code is below - I hope it helps
const AWS = require("aws-sdk");
exports.handler = (event, context, callback) => {
const session = event.request.session;
const currentSession = session ? session.length - 1 : 0
switch (event.triggerSource) {
case 'DefineAuthChallenge_Authentication':
console.log("DefineAuthChallenge_Authentication");
console.log(event);
if (session.length === 0) {
event.response = {
challengeName: 'CUSTOM_CHALLENGE',
failAuthentication: false,
issueTokens: false
};
}
else {
if (session[currentSession].challengeName === 'CUSTOM_CHALLENGE') {
if (session[currentSession].challengeResult === true) {
event.response.issueTokens = true;
event.response.failAuthentication = false;
}
else {
let metaData = JSON.parse(session[currentSession].challengeMetadata);
if (metaData.attempts <= 3) {
event.response = {
challengeName: 'CUSTOM_CHALLENGE',
failAuthentication: false,
issueTokens: false
};
}
else {
event.response.issueTokens = false;
event.response.failAuthentication = true;
}
}
}
}
console.log(event);
break;
case 'CreateAuthChallenge_Authentication':
if (event.request.challengeName === 'CUSTOM_CHALLENGE') {
console.log("CreateAuthChallenge_Authentication");
console.log(event);
if (session.length === 0) {
let answer = Math.random().toString(10).substr(2, 6);
//Your logic to send a message goes here
event.response.publicChallengeParameters = { challengeType: 'SMS_CODE' };
event.response.privateChallengeParameters = { answer: answer };
event.response.challengeMetadata = JSON.stringify({ '_sid': answer, 'challengeType': 'SMS_CODE', attempts: 1 });
}
else {
let metaData = JSON.parse(session[currentSession].challengeMetadata);
if (metaData.attempts <= 3) {
event.response.publicChallengeParameters = { challengeType: 'SMS_CODE', errorCode: 'NotAuthorizedException' };
event.response.privateChallengeParameters = { answer: metaData._sid };
event.response.challengeMetadata = JSON.stringify({ '_sid': metaData._sid, 'challengeType': 'SMS_CODE', attempts: metaData.attempts + 1 });
}
}
}
console.log(event);
break;
default:
console.log("VerifyAuthChallenge_Authentication");
console.log(event);
if (event.request.privateChallengeParameters.answer === event.request.challengeAnswer) {
event.response.answerCorrect = true;
}
else { event.response.answerCorrect = false; }
console.log(event);
break;
}
callback(null, event);
};

Related

How to mock 'FirebaseAuth.instance'?

I want to start writing unit test to my flutter app that have developed using GetX pattern. In GetX pattern, the code is separated to controller and view, so all methods that I want to test is in controller.
In my app, I am using firebase to make authentication with mobile number.
This is LoginController:
class LoginController extends GetxService {
...
LoginController(this.authService);
final _auth = FirebaseAuth.instance;
String validatePhoneNumber(String phoneNumber) {
if (!phoneNumber.startsWith('+20')) {
return 'أدخل كود البلد مثل: +20 في مصر';
} else if (phoneNumber.length < 11) {
return 'أدخل رقم صحيح';
} else if (phoneNumber.isEmpty) {
return 'أدخل رقم الهاتف';
}
return null;
}
Future<void> validate() async {
await _auth.verifyPhoneNumber(
phoneNumber: phoneNumberController.text,
timeout: timeoutDuration,
codeAutoRetrievalTimeout: (String verificationId) {
verId.value = verificationId;
currentState.value = SignInPhoneWidgetState.CodeAutoRetrievalTimeout;
},
codeSent: (String verificationId, [int forceResendingToken]) {
verId.value = verificationId;
currentState.value = SignInPhoneWidgetState.CodeSent;
DialogService.to.stopLoading();
Get.toNamed(Routes.ACCEPT_SMS);
},
verificationCompleted: (AuthCredential phoneAuthCredential) async {
currentState.value = SignInPhoneWidgetState.Complete;
try {
if (authService.currentUser.value != null) {
await authService.currentUser.value
.linkWithCredential(phoneAuthCredential);
} else {
await _auth.signInWithCredential(phoneAuthCredential);
}
Get.offAllNamed(Routes.ROOTHOME);
//widget.onLoggedIn(authResult);
} on PlatformException catch (e) {
print(e);
errorCode.value = e.code;
errorMessage.value = e.message;
} catch (e) {
print(e);
} finally {
//.......
}
},
verificationFailed: (FirebaseAuthException error) {
errorCode.value = error.code;
errorMessage.value = error.message;
currentState.value = SignInPhoneWidgetState.Failed;
},
//forceResendingToken:
);
}
Future<void> validateSmsCode() async {
//_auth.
var cred = PhoneAuthProvider.credential(
verificationId: verId.value, smsCode: verifyCodeController.text);
try {
if (authService.currentUser.value != null) {
await authService.currentUser.value.linkWithCredential(cred);
} else {
await _auth.signInWithCredential(cred);
await Get.offAllNamed(Routes.ROOTHOME);
}
} on PlatformException catch (ex) {
errorCode.value = ex.code;
errorMessage.value = ex.message;
currentState.value = SignInPhoneWidgetState.Failed;
} on FirebaseAuthException catch (ex) {
errorCode.value = ex.code;
errorMessage.value = ex.message;
currentState.value = SignInPhoneWidgetState.Failed;
}
}
...
}
This is login_test.dart file:
I should mock every outside operation like firebase. But In this case I want to test validatePhoneNumber method, that checks if the phone number is valid or not. the method it self hasn't firebase operations. But, the method is called by a LoginController object, And this object it self has instance of FirebaseAuth.instance.
final _authSerive = AuthService();
main() async {
final loginController = LoginController(_authSerive);
setUp(() async {});
tearDown(() {});
group('Phone Validation', () {
test('Valid Email', () {
String result = loginController.validatePhoneNumber('+201001234567');
expect(result, null);
});
});
}
When I tried to run this test method, This error appeared.
Failed to load "D:\bdaya\ta7t-elbeet-client\test\login_test.dart":
[core/no-app] No Firebase App '[DEFAULT]' has been created - call
Firebase.initializeApp()
The reasen is:
This line in The LoginController:
final _auth = FirebaseAuth.instance;
I certainly know that I have to mock Firebase operations.
How to mock it in this case or, What should I do?

AWS Firehose newline Character

I've read a lot of similar questions around adding newline characters to firehose, but they're all around adding the newline character to the source. The problem is that I don't have access to the source, and a third party is piping data to our Kinesis instance and I cannot add the \n to the source.
I've tried doing a Firehose data transformation using the following code:
'use strict';
console.log('Loading function');
exports.handler = (event, context, callback) => {
/* Process the list of records and transform them */
const output = [];
event.records.forEach((record) => {
const results = {
/* This transformation is the "identity" transformation, the data is left intact */
recordId: record.recordId,
result: record.data.event_type === 'alert' ? 'Dropped' : 'Ok',
data: record.data + '\n'
};
output.push(results);
});
console.log(`Processing completed. Successful records ${output.length}.`);
callback(null, { records: output });
};
but the newline is still lost. I've also tried JSON.stringify(record.data) + '\n' but then I get an Invalid output structure error.
Try decoding the record.data
add a new line
then encode it again as base64.
This is python but the idea is the same
for record in event['records']:
payload = base64.b64decode(record['data'])
# Do custom processing on the payload here
payload = payload + '\n'
output_record = {
'recordId': record['recordId'],
'result': 'Ok',
'data': base64.b64encode(json.dumps(payload))
}
output.append(output_record)
return {'records': output}
From the comment of #Matt Westlake:
For those looking for the Node.js answer, it's
const data =
JSON.parse(new Buffer.from(record.data,'base64').toString('utf8'));
and
new Buffer.from(JSON.stringify(data) + '\n').toString('base64')
The kinesis-firehose-cloudwatch-logs-processor blueprint lambda does this (with some additional handling for cloudwatch logs).
Here's the lambda code from the blueprint as of today:
/*
For processing data sent to Firehose by Cloudwatch Logs subscription filters.
Cloudwatch Logs sends to Firehose records that look like this:
{
"messageType": "DATA_MESSAGE",
"owner": "123456789012",
"logGroup": "log_group_name",
"logStream": "log_stream_name",
"subscriptionFilters": [
"subscription_filter_name"
],
"logEvents": [
{
"id": "01234567890123456789012345678901234567890123456789012345",
"timestamp": 1510109208016,
"message": "log message 1"
},
{
"id": "01234567890123456789012345678901234567890123456789012345",
"timestamp": 1510109208017,
"message": "log message 2"
}
...
]
}
The data is additionally compressed with GZIP.
The code below will:
1) Gunzip the data
2) Parse the json
3) Set the result to ProcessingFailed for any record whose messageType is not DATA_MESSAGE, thus redirecting them to the
processing error output. Such records do not contain any log events. You can modify the code to set the result to
Dropped instead to get rid of these records completely.
4) For records whose messageType is DATA_MESSAGE, extract the individual log events from the logEvents field, and pass
each one to the transformLogEvent method. You can modify the transformLogEvent method to perform custom
transformations on the log events.
5) Concatenate the result from (4) together and set the result as the data of the record returned to Firehose. Note that
this step will not add any delimiters. Delimiters should be appended by the logic within the transformLogEvent
method.
6) Any additional records which exceed 6MB will be re-ingested back into Firehose.
*/
const zlib = require('zlib');
const AWS = require('aws-sdk');
/**
* logEvent has this format:
*
* {
* "id": "01234567890123456789012345678901234567890123456789012345",
* "timestamp": 1510109208016,
* "message": "log message 1"
* }
*
* The default implementation below just extracts the message and appends a newline to it.
*
* The result must be returned in a Promise.
*/
function transformLogEvent(logEvent) {
return Promise.resolve(`${logEvent.message}\n`);
}
function putRecordsToFirehoseStream(streamName, records, client, resolve, reject, attemptsMade, maxAttempts) {
client.putRecordBatch({
DeliveryStreamName: streamName,
Records: records,
}, (err, data) => {
const codes = [];
let failed = [];
let errMsg = err;
if (err) {
failed = records;
} else {
for (let i = 0; i < data.RequestResponses.length; i++) {
const code = data.RequestResponses[i].ErrorCode;
if (code) {
codes.push(code);
failed.push(records[i]);
}
}
errMsg = `Individual error codes: ${codes}`;
}
if (failed.length > 0) {
if (attemptsMade + 1 < maxAttempts) {
console.log('Some records failed while calling PutRecordBatch, retrying. %s', errMsg);
putRecordsToFirehoseStream(streamName, failed, client, resolve, reject, attemptsMade + 1, maxAttempts);
} else {
reject(`Could not put records after ${maxAttempts} attempts. ${errMsg}`);
}
} else {
resolve('');
}
});
}
function putRecordsToKinesisStream(streamName, records, client, resolve, reject, attemptsMade, maxAttempts) {
client.putRecords({
StreamName: streamName,
Records: records,
}, (err, data) => {
const codes = [];
let failed = [];
let errMsg = err;
if (err) {
failed = records;
} else {
for (let i = 0; i < data.Records.length; i++) {
const code = data.Records[i].ErrorCode;
if (code) {
codes.push(code);
failed.push(records[i]);
}
}
errMsg = `Individual error codes: ${codes}`;
}
if (failed.length > 0) {
if (attemptsMade + 1 < maxAttempts) {
console.log('Some records failed while calling PutRecords, retrying. %s', errMsg);
putRecordsToKinesisStream(streamName, failed, client, resolve, reject, attemptsMade + 1, maxAttempts);
} else {
reject(`Could not put records after ${maxAttempts} attempts. ${errMsg}`);
}
} else {
resolve('');
}
});
}
function createReingestionRecord(isSas, originalRecord) {
if (isSas) {
return {
Data: new Buffer(originalRecord.data, 'base64'),
PartitionKey: originalRecord.kinesisRecordMetadata.partitionKey,
};
} else {
return {
Data: new Buffer(originalRecord.data, 'base64'),
};
}
}
function getReingestionRecord(isSas, reIngestionRecord) {
if (isSas) {
return {
Data: reIngestionRecord.Data,
PartitionKey: reIngestionRecord.PartitionKey,
};
} else {
return {
Data: reIngestionRecord.Data,
};
}
}
exports.handler = (event, context, callback) => {
Promise.all(event.records.map(r => {
const buffer = new Buffer(r.data, 'base64');
const decompressed = zlib.gunzipSync(buffer);
const data = JSON.parse(decompressed);
// CONTROL_MESSAGE are sent by CWL to check if the subscription is reachable.
// They do not contain actual data.
if (data.messageType === 'CONTROL_MESSAGE') {
return Promise.resolve({
recordId: r.recordId,
result: 'Dropped',
});
} else if (data.messageType === 'DATA_MESSAGE') {
const promises = data.logEvents.map(transformLogEvent);
return Promise.all(promises)
.then(transformed => {
const payload = transformed.reduce((a, v) => a + v, '');
const encoded = new Buffer(payload).toString('base64');
return {
recordId: r.recordId,
result: 'Ok',
data: encoded,
};
});
} else {
return Promise.resolve({
recordId: r.recordId,
result: 'ProcessingFailed',
});
}
})).then(recs => {
const isSas = Object.prototype.hasOwnProperty.call(event, 'sourceKinesisStreamArn');
const streamARN = isSas ? event.sourceKinesisStreamArn : event.deliveryStreamArn;
const region = streamARN.split(':')[3];
const streamName = streamARN.split('/')[1];
const result = { records: recs };
let recordsToReingest = [];
const putRecordBatches = [];
let totalRecordsToBeReingested = 0;
const inputDataByRecId = {};
event.records.forEach(r => inputDataByRecId[r.recordId] = createReingestionRecord(isSas, r));
let projectedSize = recs.filter(rec => rec.result === 'Ok')
.map(r => r.recordId.length + r.data.length)
.reduce((a, b) => a + b);
// 6000000 instead of 6291456 to leave ample headroom for the stuff we didn't account for
for (let idx = 0; idx < event.records.length && projectedSize > 6000000; idx++) {
const rec = result.records[idx];
if (rec.result === 'Ok') {
totalRecordsToBeReingested++;
recordsToReingest.push(getReingestionRecord(isSas, inputDataByRecId[rec.recordId]));
projectedSize -= rec.data.length;
delete rec.data;
result.records[idx].result = 'Dropped';
// split out the record batches into multiple groups, 500 records at max per group
if (recordsToReingest.length === 500) {
putRecordBatches.push(recordsToReingest);
recordsToReingest = [];
}
}
}
if (recordsToReingest.length > 0) {
// add the last batch
putRecordBatches.push(recordsToReingest);
}
if (putRecordBatches.length > 0) {
new Promise((resolve, reject) => {
let recordsReingestedSoFar = 0;
for (let idx = 0; idx < putRecordBatches.length; idx++) {
const recordBatch = putRecordBatches[idx];
if (isSas) {
const client = new AWS.Kinesis({ region: region });
putRecordsToKinesisStream(streamName, recordBatch, client, resolve, reject, 0, 20);
} else {
const client = new AWS.Firehose({ region: region });
putRecordsToFirehoseStream(streamName, recordBatch, client, resolve, reject, 0, 20);
}
recordsReingestedSoFar += recordBatch.length;
console.log('Reingested %s/%s records out of %s in to %s stream', recordsReingestedSoFar, totalRecordsToBeReingested, event.records.length, streamName);
}
}).then(
() => {
console.log('Reingested all %s records out of %s in to %s stream', totalRecordsToBeReingested, event.records.length, streamName);
callback(null, result);
},
failed => {
console.log('Failed to reingest records. %s', failed);
callback(failed, null);
});
} else {
console.log('No records needed to be reingested.');
callback(null, result);
}
}).catch(ex => {
console.log('Error: ', ex);
callback(ex, null);
});
};
Here is code that will solve the problem
__Author__ = "Soumil Nitin Shah"
import json
import boto3
import base64
class MyHasher(object):
def __init__(self, key):
self.key = key
def get(self):
keys = str(self.key).encode("UTF-8")
keys = base64.b64encode(keys)
keys = keys.decode("UTF-8")
return keys
def lambda_handler(event, context):
output = []
for record in event['records']:
payload = base64.b64decode(record['data'])
"""Get the payload from event bridge and just get data attr """""
serialize_payload = str(json.loads(payload)) + "\n"
hasherHelper = MyHasher(key=serialize_payload)
hash = hasherHelper.get()
output_record = {
'recordId': record['recordId'],
'result': 'Ok',
'data': hash
}
print("output_record", output_record)
output.append(output_record)
return {'records': output}

Cognito custom-message triggered lambda returns InvalidLambdaResponseException

I've created a lambda and assigned it to cognito throw the UI as its custom-message lambda.
Here is the code in typescript:
export const handler = async (event) => {
const trigger = event.triggerSource
const customMessage = cloneDeep(customMessages[trigger])
if (customMessage) {
try {
// inject cognito values to custom message
const codeParameter = event.request.codeParameter
const usernameParameter = event.request.usernameParameter
for (let key in customMessage) {
let text = customMessage[key]
if (codeParameter) {
customMessage[key] = text.replace(/{{codeParameter}}/g, codeParameter)
}
if (usernameParameter) {
customMessage[key] = text.replace(/{{usernameParameter}}/g, usernameParameter)
}
}
// load HTML template
let htmlFile = readFileSync(templateFilePath, { encoding: 'utf8' })
htmlFile = htmlFile.replace(/(\r\n|\n|\r)/gm, '')
const template = handlebars.compile(htmlFile)
const html = template(customMessage)
event.emailMessage = html
event.response.emailSubject = customMessage.title
} catch (err) {
logger.error(err)
return event
}
}
return event
}
Basically it loads an html template file and injects the code-parameters and username.
now the response our signup flow lambda returns is:
{
"message": "InvalidLambdaResponseException",
"details": "Unrecognizable lambda output"
}
I event tried to copy paste AWS example:
exports.handler = (event, context, callback) => {
//
if(event.userPoolId === "theSpecialUserPool") {
// Identify why was this function invoked
if(event.triggerSource === "CustomMessage_SignUp") {
// Ensure that your message contains event.request.codeParameter. This is the placeholder for code that will be sent
event.response.smsMessage = "Welcome to the service. Your confirmation code is " + event.request.codeParameter;
event.response.emailSubject = "Welcome to the service";
event.response.emailMessage = "Thank you for signing up. " + event.request.codeParameter + " is your verification code";
}
// Create custom message for other events
}
// Customize messages for other user pools
// Return to Amazon Cognito
callback(null, event);
};
The response is the same.
Any suggestions?
Thanks
Here is my custom message lambda. It runs on Node 8.10. Maybe you'd like to test/adapt it. I've stripped some other stuff out but it should work fine
exports.handler = function(event, context) {
const cognitoUserPool = 'us-east-1_AAAAAA';
const snsTopicArn = 'arn:aws:sns:us-east-1:9999999999:BBBBBBBBBB';
const baseurl = 'https://company.us-east-1.elasticbeanstalk.com/app';
console.log('Cognito Event:', event);
var AWS = require("aws-sdk");
if(event.userPoolId === cognitoUserPool) {
if(event.triggerSource === "CustomMessage_SignUp") {
event.response.emailSubject = "Welcome to Company";
event.response.emailMessage = "Hello etc";
context.done(null, event);
}
if(event.triggerSource === "CustomMessage_ResendCode") {
event.response.emailSubject = "Welcome to Company";
event.response.emailMessage = "Some other message etc";
context.done(null, event);
}
if(event.triggerSource === "CustomMessage_ForgotPassword") {
event.response.emailSubject = "Your password reset";
event.response.emailMessage = "Some other message again etc";
context.done(null, event);
}
// Other event types can go here
} else {
context.done(null, event);
}
};

Cognito send confirmation email using custom email

There's a way to send an email other than the one specified in the "Message customisation" tab on Cognito user pool?
I would like to use different email based on some parameters.
E.g.
verification#my-service.com for verification email
welcome#my-service.com for welcome email
You can go to the general settings in Cognito then click on triggers. There you can select Post Confirmation lambda function(this example in node) to send the email. In the lambda function you can make the subject whatever you like and change from email address.
var aws = require('aws-sdk');
var ses = new aws.SES();
exports.handler = function(event, context) {
console.log(event);
if (event.request.userAttributes.email) {
// Pull another attribute if you want
sendEmail(event.request.userAttributes.email,
"Congratulations "+event.userName+", you have been registered!"
, function(status) {
context.done(null, event);
});
} else {
// Nothing to do, the user's email ID is unknown
console.log("Failed");
context.done(null, event);
}
};
function sendEmail(to, body, completedCallback) {
var eParams = {
Destination: {
ToAddresses: [to]
},
Message: {
Body: {
Text: {
Data: body
}
},
Subject: {
Data: "Welcome to My Service!"
}
},
Source: "welcome#my-service.com"
};
var email = ses.sendEmail(eParams, function(err, data){
if (err) {
console.log(err);
} else {
console.log("===EMAIL SENT===");
}
completedCallback('Email sent');
});
console.log("EMAIL CODE END");
};
You will also have to set up SES.
If you want to handle all emails yourself, you can specify this with a CustomEmailSender Lambda. This trigger isn't currently available through the AWS Console, but you can specify it with the CLI or CDK/CloudFormation. See the docs here.
Those docs are pretty terrible though. The gist is that you'll be given a code property on the event, which is a base64-encoded blob that was encrypted with the KMS key you specified on your user pool. Depending on the triggering event, this is the verification code, temporary password, etc, generated by Cognito. Here's a simplified version of what my Lambda looks like:
import { buildClient, CommitmentPolicy, KmsKeyringNode } from '#aws-crypto/client-node';
const { decrypt } = buildClient(CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT);
const kmsKeyring = new KmsKeyringNode({
keyIds: [process.env.COGNITO_EMAILER_KEY_ARN]
});
export async function lambdaHandler(event, context) {
try {
let payload = '';
if (event.request.code) {
const { plaintext, messageHeader } = await decrypt(
kmsKeyring,
Buffer.from(event.request.code, "base64")
);
if (event.userPoolId !== messageHeader.encryptionContext["userpool-id"]) {
console.error("Encryption context does not match expected values!");
return;
}
payload = plaintext.toString();
}
let messageHtml = "";
switch (event.triggerSource) {
case "CustomEmailSender_SignUp": {
const verificationCode = payload;
messageHtml = `<p>Use this code to verify your email: ${verificationCode}</p>`;
break;
}
case "CustomEmailSender_AdminCreateUser":
case "CustomEmailSender_ResendCode": {
const tempPassword = payload;
messageHtml = `<p>Your temporary password is ${tempPassword}</p>`;
break;
}
default: {
console.warn("unhandled trigger:", event.triggerSource);
return;
}
}
await sendEmail({
subject: "Automated message",
to: event.request.userAttributes.email,
messageHtml,
});
return true;
} catch (err) {
console.error(err.message);
process.exit(1);
}
}

InvalidRequestException of status code 400 when AWS Lambda Function was invoked

var config = {};
config.IOT_BROKER_ENDPOINT = "abcdefghijk.iot.us-east-1.amazonaws.com".toLowerCase();
config.IOT_BROKER_REGION = "us-east-1";
config.IOT_THING_2 = "Thing1";
var AWS = require('aws-sdk');
AWS.config.region = config.IOT_BROKER_REGION;
AWS.config.update({accessKeyId: 'xxxxxxxxxxxxxxxxxxxx', secretAccessKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'});
var iotdata = new AWS.IotData({endpoint: config.IOT_BROKER_ENDPOINT});
// namespaces
const NAMESPACE_CONTROL = "Alexa.ConnectedHome.Control";
const NAMESPACE_DISCOVERY = "Alexa.ConnectedHome.Discovery";
// discovery
const REQUEST_DISCOVER = "DiscoverAppliancesRequest";
const RESPONSE_DISCOVER = "DiscoverAppliancesResponse";
// control
const REQUEST_TURN_ON = "TurnOnRequest";
const RESPONSE_TURN_ON = "TurnOnConfirmation";
const REQUEST_TURN_OFF = "TurnOffRequest";
const RESPONSE_TURN_OFF = "TurnOffConfirmation";
// errors
const ERROR_UNSUPPORTED_OPERATION = "UnsupportedOperationError";
const ERROR_UNEXPECTED_INFO = "UnexpectedInformationReceivedError";
// entry
exports.handler = function (event, context, callback) {
log("Received Directive", event);
var requestedNamespace = event.header.namespace;
var response = null;
try {
switch (requestedNamespace) {
case NAMESPACE_DISCOVERY:
response = handleDiscovery(event);
break;
case NAMESPACE_CONTROL:
response = handleControl(event);
break;
default:
log("Error", "Unsupported namespace: " + requestedNamespace);
response = handleUnexpectedInfo(requestedNamespace);
break;
}// switch
} catch (error) {
log("Error", error);
}// try-catch
callback(null, response);
}// exports.handler
var handleDiscovery = function (event) {
var header = createHeader(NAMESPACE_DISCOVERY, RESPONSE_DISCOVER);
var payload = {
"discoveredAppliances": [],
};
return createDirective(header, payload);
}// handleDiscovery
var handleControl = function (event) {
var response = null;
var requestedName = event.header.name;
switch (requestedName) {
case REQUEST_TURN_ON :
response = handleControlTurnOn(event);
break;
case REQUEST_TURN_OFF :
response = handleControlTurnOff(event);
break;
default:
log("Error", "Unsupported operation" + requestedName);
response = handleUnsupportedOperation();
break;
}// switch
return response;
}// handleControl
var handleControlTurnOn = function (event) {
var thingPicker = config.IOT_THING_2;
console.log("Turning On the LED now");
console.log("check 1");
var update = {
"desired": {
"led": 1,
},
};
console.log("check 2");
console.log(thingPicker);
iotdata.updateThingShadow({
payload: JSON.stringify(update),
thingName: thingPicker,
}, function (err, data) {
console.log("check 4");
if (err) {
console.log("check 5");
console.log(err);
} else {
console.log("check 6");
console.log(data);
}
});
var header = createHeader(NAMESPACE_CONTROL, RESPONSE_TURN_ON);
var payload = {};
return createDirective(header, payload);
}// handleControlTurnOn
var handleControlTurnOff = function (event) {
var thingPicker = config.IOT_THING_2;
console.log("Turning Off the LED now");
var update = {
"desired": {
"led": 0,
},
};
console.log(thingPicker);
iotdata.updateThingShadow({
payload: JSON.stringify(update),
thingName: thingPicker,
}, function (err, data) {
if (err) {
console.log(err);
} else {
console.log(data);
}
});
var header = createHeader(NAMESPACE_CONTROL, RESPONSE_TURN_OFF);
var payload = {};
return createDirective(header, payload);
}// handleControlTurnOff
var handleUnsupportedOperation = function () {
var header = createHeader(NAMESPACE_CONTROL, ERROR_UNSUPPORTED_OPERATION);
var payload = {};
return createDirective(header, payload);
}// handleUnsupportedOperation
var handleUnexpectedInfo = function (fault) {
var header = createHeader(NAMESPACE_CONTROL, ERROR_UNEXPECTED_INFO);
var payload = {
"faultingParameter": fault,
};
return createDirective(header, payload);
}// handleUnexpectedInfo
// support functions
var createMessageId = function () {
var d = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
return uuid;
}// createMessageId
var createHeader = function (namespace, name) {
return {
"messageId": createMessageId(),
"namespace": namespace,
"name": name,
"payloadVersion": "2",
};
}// createHeader
var createDirective = function (header, payload) {
return {
"header": header,
"payload": payload,
};
}// createDirective
var log = function (title, msg) {
console.log('**** ' + title + ': ' + JSON.stringify(msg));
}// log
Piece of code related to AWS Lambda Function which is of smart home API, when it was run against a testing event to turn on light, its giving "InvalidRequestException" of status code :400
the test event that was run against the code was:
{
"header": {
"messageId" : "5d599a53-fe40-405f-b0ab-233611e2dc5c",
"name" : "TurnOnRequest",
"namespace" : "Alexa.ConnectedHome.Control",
"payloadVersion" : "2"
},
"payload" : {
"accessToken" : "acc355t0ken"
}
}
Can any one please help me solve that exception.
Thanks in advance