Ninjas Practicing Multidimensionality

    ns-aws-utils
    TypeScript icon, indicating that this package has built-in type declarations

    2.3.1 • Public • Published

    ns-aws-utils

    Utility library for Nu Skin serverless applications.

    ns-aws-utils is a utility library to aid in the development of Nu Skin serverless applications. It exposes a number of modules and functions that provide support for CORS and EID headers, logging, text scrubbing, and text localization.

    CONTENTS

    NPM and Github Repo

    https://www.npmjs.com/package/ns-aws-utils
    https://github.com/kelvinatorHK/ns-aws-utils

    Installation

    Using npm:

    $ npm install ns-aws-utils

    Usage

    App Settings

    The appSettings module retrieves specific web application settings (flags/toggles) for a particular group/service.

    Example:

    const {appSettings} = require('ns-aws-utils');
    
    const {
        quantityRestrictionsEnabled,
        spendingRestrictionsEnabled
    } = await appSettings.getSettings('order-service', 'US');
    
    if (quantityRestrictionsEnabled) {...}
    
    if (spendingRestrictionsEnabled) {...}

    Country Configuration Data

    The country module supplies country-specific configuration data.

    This data includes time zone offsets, currency codes, and the AgeLOC Me master SKU.

    Example:

    const { country } = require("ns-aws-utils");
    
    // Retrieve data for all countries
    const countries = country.getCountries();
    console.log(countries["US"]); // {timezone: 'America/Denver', tmzOffset: -7, currency: 'USD', ageLocMeMasterSku: '01010000'}
    
    // Retrieve data for a specific country
    const jp = country.getCountry("JP");
    console.log(jp); // {timezone: 'Asia/Tokyo', tmzOffset: 9, currency: 'JPY', ageLocMeMasterSku: '03010000'}
    
    // Format a date and time adjusted for the market timezone
    const dateString = country.getCountryDate('US', {format: 'YYYY-MM-DD hh:mm:ss', date: '2021-04-28'});
    
    // Get the epoch time in milliseconds for the timezone adjusted
    const timestampAdj = country.getCountryTime('US', '2021-04-28');

    CORS Headers

    The cors function adds CORS headers to all responses returned by your API function.

    Pass the handler function through the cors function as shown in the example below. Both callback-style and async-style Lambda handler functions are supported.

    Example:

    const { cors } = require("ns-aws-utils");
    
    // Lambda handler function, callback style
    exports.handler = cors(function (event, context, callback) {
      
      // ... handler code ... //
    
      callback(null, response);
    });
    
    // Lambda handler function, async style
    exports.handler = cors(async function(event, context) {
        
        // ... handler code ... //
    
        return response;
    });

    Additional options may be passed to the cors function, providing fine control over the contents of CORS headers.

    Example:

    const { cors } = require("ns-aws-utils");
    
    // cors function options
    const corsOptions = {
      origins: ["http://www.google.com", "http://www.amazon.com"],
      allowCredentials: false,
      allowMethods: ["GET", "POST"],
      allowHeaders: ["Content-Type", "X-Amz-Date", "Authorization", "X-Api-Key"],
      maxAge: 600,
    };
    
    // Lambda handler function
    exports.handler = cors(function (event, context, callback) {
      // ...
      const response = { statusCode: 200, body: "Hello" };
      callback(null, res);
    }, corsOptions);

    EID Header

    The eidify function adds an EID token header to all responses returned by your API function. The EID value is taken from the Authorizer. As such, eidify should only be used with API functions that require authentication.

    Pass the handler function through the eidify function as shown in the example below. Both callback-style and async-style Lambda handler functions are supported.

    Example:

    const { eidify } = require("ns-aws-utils");
    
    // Lambda handler function, callback style
    exports.handler = eidify(function (event, context, callback) {
      
      // ... handler code ... //
    
      callback(null, response);
    });
    
    // Lambda handler function, async style
    exports.handler = eidify(async function(event, context) {
        
        // ... handler code ... //
    
        return response;
    });

    The cors and eidify functions can be used together, as demonstrated below.

    Example:

    const { cors, eidify } = require("ns-aws-utils");
    
    // Lambda handler function
    exports.handler = cors(eidify(function (event, context, callback) {
      
      // ... handler code ... //
    
      callback(null, response);
    }));

    Logging

    The logger module provides logging utilities.

    This module can

    • log data
    • set logging levels
    • add tags to logs
    • scrub sensitive data from logs
    • add tracing tags to logs, to aid in debugging

    Logging Data

    The logging module exposes debug, info, warn, and error methods for logging data.

    Log entries take the following form:

    {"level":"...","msg":{...}}
    

    If the message argument passed to the logging function is a string, the log's msg property takes the following form:

    "msg":{"message":"<YOUR MESSAGE HERE>"}
    

    The logging functions support JSON objects as messages. This is often recommended in practice. If the message object is a JSON object, the log's msg takes the following form:

    "msg": { // YOUR OBJECT HERE // }
    

    Examples:

    const { logger: log } = require("ns-aws-utils");
    
    // === Logging strings:
    
    log.debug("Hello debug");
    // LOGS: {"level":"debug","msg":{"message":"hello debug"}}
    
    log.info("Hello info");
    // LOGS: {"level":"info","msg":{"message":"hello info"}}
    
    log.warn("Hello warn");
    // LOGS: {"level":"warn","msg":{"message":"hello warn"}}
    
    log.error("Hello error");
    // LOGS: {"level":"error","msg":{"message":"hello error"}}
    
    // === Logging an object:
    
    log.info({ attr1: "it is cool", attr2: 42 });
    // LOGS: {"level":"info","msg":{"attr1":"it is cool","attr2":42}}

    Setting the Logging Level

    The logging level can be set by calling logger.setLevel(). Logging calls made that are below the set logging level will not be written to the logs.

    Example:

    const { logger } = require("ns-aws-utils");
    
    logger.setLevel("off"); // no logs are written
    
    logger.setLevel("error"); // only error logs are written
    
    logger.setLevel("warn"); // warn and error logs are written
    
    logger.setLevel("info"); // info, warn, and error logs are written
    
    logger.setLevel("debug"); // debug, info, warn, and error logs are written

    Scrubbing Sensitive Data

    The logger module can scrub sensitive data from your logs. This scrubbing is turned on by default. You can disable it by calling logger.setScrubbing().

    logger.setScrubbing(false);

    See the scrubber section for information on how to configure log scrubbing.

    Tags

    You can include a function-specific tag in your logs. Call logger.setTag() to set this tag. All logs written by your function will include your tag.

    Tagged log entries take the following form:

    {"level":"...", "tag": {...}, "msg": {...}}
    

    If the provided tag is a string, the tag property takes the following form:

    "tag":{"key":"<YOUR STRING HERE>"}
    

    If the provided tag is a JSON object, the tag property takes the following form:

    "tag":{ // YOUR OBJECT HERE // }
    

    Examples:

    const { logger: log } = require("ns-aws-utils");
    
    // set the tag as a string
    log.setTag("My new tag"); 
    log.info({ myMessage: "message 1" });
    // LOGS: {"level":"info","tag":{"key":"My new tag"},"msg":{"myMessage":"message 1"}}
    
    // set the tag as an object
    log.setTag({ requestId: "some Id", resourceId: "12345" }); 
    log.info({ myMessage: "message 2" });
    // LOGS: {"level":"info","tag":{"requestId":"some Id","resourceId":"12345"},"msg":{"myMessage":"message 2"}}

    To clear the tag, call logger.setTag() with no arguments.

    log.setTag();

    Additional data can be merged into the current tag by calling logger.addTag().

    log.setTag({ key1: "abc" });
    log.addTag({ key2: "xyz" });
    log.info({ myMessage: "message 1" });
    // LOGS: {"level":"info","tag":{"key1":"abc","key2":"xyz"},"msg":{"myMessage":"message 1"}}

    Correlation IDs

    The log correlation ID is a special tag that can be applied to logs to correlate transactions between applications, and is especially useful when debugging execution chains that span multiple applications. The correlate module provides methods for extracting an already-set correlation ID from incoming HTTP events, batchEvents and lambda to lambda calls. If a correlation ID is not found in the incoming message a correlation ID is generated.

    An incoming HTTP request or lambda to lambda call is inspected for a correlaton ID in the following order. If one is not found, a uuid is generated and provided.

    1. The x-correlation-id header on an HTTP request
    2. The x-request-id header on an HTTP request
    3. The 'correlationId' top level attribute in the body of the request. This is for a lambda to lambda call.
    4. The X_AMZN_TRACE_ID environment variable
    5. The x-amzn-trace-id header on an HTTP request
    6. The awsRequestID property of the Lambda context parameter passed to your handler function

    Usage example: For HTTP and Lambda events, call correlate.captureCorrelationID(), passing in the event and context parameters passed to your function, to retrieve or generate the correlation ID. This correlation ID should then be added as a logging tag with the key nse.correlation_id.

    const { correlate, logger } = require("ns-aws-utils");
    
    // Lambda handler function
    exports.handler = async function(event, context) {
        // capture or generate the correlation ID
        const correlationID = correlate.captureCorrelationId(event, context);
    
        // Tag all logging messages with the correlation ID
        logger.addTag({ "nse.correlation_id": correlationID });
    
        // ... handler code continues ... //
    };

    Usage example: For processing batch records, call correlate.captureRecordCorrelationID(), passing in the each record in the batch. This correlation ID should then be added as a logging tag with the key nse.correlation_id.

    const { correlate, logger } = require("ns-aws-utils");
    
    async function snsHandler(event) {
        for (const record of event.Records) {
            // capture or generate the correlation ID
            const correlationID = correlate.captureRecordCorrelationId(record);
    
            // Tag subsequent logging messages with this correlation ID
            logger.addTag({ "nse.correlation_id": correlationID });
    
            // ... code continues ... //
    ⚠️ WARNING
    This library uses a single global value to store the correlation ID. If you are processing batch records concurrently, it is likely the correlation and the associated tag will not be reliable.
     

    If needed, an already-captured correlation ID can be retrieved by calling correlate.retrieveCorrelationId().

    const savedCorrelationID = correlate.retrieveCorrelationId();

    Text Scrubbing

    The scrubber module provides text scrubbing features. It also provides configuration for the log scrubbing described in Scrubbing Sensitive Data. When a value is scrubbed, an md5 hash is created from the value being scrubbed. In the case of self referencing objects, the reference is replaced with a replacement string.

    The scrubber can be configured to scrub particular object keys or to scrub any keys matching a regular expression. The scrubber module has a built-in list of common sensitive keys that it can include in a list of keys to scrub.

    const { scrubber } = require("ns-aws-utils");
    
    // Set scrubber to scrub specific keys
    scrubber.setKeysToScrub(["sensitiveKey1", "Private_Data_Key"]);
    
    // Set scrubber to scrub specific keys, and include default key list
    scrubber.setKeysWithDefaultToScrub(["sensitiveKey1", "Private_Data_Key"]);
    
    // Set scrubber to scrub any keys matching a pattern
    scrubber.setPattern(/(my)?-sensitive-key-\d{2}/);

    To change the replacement value for self referencing objects use scrubber.setReplacement() to set the value of this replacement string.

    // Any self referenced object found will be replaced with "AAAAAAAAAA"
    scrubber.setReplacement("AAAAAAAAAA");

    The scrubber can be called directly on a JSON object to scrub sensitive data by calling scrubber.scrub(). Scrubbing is applied recursively, in other words any key at any level of the object that matches the scrubber's settings will have its value scrubbed.

    const obj = {
        "key-01": "sensitive data here",
        "details": {
            "normal-stuff": "hello",
            "my-key-02": "more sensitive data"
        }
    };
    obj.b = obj
    
    scrubber.setPattern(/(my)?-key-\d{2}/);
    scrubber.setReplacement("...---...");
    
    const scrubbedObj = scrubber.scrub(obj);
    /* 
    scrubbedObj:
    {
        "key-01": "{HASHED VALUE}",
        "details": {
            "normal-stuff": "hello",
            "my-key-02": "{HASHED VALUE}"
        },
        b: "...---..."
    }
     */

    Any configuration settings provided to the scrubber module are used to scrub logs written by the logger module.

    const { logger, scrubber } = require("ns-aws-utils");
    
    // Set scrubber to scrub specific keys
    scrubber.setKeysToScrub(["pi", "myPassword"]);
    scrubber.setReplacement("::::::");
    
    logger.info({"pi": "3.14159", "user": {"name":"Bob","myPassword": "password1"}});
    // LOGS: {"level":"info","msg":{"pi":"::::::","user": {"name":"Bob","myPassword": "::::::"}}}

    Text Localization

    The localize function retrieves localized strings from the CREW Text Cache for use in your application.

    When the localize function is called, it first checks the Localizations table in DynamoDB to see if it has been copied there. If the string is there, it is returned. If not found, or the string has been marked as expired (deleted), the string will be requested from Text Cache and the result of that request is then stored in Localizations. Note that once the string is in Localizations, Text Cache is not queried again. If an update is made to the string in Text Cache, the string in Localizations needs to be deleted so that the next time the string is requested it will be retrieved from Text Cache again.

    Prerequisites

    For the localize function to work properly, your serverless project must contain a config directory with a <STAGE>.json file for each of your application's deployment stages (e.g. dev.json, test.json, prod.json, etc.) Each file should be set up like the following, with the base URL of Nu Skin's AWS cloud services for that stage:

    // config/test.json
    {
        "properties": {
            "awsBase": "https://testapi.cloud.nuskin.com"
        }
    }

    Retrieving Localized Strings

    Call localize() to retrieve localized strings that ultimately come from Text Cache. You should specify the string names being requested, the applications within Text Cache that should be searched for those strings, and the locales for which you need localizations.

    Example:

    const { localize } = require("ns-aws-utils");
    
    // localize([strings to request], [applications], [locales])
    localize([{stringName: "appLabel"}, {stringName: "appMsg"}], ["shop"], ["en_GB"])
        .then(results => {
            /* 
            results:
            [{
                stringName: "appLabel",
                rawString: "Label for your App",
                localizedString: "Label for your App"
            }, {
                stringName: "appMsg",
                rawString: "This is a message",
                localizedString: "This is a message"
            }]
             */
        });

    Replacing String Variables

    Some strings in Text Cache have variable tags (<%tagName%>) where values can be injected by your application. The localize function can provide variable replacement, as shown below.

    Example:

    const { localize } = require("ns-aws-utils");
    
    const requests = [{
        stringName: "qtyDateMsg",
        variables: {
            count: 6,
            date: "Jan 12th"
        }
    }];
    localize(requests, ["demoApp"], ["en_TW"])
        .then(results => {
            /* 
            results:
            [{
                stringName: "appLabel",
                rawString: "Your<%count%> items will ship on <%date%>",
                localizedString: "Your 6 items will ship on Jan 12th"
            }]
             */
        });

    By default, the localize function replaces variables by matching tag names to keys in the variables map, as shown in the example above.

    If needed, it can instead replace variables by order of occurrance. In other words, the first variable tag in the string is replaced with the first ordered item in the variables map. This is done by calling localize while specifying "order" as the replacement mode argument, as shown below.

    Example:

    const { localize } = require("ns-aws-utils");
    
    const requests = [{
        stringName: "qtyDateMsg",
        variables: {
            "1": 6,
            "2": "Jan 12th"
        }
    }];
    
    // Call localize function, specify replacement by order
    localize(requests, ["demoApp"], ["en_TW"], "order")
        .then(results => {
            /* 
            results:
            [{
                stringName: "appLabel",
                rawString: "Your<%count%> items will ship on <%date%>",
                localizedString: "Your 6 items will ship on Jan 12th"
            }]
             */
        });

    Caching

    Example:

    cache.set('ssmProperties', 'projects', projects);
    
    cache.remove('ssmProperties', 'projects');
    
    const request = {awsCreds: {}};
    const results = await cache.wrapper(request, {
        cacheName: 'ssmProperties',
        maxCount: 10,
        fnProcess: async function(request) {
            const ssmRequest = {Name: config.ssmAwsCredsPath, WithDecryption: true};
            const response = await ssm.getParameter(ssmRequest).promise();
            request.awsCreds = JSON.parse(response.Parameter.Value);
            return request;
        }
    });

    Response & ErrorResponse

    We have created a standardized way of throwing errors and also returning responses to the client. Currently these methods exist in the serverless lambda template but nobody seems to be modifying them in any way so I felt we should move them here. They are very simple and all you need to do is pass in statusCode, headers, and body. It'll format those into a nice JSON object for you and you can return that to the client.

    The ErrorResponse has been created in an effort to allow you to keep track of the original error message and propagate that up through the project. If you throw an error 2 or 3 functions deep, you'll be able to pass the message up to the index and retain control of the original error message, rather than potentially losing the message through the stack. Example:

    const processHTTP = async (event, context) => {
        log.debug({context})
        let response = {}
        const locale = event.queryStringParameters ? event.queryStringParameters.locale : 'en_US'
    
        try {
            switch (event.httpMethod) {
                case 'GET':
                    response = await app.get(event)
                    break
                case 'DELETE':
                    response = await app.remove(event.pathParameters.id)
                    break
                default:
                    throw new ErrorResponse(405,`httpMethod: ${event.httpMethod} is not supported`)
            }
            response = formatResponse(response)
        } catch (err) {
            log.error(err)
            err.message = await localized(locale, err.message, null)
            response = formatErrorResponse(err)
        }
    
        return response
    }

    Keywords

    none

    Install

    npm i ns-aws-utils

    DownloadsWeekly Downloads

    502

    Version

    2.3.1

    License

    MIT

    Unpacked Size

    81.9 kB

    Total Files

    19

    Last publish

    Collaborators

    • klau
    • ruckuz99
    • treganderson
    • nuskin-cws
    • brycesheffield
    • rkstamm