A New Collection of Thoughtful Learning Apps — Now Available on iOS & Android

Image
I’m excited to share a set of mobile apps I’ve recently completed and published on both the Google Play Store and the Apple App Store. These apps are designed with a simple goal in mind: to make meaningful, structured content more accessible, whether you’re studying theology or improving your English vocabulary. 📱 Now Available on Both Platforms All apps are live and available for download: Google Play Developer Page: https://play.google.com/store/apps/dev?id=5835943159853189043 Apple App Store Developer Page: https://apps.apple.com/ca/developer/q-z-l-corp/id1888794100 📖 Theology & Confession Study Apps For those interested in Reformed theology and classical Christian teachings, I’ve developed a series of apps that present foundational texts in a clean, focused reading format: The Belgic Confession Canons of Dort Heidelberg Catechism Westminster Shorter Catechism Each app is designed to provide a distraction-free experience, making it easier to read, reflect, and revisit these im...

node.js download file from aws s3 bucket via http request with AWS Signature Version 4

Main codes

function getYMD(d) {
    let r = d.getFullYear().toString();
    let m = d.getUTCMonth() + 1;
    if (m < 10)
      m = "0" + m.toString();
    else
      m = m.toString();
    r = r + m;
    let day = d.getUTCDate();
    if (day < 10)
      day = "0" + day.toString();
    else
      day = day.toString();
    r = r + day;
    return r;
}

function getTZ(d) {
    let r = d.getFullYear().toString();
    let m = d.getUTCMonth() + 1;
    if (m < 10)
      m = "0" + m.toString();
    else
      m = m.toString();
    r = r + m;
    let day = d.getUTCDate();
    if (day < 10)
      day = "0" + day.toString();
    else
      day = day.toString();
    r = r + day + "T";
    let h = d.getUTCHours();
    if (h < 10)
      h = "0" + h.toString();
    else
      h = h.toString();
    r = r + h;
    let min = d.getUTCMinutes();
    if (min < 10)
      min = "0" + min.toString();
    else
      min = min.toString();
    r = r + min;
    let s = d.getUTCSeconds();
    if (s < 10)
      s = "0" + s.toString();
    else
      s = s.toString();
    r = r + s;
    r = r + "Z";
    return r;
}

const crypto = require('crypto');
function Hmac(key, string){
    const hmac = crypto.createHmac('sha256', key);
    hmac.end(string);
    return hmac.read();
}

function Signature(date, region, service, toSign) {
    let dateKey = Hmac('AWS4' + AWS_SECRETACCESS_KEY, date);
    let dateRegionKey = Hmac(dateKey, region);
    let dateRegionServiceKey = Hmac(dateRegionKey, service);
    let signingKey = Hmac(dateRegionServiceKey, 'aws4_request');
    let signature = Hmac(signingKey, toSign).toString('hex');
    return signature;
}

function stringToSign(timeStamp, scope, canonicalRequest) {
    var signParts = [];
    signParts.push('AWS4-HMAC-SHA256');
    signParts.push(timeStamp);
    signParts.push(scope);
    signParts.push(crypto.createHash('sha256').update(canonicalRequest).digest(
'hex'));
    var result = signParts.join('\n');
    console.log('string to sign');
    console.log(result);
    return result;
};

function canonicalRequest(method, uri, queryString, headers, signedHeaders, has
hedPayload) {
  var canonicalParts = [];
  canonicalParts.push(method);
  canonicalParts.push(uri);
  canonicalParts.push(queryString);
  canonicalParts.push(headers);
  canonicalParts.push(signedHeaders);
  canonicalParts.push(hashedPayload);
  var result = canonicalParts.join('\n');
  console.log('canonical request');
  console.log(result);
  return result;
}

function download(filepath) {
    let filename = filepath;
    let position = filename.lastIndexOf('/');
    if (position != -1) {
      filename = filename.substr(position+1);
    }
    let d = new Date();
    let dtz = getTZ(d);
    let dymd = getYMD(d);
    let scope = dymd + '/' + AWS_REGION + '/' + AWS_SERVICE + '/aws4_request';
    let uri = filepath;
    let queryString = "X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=" + en
codeURIComponent(AWS_KEY + "/" + scope) + "&X-Amz-Date=" + dtz + "&X-Amz-Expire
s=400&X-Amz-SignedHeaders=host&response-content-disposition=attachment%3Bfilena
me%3D" + filename;
    let headers = "host:s3.amazonaws.com";
    let signedHeaders = "";
    let hashedPayload = "host\nUNSIGNED-PAYLOAD";
    let request = canonicalRequest('GET', uri, queryString, headers, signedHead
ers, hashedPayload);
    let signature = Signature(dymd, AWS_REGION, AWS_SERVICE, stringToSign(dtz,
scope, request));
    let url = 'https://s3.amazonaws.com' + filepath + '?response-content-dispos
ition=attachment;filename=' + filename + '&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-A
mz-Credential=' + AWS_KEY + "/" + scope + '&X-Amz-Date=' + dtz + '&X-Amz-Expire
s=400&X-Amz-SignedHeaders=host&X-Amz-Signature=' + signature;
    console.log(url);
    var link = document.createElement('a');
    link.innerHTML = 'download';
    link.href = url;
    document.body.appendChild(link);
}

Usage

const AWS_KEY = 'your aws key';
const AWS_SECRETACCESS_KEY = 'your aws secret access key';
const AWS_REGION = 'your bucket region'; // default us-east-1
const AWS_SERVICE = 's3';

download('/yourbucket/objectkey');

examle

download

Refers

GET Object
Signing AWS Requests with Signature Version 4
Authenticating Requests: Using Query Parameters (AWS Signature Version 4)
Key point:
?response-content-disposition=attachment;filename=objectname
Using this header, browser will open an save dialog once the down load url is correct signed.
You will get a url link like:
https://s3.amazonaws.com/bucket/objectkey?response-content-disposition=attachment;filename=objectname&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJWWH7EGSUBWE34IQ/20181218/us-east-1/s3/aws4_request&X-Amz-Date=20181218T053905Z&X-Amz-Expires=400&X-Amz-SignedHeaders=host&X-Amz-Signature=71d82d4a99338d777bfa3517315d34cad94619a9ca891222808e16dd02de7835

❤️ Support This Blog


If this post helped you, you can support my writing with a small donation. Thank you for reading.


Comments

Popular Posts

Fix “A problem occurred starting process 'command node'” in Android Studio for React Native

Fix up watchman issue with Ghost

Using Mutual TLS (mTLS) in Next.js (Server-Side Only)