Help creating a pack for portal.io

Hi there,

I was wondering if someone could guide me creating a pack for Portal.io which is a proposal software for audio, video and home automation companies.

The API documentation is here:
https://portalio.redoc.ly/

I was able to authenticate and send requests in postman. However, I’m struggling finding out how to translate that to the coda pack javascript language. Specifically the authentication part and signature generation. I think I could add the formulas after that, but the authentication to get the user key and then start using it in all the requests as well as the signature, I just didn’t know how to translate that to the pack language.

Any help would be greatly appreciated!!

Thanks

M

Hi @Martin_Vilches - As far as I can tell this API’s authentication mechanism isn’t compatible with the Packs SDK, for two separate reasons:

  1. The only form of token exchange supported by the SDK is OAuth2.
  2. The authentication requires that a signature be generated for each request, which includes the user’s secret. Pack code doesn’t have direct access to the secrets, and they are injected into the outgoing request by the platform. There is no way however to inject into a signature string however.

At the moment I think the only workaround, which isn’t ideal, is for the user to pass their API key as a parameter to each formula / sync table. Then you would have direct access to the value and could generate the signature from it.

Hi @Eric_Koleda ! Thanks for your reply.
Yes, the signature process is where I got stuck. I did try to insert everything manually just to confirm I could connect to portal and I was able to get my proposal lists. However, in this manual process I had to generate the signature externally and paste it just to test it.

This is the code I used:

import * as coda from "@codahq/packs-sdk";

export const pack = coda.newPack();

// Set up the authentication parameters
pack.addNetworkDomain('api.portal.io');

pack.setUserAuthentication({
  type: coda.AuthenticationType.Custom,
  params: [
    {
      name: 'X-MSS-API-APPID',
      description: 'Enter your API App ID',
    },
    {
      name: 'X-MSS-CUSTOM-DATE',
      description: 'Enter the custom date in GMT',
    },
    {
      name: 'X-MSS-SIGNATURE',
      description: 'Enter the API signature',
    },
    {
      name: 'X-MSS-API-USERKEY',
      description: 'Enter your API user key',
    },
  ],
  instructionsUrl: 'https://portalio.redoc.ly/',
});

// Define the schema for your sync table
const ProposalSchema = coda.makeObjectSchema({
  properties: {
    id: { type: coda.ValueType.String, fromKey: 'id' },
    title: { type: coda.ValueType.String, fromKey: 'name' },
    status: { type: coda.ValueType.String, fromKey: 'status' },
    proposalTotal: { type: coda.ValueType.Number, fromKey: 'total.proposalTotal' },
    createdDate: { type: coda.ValueType.String, fromKey: 'createdDate' },
    lastModifiedDate: { type: coda.ValueType.String, fromKey: 'lastModifiedDate' },
    customerName: { type: coda.ValueType.String, fromKey: 'customer.firstName' },
  },
  idProperty: 'id',
  displayProperty: 'title',
});

// Add the sync table
pack.addSyncTable({
  name: 'Proposals',
  description: 'Sync table to fetch proposals from API',
  identityName: 'Proposal',
  schema: ProposalSchema,
  formula: {
    name: 'SyncProposals',
    description: 'Fetches proposals from the API',
    parameters: [
      coda.makeParameter({
        type: coda.ParameterType.String,
        name: 'X_MSS_API_APPID',
        description: 'Enter your API App ID',
      }),
      coda.makeParameter({
        type: coda.ParameterType.String,
        name: 'X_MSS_CUSTOM_DATE',
        description: 'Enter the custom date in GMT',
      }),
      coda.makeParameter({
        type: coda.ParameterType.String,
        name: 'X_MSS_SIGNATURE',
        description: 'Enter the API signature',
      }),
      coda.makeParameter({
        type: coda.ParameterType.String,
        name: 'X_MSS_API_USERKEY',
        description: 'Enter your API user key',
      }),
    ],
    execute: async function (args, context) {
      const [X_MSS_API_APPID, X_MSS_CUSTOM_DATE, X_MSS_SIGNATURE, X_MSS_API_USERKEY] = args;

      const url = 'https://api.portal.io/public/proposals';
      const response = await context.fetcher.fetch({
        method: 'GET',
        url: url,
        headers: {
          'X-MSS-API-APPID': X_MSS_API_APPID,
          'X-MSS-CUSTOM-DATE': X_MSS_CUSTOM_DATE,
          'X-MSS-SIGNATURE': X_MSS_SIGNATURE,
          'X-MSS-API-USERKEY': X_MSS_API_USERKEY,
        },
      });

      const proposals = response.body.proposals; // Access the proposals array within the response body

      return proposals.map(proposal => ({
        id: proposal.id.toString(),
        title: proposal.name,
        status: proposal.status,
        proposalTotal: proposal.total.proposalTotal,
        createdDate: proposal.createdDate,
        lastModifiedDate: proposal.lastModifiedDate,
        customerName: proposal.customer.firstName,
      }));
    },
  },
});

Of course this code is not usable since I can’t rely on generating the signatuare manually and externally.
What would be the way that you suggested (even though as you said, it is not ideal) to pass the information as parameters?

Any help would be greatly appreciated!

Best,
MV

How about creating two packs:

Portal.io Auth Pack authenticates with username/password, user key, secret key and has a single formula to generate the user key

Portal.io Pack uses the user key from the auth pack

@Martin_Vilches - Upon re-reading the API docs I think you could actually do this without passing the credentials as parameters, but it would require approval to use Custom auth. It would involve:

  • Use Custom auth to collect the username and password.
  • At the start of every request, exchange the username and password for a user token.
  • Use the user token to authenticate the requests you need to make to the API.

When you exchange the username and password for the user token the signature doesn’t include the credentials, they are passed in the body. The resulting user token will be returned to your code directly, so it can use it when generating later signatures.

Thanks. Your are correct.
The user and password to obtain the user key is something I would do externally since the user key does not change. Of course, that is not ideal (specially if other portal users would like to use the pack eventually). But at least to start, that could be handled that way. The problem I have is the generation of the HMAC SHA256 signature.

Thanks!

Ah, yes. To generate that signature you’ll need to use a library like jsrsasign, and libraries can only be used when you develop from the command line. Here’s a quick sample showing how to generate an HMAC SHA256 signature using that library in a Pack:

Thank you Erick! I’ll give it a try!

Best
MV