UNPKG

@incdevco/framework

Version:
981 lines (582 loc) 22.6 kB
/* global angular AWS CryptoJS */ angular.module('aws', []) .constant('AWS_SHA_256', 'AWS4-HMAC-SHA256') .constant('AWS4', 'AWS4') .constant('AWS4_REQUEST', 'aws4_request') .factory('AWSHelper', [ function () { return { getCognitoIdentityCredentials: function (config) { return new AWS.CognitoIdentityCredentials(config); }, getCognitoSyncManager: function () { return new AWS.CognitoSyncManager(); } }; } ]) .factory('ApiGatewayRequestInterceptor', [ 'AwsSignature', '$injector', '$log', function (signature, $injector, $log) { function Interceptor(config, authenticate) { var $rootScope, self = this; if (!config.apiKey) { throw new Error('No apiKey Provided'); } this.apiKey = config.apiKey; this._authenticate = authenticate; this.credentials = null; this.endpoint = config.endpoint; this.maxAttempts = config.maxAttempts || 2; this.region = config.region; $rootScope = $injector.get('$rootScope'); $rootScope.$on('aws.credentials', function (event, credentials) { self.setCredentials(credentials); $log.debug('Interceptor', 'aws.credentials', self.credentials); }); $rootScope.$on('deauthenticated', function () { self.setCredentials(null); $log.debug('Interceptor', 'deauthenticated', self.credentials); }); } Interceptor.prototype.authenticate = function () { return this._authenticate(); }; Interceptor.prototype.generate = function () { var self = this; return { request: function (config) { $log.debug('ApiGatewayRequestInterceptor'); if (self.match(config)) { return self.sign(config); } else { return config; } }, responseError: function (response) { $log.debug('ApiGatewayRequestInterceptor', 'responseError', response); if (response.config && self.match(response.config) && self.shouldRetry(response)) { $log.debug('ApiGatewayRequestInterceptor', 'handling', response); return self.authenticate() .then(function () { self.prepareForRetry(response.config); return $injector.get('$http')(response.config); }); } else { $log.debug('ApiGatewayRequestInterceptor', 'not handling', response); return $injector.get('$q').reject(response); } } }; }; Interceptor.prototype.match = function (config) { //console.log('ApiGatewayRequestInterceptor', config.url); //console.log('ApiGatewayRequestInterceptor', this.endpoint); //console.log('ApiGatewayRequestInterceptor', (config.url.indexOf(this.endpoint) === 0)); return (config.url.indexOf(this.endpoint) === 0); }; Interceptor.prototype.prepareForRetry = function (config) { delete config.headers['Authorization']; delete config.headers['x-amz-security-token']; }; Interceptor.prototype.setCredentials = function (credentials) { this.credentials = credentials; }; Interceptor.prototype.sign = function (config) { var credentials = this.credentials; var self = this; var params = { apiKey: this.apiKey, endpoint: this.endpoint, region: this.region, serviceName: 'execute-api' }; $log.debug('sign', credentials); if (credentials && credentials.secretKey) { return signature.sign(config, credentials, params); } else if (config.auth) { return this.authenticate() .then(function () { return signature.sign(config, self.credentials, params); }); } else { return config; } }; Interceptor.prototype.shouldRetry = function (response) { $log.debug('shouldRetry', response); if (response.status === 403 || response.status < 0) { response.config.attempts = parseInt(response.config.attempts) || 0; response.config.attempts++; if (response.config.attempts < this.maxAttempts) { return true; } } return false; }; return Interceptor; } ]) .factory('ApiGatewayRequestSignatureInterceptor', [ 'AwsSignature', '$injector', '$log', function (signature, $injector, $log) { function Interceptor(config) { if (!config.apiKey) { throw new Error('No apiKey Provided'); } this.apiKey = config.apiKey; this.credentials = null; this.endpoint = config.endpoint; this.maxAttempts = config.maxAttempts || 2; this.region = config.region; } Interceptor.prototype.clearCredentials = function () { this.credentials = null; }; Interceptor.prototype.generate = function () { var self = this; return { request: function (config) { $log.debug('ApiGatewayRequestInterceptor'); if (self.match(config) && self.shouldSign(config)) { return self.sign(config); } else { return config; } } }; }; Interceptor.prototype.match = function (config) { //console.log('ApiGatewayRequestInterceptor', config.url); //console.log('ApiGatewayRequestInterceptor', this.endpoint); //console.log('ApiGatewayRequestInterceptor', (config.url.indexOf(this.endpoint) === 0)); return (config.url.indexOf(this.endpoint) === 0); }; Interceptor.prototype.setCredentials = function (credentials) { if (!credentials.accessKey) { throw new Error('accessKey is required to setCredentials'); } if (!credentials.secretKey) { throw new Error('secretKey is required to setCredentials'); } if (!credentials.sessionToken) { throw new Error('sessionToken is required to setCredentials'); } this.credentials = credentials; }; Interceptor.prototype.shouldSign = function (config) { if (config.auth || config.sign) { return true; } return false; }; Interceptor.prototype.sign = function (config) { var params = { apiKey: this.apiKey, endpoint: this.endpoint, region: this.region, serviceName: 'execute-api' }; $log.debug('sign', this.credentials); if (this.credentials) { return signature.sign(config, this.credentials, params); } else { var exception = new Error('no credentials to sign with'); exception.code = 'NoCredentials'; exception.requestConfig = config; throw exception; } }; return Interceptor; } ]) .factory('ApiGatewaySendRequestGenerator', [ 'AwsSignature', '$injector', '$log', function (signature, $injector, $log) { var defaultHeaders = { 'Accept': 'application/json', 'Content-Type': 'application/json' }; return function (config) { var apiEndpoint = config.apiEndpoint; var apiKey = config.apiKey; var apiRegion = config.apiRegion; var headers = config.headers; return function (input, credentials) { var request = { auth: input.auth || false, headers: angular.copy(defaultHeaders), method: input.method || 'GET', sign: (input.sign !== undefined) ? input.sign : true, url: apiEndpoint + input.url }; angular.extend(request.headers, headers, input.headers); if (input.params) { request.params = angular.copy(input.params); } else { request.params = {}; } if (request.method === 'GET') { request.data = ''; } else { if (input.data) { if (typeof input.data !== 'string') { input.data = angular.toJson(input.data); } request.data = input.data; } } if (credentials) { signature.sign(request, credentials, { apiKey: apiKey, endpoint: apiEndpoint, region: apiRegion, serviceName: 'execute-api' }); } return $injector.get('$http')(request); }; }; } ]) .factory('AwsSignature', [ 'AWS_SHA_256', 'AWS4', 'AWS4_REQUEST', '$log', function (AWS_SHA_256, AWS4, AWS4_REQUEST, $log) { var parser = document.createElement('a'); return { buildAuthorizationHeader: function (accessKey, credentialScope, headers, signature) { if (!accessKey) { throw new Error('No accessKey Provided'); } return AWS_SHA_256 + ' Credential=' + accessKey + '/' + credentialScope + ', SignedHeaders=' + this.buildCanonicalSignedHeaders(headers) + ', Signature=' + signature; }, buildCanonicalHeaders: function (headers) { var canonical = '', sortedKeys; sortedKeys = Object.keys(headers); sortedKeys.sort(); sortedKeys.forEach(function (key) { canonical += key.toLowerCase() + ':' + headers[key] + '\n'; }); return canonical; }, buildCanonicalQueryString: function (query) { var canonical = '', sortedQuery; if (!query || Object.keys(query).length < 1) { return ''; } sortedQuery = Object.keys(query); sortedQuery.sort(); sortedQuery.forEach(function (key, index) { if (index != 0) { canonical += '&'; } canonical += key + '=' + encodeURIComponent(query[key]); }); return canonical; }, buildCanonicalRequest: function (request) { var canonical = request.method + '\n'; canonical += this.buildCanonicalUri(request.url) + '\n'; canonical += this.buildCanonicalQueryString(request.params) + '\n'; canonical += this.buildCanonicalHeaders(request.headers) + '\n'; canonical += this.buildCanonicalSignedHeaders(request.headers) + '\n'; canonical += this.hexEncode(this.hash(request.data)); return canonical; }, buildCanonicalSignedHeaders: function (headers) { var canonical = '', keys, sortedHeaders = []; keys = Object.keys(headers); keys.forEach(function (key) { sortedHeaders.push(key.toLowerCase()); }); //$log.debug('sortedHeaders', sortedHeaders); sortedHeaders.sort(); //$log.debug('sortedHeaders', sortedHeaders); sortedHeaders.forEach(function (key) { canonical += key + ';'; }); return canonical.replace(/;$/, ''); }, buildCanonicalUri: function (url) { return encodeURI(url); }, buildCredentialScope: function (datetime, region, serviceName) { return datetime.substr(0, 8) + '/' + region + '/' + serviceName + '/' + AWS4_REQUEST; }, buildStringToSign: function (datetime, credentialScope, hashedCanonicalRequest) { return AWS_SHA_256 + '\n' + datetime + '\n' + credentialScope + '\n' + hashedCanonicalRequest; }, calculateSignature: function (key, stringToSign) { return this.hexEncode(this.hmac(key, stringToSign)); }, calculateSigningKey: function (datetime, secretKey, region, serviceName) { var key = this.hmac(AWS4 + secretKey, datetime.substr(0, 8)); key = this.hmac(key, region); key = this.hmac(key, serviceName); key = this.hmac(key, AWS4_REQUEST); return key; }, createDateTime: function () { var datetime = new Date().toISOString() .replace(/\.\d{3}Z$/, 'Z') .replace(/[:\-]|\.\d{3}/g, ''); return datetime; }, hash: function (value) { return CryptoJS.SHA256(value); }, hashCanonicalRequest: function (request) { return this.hexEncode(this.hash(request)); }, hexEncode: function (value) { return value.toString(CryptoJS.enc.Hex); }, hmac: function (secret, value) { return CryptoJS.HmacSHA256(value, secret, { asBytes: true }); }, sign: function (request, credentials, config) { var datetime = this.createDateTime(); var originalUrl = request.url; if (!credentials || !credentials.secretKey) { return request; } parser.href = config.endpoint; request.url = request.url.replace(config.endpoint, ''); request.headers['Host'] = parser.hostname; request.headers['x-amz-date'] = datetime; request.headers['x-api-key'] = config.apiKey; if (request.method === 'GET') { delete request.headers['Content-Type']; } var canonicalRequest = this.buildCanonicalRequest(request); //console.warn('canonicalRequest', canonicalRequest); var hashedCanonicalRequest = this.hashCanonicalRequest(canonicalRequest); //console.warn('hashedCanonicalRequest', hashedCanonicalRequest); var credentialScope = this.buildCredentialScope(datetime, config.region, config.serviceName); //console.warn('credentialScope', credentialScope); var stringToSign = this.buildStringToSign(datetime, credentialScope, hashedCanonicalRequest); //console.warn('stringToSign', stringToSign); var signingKey = this.calculateSigningKey(datetime, credentials.secretKey, config.region, config.serviceName); //console.warn('signingKey', signingKey); var signature = this.calculateSignature(signingKey, stringToSign); //console.warn('signature', signature); request.headers['Authorization'] = this.buildAuthorizationHeader(credentials.accessKey, credentialScope, request.headers, signature); //console.warn('request.headers[\'Authorization\']', request.headers['Authorization']); request.headers['Content-Type'] = 'application/json'; request.headers['x-amz-security-token'] = credentials.sessionToken; //console.warn('request.headers[\'x-amz-security-token\']', request.headers['x-amz-security-token']); delete request.headers['Host']; request.url = originalUrl; var querystring = this.buildCanonicalQueryString(request.params); if (querystring != '') { request.url += '?' + querystring; } delete request.params; return request; } }; } ]) .factory('CognitoIdentity', [ 'AWSHelper', '$log', '$q', '$rootScope', function (AWSHelper, $log, $q, $rootScope) { /** * { * IdentityId: IdentityId, * IdentityPoolId: IdentityPoolId, * Logins: Logins, * RoleSessionName: RoleSessionName * } */ var fn = function (config, region) { var configString = JSON.stringify(config, null, 2); var deferred = $q.defer(); AWS.config.region = region; AWS.config.credentials = AWSHelper.getCognitoIdentityCredentials(config); AWS.config.credentials.get(function (exception) { if (exception) { $log.error('AWS.config.credentials.get', exception); return deferred.reject(exception); } else { var credentials = { accessKey: AWS.config.credentials.accessKeyId, expireTime: AWS.config.credentials.expireTime, secretKey: AWS.config.credentials.secretAccessKey, sessionToken: AWS.config.credentials.sessionToken }; var identity = { expireTime: AWS.config.credentials.expireTime, identityId: AWS.config.credentials.identityId, identityPoolId: config.IdentityPoolId, logins: config.logins }; $log.debug('broadcasting aws.credentials', configString); //$rootScope.$broadcast('aws.credentials', credentials); //$rootScope.$broadcast('cognito.identity', identity); return deferred.resolve({ credentials: credentials, expireTime: AWS.config.credentials.expireTime, identity: identity }); } }); return deferred.promise; }; fn.clearCachedId = function () { AWS.config.credentials.clearCachedId(); }; return fn; } ]) .factory('CognitoSync', [ 'AWSHelper', '$log', '$q', '$rootScope', function (AWSHelper, $log, $q, $rootScope) { var manager, ready = $q.defer(); $rootScope.$on('aws.credentials', function () { console.log('CognitoSync', 'aws.credentials'); manager = AWSHelper.getCognitoSyncManager(); }); function getManager() { if (!manager) { manager = AWSHelper.getCognitoSyncManager(); } return manager; } function Dataset(name, dataset) { this.name = name; this._dataset = dataset; } Dataset.prototype.get = function (key) { var deferred = $q.defer(); this._dataset.get(key, function (exception, value) { if (exception) { $log.error(exception); deferred.reject(exception); } else { deferred.resolve(value); } }); return deferred.promise; }; Dataset.prototype.set = function (key, value) { var deferred = $q.defer(); this._dataset.put(key, value, function (exception) { if (exception) { $log.error(exception); deferred.reject(exception); } else { deferred.resolve(value); } }); return deferred.promise; }; Dataset.prototype.syncResolveLocal = function () { var deferred = $q.defer(); this._dataset.synchronize({ onSuccess: function (dataset, newRecords) { deferred.resolve(newRecords); }, onFailure: function (error) { $log.error('onFailure', error); deferred.reject(error); }, onConflict: function (dataset, conflicts, callback) { var resolved = []; conflicts.forEach(function (conflict) { resolved.push(conflict.resolveWithLocalRecord()); }); dataset.resolve(resolved, function () { deferred.resolve(); return callback(true); }); }, onDatasetDeleted: function (dataset, name, callback) { deferred.reject(new Error('Dataset Deleted')); return callback(true); } }); return deferred.promise; }; Dataset.prototype.syncResolveRemote = function () { var deferred = $q.defer(); this._dataset.synchronize({ onSuccess: function (dataset, newRecords) { deferred.resolve(newRecords); }, onFailure: function (error) { $log.error('onFailure', error); deferred.reject(error); }, onConflict: function (dataset, conflicts, callback) { var resolved = []; conflicts.forEach(function (conflict) { resolved.push(conflict.resolveWithRemoteRecord()); }); dataset.resolve(resolved, function () { deferred.resolve(); return callback(true); }); }, onDatasetDeleted: function (dataset, name, callback) { deferred.reject(new Error('Dataset Deleted')); return callback(true); } }); return deferred.promise; }; return function (datasetName) { var deferred = $q.defer(); console.log('CognitoSync', datasetName); getManager() .openOrCreateDataset(datasetName, function (exception, dataset) { if (exception) { return deferred.reject(exception); } else { return deferred.resolve(new Dataset(datasetName, dataset)); } }); return deferred.promise; }; } ]); angular.module('aws') .run([ '$templateCache', function ($templateCache) {} ]);