Skip to content

Direct S3 upload and Node signing example

Taylor Ackley edited this page Sep 9, 2016 · 4 revisions

Simple direct S3 upload with using Node to generate policy and signature

HTML

<input type="file" ngf-select ngf-change="onFileSelect($files)">

Angular

$scope.onFileSelect = function(files) {

    if (files.length > 0) {
        var filename = files[0].name;
        var type = files[0].type;
        var query = {
            filename: filename,
            type: type
        };
        $http.post('/signing', query)
            .success(function(result) {
                Upload.upload({
                    url: result.url, //s3Url
                    transformRequest: function(data, headersGetter) {
                        var headers = headersGetter();
                        delete headers.Authorization;
                        return data;
                    },
                    fields: result.fields, //credentials
                    method: 'POST',
                    file: files[0]
                }).progress(function(evt) {
                    console.log('progress: ' + parseInt(100.0 * evt.loaded / evt.total));
                }).success(function(data, status, headers, config) {
                    // file is uploaded successfully
                    console.log('file ' + config.file.name + 'is uploaded successfully. Response: ' + data);
                }).error(function() {

                });
            })
            .error(function(data, status, headers, config) {
                // called asynchronously if an error occurs
                // or server returns response with an error status.
            });
    }


};

NOTE for Angular 1.4:

If removing headers does not work from the example above (Angular 1.4), a more reliable method may be to add an interceptor in your config block. Here is a simple example that strips out the authorization header if your url contains "amazonaws".

   angular
    .module('myApp', [
      'ngFileUpload'
    ])
    .config(config);

    config.$inject = ['$httpProvider'];

  function config($httpProvider) {

    $httpProvider.interceptors.push('authInterceptor');

  }


 angular
    .module('myApp')
    .factory('authInterceptor', authInterceptor);

  authInterceptor.$inject = ['$rootScope', '$q', '$location'];

  function authInterceptor($rootScope, $q, $location) {

    return {

      // intercept every request
      request: function(config) {
        var foreignUrl = config.url.indexOf('amazonaws') > -1;
        if(foreignUrl) {
          config.headers['Authorization'] = undefined;
        }
        return config;
      }
    };
  }

Node

var aws = 'path to credentials';
var crypto = require('crypto');
var moment = require('moment');

var s3Url = 'https://' + aws.bucket + '.s3-' + aws.region + '.amazonaws.com';

exports.signing = function(req, res) {
    var request = req.body;
    var fileName = request.filename
    var path = 'somedirectory in s3' + fileName;

    var readType = 'private';

    var expiration = moment().add(5, 'm').toDate(); //15 minutes

    var s3Policy = {
        'expiration': expiration,
        'conditions': [{
                'bucket': aws.bucket
            },
            ['starts-with', '$key', path], 
            {
                'acl': readType
            },
            {
              'success_action_status': '201'
            },
            ['starts-with', '$Content-Type', request.type],
            ['content-length-range', 2048, 10485760], //min and max
        ]
    };

    var stringPolicy = JSON.stringify(s3Policy);
    var base64Policy = new Buffer(stringPolicy, 'utf-8').toString('base64');

    // sign policy
    var signature = crypto.createHmac('sha1', aws.secret)
        .update(new Buffer(base64Policy, 'utf-8')).digest('base64');

    var credentials = {
        url: s3Url,
        fields: {
            key: path,
            AWSAccessKeyId: aws.key,
            acl: readType,
            policy: base64Policy,
            signature: signature,
            'Content-Type': request.type,
            success_action_status: 201
        }
    };
    res.jsonp(credentials);
};