diff --git a/Lambda/AddVistorInfo.py b/Lambda/AddVistorInfo.py new file mode 100644 index 0000000..0ac4f9d --- /dev/null +++ b/Lambda/AddVistorInfo.py @@ -0,0 +1,116 @@ +import json +import logging +import boto3 +from datetime import datetime +from datetime import timedelta +import random + +def sendSMS(phone,faceId,otp): + sns = boto3.client('sns', region_name = 'us-east-1') + url = "https://smartdoorauthentication.s3.amazonaws.com/OTPlogin.html?faceId="+faceId + message = "Your OTP is - " + str(otp) + "\n\n Click here to enter OTP " + url + print(message) + try: + res = sns.publish( + PhoneNumber = phone, + Message = message, + MessageStructure = 'string' + ) + except KeyError: + print("error in sending sms") + + +def generateOTP(): + return random.randint(1000,9999) + + +def putToDynamoDbPasscodes(table, faceId): + epochafter5 = int(datetime.now().timestamp()) + 300 + otp = generateOTP() + table.put_item( + Item={ + 'faceID' : faceId, + 'ExpirationTimeStamp' : epochafter5, + 'CurrentTimeStamp' : (epochafter5 - 300), + 'OTP' :otp + }) + return otp + + +def getTimeStamp(fileName): + s3 = boto3.client('s3') + response = s3.get_object( + Bucket = 'smartdoor-visitor-faces', + Key = fileName + ) + return response['LastModified'].strftime("%Y-%m-%d %H:%M:%S") + + +def putToDynamoDbVisitors(table,faceId,name,phone,fileName): + table.put_item( + Item={ + 'faceId': faceId, + 'name': name, + 'phone': phone, + 'photos': { + 'objectKey' : fileName, + 'bucket' : "smartdoor-visitor-faces", + 'createdTimestamp' : getTimeStamp(fileName) + }, + 'account_type': 'standard_user', + } + ) + + +def connectToDB(tableName): + dynamodb = boto3.resource('dynamodb') + table = dynamodb.Table(tableName) + print(table.creation_date_time) + return table + + +def extractInformation(event): + request = event["message"] + #faceId = request["faceId"] + name = request["name"] + phone = request["phone"] + fileName = request["fileName"] + return name, phone, fileName + + +def indexFaceRekognition(buffer,name): + rek = boto3.client('rekognition') + res = rek.index_faces( + CollectionId = 'smartdoor-visitors', + Image={ + 'Bytes' : buffer + }, + ExternalImageId = name + ) + return res + + +def getFaceId(fileName,name): + s3 = boto3.client('s3') + res = s3.get_object( + Bucket = 'smartdoor-visitor-faces', + Key = fileName + ) + data = res['Body'].read() + res = indexFaceRekognition(data,name) + return res['FaceRecords'][0]['Face']['FaceId'] + + +def lambda_handler(event, context): + name, phone, fileName = extractInformation(event) + faceId = getFaceId(fileName, name) + table = connectToDB('visitors') + putToDynamoDbVisitors(table,faceId,name,phone,fileName) + table = connectToDB('passcodes') + otp = putToDynamoDbPasscodes(table,faceId) + sendSMS(phone,faceId,otp) + message = "Thank you, visitor added to database" + return { + 'statusCode': 200, + 'body': message + } diff --git a/Lambda/Kinesis_stream_data.py b/Lambda/Kinesis_stream_data.py new file mode 100644 index 0000000..36ca3cd --- /dev/null +++ b/Lambda/Kinesis_stream_data.py @@ -0,0 +1,271 @@ +from __future__ import print_function +import json +import base64 +import boto3 +import cv2 +import random +import uuid +from datetime import datetime + + +def sendSMS(faceId, phone, otp): + sns = boto3.client('sns',region_name= 'us-east-1') + url = "https://smartdoorauthentication.s3.amazonaws.com/OTPlogin.html?faceId="+faceId + message = "Your OTP is - " + str(otp) + "\n\n click here to enter OTP " + url + print(message) + try: + res = sns.publish( + PhoneNumber = phone, + Message = message, + MessageStructure = 'string' + ) + except KeyError: + print("error in sending sms") + +def generateOTP(): + return random.randint(1000,9999) + +def putToDynamoDbPasscodes(table, faceId): + epochafter5 = int(datetime.now().timestamp()) + 300 + otp = generateOTP() + table.put_item( + Item={ + 'faceID' : faceId, + 'ExpirationTimeStamp' : epochafter5, + 'CurrentTimeStamp' : (epochafter5 - 300), + 'OTP' :otp + }) + return otp + + +def getVisitorsPhoneNumber(tableName,faceId): + print("get_visitor_phone_number") + item = tableName.get_item( + Key={'faceId': faceId + }) + return item['Item']['phone'] + + +def connectToDB(tableName): + dynamodb = boto3.resource('dynamodb') + table = dynamodb.Table(tableName) + print(table.creation_date_time) + return table + + +def sendEmail(url,fileName): + visitorUrl = "http://smartdoorauthentication.s3-website-us-east-1.amazonaws.com/VisitorInfo.html?fileName="+fileName + ses = boto3.client('ses') + response = ses.send_email( Destination={ + 'ToAddresses': [ + 'kp2492@nyu.edu', + 'maheshg@nyu.edu', + ], + }, + Message={ + 'Body': { + 'Html': { + 'Charset': 'UTF-8', + 'Data': 'Click here for Vistors photo.

'+ + 'Click here to enter the Visitor Information', + } + }, + 'Subject': { + 'Charset': 'UTF-8', + 'Data': 'SmartDoor Email Lambda Function', + }, + }, + Source='prashkar666@gmail.com' + ) + + +def countNumberOfFrames(vidcap,fileName): + total = 0 + while True: + success, image = vidcap.read() + if not success: + break + total+=1 + if total > 500: + break + vidcap.release() + vidcap.open(fileName) + for i in range(1,int(total/2)): + success, image = vidcap.read() + return success,image + + +def getEndpoint(streamName): + kvm = boto3.client("kinesisvideo") + endpt = kvm.get_data_endpoint( + APIName = "GET_MEDIA_FOR_FRAGMENT_LIST", + StreamName=streamName, + ) + return endpt["DataEndpoint"] + +def getImageFromFragments(endpoint, streamName, fragmentNumber): + kvm = boto3.client('kinesis-video-archived-media',endpoint_url = endpoint) + response = kvm.get_media_for_fragment_list( + StreamName=streamName, + Fragments=[ + fragmentNumber, + ] + ) + print("FragmentNumber: "+fragmentNumber) + + mkvFileName = '/tmp/test.mkv' + jpgFileName = '/tmp/frame.jpg' + stream = response["Payload"] + f = open(mkvFileName,'wb') + f.write(stream.read()) + f.close() + + vidcap = cv2.VideoCapture(mkvFileName) + success,image = countNumberOfFrames(vidcap,mkvFileName) + if success: + cv2.imwrite(jpgFileName , image) #apparently we cannot see this file im lambda, check if uploaded to S3 + + return jpgFileName + +def uploadFileToS3Bucket(bucket, jpgFileName, identifier): + # s3 = boto3.resource('s3') + # s3.Bucket(bucket).upload_file(jpgFileName, fileName) + s3_client = boto3.client('s3') + s3_client.upload_file( + jpgFileName, #filepath + bucket, #bucket Name + 'frame_{}.jpeg'.format(identifier), #key + ) + + +def getEmailDetails(bucket,identifier): + fileName = "frame_"+identifier+".jpeg" + url = "https://"+bucket+".s3.amazonaws.com/"+fileName + return url,fileName + + +def getParametersFromKDS(event): + for record in event['Records']: + #Kinesis data is base64 encoded so decode here + payload=base64.b64decode(record["kinesis"]["data"]) + print("Decoded payload: " + str(payload)) + + json_data = json.loads(payload.decode('utf-8')) + # print("Decoded json_data payload: " + str(json_data)) + face_search_response = json_data['FaceSearchResponse'] + + # faceId = "f81b6fb6-f56a-4b32-b4b9-4b3b96ce4ee7" + # return True, faceId , None + + if face_search_response: + # return ("No one at the door") + for faces in face_search_response: + if faces['MatchedFaces']: + faceId = faces['MatchedFaces'][0]['Face']['FaceId'] + print('FACEID ',faceId) + return True, faceId , None + else: + fragmentNumber= json_data['InputInformation']['KinesisVideo']['FragmentNumber'] + return True, None, fragmentNumber + else: + # fragmentNumber= json_data['InputInformation']['KinesisVideo']['FragmentNumber'] + # return False, None, fragmentNumber + return False,None,None + +def checkForDuplicates(passcodetable,faceId): + + try: + dynamodb = boto3.client('dynamodb') + res = dynamodb.get_item( + TableName = "passcodes", + Key = { + 'faceID' : { + 'S' : faceId + } + }) + timeStamp = res["Item"]["ExpirationTimeStamp"]["N"] + curTimeStamp = int(datetime.now().timestamp()) + return curTimeStamp <= int(timeStamp) + except KeyError: + return False + +def checkEmailDuplicate(emailTable, ownerEmailId): + try: + dynamodb = boto3.client('dynamodb') + res = dynamodb.get_item( + TableName = "emailFilter", + Key = { + 'emailId' : { + 'S' : ownerEmailId + } + }) + + timeStamp = res["Item"]["ExpirationTimeStamp"]["N"] + curTimeStamp = int(datetime.now().timestamp()) + return curTimeStamp <= int(timeStamp) + except KeyError: + return False + + +def putToDynamoDbEmailFilter(table, ownerEmailId): + currenttimestamp = int(datetime.now().timestamp()) + table.put_item( + Item={ + 'emailId' : ownerEmailId, + 'ExpirationTimeStamp' : (currenttimestamp + 300), + 'CurrentTimeStamp' : currenttimestamp, + }) + print("Added email to the email filter"); + return True + + +def lambda_handler(event, context): + + print(event) + #if old face return faceId in parameter, otherwise return fragment number in parameter + #success should be true if old face, false if new face + success, faceId, fragmentNumber = getParametersFromKDS(event) + + #fragmentNumber= json_data['InputInformation']['KinesisVideo']['FragmentNumber'] + # fragmentNumber = '91343852333181521524364891175229548375108484567' + + if success: + if faceId is not None: + print("Valid Vistor") + passcodetable = connectToDB("passcodes") + if not checkForDuplicates(passcodetable,faceId): + otp = putToDynamoDbPasscodes(passcodetable,faceId) + vistiorsTable = connectToDB("visitors") + phoneNumber = getVisitorsPhoneNumber(vistiorsTable,faceId) + print("phone "+ phoneNumber) + sendSMS(faceId, phoneNumber, otp) + else: + print("Duplicate Request for FaceId - " + faceId) + + elif fragmentNumber is not None: + print("Unknown Vistor") + streamName="KVS1" + bucket = "smartdoor-visitor-faces" + identifier=str(uuid.uuid1()) + emailTable = connectToDB("emailFilter") + print("uuid "+identifier) + ownerEmailId = "maheshg@nyu.edu" + if not checkEmailDuplicate(emailTable, ownerEmailId): + endpoint = getEndpoint(streamName) + jpgFileName = getImageFromFragments(endpoint, streamName, fragmentNumber) + url, fileName = getEmailDetails(bucket, identifier) + uploadFileToS3Bucket(bucket, jpgFileName, identifier) + print("Send Email") + # add data in emailfilter table + putToDynamoDbEmailFilter(emailTable,ownerEmailId) + sendEmail(url, fileName) + else: + print("Duplicate Email Request") + else: + print("Noone at the Door") + return ("Noone at the Door") + + return { + 'statusCode': 200, + 'body': json.dumps('Hello from Lambda!') + } diff --git a/Lambda/ValidateOTP.py b/Lambda/ValidateOTP.py new file mode 100644 index 0000000..8cf3953 --- /dev/null +++ b/Lambda/ValidateOTP.py @@ -0,0 +1,59 @@ +import json +import boto3 +import logging +from datetime import datetime + +logger = logging.getLogger() +logger.setLevel(logging.DEBUG) + +def validateOTP(res,userOtp): + try: + if res["Item"] is None: + message = "invalid OTP" + else: + print(res["Item"]) + dbOTP = res["Item"]["OTP"]["N"] + timeStamp = res["Item"]["ExpirationTimeStamp"]["N"] + curTimeStamp = int(datetime.now().timestamp()) + userOtp = int(userOtp) + dbOTP = int(dbOTP) + timeStamp = int(timeStamp) + if userOtp == dbOTP and timeStamp > curTimeStamp: + message = "ACCESS GRANTED" + else: + message = "ACCESS DENIED" + logger.debug("db - " + str(timeStamp) + " now - " + str(curTimeStamp)) + logger.debug("db - " + str(dbOTP) + " user - " + str(userOtp)) + except KeyError: + message = "Invalid OTP" + return message + + +def queryPasscodesDb(otp, faceId): + dynamodb = boto3.client('dynamodb') + res = dynamodb.get_item( + TableName = "passcodes", + Key = { + 'faceID' : { + 'S' : faceId + } + } + ) + return res + + +def extractAttributes(res): + return res["otp"],res["faceId"] + + +def lambda_handler(event, context): + logger.debug("Helloo") + otp, faceId = extractAttributes(event["message"]) + res = queryPasscodesDb(otp,faceId) + logger.debug("here") + message = validateOTP(res,otp) + + return { + 'statusCode': 200, + 'body': message + } diff --git a/OTPlogin.html b/OTPlogin.html new file mode 100644 index 0000000..a0ed12c --- /dev/null +++ b/OTPlogin.html @@ -0,0 +1,73 @@ + + + + + OTP Validation Page + + + + + + + + + + + + + + + + + + + + +
+

OTP Validation Page

+ One Time Password (OTP):
+ +

+

+

If you have entered the correct OTP you will be given access to the Door

+
+ + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..aa0e655 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Smart-Door-Authentication-System +##### [Link To Web Application Known Visitor](http://smartdoorauthentication.s3-website-us-east-1.amazonaws.com/) +##### [Link To Web Application UnKnown Visitor](http://smartdoorauthentication.s3-website-us-east-1.amazonaws.com/VisitorInfo.html) + + +## FRONTEND (HTML, JavaScript, CSS) +The frontend is hosted in AWS S3 and provides a web-app user interface to interact with the chat bot. Many open source libraries and frameworks were used to design the UI/UX of the bot. + +## DESCRIPTION +"Smart-Door-Authentication-System" is a serverless, microservice driven web-based application. It builds a distributed system that authenticates people and provides them with access to a virtual door. It is designed using multiple AWS components :- +##### AWS Kinesis, Rekognition, Kinesis Data Stream, S3-Buckets, API-Gateway, Swagger, Lambda Functions, Cognito, DynamoDB, SNS, SES, Cloud Watch. + +This is a door authentication system that will scan a person at the door using AWS Kinesis Video Streams and AWS Rekognition and provide an OTP passcode to the User using which the person can enter the door. + +## ARCHITECHTURE :- +![alt text](https://github.com/maheshg23/Smart-Door-Authentication-System/blob/master/images/Architecture.png) + + +## SAMPLE UI OF THE WEB APPLICATION +![alt text](https://github.com/maheshg23/Smart-Door-Authentication-System/blob/master/images/ApplicationUI.jpg) + + +## SAMPLE OUTPUT +### Known Visitor +The person at the door recieves an SMS of the OTP which is valid only for 5 minutes :- +![alt text](https://github.com/maheshg23/Smart-Door-Authentication-System/blob/master/images/KnownVisitor.jpeg) + +### Unknown Visitor +The Owner recieves an Email with the persons image and a link to add the Unknown vistitor to the database :- +![alt text](https://github.com/maheshg23/Smart-Door-Authentication-System/blob/master/images/UnknownVisitor.png) + + + diff --git a/SetupInformation.md b/SetupInformation.md new file mode 100644 index 0000000..52fafd6 --- /dev/null +++ b/SetupInformation.md @@ -0,0 +1,32 @@ +# Setup Infromation for the Application + +## GStreamer Plugin for Mac +Go to [2] and build you Gstreamer Plugin + +Go to the GStreamer Producer folder to start the Gstreamer +`cd amazon-kinesis-video-streams-producer-sdk-cpp/kinesis-video-native-build/downloads/local/bin` + +Run the following Command to start the GStreamer to capture the video stream + +`gst-launch-1.0 -v avfvideosrc ! videoconvert ! vtenc_h264_hw allow-frame-reordering=FALSE realtime=TRUE max-keyframe-interval=45 ! kvssink name=sink stream-name="YOUR_KENISIS_STREAM_NAME" access-key="YOUR_ACCESS_KEY" secret-key="YOUR_SECRET_KEY" aws-region="us-east-1" osxaudiosrc ! audioconvert ! avenc_aac ! queue ! sink.` + +## Setup AWS Rekognition +run the commands in the rekog_aws_cli_commands.txt before you start the GStreamer stream + +## OpenCV Library Build +We need to build our own openCV library which we can attach it to the Kinesis_stream_data lambda funtion using the lambda layers +Refer to [5] create a zip files and add it to the Lambda Layer. + +After you add the OpenCV Zip file into the lambda layer and then attach the layer to the lambda function we need to change the environment variable PYTHONPATH. "/var/runtime" is for Boto3 and other python libraries and "/opt/"is for the OpenCV library that we added to the Lambda layer + +`PYTHONPATH = /var/runtime:/opt/` + +## DynamoDB Tables +Create 3 DynamoDB Tables - visitors, passcode and emailfilter + +## Reference - +[1] https://medium.com/@matt.collins/facial-recognition-with-a-raspberry-pi-and-kinesis-video-streams-part-2-9c9a631e8c24 +[2] https://github.com/awslabs/amazon-kinesis-video-streams-producer-sdk-cpp/blob/master/README.md +[3] https://medium.com/faun/a-quick-introduction-to-aws-rekognition-8257d4777198 +[4] https://github.com/aeddi/aws-lambda-python-opencv/blob/master/build.sh +[5] https://itnext.io/create-a-highly-scalable-image-processing-service-on-aws-lambda-and-api-gateway-in-10-minutes-7cbb2893a479 diff --git a/Smart-Door-Authentication-swagger.yaml b/Smart-Door-Authentication-swagger.yaml new file mode 100644 index 0000000..56ae3c6 --- /dev/null +++ b/Smart-Door-Authentication-swagger.yaml @@ -0,0 +1,220 @@ +--- +swagger: "2.0" +info: + description: "Smart Door Authentication Application, " + version: "1.0.0" + title: "Smart Door Authentication" +host: "viqzluwo1m.execute-api.us-east-1.amazonaws.com" +basePath: "/test" +schemes: +- "https" +paths: + /Assign2_LF2: + x-amazon-apigateway-any-method: + responses: + 200: + description: "200 response" + /OTPValidate: + post: + operationId: "sendMessage1" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - in: "body" + name: "OTPRequest" + required: true + schema: + $ref: "#/definitions/OTPRequest" + responses: + 200: + description: "200 response" + schema: + $ref: "#/definitions/OTPResponse" + headers: + X-Requested-With: + type: "string" + Access-Control-Allow-Origin: + type: "string" + Access-Control-Allow-Methods: + type: "string" + Access-Control-Allow-Headers: + type: "string" + 500: + description: "500 response" + schema: + $ref: "#/definitions/Error" + headers: + X-Requested-With: + type: "string" + Access-Control-Allow-Origin: + type: "string" + Access-Control-Allow-Methods: + type: "string" + Access-Control-Allow-Headers: + type: "string" + 403: + description: "403 response" + schema: + $ref: "#/definitions/Error" + headers: + X-Requested-With: + type: "string" + Access-Control-Allow-Origin: + type: "string" + Access-Control-Allow-Methods: + type: "string" + Access-Control-Allow-Headers: + type: "string" + options: + consumes: + - "application/json" + produces: + - "application/json" + responses: + 200: + description: "200 response" + schema: + $ref: "#/definitions/OTPResponse" + headers: + X-Requested-With: + type: "string" + Access-Control-Allow-Origin: + type: "string" + Access-Control-Allow-Methods: + type: "string" + Access-Control-Allow-Headers: + type: "string" + 500: + description: "500 response" + schema: + $ref: "#/definitions/Error" + headers: + X-Requested-With: + type: "string" + Access-Control-Allow-Origin: + type: "string" + Access-Control-Allow-Methods: + type: "string" + Access-Control-Allow-Headers: + type: "string" + 403: + description: "403 response" + schema: + $ref: "#/definitions/Error" + headers: + X-Requested-With: + type: "string" + Access-Control-Allow-Origin: + type: "string" + Access-Control-Allow-Methods: + type: "string" + Access-Control-Allow-Headers: + type: "string" + /visitorCheck: + post: + operationId: "sendMessage" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - in: "body" + name: "ApproveRequest" + required: true + schema: + $ref: "#/definitions/ApproveRequest" + responses: + 200: + description: "200 response" + schema: + $ref: "#/definitions/ApproveResponse" + headers: + X-Requested-With: + type: "string" + Access-Control-Allow-Origin: + type: "string" + 500: + description: "500 response" + schema: + $ref: "#/definitions/Error" + 403: + description: "403 response" + schema: + $ref: "#/definitions/Error" + options: + produces: + - "application/json" + responses: + 200: + description: "200 response" + schema: + $ref: "#/definitions/ApproveResponse" + headers: + X-Requested-With: + type: "string" + Access-Control-Allow-Origin: + type: "string" + Access-Control-Allow-Methods: + type: "string" + Access-Control-Allow-Headers: + type: "string" + 500: + description: "500 response" + 403: + description: "403 response" +definitions: + Message: + type: "object" + properties: + type: + type: "string" + unstructured: + $ref: "#/definitions/UnstructuredMessage" + UnstructuredMessage: + type: "object" + properties: + id: + type: "string" + text: + type: "string" + timestamp: + type: "string" + format: "datetime" + ApproveRequest: + type: "object" + properties: + messages: + type: "array" + items: + $ref: "#/definitions/Message" + OTPRequest: + type: "object" + properties: + messages: + type: "array" + items: + $ref: "#/definitions/Message" + Error: + type: "object" + properties: + code: + type: "integer" + format: "int32" + message: + type: "string" + OTPResponse: + type: "object" + properties: + messages: + type: "array" + items: + $ref: "#/definitions/Message" + ApproveResponse: + type: "object" + properties: + messages: + type: "array" + items: + $ref: "#/definitions/Message" diff --git a/VisitorInfo.html b/VisitorInfo.html new file mode 100644 index 0000000..9cb6888 --- /dev/null +++ b/VisitorInfo.html @@ -0,0 +1,91 @@ + + + + + Visitor Information WebPage + + + + + + + + + + + + + + + + + + + + + + + + + +

Visitor Information WebPage

+ +
+ +
+ +

+

+

If you click the submit button visitor will be given the access to door

+
+ + + + + + \ No newline at end of file diff --git a/images/ApplicationUI.jpg b/images/ApplicationUI.jpg new file mode 100644 index 0000000..ec19c15 Binary files /dev/null and b/images/ApplicationUI.jpg differ diff --git a/images/Architecture.png b/images/Architecture.png new file mode 100644 index 0000000..59e74aa Binary files /dev/null and b/images/Architecture.png differ diff --git a/images/KnownVisitor.jpeg b/images/KnownVisitor.jpeg new file mode 100644 index 0000000..60e1fef Binary files /dev/null and b/images/KnownVisitor.jpeg differ diff --git a/images/UnknownVisitor.png b/images/UnknownVisitor.png new file mode 100644 index 0000000..32fb211 Binary files /dev/null and b/images/UnknownVisitor.png differ diff --git a/js/apigClient.js b/js/apigClient.js new file mode 100644 index 0000000..ba360a1 --- /dev/null +++ b/js/apigClient.js @@ -0,0 +1,159 @@ +/* + * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +var apigClientFactory = {}; +apigClientFactory.newClient = function (config) { + var apigClient = { }; + if(config === undefined) { + config = { + accessKey: '', + secretKey: '', + sessionToken: '', + region: '', + apiKey: undefined, + defaultContentType: 'application/json', + defaultAcceptType: 'application/json' + }; + } + if(config.accessKey === undefined) { + config.accessKey = ''; + } + if(config.secretKey === undefined) { + config.secretKey = ''; + } + if(config.apiKey === undefined) { + config.apiKey = ''; + } + if(config.sessionToken === undefined) { + config.sessionToken = ''; + } + if(config.region === undefined) { + config.region = 'us-east-1'; + } + //If defaultContentType is not defined then default to application/json + if(config.defaultContentType === undefined) { + config.defaultContentType = 'application/json'; + } + //If defaultAcceptType is not defined then default to application/json + if(config.defaultAcceptType === undefined) { + config.defaultAcceptType = 'application/json'; + } + + + // extract endpoint and path from url + var invokeUrl = 'https://viqzluwo1m.execute-api.us-east-1.amazonaws.com/test'; + var endpoint = /(^https?:\/\/[^\/]+)/g.exec(invokeUrl)[1]; + var pathComponent = invokeUrl.substring(endpoint.length); + + var sigV4ClientConfig = { + accessKey: config.accessKey, + secretKey: config.secretKey, + sessionToken: config.sessionToken, + serviceName: 'execute-api', + region: config.region, + endpoint: endpoint, + defaultContentType: config.defaultContentType, + defaultAcceptType: config.defaultAcceptType + }; + + var authType = 'NONE'; + if (sigV4ClientConfig.accessKey !== undefined && sigV4ClientConfig.accessKey !== '' && sigV4ClientConfig.secretKey !== undefined && sigV4ClientConfig.secretKey !== '') { + authType = 'AWS_IAM'; + } + + var simpleHttpClientConfig = { + endpoint: endpoint, + defaultContentType: config.defaultContentType, + defaultAcceptType: config.defaultAcceptType + }; + + var apiGatewayClient = apiGateway.core.apiGatewayClientFactory.newClient(simpleHttpClientConfig, sigV4ClientConfig); + + + + apigClient.oTPValidatePost = function (params, body, additionalParams) { + if(additionalParams === undefined) { additionalParams = {}; } + + apiGateway.core.utils.assertParametersDefined(params, ['body'], ['body']); + + var oTPValidatePostRequest = { + verb: 'post'.toUpperCase(), + path: pathComponent + uritemplate('/OTPValidate').expand(apiGateway.core.utils.parseParametersToObject(params, [])), + headers: apiGateway.core.utils.parseParametersToObject(params, []), + queryParams: apiGateway.core.utils.parseParametersToObject(params, []), + body: body + }; + + + return apiGatewayClient.makeRequest(oTPValidatePostRequest, authType, additionalParams, config.apiKey); + }; + + + apigClient.oTPValidateOptions = function (params, body, additionalParams) { + if(additionalParams === undefined) { additionalParams = {}; } + + apiGateway.core.utils.assertParametersDefined(params, [], ['body']); + + var oTPValidateOptionsRequest = { + verb: 'options'.toUpperCase(), + path: pathComponent + uritemplate('/OTPValidate').expand(apiGateway.core.utils.parseParametersToObject(params, [])), + headers: apiGateway.core.utils.parseParametersToObject(params, []), + queryParams: apiGateway.core.utils.parseParametersToObject(params, []), + body: body + }; + + + return apiGatewayClient.makeRequest(oTPValidateOptionsRequest, authType, additionalParams, config.apiKey); + }; + + + apigClient.visitorCheckPost = function (params, body, additionalParams) { + if(additionalParams === undefined) { additionalParams = {}; } + + apiGateway.core.utils.assertParametersDefined(params, ['body'], ['body']); + + var visitorCheckPostRequest = { + verb: 'post'.toUpperCase(), + path: pathComponent + uritemplate('/visitorCheck').expand(apiGateway.core.utils.parseParametersToObject(params, [])), + headers: apiGateway.core.utils.parseParametersToObject(params, []), + queryParams: apiGateway.core.utils.parseParametersToObject(params, []), + body: body + }; + + + return apiGatewayClient.makeRequest(visitorCheckPostRequest, authType, additionalParams, config.apiKey); + }; + + + apigClient.visitorCheckOptions = function (params, body, additionalParams) { + if(additionalParams === undefined) { additionalParams = {}; } + + apiGateway.core.utils.assertParametersDefined(params, [], ['body']); + + var visitorCheckOptionsRequest = { + verb: 'options'.toUpperCase(), + path: pathComponent + uritemplate('/visitorCheck').expand(apiGateway.core.utils.parseParametersToObject(params, [])), + headers: apiGateway.core.utils.parseParametersToObject(params, []), + queryParams: apiGateway.core.utils.parseParametersToObject(params, []), + body: body + }; + + + return apiGatewayClient.makeRequest(visitorCheckOptionsRequest, authType, additionalParams, config.apiKey); + }; + + + return apigClient; +}; diff --git a/js/lib/CryptoJS/components/enc-base64.js b/js/lib/CryptoJS/components/enc-base64.js new file mode 100644 index 0000000..739f4a8 --- /dev/null +++ b/js/lib/CryptoJS/components/enc-base64.js @@ -0,0 +1,109 @@ +/* +CryptoJS v3.1.2 +code.google.com/p/crypto-js +(c) 2009-2013 by Jeff Mott. All rights reserved. +code.google.com/p/crypto-js/wiki/License +*/ +(function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_enc = C.enc; + + /** + * Base64 encoding strategy. + */ + var Base64 = C_enc.Base64 = { + /** + * Converts a word array to a Base64 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The Base64 string. + * + * @static + * + * @example + * + * var base64String = CryptoJS.enc.Base64.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + var map = this._map; + + // Clamp excess bits + wordArray.clamp(); + + // Convert + var base64Chars = []; + for (var i = 0; i < sigBytes; i += 3) { + var byte1 = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff; + var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff; + + var triplet = (byte1 << 16) | (byte2 << 8) | byte3; + + for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) { + base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f)); + } + } + + // Add padding + var paddingChar = map.charAt(64); + if (paddingChar) { + while (base64Chars.length % 4) { + base64Chars.push(paddingChar); + } + } + + return base64Chars.join(''); + }, + + /** + * Converts a Base64 string to a word array. + * + * @param {string} base64Str The Base64 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Base64.parse(base64String); + */ + parse: function (base64Str) { + // Shortcuts + var base64StrLength = base64Str.length; + var map = this._map; + + // Ignore padding + var paddingChar = map.charAt(64); + if (paddingChar) { + var paddingIndex = base64Str.indexOf(paddingChar); + if (paddingIndex != -1) { + base64StrLength = paddingIndex; + } + } + + // Convert + var words = []; + var nBytes = 0; + for (var i = 0; i < base64StrLength; i++) { + if (i % 4) { + var bits1 = map.indexOf(base64Str.charAt(i - 1)) << ((i % 4) * 2); + var bits2 = map.indexOf(base64Str.charAt(i)) >>> (6 - (i % 4) * 2); + words[nBytes >>> 2] |= (bits1 | bits2) << (24 - (nBytes % 4) * 8); + nBytes++; + } + } + + return WordArray.create(words, nBytes); + }, + + _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' + }; +}()); diff --git a/js/lib/CryptoJS/components/hmac.js b/js/lib/CryptoJS/components/hmac.js new file mode 100644 index 0000000..b75cd0b --- /dev/null +++ b/js/lib/CryptoJS/components/hmac.js @@ -0,0 +1,131 @@ +/* +CryptoJS v3.1.2 +code.google.com/p/crypto-js +(c) 2009-2013 by Jeff Mott. All rights reserved. +code.google.com/p/crypto-js/wiki/License +*/ +(function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var C_enc = C.enc; + var Utf8 = C_enc.Utf8; + var C_algo = C.algo; + + /** + * HMAC algorithm. + */ + var HMAC = C_algo.HMAC = Base.extend({ + /** + * Initializes a newly created HMAC. + * + * @param {Hasher} hasher The hash algorithm to use. + * @param {WordArray|string} key The secret key. + * + * @example + * + * var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key); + */ + init: function (hasher, key) { + // Init hasher + hasher = this._hasher = new hasher.init(); + + // Convert string to WordArray, else assume WordArray already + if (typeof key == 'string') { + key = Utf8.parse(key); + } + + // Shortcuts + var hasherBlockSize = hasher.blockSize; + var hasherBlockSizeBytes = hasherBlockSize * 4; + + // Allow arbitrary length keys + if (key.sigBytes > hasherBlockSizeBytes) { + key = hasher.finalize(key); + } + + // Clamp excess bits + key.clamp(); + + // Clone key for inner and outer pads + var oKey = this._oKey = key.clone(); + var iKey = this._iKey = key.clone(); + + // Shortcuts + var oKeyWords = oKey.words; + var iKeyWords = iKey.words; + + // XOR keys with pad constants + for (var i = 0; i < hasherBlockSize; i++) { + oKeyWords[i] ^= 0x5c5c5c5c; + iKeyWords[i] ^= 0x36363636; + } + oKey.sigBytes = iKey.sigBytes = hasherBlockSizeBytes; + + // Set initial values + this.reset(); + }, + + /** + * Resets this HMAC to its initial state. + * + * @example + * + * hmacHasher.reset(); + */ + reset: function () { + // Shortcut + var hasher = this._hasher; + + // Reset + hasher.reset(); + hasher.update(this._iKey); + }, + + /** + * Updates this HMAC with a message. + * + * @param {WordArray|string} messageUpdate The message to append. + * + * @return {HMAC} This HMAC instance. + * + * @example + * + * hmacHasher.update('message'); + * hmacHasher.update(wordArray); + */ + update: function (messageUpdate) { + this._hasher.update(messageUpdate); + + // Chainable + return this; + }, + + /** + * Finalizes the HMAC computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} messageUpdate (Optional) A final message update. + * + * @return {WordArray} The HMAC. + * + * @example + * + * var hmac = hmacHasher.finalize(); + * var hmac = hmacHasher.finalize('message'); + * var hmac = hmacHasher.finalize(wordArray); + */ + finalize: function (messageUpdate) { + // Shortcut + var hasher = this._hasher; + + // Compute HMAC + var innerHash = hasher.finalize(messageUpdate); + hasher.reset(); + var hmac = hasher.finalize(this._oKey.clone().concat(innerHash)); + + return hmac; + } + }); +}()); diff --git a/js/lib/CryptoJS/rollups/hmac-sha256.js b/js/lib/CryptoJS/rollups/hmac-sha256.js new file mode 100644 index 0000000..c822cfb --- /dev/null +++ b/js/lib/CryptoJS/rollups/hmac-sha256.js @@ -0,0 +1,18 @@ +/* +CryptoJS v3.1.2 +code.google.com/p/crypto-js +(c) 2009-2013 by Jeff Mott. All rights reserved. +code.google.com/p/crypto-js/wiki/License +*/ +var CryptoJS=CryptoJS||function(h,s){var f={},g=f.lib={},q=function(){},m=g.Base={extend:function(a){q.prototype=this;var c=new q;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, +r=g.WordArray=m.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=s?c:4*a.length},toString:function(a){return(a||k).stringify(this)},concat:function(a){var c=this.words,d=a.words,b=this.sigBytes;a=a.sigBytes;this.clamp();if(b%4)for(var e=0;e>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< +32-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=m.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>3]|=parseInt(a.substr(b, +2),16)<<24-4*(b%8);return new r.init(d,c/2)}},n=l.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new r.init(d,c)}},j=l.Utf8={stringify:function(a){try{return decodeURIComponent(escape(n.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return n.parse(unescape(encodeURIComponent(a)))}}, +u=g.BufferedBlockAlgorithm=m.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=j.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var g=0;gn;){var j;a:{j=k;for(var u=h.sqrt(j),t=2;t<=u;t++)if(!(j%t)){j=!1;break a}j=!0}j&&(8>n&&(m[n]=l(h.pow(k,0.5))),r[n]=l(h.pow(k,1/3)),n++);k++}var a=[],f=f.SHA256=q.extend({_doReset:function(){this._hash=new g.init(m.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],g=b[2],j=b[3],h=b[4],m=b[5],n=b[6],q=b[7],p=0;64>p;p++){if(16>p)a[p]= +c[d+p]|0;else{var k=a[p-15],l=a[p-2];a[p]=((k<<25|k>>>7)^(k<<14|k>>>18)^k>>>3)+a[p-7]+((l<<15|l>>>17)^(l<<13|l>>>19)^l>>>10)+a[p-16]}k=q+((h<<26|h>>>6)^(h<<21|h>>>11)^(h<<7|h>>>25))+(h&m^~h&n)+r[p]+a[p];l=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&g^f&g);q=n;n=m;m=h;h=j+k|0;j=g;g=f;f=e;e=k+l|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+g|0;b[3]=b[3]+j|0;b[4]=b[4]+h|0;b[5]=b[5]+m|0;b[6]=b[6]+n|0;b[7]=b[7]+q|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes; +d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=q.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=q._createHelper(f);s.HmacSHA256=q._createHmacHelper(f)})(Math); +(function(){var h=CryptoJS,s=h.enc.Utf8;h.algo.HMAC=h.lib.Base.extend({init:function(f,g){f=this._hasher=new f.init;"string"==typeof g&&(g=s.parse(g));var h=f.blockSize,m=4*h;g.sigBytes>m&&(g=f.finalize(g));g.clamp();for(var r=this._oKey=g.clone(),l=this._iKey=g.clone(),k=r.words,n=l.words,j=0;j>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< +32-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=j.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>3]|=parseInt(a.substr(b, +2),16)<<24-4*(b%8);return new q.init(d,c/2)}},k=v.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new q.init(d,c)}},l=v.Utf8={stringify:function(a){try{return decodeURIComponent(escape(k.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return k.parse(unescape(encodeURIComponent(a)))}}, +x=t.BufferedBlockAlgorithm=j.extend({reset:function(){this._data=new q.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=l.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var m=0;mk;){var l;a:{l=u;for(var x=h.sqrt(l),w=2;w<=x;w++)if(!(l%w)){l=!1;break a}l=!0}l&&(8>k&&(j[k]=v(h.pow(u,0.5))),q[k]=v(h.pow(u,1/3)),k++);u++}var a=[],f=f.SHA256=g.extend({_doReset:function(){this._hash=new t.init(j.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],m=b[2],h=b[3],p=b[4],j=b[5],k=b[6],l=b[7],n=0;64>n;n++){if(16>n)a[n]= +c[d+n]|0;else{var r=a[n-15],g=a[n-2];a[n]=((r<<25|r>>>7)^(r<<14|r>>>18)^r>>>3)+a[n-7]+((g<<15|g>>>17)^(g<<13|g>>>19)^g>>>10)+a[n-16]}r=l+((p<<26|p>>>6)^(p<<21|p>>>11)^(p<<7|p>>>25))+(p&j^~p&k)+q[n]+a[n];g=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&m^f&m);l=k;k=j;j=p;p=h+r|0;h=m;m=f;f=e;e=r+g|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+m|0;b[3]=b[3]+h|0;b[4]=b[4]+p|0;b[5]=b[5]+j|0;b[6]=b[6]+k|0;b[7]=b[7]+l|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes; +d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=g.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=g._createHelper(f);s.HmacSHA256=g._createHmacHelper(f)})(Math); diff --git a/js/lib/apiGatewayCore/apiGatewayClient.js b/js/lib/apiGatewayCore/apiGatewayClient.js new file mode 100644 index 0000000..f138588 --- /dev/null +++ b/js/lib/apiGatewayCore/apiGatewayClient.js @@ -0,0 +1,53 @@ +/* + * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +var apiGateway = apiGateway || {}; +apiGateway.core = apiGateway.core || {}; + +apiGateway.core.apiGatewayClientFactory = {}; +apiGateway.core.apiGatewayClientFactory.newClient = function (simpleHttpClientConfig, sigV4ClientConfig) { + var apiGatewayClient = { }; + //Spin up 2 httpClients, one for simple requests, one for SigV4 + var sigV4Client = apiGateway.core.sigV4ClientFactory.newClient(sigV4ClientConfig); + var simpleHttpClient = apiGateway.core.simpleHttpClientFactory.newClient(simpleHttpClientConfig); + + apiGatewayClient.makeRequest = function (request, authType, additionalParams, apiKey) { + //Default the request to use the simple http client + var clientToUse = simpleHttpClient; + + //Attach the apiKey to the headers request if one was provided + if (apiKey !== undefined && apiKey !== '' && apiKey !== null) { + request.headers['x-api-key'] = apiKey; + } + + if (request.body === undefined || request.body === '' || request.body === null || Object.keys(request.body).length === 0) { + request.body = undefined; + } + + // If the user specified any additional headers or query params that may not have been modeled + // merge them into the appropriate request properties + request.headers = apiGateway.core.utils.mergeInto(request.headers, additionalParams.headers); + request.queryParams = apiGateway.core.utils.mergeInto(request.queryParams, additionalParams.queryParams); + + //If an auth type was specified inject the appropriate auth client + if (authType === 'AWS_IAM') { + clientToUse = sigV4Client; + } + + //Call the selected http client to make the request, returning a promise once the request is sent + return clientToUse.makeRequest(request); + }; + return apiGatewayClient; +}; diff --git a/js/lib/apiGatewayCore/sigV4Client.js b/js/lib/apiGatewayCore/sigV4Client.js new file mode 100644 index 0000000..71cc855 --- /dev/null +++ b/js/lib/apiGatewayCore/sigV4Client.js @@ -0,0 +1,219 @@ +/* + * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +var apiGateway = apiGateway || {}; +apiGateway.core = apiGateway.core || {}; + +apiGateway.core.sigV4ClientFactory = {}; +apiGateway.core.sigV4ClientFactory.newClient = function (config) { + var AWS_SHA_256 = 'AWS4-HMAC-SHA256'; + var AWS4_REQUEST = 'aws4_request'; + var AWS4 = 'AWS4'; + var X_AMZ_DATE = 'x-amz-date'; + var X_AMZ_SECURITY_TOKEN = 'x-amz-security-token'; + var HOST = 'host'; + var AUTHORIZATION = 'Authorization'; + + function hash(value) { + return CryptoJS.SHA256(value); + } + + function hexEncode(value) { + return value.toString(CryptoJS.enc.Hex); + } + + function hmac(secret, value) { + return CryptoJS.HmacSHA256(value, secret, {asBytes: true}); + } + + function buildCanonicalRequest(method, path, queryParams, headers, payload) { + return method + '\n' + + buildCanonicalUri(path) + '\n' + + buildCanonicalQueryString(queryParams) + '\n' + + buildCanonicalHeaders(headers) + '\n' + + buildCanonicalSignedHeaders(headers) + '\n' + + hexEncode(hash(payload)); + } + + function hashCanonicalRequest(request) { + return hexEncode(hash(request)); + } + + function buildCanonicalUri(uri) { + return encodeURI(uri); + } + + function buildCanonicalQueryString(queryParams) { + if (Object.keys(queryParams).length < 1) { + return ''; + } + + var sortedQueryParams = []; + for (var property in queryParams) { + if (queryParams.hasOwnProperty(property)) { + sortedQueryParams.push(property); + } + } + sortedQueryParams.sort(); + + var canonicalQueryString = ''; + for (var i = 0; i < sortedQueryParams.length; i++) { + canonicalQueryString += sortedQueryParams[i] + '=' + fixedEncodeURIComponent(queryParams[sortedQueryParams[i]]) + '&'; + } + return canonicalQueryString.substr(0, canonicalQueryString.length - 1); + } + + function fixedEncodeURIComponent (str) { + return encodeURIComponent(str).replace(/[!'()*]/g, function(c) { + return '%' + c.charCodeAt(0).toString(16).toUpperCase(); + }); + } + + function buildCanonicalHeaders(headers) { + var canonicalHeaders = ''; + var sortedKeys = []; + for (var property in headers) { + if (headers.hasOwnProperty(property)) { + sortedKeys.push(property); + } + } + sortedKeys.sort(); + + for (var i = 0; i < sortedKeys.length; i++) { + canonicalHeaders += sortedKeys[i].toLowerCase() + ':' + headers[sortedKeys[i]] + '\n'; + } + return canonicalHeaders; + } + + function buildCanonicalSignedHeaders(headers) { + var sortedKeys = []; + for (var property in headers) { + if (headers.hasOwnProperty(property)) { + sortedKeys.push(property.toLowerCase()); + } + } + sortedKeys.sort(); + + return sortedKeys.join(';'); + } + + function buildStringToSign(datetime, credentialScope, hashedCanonicalRequest) { + return AWS_SHA_256 + '\n' + + datetime + '\n' + + credentialScope + '\n' + + hashedCanonicalRequest; + } + + function buildCredentialScope(datetime, region, service) { + return datetime.substr(0, 8) + '/' + region + '/' + service + '/' + AWS4_REQUEST + } + + function calculateSigningKey(secretKey, datetime, region, service) { + return hmac(hmac(hmac(hmac(AWS4 + secretKey, datetime.substr(0, 8)), region), service), AWS4_REQUEST); + } + + function calculateSignature(key, stringToSign) { + return hexEncode(hmac(key, stringToSign)); + } + + function buildAuthorizationHeader(accessKey, credentialScope, headers, signature) { + return AWS_SHA_256 + ' Credential=' + accessKey + '/' + credentialScope + ', SignedHeaders=' + buildCanonicalSignedHeaders(headers) + ', Signature=' + signature; + } + + var awsSigV4Client = { }; + if(config.accessKey === undefined || config.secretKey === undefined) { + return awsSigV4Client; + } + awsSigV4Client.accessKey = apiGateway.core.utils.assertDefined(config.accessKey, 'accessKey'); + awsSigV4Client.secretKey = apiGateway.core.utils.assertDefined(config.secretKey, 'secretKey'); + awsSigV4Client.sessionToken = config.sessionToken; + awsSigV4Client.serviceName = apiGateway.core.utils.assertDefined(config.serviceName, 'serviceName'); + awsSigV4Client.region = apiGateway.core.utils.assertDefined(config.region, 'region'); + awsSigV4Client.endpoint = apiGateway.core.utils.assertDefined(config.endpoint, 'endpoint'); + + awsSigV4Client.makeRequest = function (request) { + var verb = apiGateway.core.utils.assertDefined(request.verb, 'verb'); + var path = apiGateway.core.utils.assertDefined(request.path, 'path'); + var queryParams = apiGateway.core.utils.copy(request.queryParams); + if (queryParams === undefined) { + queryParams = {}; + } + var headers = apiGateway.core.utils.copy(request.headers); + if (headers === undefined) { + headers = {}; + } + + //If the user has not specified an override for Content type the use default + if(headers['Content-Type'] === undefined) { + headers['Content-Type'] = config.defaultContentType; + } + + //If the user has not specified an override for Accept type the use default + if(headers['Accept'] === undefined) { + headers['Accept'] = config.defaultAcceptType; + } + + var body = apiGateway.core.utils.copy(request.body); + if (body === undefined || verb === 'GET') { // override request body and set to empty when signing GET requests + body = ''; + } else { + body = JSON.stringify(body); + } + + //If there is no body remove the content-type header so it is not included in SigV4 calculation + if(body === '' || body === undefined || body === null) { + delete headers['Content-Type']; + } + + var datetime = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z').replace(/[:\-]|\.\d{3}/g, ''); + headers[X_AMZ_DATE] = datetime; + var parser = document.createElement('a'); + parser.href = awsSigV4Client.endpoint; + headers[HOST] = parser.hostname; + + var canonicalRequest = buildCanonicalRequest(verb, path, queryParams, headers, body); + var hashedCanonicalRequest = hashCanonicalRequest(canonicalRequest); + var credentialScope = buildCredentialScope(datetime, awsSigV4Client.region, awsSigV4Client.serviceName); + var stringToSign = buildStringToSign(datetime, credentialScope, hashedCanonicalRequest); + var signingKey = calculateSigningKey(awsSigV4Client.secretKey, datetime, awsSigV4Client.region, awsSigV4Client.serviceName); + var signature = calculateSignature(signingKey, stringToSign); + headers[AUTHORIZATION] = buildAuthorizationHeader(awsSigV4Client.accessKey, credentialScope, headers, signature); + if(awsSigV4Client.sessionToken !== undefined && awsSigV4Client.sessionToken !== '') { + headers[X_AMZ_SECURITY_TOKEN] = awsSigV4Client.sessionToken; + } + delete headers[HOST]; + + var url = config.endpoint + path; + var queryString = buildCanonicalQueryString(queryParams); + if (queryString != '') { + url += '?' + queryString; + } + + //Need to re-attach Content-Type if it is not specified at this point + if(headers['Content-Type'] === undefined) { + headers['Content-Type'] = config.defaultContentType; + } + + var signedRequest = { + method: verb, + url: url, + headers: headers, + data: body + }; + return axios(signedRequest); + }; + + return awsSigV4Client; +}; diff --git a/js/lib/apiGatewayCore/simpleHttpClient.js b/js/lib/apiGatewayCore/simpleHttpClient.js new file mode 100644 index 0000000..3fb1e5a --- /dev/null +++ b/js/lib/apiGatewayCore/simpleHttpClient.js @@ -0,0 +1,81 @@ +/* + * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +var apiGateway = apiGateway || {}; +apiGateway.core = apiGateway.core || {}; + +apiGateway.core.simpleHttpClientFactory = {}; +apiGateway.core.simpleHttpClientFactory.newClient = function (config) { + function buildCanonicalQueryString(queryParams) { + //Build a properly encoded query string from a QueryParam object + if (Object.keys(queryParams).length < 1) { + return ''; + } + + var canonicalQueryString = ''; + for (var property in queryParams) { + if (queryParams.hasOwnProperty(property)) { + canonicalQueryString += encodeURIComponent(property) + '=' + encodeURIComponent(queryParams[property]) + '&'; + } + } + + return canonicalQueryString.substr(0, canonicalQueryString.length - 1); + } + + var simpleHttpClient = { }; + simpleHttpClient.endpoint = apiGateway.core.utils.assertDefined(config.endpoint, 'endpoint'); + + simpleHttpClient.makeRequest = function (request) { + var verb = apiGateway.core.utils.assertDefined(request.verb, 'verb'); + var path = apiGateway.core.utils.assertDefined(request.path, 'path'); + var queryParams = apiGateway.core.utils.copy(request.queryParams); + if (queryParams === undefined) { + queryParams = {}; + } + var headers = apiGateway.core.utils.copy(request.headers); + if (headers === undefined) { + headers = {}; + } + + //If the user has not specified an override for Content type the use default + if(headers['Content-Type'] === undefined) { + headers['Content-Type'] = config.defaultContentType; + } + + //If the user has not specified an override for Accept type the use default + if(headers['Accept'] === undefined) { + headers['Accept'] = config.defaultAcceptType; + } + + var body = apiGateway.core.utils.copy(request.body); + if (body === undefined) { + body = ''; + } + + var url = config.endpoint + path; + var queryString = buildCanonicalQueryString(queryParams); + if (queryString != '') { + url += '?' + queryString; + } + var simpleHttpRequest = { + method: verb, + url: url, + headers: headers, + data: body + }; + return axios(simpleHttpRequest); + }; + return simpleHttpClient; +}; \ No newline at end of file diff --git a/js/lib/apiGatewayCore/utils.js b/js/lib/apiGatewayCore/utils.js new file mode 100644 index 0000000..9bf7356 --- /dev/null +++ b/js/lib/apiGatewayCore/utils.js @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +var apiGateway = apiGateway || {}; +apiGateway.core = apiGateway.core || {}; + +apiGateway.core.utils = { + assertDefined: function (object, name) { + if (object === undefined) { + throw name + ' must be defined'; + } else { + return object; + } + }, + assertParametersDefined: function (params, keys, ignore) { + if (keys === undefined) { + return; + } + if (keys.length > 0 && params === undefined) { + params = {}; + } + for (var i = 0; i < keys.length; i++) { + if(!apiGateway.core.utils.contains(ignore, keys[i])) { + apiGateway.core.utils.assertDefined(params[keys[i]], keys[i]); + } + } + }, + parseParametersToObject: function (params, keys) { + if (params === undefined) { + return {}; + } + var object = { }; + for (var i = 0; i < keys.length; i++) { + object[keys[i]] = params[keys[i]]; + } + return object; + }, + contains: function(a, obj) { + if(a === undefined) { return false;} + var i = a.length; + while (i--) { + if (a[i] === obj) { + return true; + } + } + return false; + }, + copy: function (obj) { + if (null == obj || "object" != typeof obj) return obj; + var copy = obj.constructor(); + for (var attr in obj) { + if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr]; + } + return copy; + }, + mergeInto: function (baseObj, additionalProps) { + if (null == baseObj || "object" != typeof baseObj) return baseObj; + var merged = baseObj.constructor(); + for (var attr in baseObj) { + if (baseObj.hasOwnProperty(attr)) merged[attr] = baseObj[attr]; + } + if (null == additionalProps || "object" != typeof additionalProps) return baseObj; + for (attr in additionalProps) { + if (additionalProps.hasOwnProperty(attr)) merged[attr] = additionalProps[attr]; + } + return merged; + } +}; diff --git a/js/lib/axios/dist/axios.standalone.js b/js/lib/axios/dist/axios.standalone.js new file mode 100644 index 0000000..1916525 --- /dev/null +++ b/js/lib/axios/dist/axios.standalone.js @@ -0,0 +1,1089 @@ +/* +axios v0.7.0 +Copyright (c) 2014 Matt Zabriskie + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["axios"] = factory(); + else + root["axios"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; +/******/ +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.loaded = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + + module.exports = __webpack_require__(1); + +/***/ }, +/* 1 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var defaults = __webpack_require__(2); + var utils = __webpack_require__(3); + var dispatchRequest = __webpack_require__(4); + var InterceptorManager = __webpack_require__(12); + + var axios = module.exports = function (config) { + // Allow for axios('example/url'[, config]) a la fetch API + if (typeof config === 'string') { + config = utils.merge({ + url: arguments[0] + }, arguments[1]); + } + + config = utils.merge({ + method: 'get', + headers: {}, + timeout: defaults.timeout, + transformRequest: defaults.transformRequest, + transformResponse: defaults.transformResponse + }, config); + + // Don't allow overriding defaults.withCredentials + config.withCredentials = config.withCredentials || defaults.withCredentials; + + // Hook up interceptors middleware + var chain = [dispatchRequest, undefined]; + var promise = Promise.resolve(config); + + axios.interceptors.request.forEach(function (interceptor) { + chain.unshift(interceptor.fulfilled, interceptor.rejected); + }); + + axios.interceptors.response.forEach(function (interceptor) { + chain.push(interceptor.fulfilled, interceptor.rejected); + }); + + while (chain.length) { + promise = promise.then(chain.shift(), chain.shift()); + } + + return promise; + }; + + // Expose defaults + axios.defaults = defaults; + + // Expose all/spread + axios.all = function (promises) { + return Promise.all(promises); + }; + axios.spread = __webpack_require__(13); + + // Expose interceptors + axios.interceptors = { + request: new InterceptorManager(), + response: new InterceptorManager() + }; + + // Provide aliases for supported request methods + (function () { + function createShortMethods() { + utils.forEach(arguments, function (method) { + axios[method] = function (url, config) { + return axios(utils.merge(config || {}, { + method: method, + url: url + })); + }; + }); + } + + function createShortMethodsWithData() { + utils.forEach(arguments, function (method) { + axios[method] = function (url, data, config) { + return axios(utils.merge(config || {}, { + method: method, + url: url, + data: data + })); + }; + }); + } + + createShortMethods('delete', 'get', 'head'); + createShortMethodsWithData('post', 'put', 'patch'); + })(); + + +/***/ }, +/* 2 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var utils = __webpack_require__(3); + + var PROTECTION_PREFIX = /^\)\]\}',?\n/; + var DEFAULT_CONTENT_TYPE = { + 'Content-Type': 'application/json' + }; + + module.exports = { + transformRequest: [function (data, headers) { + if(utils.isFormData(data)) { + return data; + } + if (utils.isArrayBuffer(data)) { + return data; + } + if (utils.isArrayBufferView(data)) { + return data.buffer; + } + if (utils.isObject(data) && !utils.isFile(data) && !utils.isBlob(data)) { + // Set application/json if no Content-Type has been specified + if (!utils.isUndefined(headers)) { + utils.forEach(headers, function (val, key) { + if (key.toLowerCase() === 'content-type') { + headers['Content-Type'] = val; + } + }); + + if (utils.isUndefined(headers['Content-Type'])) { + headers['Content-Type'] = 'application/json;charset=utf-8'; + } + } + return JSON.stringify(data); + } + return data; + }], + + transformResponse: [function (data) { + if (typeof data === 'string') { + data = data.replace(PROTECTION_PREFIX, ''); + try { + data = JSON.parse(data); + } catch (e) { /* Ignore */ } + } + return data; + }], + + headers: { + common: { + 'Accept': 'application/json, text/plain, */*' + }, + patch: utils.merge(DEFAULT_CONTENT_TYPE), + post: utils.merge(DEFAULT_CONTENT_TYPE), + put: utils.merge(DEFAULT_CONTENT_TYPE) + }, + + timeout: 0, + + xsrfCookieName: 'XSRF-TOKEN', + xsrfHeaderName: 'X-XSRF-TOKEN' + }; + + +/***/ }, +/* 3 */ +/***/ function(module, exports) { + + 'use strict'; + + /*global toString:true*/ + + // utils is a library of generic helper functions non-specific to axios + + var toString = Object.prototype.toString; + + /** + * Determine if a value is an Array + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an Array, otherwise false + */ + function isArray(val) { + return toString.call(val) === '[object Array]'; + } + + /** + * Determine if a value is an ArrayBuffer + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an ArrayBuffer, otherwise false + */ + function isArrayBuffer(val) { + return toString.call(val) === '[object ArrayBuffer]'; + } + + /** + * Determine if a value is a FormData + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an FormData, otherwise false + */ + function isFormData(val) { + return toString.call(val) === '[object FormData]'; + } + + /** + * Determine if a value is a view on an ArrayBuffer + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a view on an ArrayBuffer, otherwise false + */ + function isArrayBufferView(val) { + if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) { + return ArrayBuffer.isView(val); + } else { + return (val) && (val.buffer) && (val.buffer instanceof ArrayBuffer); + } + } + + /** + * Determine if a value is a String + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a String, otherwise false + */ + function isString(val) { + return typeof val === 'string'; + } + + /** + * Determine if a value is a Number + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Number, otherwise false + */ + function isNumber(val) { + return typeof val === 'number'; + } + + /** + * Determine if a value is undefined + * + * @param {Object} val The value to test + * @returns {boolean} True if the value is undefined, otherwise false + */ + function isUndefined(val) { + return typeof val === 'undefined'; + } + + /** + * Determine if a value is an Object + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an Object, otherwise false + */ + function isObject(val) { + return val !== null && typeof val === 'object'; + } + + /** + * Determine if a value is a Date + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Date, otherwise false + */ + function isDate(val) { + return toString.call(val) === '[object Date]'; + } + + /** + * Determine if a value is a File + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a File, otherwise false + */ + function isFile(val) { + return toString.call(val) === '[object File]'; + } + + /** + * Determine if a value is a Blob + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Blob, otherwise false + */ + function isBlob(val) { + return toString.call(val) === '[object Blob]'; + } + + /** + * Trim excess whitespace off the beginning and end of a string + * + * @param {String} str The String to trim + * @returns {String} The String freed of excess whitespace + */ + function trim(str) { + return str.replace(/^\s*/, '').replace(/\s*$/, ''); + } + + /** + * Determine if a value is an Arguments object + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an Arguments object, otherwise false + */ + function isArguments(val) { + return toString.call(val) === '[object Arguments]'; + } + + /** + * Determine if we're running in a standard browser environment + * + * This allows axios to run in a web worker, and react-native. + * Both environments support XMLHttpRequest, but not fully standard globals. + * + * web workers: + * typeof window -> undefined + * typeof document -> undefined + * + * react-native: + * typeof document.createelement -> undefined + */ + function isStandardBrowserEnv() { + return ( + typeof window !== 'undefined' && + typeof document !== 'undefined' && + typeof document.createElement === 'function' + ); + } + + /** + * Iterate over an Array or an Object invoking a function for each item. + * + * If `obj` is an Array or arguments callback will be called passing + * the value, index, and complete array for each item. + * + * If 'obj' is an Object callback will be called passing + * the value, key, and complete object for each property. + * + * @param {Object|Array} obj The object to iterate + * @param {Function} fn The callback to invoke for each item + */ + function forEach(obj, fn) { + // Don't bother if no value provided + if (obj === null || typeof obj === 'undefined') { + return; + } + + // Check if obj is array-like + var isArrayLike = isArray(obj) || isArguments(obj); + + // Force an array if not already something iterable + if (typeof obj !== 'object' && !isArrayLike) { + obj = [obj]; + } + + // Iterate over array values + if (isArrayLike) { + for (var i = 0, l = obj.length; i < l; i++) { + fn.call(null, obj[i], i, obj); + } + } + // Iterate over object keys + else { + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + fn.call(null, obj[key], key, obj); + } + } + } + } + + /** + * Accepts varargs expecting each argument to be an object, then + * immutably merges the properties of each object and returns result. + * + * When multiple objects contain the same key the later object in + * the arguments list will take precedence. + * + * Example: + * + * ```js + * var result = merge({foo: 123}, {foo: 456}); + * console.log(result.foo); // outputs 456 + * ``` + * + * @param {Object} obj1 Object to merge + * @returns {Object} Result of all merge properties + */ + function merge(/*obj1, obj2, obj3, ...*/) { + var result = {}; + forEach(arguments, function (obj) { + forEach(obj, function (val, key) { + result[key] = val; + }); + }); + return result; + } + + module.exports = { + isArray: isArray, + isArrayBuffer: isArrayBuffer, + isFormData: isFormData, + isArrayBufferView: isArrayBufferView, + isString: isString, + isNumber: isNumber, + isObject: isObject, + isUndefined: isUndefined, + isDate: isDate, + isFile: isFile, + isBlob: isBlob, + isStandardBrowserEnv: isStandardBrowserEnv, + forEach: forEach, + merge: merge, + trim: trim + }; + + +/***/ }, +/* 4 */ +/***/ function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(process) {'use strict'; + + /** + * Dispatch a request to the server using whichever adapter + * is supported by the current environment. + * + * @param {object} config The config that is to be used for the request + * @returns {Promise} The Promise to be fulfilled + */ + module.exports = function dispatchRequest(config) { + return new Promise(function (resolve, reject) { + try { + // For browsers use XHR adapter + if ((typeof XMLHttpRequest !== 'undefined') || (typeof ActiveXObject !== 'undefined')) { + __webpack_require__(6)(resolve, reject, config); + } + // For node use HTTP adapter + else if (typeof process !== 'undefined') { + __webpack_require__(6)(resolve, reject, config); + } + } catch (e) { + reject(e); + } + }); + }; + + + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) + +/***/ }, +/* 5 */ +/***/ function(module, exports) { + + // shim for using process in browser + + var process = module.exports = {}; + var queue = []; + var draining = false; + var currentQueue; + var queueIndex = -1; + + function cleanUpNextTick() { + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } + } + + function drainQueue() { + if (draining) { + return; + } + var timeout = setTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + clearTimeout(timeout); + } + + process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + setTimeout(drainQueue, 0); + } + }; + + // v8 likes predictible objects + function Item(fun, array) { + this.fun = fun; + this.array = array; + } + Item.prototype.run = function () { + this.fun.apply(null, this.array); + }; + process.title = 'browser'; + process.browser = true; + process.env = {}; + process.argv = []; + process.version = ''; // empty string to avoid regexp issues + process.versions = {}; + + function noop() {} + + process.on = noop; + process.addListener = noop; + process.once = noop; + process.off = noop; + process.removeListener = noop; + process.removeAllListeners = noop; + process.emit = noop; + + process.binding = function (name) { + throw new Error('process.binding is not supported'); + }; + + process.cwd = function () { return '/' }; + process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); + }; + process.umask = function() { return 0; }; + + +/***/ }, +/* 6 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + /*global ActiveXObject:true*/ + + var defaults = __webpack_require__(2); + var utils = __webpack_require__(3); + var buildUrl = __webpack_require__(7); + var parseHeaders = __webpack_require__(8); + var transformData = __webpack_require__(9); + + module.exports = function xhrAdapter(resolve, reject, config) { + // Transform request data + var data = transformData( + config.data, + config.headers, + config.transformRequest + ); + + // Merge headers + var requestHeaders = utils.merge( + defaults.headers.common, + defaults.headers[config.method] || {}, + config.headers || {} + ); + + if (utils.isFormData(data)) { + // Content-Type needs to be sent in all requests so the mapping template can be applied + //delete requestHeaders['Content-Type']; // Let the browser set it + } + + // Create the request + var request = new (XMLHttpRequest || ActiveXObject)('Microsoft.XMLHTTP'); + request.open(config.method.toUpperCase(), buildUrl(config.url, config.params), true); + + // Set the request timeout in MS + request.timeout = config.timeout; + + // Listen for ready state + request.onreadystatechange = function () { + if (request && request.readyState === 4) { + // Prepare the response + var responseHeaders = parseHeaders(request.getAllResponseHeaders()); + var responseData = ['text', ''].indexOf(config.responseType || '') !== -1 ? request.responseText : request.response; + var response = { + data: transformData( + responseData, + responseHeaders, + config.transformResponse + ), + status: request.status, + statusText: request.statusText, + headers: responseHeaders, + config: config + }; + + // Resolve or reject the Promise based on the status + (request.status >= 200 && request.status < 300 ? + resolve : + reject)(response); + + // Clean up request + request = null; + } + }; + + // Add xsrf header + // This is only done if running in a standard browser environment. + // Specifically not if we're in a web worker, or react-native. + if (utils.isStandardBrowserEnv()) { + var cookies = __webpack_require__(10); + var urlIsSameOrigin = __webpack_require__(11); + + // Add xsrf header + var xsrfValue = urlIsSameOrigin(config.url) ? + cookies.read(config.xsrfCookieName || defaults.xsrfCookieName) : + undefined; + + if (xsrfValue) { + requestHeaders[config.xsrfHeaderName || defaults.xsrfHeaderName] = xsrfValue; + } + } + + // Add headers to the request + utils.forEach(requestHeaders, function (val, key) { + // Remove Content-Type if data is undefined + if (!data && key.toLowerCase() === 'content-type') { + delete requestHeaders[key]; + } + // Otherwise add header to the request + else { + request.setRequestHeader(key, val); + } + }); + + // Add withCredentials to request if needed + if (config.withCredentials) { + request.withCredentials = true; + } + + // Add responseType to request if needed + if (config.responseType) { + try { + request.responseType = config.responseType; + } catch (e) { + if (request.responseType !== 'json') { + throw e; + } + } + } + + if (utils.isArrayBuffer(data)) { + data = new DataView(data); + } + + // Send the request + request.send(data); + }; + + +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var utils = __webpack_require__(3); + + function encode(val) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, '+'). + replace(/%5B/gi, '['). + replace(/%5D/gi, ']'); + } + + /** + * Build a URL by appending params to the end + * + * @param {string} url The base of the url (e.g., http://www.google.com) + * @param {object} [params] The params to be appended + * @returns {string} The formatted url + */ + module.exports = function buildUrl(url, params) { + if (!params) { + return url; + } + + var parts = []; + + utils.forEach(params, function (val, key) { + if (val === null || typeof val === 'undefined') { + return; + } + + if (utils.isArray(val)) { + key = key + '[]'; + } + + if (!utils.isArray(val)) { + val = [val]; + } + + utils.forEach(val, function (v) { + if (utils.isDate(v)) { + v = v.toISOString(); + } + else if (utils.isObject(v)) { + v = JSON.stringify(v); + } + parts.push(encode(key) + '=' + encode(v)); + }); + }); + + if (parts.length > 0) { + url += (url.indexOf('?') === -1 ? '?' : '&') + parts.join('&'); + } + + return url; + }; + + +/***/ }, +/* 8 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var utils = __webpack_require__(3); + + /** + * Parse headers into an object + * + * ``` + * Date: Wed, 27 Aug 2014 08:58:49 GMT + * Content-Type: application/json + * Connection: keep-alive + * Transfer-Encoding: chunked + * ``` + * + * @param {String} headers Headers needing to be parsed + * @returns {Object} Headers parsed into an object + */ + module.exports = function parseHeaders(headers) { + var parsed = {}, key, val, i; + + if (!headers) { return parsed; } + + utils.forEach(headers.split('\n'), function(line) { + i = line.indexOf(':'); + key = utils.trim(line.substr(0, i)).toLowerCase(); + val = utils.trim(line.substr(i + 1)); + + if (key) { + parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; + } + }); + + return parsed; + }; + + +/***/ }, +/* 9 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var utils = __webpack_require__(3); + + /** + * Transform the data for a request or a response + * + * @param {Object|String} data The data to be transformed + * @param {Array} headers The headers for the request or response + * @param {Array|Function} fns A single function or Array of functions + * @returns {*} The resulting transformed data + */ + module.exports = function transformData(data, headers, fns) { + utils.forEach(fns, function (fn) { + data = fn(data, headers); + }); + + return data; + }; + + +/***/ }, +/* 10 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + /** + * WARNING: + * This file makes references to objects that aren't safe in all environments. + * Please see lib/utils.isStandardBrowserEnv before including this file. + */ + + var utils = __webpack_require__(3); + + module.exports = { + write: function write(name, value, expires, path, domain, secure) { + var cookie = []; + cookie.push(name + '=' + encodeURIComponent(value)); + + if (utils.isNumber(expires)) { + cookie.push('expires=' + new Date(expires).toGMTString()); + } + + if (utils.isString(path)) { + cookie.push('path=' + path); + } + + if (utils.isString(domain)) { + cookie.push('domain=' + domain); + } + + if (secure === true) { + cookie.push('secure'); + } + + document.cookie = cookie.join('; '); + }, + + read: function read(name) { + var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)')); + return (match ? decodeURIComponent(match[3]) : null); + }, + + remove: function remove(name) { + this.write(name, '', Date.now() - 86400000); + } + }; + + +/***/ }, +/* 11 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + /** + * WARNING: + * This file makes references to objects that aren't safe in all environments. + * Please see lib/utils.isStandardBrowserEnv before including this file. + */ + + var utils = __webpack_require__(3); + var msie = /(msie|trident)/i.test(navigator.userAgent); + var urlParsingNode = document.createElement('a'); + var originUrl; + + /** + * Parse a URL to discover it's components + * + * @param {String} url The URL to be parsed + * @returns {Object} + */ + function urlResolve(url) { + var href = url; + + if (msie) { + // IE needs attribute set twice to normalize properties + urlParsingNode.setAttribute('href', href); + href = urlParsingNode.href; + } + + urlParsingNode.setAttribute('href', href); + + // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils + return { + href: urlParsingNode.href, + protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', + host: urlParsingNode.host, + search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', + hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', + hostname: urlParsingNode.hostname, + port: urlParsingNode.port, + pathname: (urlParsingNode.pathname.charAt(0) === '/') ? + urlParsingNode.pathname : + '/' + urlParsingNode.pathname + }; + } + + originUrl = urlResolve(window.location.href); + + /** + * Determine if a URL shares the same origin as the current location + * + * @param {String} requestUrl The URL to test + * @returns {boolean} True if URL shares the same origin, otherwise false + */ + module.exports = function urlIsSameOrigin(requestUrl) { + var parsed = (utils.isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; + return (parsed.protocol === originUrl.protocol && + parsed.host === originUrl.host); + }; + + +/***/ }, +/* 12 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var utils = __webpack_require__(3); + + function InterceptorManager() { + this.handlers = []; + } + + /** + * Add a new interceptor to the stack + * + * @param {Function} fulfilled The function to handle `then` for a `Promise` + * @param {Function} rejected The function to handle `reject` for a `Promise` + * + * @return {Number} An ID used to remove interceptor later + */ + InterceptorManager.prototype.use = function (fulfilled, rejected) { + this.handlers.push({ + fulfilled: fulfilled, + rejected: rejected + }); + return this.handlers.length - 1; + }; + + /** + * Remove an interceptor from the stack + * + * @param {Number} id The ID that was returned by `use` + */ + InterceptorManager.prototype.eject = function (id) { + if (this.handlers[id]) { + this.handlers[id] = null; + } + }; + + /** + * Iterate over all the registered interceptors + * + * This method is particularly useful for skipping over any + * interceptors that may have become `null` calling `remove`. + * + * @param {Function} fn The function to call for each interceptor + */ + InterceptorManager.prototype.forEach = function (fn) { + utils.forEach(this.handlers, function (h) { + if (h !== null) { + fn(h); + } + }); + }; + + module.exports = InterceptorManager; + + +/***/ }, +/* 13 */ +/***/ function(module, exports) { + + 'use strict'; + + /** + * Syntactic sugar for invoking a function and expanding an array for arguments. + * + * Common use case would be to use `Function.prototype.apply`. + * + * ```js + * function f(x, y, z) {} + * var args = [1, 2, 3]; + * f.apply(null, args); + * ``` + * + * With `spread` this example can be re-written. + * + * ```js + * spread(function(x, y, z) {})([1, 2, 3]); + * ``` + * + * @param {Function} callback + * @returns {Function} + */ + module.exports = function spread(callback) { + return function (arr) { + return callback.apply(null, arr); + }; + }; + + +/***/ } +/******/ ]) +}); +; +//# sourceMappingURL=axios.map \ No newline at end of file diff --git a/js/lib/url-template/url-template.js b/js/lib/url-template/url-template.js new file mode 100644 index 0000000..048c2c7 --- /dev/null +++ b/js/lib/url-template/url-template.js @@ -0,0 +1,438 @@ +/* + UriTemplates Template Processor - Version: @VERSION - Dated: @DATE + (c) marc.portier@gmail.com - 2011-2012 + Licensed under APLv2 (http://opensource.org/licenses/Apache-2.0) + */ + +; +var uritemplate = (function() { + +// Below are the functions we originally used from jQuery. +// The implementations below are often more naive then what is inside jquery, but they suffice for our needs. + + function isFunction(fn) { + return typeof fn == 'function'; + } + + function isEmptyObject (obj) { + for(var name in obj){ + return false; + } + return true; + } + + function extend(base, newprops) { + for (var name in newprops) { + base[name] = newprops[name]; + } + return base; + } + + /** + * Create a runtime cache around retrieved values from the context. + * This allows for dynamic (function) results to be kept the same for multiple + * occuring expansions within one template. + * Note: Uses key-value tupples to be able to cache null values as well. + */ + //TODO move this into prep-processing + function CachingContext(context) { + this.raw = context; + this.cache = {}; + } + CachingContext.prototype.get = function(key) { + var val = this.lookupRaw(key); + var result = val; + + if (isFunction(val)) { // check function-result-cache + var tupple = this.cache[key]; + if (tupple !== null && tupple !== undefined) { + result = tupple.val; + } else { + result = val(this.raw); + this.cache[key] = {key: key, val: result}; + // NOTE: by storing tupples we make sure a null return is validly consistent too in expansions + } + } + return result; + }; + + CachingContext.prototype.lookupRaw = function(key) { + return CachingContext.lookup(this, this.raw, key); + }; + + CachingContext.lookup = function(me, context, key) { + var result = context[key]; + if (result !== undefined) { + return result; + } else { + var keyparts = key.split('.'); + var i = 0, keysplits = keyparts.length - 1; + for (i = 0; i