Skip to Content
Technical Articles

Timesheet Management with CAP & Trello ⏱️ – Connect to Trello API’s via a Node.js Module #3

Hello there!

Welcome back to the third hands-on blog of this “Timesheet Management with CAP & Trello ⏱️” blog series. In this blog “Connect to Trello API’s via a Node.js Module #3” we will integrate the Trello API’s in our Node.js module.

If you missed some other blogs of this series, you can find them here:

Timesheet Management with CAP & Trello ⏱️
Timesheet Management with CAP & Trello ⏱️ – Setup the IDE & MTA Project #1
Timesheet Management with CAP & Trello ⏱️ – Setup a Database and Service Module #2
Timesheet Management with CAP & Trello ⏱️ – Connect to Trello API’s via a Node.js Module #3 (this blog)
Timesheet Management with CAP & Trello ⏱️ – Build the Trello Timesheet HTML5 Module #4
Timesheet Management with CAP & Trello ⏱️ – Add a Fiori Launchpad Site Module #5

The GitHub Repository is available here:

Timesheet Management with CAP & Trello GitHub Repository

 

Introduction

In this blog we will build a Node.js application, and we will use our Trello API keys and secrets to authorize the user against the Trello APIs. Once authorized, we can start requesting our Trello boards and cards via our Approuter. We prepare this Node module inside our Multi Target Application project, just like we did for the other Modules. By placing it inside our MTA project we can use our Approuter again to target the Node Module and retrieve the Trello data.

I wanted to use the SCP Credential Store Service to store our Trello API Keys and Secrets in a secure way. But on the Trial account it is not possible to create such an instance at the moment, since the required plan is missing.

More information about this missing plan can be found here in my question on the Community:

https://answers.sap.com/questions/13088889/sap-cloud-platform-credential-store-cloud-foundry.html

More information about this Credential Store Service can be found here:

https://help.sap.com/viewer/601525c6e5604e4192451d5e7328fa3c/Cloud/en-US/ad63368e8e6f44a1b3ac336e8d1c32b8.html

Instead of using this Credential Store Service (since it is not possible) we will store these credentials in “User-Provided Services” inside our Cloud Foundry Space.

More information about these “User Provided Services” can be found here:

https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/a44355e200b44b968d98ddaa42f07c3a.html

Let’s get started by retrieving our Trello Developers keys.

 

Retrieve your Trello API Tokens & Secrets

Before we will start building our Node Authorization app, we will fetch our API Token and Secret.

Be sure you are logged in into Trello.

These can be retrieved via the following url: https://trello.com/app-key

Copy the both keys since we will need them later in our Node.js App.

 

Retrieve the API-Key

To be able to perform calls via the Trello API we need the API-Key.

Section on page: API-Key for developers

Type of key: API-Key

Length: 32-characters

 

Retrieve the OAuth / Secret key

Trello supports authorizations via OAuth 1. This via the OAuth / Secret Key.

Before we continue, I would like to mention that this key should be kept secret!

Section on page: OAuth

Type of key: OAuth / Secret Key.

Length: 54-characters

More information according to the API’s and Authorization options can be found on the Trello Developers website: https://developers.trello.com/

 

Build the Node.js Application

Like I mentioned before, the development of this Node.js module will take place inside our Multi Target Application. Just like all the other modules we built so far. At this point you retrieved your Trello Developer API-key and your OAuth Secret Key. These two keys will be necessary to perform authenticated and authorized request to Trello. The Node Module will perform all the requests for us and reply with the Trello information in json-format.

 

Setup the Node Module

To build such a Node Module, go to the root directory of your project and execute the following command to create the directory, and initialize the Node.JS app using “npm” (or use the GUI in combination with the required terminal commands):

cd Trello-CAP-TimesheetManagement
mkdir TrelloAuthorizer
cd TrelloAuthorizer
npm init (keep default configurations)

Now that the “package.json” has been generated by the “npm init” command, we add the following “engines” property to tell our Node app to use a node version of at least “10”.

"engines": {
    "node": "10.x"
}

Next we adjust the “scripts” property, so it looks like this:

"scripts": {
    "start": "node index.js"
}

With this we can start our Node app by running the “npm run start” command. It will execute the “node index.js” command in the background. But of course, we first need to create our “index.js” file. You can use the GUI or execute the following command:

touch index.js

Now that we created our “index.js” file, we will install all the required dependencies for our Node Module. This can be done by executing the following command inside the “TrelloAuthorizer” directory:

npm i express oauth passport node-cache @sap/xssec @sap/xsenv axios querystring

It will install these modules inside your project and they will be displayed inside the “package.json” file under the “dependencies” property.

But what are these modules used for? Let’s sum them up:

 

Module/Package

Purpose

express

The Express philosophy is to provide small, robust tooling for HTTP servers, making it a great solution for single page applications, web sites, hybrids, or public HTTP APIs.”

– Express on NPMjs

https://www.npmjs.com/package/express

 

oauth

“This API allows users to authenticate against OAUTH providers, and thus act as OAuth consumers.”

– node-oauth on NPMjs

https://www.npmjs.com/package/oauth

 

passport

Passport is Express-compatible authentication middleware for Node.js

– Passport on NPMjs

https://www.npmjs.com/package/passport

 

node-cache

“A simple caching module that has set, get and delete methods and works a little bit like memcached. Keys can have a timeout (ttl) after which they expire and are deleted from the cache. All keys are stored in a single object so the practical limit is at around 1m keys.” – Node-Cache on NPMjs

https://www.npmjs.com/package/node-cache

 

@sap/xssec

“Authentication for node applications in XS Advanced relies on a special usage of the OAuth 2.0 protocol, which is based on central authentication at the UAA server that then vouches for the authenticated user’s identity via a so-called OAuth Access Token. The current implementation uses as access token a JSON web token (JWT), which is a signed text-based token following the JSON syntax.”

– SAP Xssec on NPMjs

https://www.npmjs.com/package/@sap/xssec

 

@sap/xsenv

“Utility for easily reading application configurations for bound services and certificates in the SAP Cloud Platform Cloud Foundry environment, SAP XS advanced model and Kubernetes (K8S).”

SAP Xsenv on NPMjs

https://www.npmjs.com/package/@sap/xsenv

axios

 

“Promise based HTTP client for the browser and node.js”

– Axios on NPMjs

https://www.npmjs.com/package/axios

querystring

Node’s querystring module for all engines.

querystring on NPMjs

https://www.npmjs.com/package/querystring

 

When you have a look at the purpose of the modules, you will be able to understand to purpose they serve inside this “TrelloAuhtorizer” Node.JS Module.

  • The “express” module will serve as an http-server, which provides endpoints on which we can request our Trello information.
  • The “oauth” module will allow us to use the OAuth authentication protocol against the Trello API’s to gain access to our information.
  • With the “passport” authentication module, we can add authentication to our “Express” application.
  • With the “node-cache” module we cache our Trello-Access tokens for the desired time.
  • With the “@sap/xssec” module we can use “JWT-tokens”, which are tokens that hold our authentication and authorization information, to gain access via our “uaa” service to our different resources.
  • The “@sap/xsenv” module allows us to read the configuration for our bound services in the Cloud Foundry environment, such as the “xsuaa” service. This module will also allow us to read the credentials from our “default-env.json” file inside the Node.js Module.
  • The “axios” module will allow us to perform http-requests to the required services.
  • Since “axios” is formatting the passed content in the requests as JSON we will have troubles with passing some of our configurations to the request since they need to be query stringed. Therefor we can use the “querstring” module to enforce our right format.

But we still have to add this “default-env.json” inside our “TrelloAuthorizer” directory, and it needs to hold the “xsuaa” information of the local “xsuaa” service we created. This because we want to secure our Node Module as well. For this we bind our local “xsuaa” service via the command pallet, just like we did in the previous blogs. Bind the service and make a valid “default-env.json” file from the generated “.env” file.

Also make sure to delete the “.env” file if you did not rename the file but created a new one.

Your “TrelloAuthorizer” directory inside your MTA project should look like this:

While the “default-env.json” file content should look like this:

Add the minimum code to create an “Express” server inside “index.js” file, so it can be deployed:

const express = require('express');
const app = express();
app.get('/', function (req, res) {
  res.send('Hello World!');
});
const port = process.env.PORT || 3000;
app.listen(port, function () {
  console.log('myapp listening on port ' + port);
});

Add the following module to the “mta.yaml” file, just add the end of the modules section:

- name: TrelloAuthorizer
    type: nodejs
    path: TrelloAuthorizer
    provides:
      - name: TrelloAuthorizer_api
        properties:
          url: "${default-url}"

It will build the “TrelloAuthorizer” Node Module during the deployment, and it will provide/serve an API endpoint, since we want to make our “Express” module/app accessible.

Now we will build our MTA Project again, by executing the following command inside the root directory of our project:

mbt build

Once our project is built, we deploy it again by executing the following command:

cf deploy mta_archives/Trello-CAP-TimesheetManagement_0.0.1.mtar --delete-services

 

User Provided Service/Instance

Create a “User Provided Service/Instance” to store your Trello API Key and secret by executing the following command on the terminal, replace the API-key and secret with your key and secret:

cf cups Trello-API-Keys -p '{"desc": "Trello credentials","api-key": "YOUR TRELLO API KEY","api-secret": "YOUR TRELLO API SECRET", "appName": "TrelloCAPauthorizer", "scope": "read", "expiration": "1hour"}' 

The alias for cf create-user-provided-service is cf cups which makes our command a little less longer. This command will create a “User Provided Service/Instance” with the name “Trello-API-Keys” containing a description and the key and secret. Apart of these keys it will also provide a scope, app name and expiration time for the access token.

Now that we created our service instance, we will bind our “TrelloAuthorizer” to it, this by executing the following command:

cf bind-service TrelloAuthorizer Trello-API-Keys

Now that our application is bound to the “User Provided Service” we can retrieve the information from this service via the following command:

cf env TrelloAuthorizer

This will log all the bounded services their information on the terminal, and we copy the following part inside “VCAP_SERVICES” property and we add it to our “default-env.json” file inside our “TrelloAuthorizer” directory:

"user-provided": [
   {
    "binding_name": null,
    "credentials": {
     "api-key": "YOUR TRELLO API KEY",
     "api-secret": "YOUR TRELLO API SECRET",
     "appName": "TrelloCAPauthorizer",
     "desc": "Trello credentials",
     "expiration": "1hour",
     "scope": "read"
    },
    "instance_name": "Trello-API-Keys",
    "label": "user-provided",
    "name": "Trello-API-Keys",
    "syslog_drain_url": "",
    "tags": [],
    "volume_mounts": []
   }
]

The structure of your “default-env.json” file inside the “TrelloAuthorizer” directory should look like this:

Now we remove all the content from our “index.js” file, inside our “TrelloAuthorizer” directory and we add the following code to it:

const xsenv = require("@sap/xsenv");
xsenv.loadEnv();

const services = xsenv.getServices({ "user-provided": { instance_name: "Trello-API-Keys" } });
console.log(services)

This code will require “@sap/xsenv” to load the Cloud Foundry Environment variables, here locally accessible via the “default-env.json” file. Next it will load the environment with the “xsenv.loadEnv()” function and it will get the service by its type and instance name and logs it to the console.

Next we execute the following command inside our “TrelloAuthorizer” directory to run the Node Module:

npm run start

As you can see the information from our “User Provided Service” is accessible and is logged on the terminal:

Now let us store the key and secret inside some constants, so your code looks like this:

// 1. Retrieve the Trello API key and secret from the user provided instance
const xsenv = require("@sap/xsenv");
xsenv.loadEnv();
const services = xsenv.getServices({ "user-provided": { instance_name: "Trello-API-Keys" } });
console.log(services)
const trelloApiKey = services["user-provided"]["api-key"];
const trelloApiSecret = services["user-provided"]["api-secret"];
const trelloApiAppName = services["user-provided"]["appName"];
const trelloApiScope = services["user-provided"]["scope"];
const trelloApiExpiration = services["user-provided"]["expiration"];

 

Destination Service/Instance

To connect to the Trello API’s, we will need to authenticate and authorize ourselves against the Trello OAuth Services. Trello uses OAuth 1.0. to protect the Trello APIs along with its resources.

Now what is OAuth?

OAuth is an open standard for access delegation, commonly used as a way for Internet users to grant websites or applications access to their information on other websites but without giving them the passwords.” ~ Wikipedia

Like the definition above describes, we can access the Trello data without providing our Trello password. OAuth takes care of this by providing us a token(s), that allow us to access the data.

But how do we retrieve those OAuth tokens? This is done by providing your API-Key and OAuth Secret to the OAuth service. Once you authenticate/authorize yourself with those tokens, you will receive an access token and access token secret that allow you to request your Trello information.

At this point you are able to retrieve your Trello API keys and secrets, but you still need the Trello API endpoints to authorize yourself and get access to the Trello data. We will make those endpoints accessible by using a destination service and destinations in SCP Cloud Foundry. Since we have these Trello APIs providing an authorization page using OAuth 1.0, we will have to grant our application access to our Trello data by confirming the required rights inside this page. Which is the reason I did not put all this API keys and URLs inside a destination, it will make it only more difficult to implement this authorize screen inside our app, or maybe even impossible.

Such a destination service can be created via the command pallet or using the terminal. Let us use the terminal this time, and we choose the “lite” plan and name the service “Trello-CAP-TimesheetManagement-destination-service”:

cf create-service destination lite Trello-CAP-TimesheetManagement-destination-service

With this destination service created, you just allowed yourself to access the information inside your destinations, which we will create right now.

The following endpoints will be used to request our OAuth access tokens.

requestURL https://trello.com/1/OAuthGetRequestToken
accessURL https://trello.com/1/OAuthGetAccessToken
authorizeURL https://trello.com/1/OAuthAuthorizeToken

When requesting those access tokens, we will have to tell the OAuth Service the App Namescope and expiration time of the tokens that can be requested. This means that every token that will be requested is valid for one hour and will only grant you the authorization to read your Trello information. You will not be able to create, update or delete any Trello data. The App Name will be shown in the OAuth screen when you have to approve that the Node Module can read your Trello data for 1 hour.

appName TrelloAuthorizer
scopeRead read
expiration1hour 1hour

Once you retrieved your access tokens, the Trello data can be requested on the following API endpoints. When performing the request, the access tokens will need to be send among the authorization headers of your request.

trelloApiMembersMe https://api.trello.com/1/members/me
trelloApiBoards https://api.trello.com/1/boards

When we try to distinguish the different URLs and hosts, we see that we only have to define the following 2 destinations inside the SCP:

  1. https://trello.com/1
  2. https://api.trello.com/1

Your destinations should look like this:

The first destination has the following configuration:

Property

Value

Name TrelloAPI
Type HTTP
Description Trello API to access the Trello information.
URL https://api.trello.com/1
Proxy Type Internet
Authentication NoAuthentication

The second destination has the following configuration:

Property

Value

Name TrelloOAuth
Type HTTP
Description Destination to authorize users against the Trello APIs.
URL https://trello.com/1
Proxy Type Internet
Authentication NoAuthentication

They both do not use authentication, since we will handle this inside our Node.js Application.

We will use the destination service to only request the URLs from these destinations.

Now we will add one last destination that points to our Approuter. Since we are using OAuth 1.0 we will have to provide a callback URL, for when the permissions to use our Trello information are granted. Trello will redirect us to that URL once we have the permission to consume Trello data.

Property

Value

Name trello-cap-timesheetmanagement-approuter
Type HTTP
Description Destination pointing to the Approuter of the MTA Trello-CAP-TimesheetManagement.
URL https:// {Your-Approuter-URL} (ends with .com)
Proxy Type Internet
Authentication NoAuthentication

We are not using authentication here since our Node App will perform the authentication to our Approuter. We only need to consume the URL of the Approuter here.

In the end you will have the following 3 destinations configured:

Now let us try to access our destinations URLs. Before we can access them we will have to bind our destination service locally using the command pallet again so the “default-env.json” file of our “TrelloAuthorizer” directory looks like this:

Now we will have access to our destination service locally (in the Business Application Studio). Do not forget to remove your generated “.env” file afterwards. With this you successfully setup the Node Module and you are ready to start the development of this “TrelloAuthorizer” Node.js application.

 

Develop the Node Module

Let us tweak our code a little bit so it supports async await (async functions) and merge our previous code along with the code to retrieve information from the destination service. Since we will have quite some functions inside this Node App, we will split them up over different and meaningful files.

But first let us add the code to our “index.js” file. Remove the previous content inside this file and add the following code to it:

const environment = require('./environment');

(async () => {
    // Get the environment variables
    const oEnvironmentVariables = await environment.getVariables();
    console.log(oEnvironmentVariables);
})();

This code will require our “environment.js” file which we will create in a second and offers the possibility to wait via “await” for our result since we start the “index.js” file with “(async () => {})();” Next we use our “environment.js” file it “getVariables()” async function.

Now we have to create this “environment.js” file inside our “TrelloAuthorizer” directory via the GUI or via the following command:

touch environment.js

We add the following code to:

// Require the required modules
const axios = require('axios');
const querystring = require('querystring');
const xsenv = require("@sap/xsenv");

// Load the environment variables
xsenv.loadEnv();

// Access the xsuaa and destination service and build the uaa credentials
const dest_service = xsenv.getServices({ dest: { tag: 'destination' } }).dest;
const uaa_service = xsenv.getServices({ uaa: { tag: 'xsuaa' } }).uaa;
const sUaaCredentials = dest_service.clientid + ':' + dest_service.clientsecret;

// Define the (async) functions to export
module.exports = {
    // Single function to retrieve all variables
    getVariables: async function () {
        const uaaAccessToken = await getUaaAccessToken();
        const aDestinations = await (await getDestiations(uaaAccessToken, ["trello-cap-timesheetmanagement-approuter", "TrelloAPI", " TrelloOAuth"])).map(oDestination => oDestination.data);
        const oEnvironmentVariables = getTrelloKeys();
        oEnvironmentVariables.destinations = aDestinations;
        return oEnvironmentVariables;
    },

};

// Get the UAA access token
async function getUaaAccessToken() {
    try {

        const response = await axios({
            method: "POST",
            url: uaa_service.url + '/oauth/token',
            headers: {
                'Authorization': 'Basic ' + Buffer.from(sUaaCredentials).toString('base64'),
                'Content-type': 'application/x-www-form-urlencoded'
            },
            data: querystring.stringify({
                'client_id': dest_service.clientid,
                'grant_type': 'client_credentials'
            })
        })
        return response.data["access_token"];
    }
    catch (oError) {
        console.log(oError)
    }
}

// Get all destinations defined in the aDestinationNames
async function getDestiations(sAccessToken, aDestinationNames) {
    try {
        const aDestinationPromises = aDestinationNames.map(sDestinationName => new Promise((resolve, reject) => axios({
            method: "GET",
            url: dest_service.uri + '/destination-configuration/v1/destinations/' + sDestinationName,
            headers: {
                Accept: 'application/json',
                Authorization: 'Bearer ' + sAccessToken
            }
        }).then(result => resolve(result)).catch(oError => reject(oError))));
        return Promise.all(aDestinationPromises);
    }
    catch (oError) {
        console.log(oError)
    }
}

// Get the Trello key and secret from the user provided instance
function getTrelloKeys() {
    const services = xsenv.getServices({ "user-provided": { instance_name: "Trello-API-Keys" } });
    const trelloApiKey = services["user-provided"]["api-key"];
    const trelloApiSecret = services["user-provided"]["api-secret"];
    const trelloApiAppName = services["user-provided"]["appName"];
    const trelloApiScope = services["user-provided"]["scope"];  
    const trelloApiExpiration = services["user-provided"]["expiration"];
    return {
        trelloApiKey,
        trelloApiSecret,
        trelloApiAppName,
        trelloApiScope,
        trelloApiExpiration
    }
}

This code contains the following 4 functions:

  1. getVariables”: this function is exported and accessible in other files to retrieve all the variables.
  2. getUaaAccessToken”: this function is not accessible from the outside and will retrieve an access-token from the “xsuaa” service.
  3. getDestiations”: Takes in the access-token from the “getUaaAccessToken” function and an array<String> containing all the destination names that need to be retrieved. This function is also not accessible from the outside.
  4. getTrelloKeys”: Will retrieve the Trello key, secret, app name, scope and expiration defined in the user provided service we created earlier. This function is also not accessible from the outside.

In case you would like some more valuable information about destinations and how to consume them, you can check out the following blogs:

  1. Using the Destination service in the Cloud Foundry Environment

https://blogs.sap.com/2018/10/08/using-the-destination-service-in-the-cloud-foundry-environment/

  1. This function is also not accessible from the outside.

https://blogs.sap.com/2020/05/28/use-the-destination-service-in-nodejs-locally/

  1. Consuming destinations in cloud foundry using axios in a nodejs application

https://blogs.sap.com/2019/11/13/consuming-destinations-in-cloud-foundry-using-axios-in-a-nodejs-application/

With this you finished the implementation to access the environment variables and destination information.

Time to test it! Let’s run the following command inside our “TrelloAuthorizer” directory:

npm run start

As you can see your Trello credentials and destination information is logged on the terminal (I updated and hid mine obviously):

 

Setup the Express Server

Now that you have all your environment variables available, you are ready to setup your express server along with the authentication, Trello OAuth and Node Cache to cache the Trello access tokens (Since we do not want our user to grant access every time he refreshes the page).

We will tweak our code a little bit again in the “index.js”. Replace all your code inside the “index.js” file with the following code:

const environment = require('./environment');
const Trello = require('./trello');

const JWTStrategy = require('@sap/xssec').JWTStrategy;
const passport = require('passport');
const xsenv = require('@sap/xsenv');
const express = require('express');

// Access the xsuaa service
const uaaService = xsenv.getServices({ uaa: { tag: 'xsuaa' } }).uaa;

// Make passport use read and access your JWT Tokens in the xsuaa service.
passport.use(new JWTStrategy(uaaService));

// Create the express server
const app = express();
// Apply the settings on the express server
app.use(passport.initialize());
app.use(passport.authenticate('JWT', {
    session: false
}));

// Trello object reference
this.oTrello = null;

// Run app and listen on port
const port = process.env.PORT || 3002;
app.listen(port, async () => {
    console.log('Trello Authorizer is listening on port: ' + port);

    // Get the environment variables
    const oEnvironmentVariables = await environment.getVariables();

    // Create a Trello object from the Trello Class=
    const oTrello = new Trello(oEnvironmentVariables);
    this.oTrello = oTrello;
});

// -----------------------------------
//  Express Server Endpoints
// -----------------------------------

// Root endpoint with welcome message
app.get("/", function (req, res) {
    res.send("Welcome to the Trello authorization Node.js app of the Trello CAP Timesheet Management project!");
});

app.get("/login", function (req, res) {
    return !this.oTrello.getCache().has(req.user.id) ? this.oTrello.login(req, res) : res.status(200).send({ message: "Already logged in and authorized by Trello." });
}.bind(this));

app.get("/loginCallback", function (req, res) {
    this.oTrello.loginCallback(req, res);
}.bind(this));

app.get('/getUserInfo', function (req, res) {
    // Read cache here already and if found continue, else return the login endpoint function.
    // The req.user.id holds the e-mail-address of the logged in user.
    return this.oTrello.getCache().has(req.user.id) ? this.oTrello.getUserInfo(req, res) : this.oTrello.unauthorizedAgainstTrello(res);
}.bind(this));

app.get("/getAllBoards", function (req, res) {
    return this.oTrello.getCache().has(req.user.id) ? this.oTrello.getAllBoards(req, res) : this.oTrello.unauthorizedAgainstTrello(res);
}.bind(this));

app.get("/getBoardById", function (req, res) {
    return this.oTrello.getCache().has(req.user.id) ? this.oTrello.getBoardById(req, res) : this.oTrello.unauthorizedAgainstTrello(res);
}.bind(this));

app.get("/getCardsByBoardId", function (req, res) {
    return this.oTrello.getCache().has(req.user.id) ? this.oTrello.getCardsByBoardId(req, res) : this.oTrello.unauthorizedAgainstTrello(res);
}.bind(this));

This code will require still the “environment.js” file but will also require the “trello.js” that we will create in a minute.

Further it requires “passport”, the “JWTStrategy” from the “xssec” module and the “xsenv” and “express” module. With these modules we can setup our Express-Server with authentication using our “xsuaa” service. Once the Express-Server has started it will request the environment-variables via our “environment.js” file. Once retrieved it will create a Trello-Object from the “Trello“ Class and it will pass the retrieved environment-variables to the constructor. Once created it will also place it in the invoking object “this” so it is accessible afterwards.

The rest of the code is only providing API-endpoints to request our Trello information on, as well as a login endpoint to authorize ourselves against Trello. For every request on these endpoints a check will be executed, which will check if the user granted access to his Trello information (access tokens in cache or not). If not, he will receive an unauthorized error.

Now we have to create this “trello.js” file inside our “TrelloAuthorizer” directory via the GUI or via the following command:

touch trello.js

This file will export a Class named “Trello” and contains the following code:

// trello.js
'use strict';const OAuth = require('oauth').OAuth;
const NodeCache = require("node-cache");
const url = require('url');

module.exports = class Trello {
    constructor(config) {
        this.config = config;
        this.oAuth = new OAuth(this.getRequestURL(), this.getAccessURL(), this.getTrelloApiKey(), this.getTrelloApiSecret(), "1.0A", this.getLoginCallbackURL(), "HMAC-SHA1");
        this.cache = new NodeCache({
            stdTTL: 3600,
            checkperiod: 60,
            deleteOnExpire: true
        });

    }

    getCache() {
        return this.cache;
    }

    getOauth() {
        return this.oAuth;
    }

    getTrelloApiKey() {
        return this.config.trelloApiKey;
    }

    getTrelloApiSecret() {
        return this.config.trelloApiSecret;
    }

    getRequestURL() {
        return `${this.config.destinations.find(oDest => oDest.destinationConfiguration.Name === "TrelloOAuth").destinationConfiguration.URL}/OAuthGetRequestToken`;
    }

    getAccessURL() {
        return `${this.config.destinations.find(oDest => oDest.destinationConfiguration.Name === "TrelloOAuth").destinationConfiguration.URL}/OAuthGetAccessToken`;
    }

    getAuthorizeURL() {
        return `${this.config.destinations.find(oDest => oDest.destinationConfiguration.Name === "TrelloOAuth").destinationConfiguration.URL}/OAuthAuthorizeToken`;
    }

    getLoginCallbackURL() {
        return `${this.config.destinations.find(oDest => oDest.destinationConfiguration.Name === "trello-cap-timesheetmanagement-approuter").destinationConfiguration.URL}/TrelloAuthorizer/loginCallback`;
    }

getApprouterURL() {
        return `${this.config.destinations.find(oDest => oDest.destinationConfiguration.Name === "trello-cap-timesheetmanagement-approuter").destinationConfiguration.URL}`;
    }

    getTrelloAPI() {
        return this.config.destinations.find(oDest => oDest.destinationConfiguration.Name === "TrelloAPI").destinationConfiguration.URL;
    }

    getTrelloAppName() {
        return this.config.trelloApiAppName;
    }

    getTrelloScope() {
        return this.config.trelloApiScope;
    }

    getTrelloExpiration() {
        return this.config.trelloApiExpiration;
    }

    // Return an unauthorized message to the requestor
    unauthorizedAgainstTrello(res) {
        return res.status(401).send({ message: "Please first authenticate and authorize against the Trello APIs." });
    }

    // Login and fetch tokens (authenticate and authorize)
    login(req, res) {
        this.getOauth().getOAuthRequestToken(function (error, token, tokenSecret) {
            // This key is only needed temporary, it is set here and later on removed again in the callback function with 
            this.getCache().set(token, tokenSecret);
            return res.redirect(`${this.getAuthorizeURL()}?oauth_token=${token}&name=${this.getTrelloAppName()}&scope=${this.getTrelloScope()}&expiration=${this.getTrelloExpiration()}`);
        }.bind(this));
    }

    loginCallback(req, res) {
        //Read query params to get OAuth Access Token
        const query = url.parse(req.url, true).query;
        const token = query.oauth_token;

        // taken() on the cache is the equivalent to calling get(key) + del(key).
        const tokenSecret = this.getCache().take(token);
        const verifier = query.oauth_verifier;
        this.getOauth().getOAuthAccessToken(token, tokenSecret, verifier, function (error, accessToken, accessTokenSecret) {

            // Here we have the accessToken and accessTokenSecret. Put them to the cache. 
            //The expiration (stdTTL) is passed to the constructor of the cache and does not have to be set again.
            // The key is the e-mail-address.

            const oTokens = {
                accessToken: accessToken,
                accessTokenSecret: accessTokenSecret
            };

            // Cache the user his tokens
            this.getCache().set(req.user.id, oTokens);

            // Return the user back to the HTML5 Module
            res.redirect(this.getApprouterURL());
        }.bind(this));
    }

    // Get the userInfo
    getUserInfo(req, res) {
        const cachedUser = this.getCache().get(req.user.id);
        const accessToken = cachedUser.accessToken;
        const accessTokenSecret = cachedUser.accessTokenSecret;
        this.getOauth().getProtectedResource(`${this.getTrelloAPI()}/members/me`, "GET", accessToken, accessTokenSecret, function (error, data) {
            res.send(data);
        });
    }

    // Get all the boards
    getAllBoards(req, res) {
        const cachedUser = this.getCache().get(req.user.id);
        const accessToken = cachedUser.accessToken;
        const accessTokenSecret = cachedUser.accessTokenSecret;

        this.getOauth().getProtectedResource(`${this.getTrelloAPI()}/members/me/boards`, "GET", accessToken, accessTokenSecret, function (error, data) {
            res.send(data);
        });
    }

    // Get a board by id
    getBoardById(req, res) {
        const cachedUser = this.getCache().get(req.user.id);
        const accessToken = cachedUser.accessToken;
        const accessTokenSecret = cachedUser.accessTokenSecret;

        //Read query params => Example: http://localhost:3000/getBoardById?boardId=YOUR-BOARD-ID
        const query = url.parse(req.url, true).query;

        // Read query param key = boardId and YOUR-BOARD-ID = value
        const boardId = query.boardId;

        // Set url 
        const boardUrl = `${this.getTrelloAPI()}/boards/${boardId}`;

        // Get data
        this.getOauth().getProtectedResource(boardUrl, "GET", accessToken, accessTokenSecret, function (error, data) {
            res.send(data);
        });
    }

    // Get a cards by boardId (same logic as above)
    getCardsByBoardId(req, res) {
        const cachedUser = this.getCache().get(req.user.id);
        const accessToken = cachedUser.accessToken;
        const accessTokenSecret = cachedUser.accessTokenSecret;

        const query = url.parse(req.url, true).query;
        const boardId = query.boardId;
        const boardUrl = `${this.getTrelloAPI()}/boards/${boardId}/cards`;


        this.getOauth().getProtectedResource(boardUrl, "GET", accessToken, accessTokenSecret, function (error, data) {
            res.send(data);
        });
    }

}

It requires the “oauth”, “node-cache”, and “url” modules and exports the “Trello” Class like mentioned before. The “node-cach” will also delete the expired tokens from the cache.

The constructor takes in the configuration from the environment-variables that were retrieved.

Further it creates an “OAuth” attribute which uses the “request-url”, “access-url” and “Trello-key” and “Trello-secret” along with the OAuth version “1.0” and the “callback-url” and encryption “HMAC-SHA1”. Most of these configuration parameters are accessible and extend via their getters that are searching the correct configuration in the config object passed to the constructor.

The rest of the code inside this “Trello” Class contains functions that are called when their corresponding Express-Endpoint is called. The “request” and “response” parameters are passed to it, and the access-tokens to retrieve the Trello information are cached.

When the Trello request-token is received the user will be redirected to the Trello Authorization page where he needs to grant access to access his Trello information. Once access is granted the user will be redirected to the URL that was passed to the constructor of the OAuth object. In this case the deployed HTML5 App (via the Approuter).

The other functions are just checking the cache for an access-token (user-specific of course, so he can only retrieve his own boards) and they request the Trello information via the Trello APIs.

 

Consume the TrelloAuthorizer

Now when we would run this “TrelloAuthorizer” by running the following command in the “TrelloAuthorizer” directory (and we expose the service in the Business Application Studio):

npm run start

We would see that we get an “unauthorized” error:

This makes sense since we secured it using passport. That means we have to access the service via our Approuter, which can forward our authentication token, so we have authenticated access to it.

To do so add the following route to the “xs-app.json” file above your “html5-apps-repo-rt” route:

{
    "source": "^/TrelloAuthorizer/(.*)$",
    "target": "$1",
    "authenticationType": "xsuaa",
    "destination": "TrelloAuthorizer_api",
    "csrfProtection": false
}

Your “xs-app.json” file should look like this:

Now we can forward the requests starting with “/TrelloAuthorizer” to our “TrelloAuthorizer” Express-Server. To test this in the Business Application Studio we will have to add this destination and the port it is running on, to our “default-env.json” file inside our “Approuter” directory inside the destinations array like this:

{
    "name": "TrelloAuthorizer_api",
    "url": "http://localhost:3002",
    "forwardAuthToken": true
}

This file should look like this:

Now make sure the “TrelloAuthorizer” service is still running and start the Approuter too.

Once started, navigate to the following URL:

/captrelloTimesheetManager/TrelloAuthorizer/login

This will bring you to the Trello Authorization page with more information about what our app can do with our information. Also, more information is available when you scroll down.

When you press “Allow” you will be redirected to the deployed HTML5 app like we configured.

At this point your tokens were stored and your HTML5 app could make authenticated requests to Trello to receive the Trello Information.

 

Prepare the Node Module for deployment

If we would build and deploy our app again at this point there would be some important “mta.yaml” configurations missing.

We added the “TrelloAuthorizer” Module to our “mta.yaml” already but we did not bind the “xsuaa” service to it. Also, a redeploy of this MTA with “–delete services” would mean that our bound User provided instance would be unbound. That’s why we will also place this user provided instance inside our “mta.yaml” file so it will get bound automatically. Last but not least we will also have to bind our destination instance to our “TrelloAuthoirzer” and define it under the resources.

First, we will place the destination service under the “resources” section like this:

- name: Trello-CAP-TimesheetManagement-destination-service
    type: org.cloudfoundry.managed-service
    parameters:
      service-plan: lite
      service: destination

Next we will also add our User provided service under the “resources” section so it can be created during deployment too. We add it like this:

- name: Trello-API-Keys
    type: org.cloudfoundry.user-provided-service
    parameters:
      path: ./Trello-API-Keys.json

As you can see, we make this point to the “Trello-API-Keys.json” file inside your root project. This file does not exist there yet, so we will create it using the following command inside our root directory:

touch Trello-API-Keys.json

We add the following configuration to it, just like we did when we created it via the command line, do not forget to update it with your Trello API key and secret. Since it would be way more secure to not store credentials inside our project, you can leave it like below and update the keys afterwards via the command line or GUI:

{
    "desc": "Trello credentials",
    "api-key": "YOUR TRELLO API KEY",
    "api-secret": "YOUR TRELLO API SECRET",
    "appName": "TrelloCAPauthorizer",
    "scope": "read",
    "expiration": "1hour"
}

The CF CLI command to update our user provided service is “cf uups” where “uups” is the abbreviation for “update-user-provided-service”, just like we had “cups” for “create- user-provided-service”. Here you would provide the same content as in your “Trello-API-Keys.json” but this time with your credentials of course.

Do note: you can only execute this command after deployment since your service needs to be created first:

cf uups Trello-API-Keys -p ' { "desc": "Trello credentials", "api-key": "YOUR TRELLO API KEY", "api-secret": "YOUR TRELLO API SECRET", "appName": "TrelloCAPauthorizer", "scope": "read", "expiration": "1hour"} '

Next we make sure our “TrelloAuthorizer” Module is extended with the requires sections that requires the “xsuaa”, “destination” and “user provided instance” (Trello-API-Keys) like this:

- name: TrelloAuthorizer
    type: nodejs
    path: TrelloAuthorizer
    provides:
      - name: TrelloAuthorizer_api
        properties:
          url: "${default-url}"
    requires:
      - name: uaa_Trello-CAP-TimesheetManagement
      - name: Trello-CAP-TimesheetManagement-destination-service
      - name: Trello-API-Keys

Last but not least we add the “TrelloAuthorizer_api” that is provided by our Node module to our Approuter, so it can access it when the route in the “xs-app.json” file is matched. We do this by adding the following configuration to the “requires” section of the Approuter:

- name: TrelloAuthorizer_api
        group: destinations
        properties:
          forwardAuthToken: true
          name: TrelloAuthorizer_api
          url: "~{url}"

Your Approuter in the “mta.yaml” file should look like this:

 

Deploy the MTA Project

Time to build our MTA project again by running the following command in the root directory of our project:

mbt build

Once it is built, we deploy it again by running the following command in the root directory of our project:

cf deploy mta_archives/Trello-CAP-TimesheetManagement_0.0.1.mtar --delete-services

If you did not pass your keys in the “Trello-API-Keys.json” file before, now would be the time to execute the user provided service update command mentioned earlier.

Open the deployed Approuter URL again and navigate to the following URL:

/TrelloAuthorizer/login

You will see that you get the following error if you did not pass the credentials to the “Trello-API-Keys.json”:

The reason for that is because when the Node app will start the “Express Server”, it will fetch the Trello Keys from our User Provided Instance immediately. When we then update this service via the command line with our keys, the key placeholders were already retrieved by the Node app. After updating the keys, we just restart the app via the GUI or via the following command, so the Node app fetches the Trello Keys again and they are now provided:

cf restart TrelloAuthorizer

When we refresh the page, we will see the following Trello Authorization screen again:

If you like you can replace the icon next to your user image where you retrieved your Trello keys.

Grant access by pressing the “Allow” button and you will be redirected to the HTML5 app.

 

Wrap Up 🎁

In this blog we setup, configured and implemented our “TrelloAurhorizer” Node.JS Module. This Module contains the Express-Server which exposes the required API-Endpoints to connect to the Trello APIs via OAuth. It caches the access-tokens, so they can be reused, and the user does not have to grant access for every request to his Trello Information. We use the User Provided Instance on Cloud Foundry to store our Trello API key and secret along with the scopes and expiration time of the access-tokens. This because the “Credential Store” service on SCP is not accessible for the moment. Last but not least we use the destination service to retrieve the Trello API and OAuth endpoints from our SCP destinations.

Maybe one important tip here, in case you would need all or a bunch of the Trello API’s. You might want to adjust the setup of this Node Module a little, so you actually proxy your request via the “Express” Module to the Trello APIs. This way you do not have to foresee an endpoint for all the available Trello API’s inside your “Express” server. Write a wrapper that handles your authentication and filters and you will have a way cleaner and smaller setup in the end.

We described the following requirements from the beginning and implemented the following ones:

  • A Multi Target Application (MTA) project
  • An Approuter
  • An xsuaa instance/service
  • Appropriate roles assigned by role collections (xsuaa)
  • A HANA database (via CDS)
  • An OData V4 service (via CDS)
  • A Node.js application to authenticate and authorize against Trello
  • A User-Provided Service to store the Trello API Keys.
  • A UI5 App
  • A Fiori Launchpad Module

See you in the next blog Timesheet Management with CAP & Trello ⏱️ – Build the Trello Timesheet HTML5 Module #4. Where we will consume our OData V4 Service along with our Trello information via our just created “TrelloAuthorizer” app.

Kind regards,

Dries

Be the first to leave a comment
You must be Logged on to comment or reply to a post.