MDRTKNAPI RPGLE

The following source code example is available in MDRST/EXAMPLE/MDRTKNAPI. It's is an API which accepts a POST method and can be used to carry out the various tasks associated with creating and managing tokens

**FREE
// This example API shows how to create the token

// CRTSQLRPGI PGM(MDRTST14/MDRTKNAPI) SRCFILE(MDRTST14/EXAMPLE) -
// SRCMBR(MDRTKNAPI) OBJTYPE(*PGM) DBGVIEW(*SOURCE) TGTRLS(*CURRENT)

ctl-opt dftactgrp(*no) actgrp(*caller) option(*srcstmt)
        bnddir('MDRFRAME');

/copy MDRFRAME
/copy MDRTOKEN
/copy MDRYAJL_H

dcl-ds dftExpiry dtaara('MDRTKNEXP') qualified;
  tokenExpPeriod char(10);
  expUnit char(10);
  refreshExpPeriod char(10);
end-ds;

dcl-pi *n;
  handle    like(MDR_Handle_t);
  method    char(32) const;
  body      varchar(500000) ccsid(*utf8); // MAxSize: 16000000
end-pi;

select;
  when method = 'POST';
    mdr_post();
endsl;

*inlr = *on;
return;

//----------------------------------------------------------------------------
// Process post Method
//----------------------------------------------------------------------------
dcl-proc mdr_post;

  // Define common use variables
  dcl-s errorMsg varchar(100:4);
  // Variable declaration to parse json string
  dcl-s docNode pointer;

  // Variables used to fetch Json generated by YAJL functions
  dcl-s jsonDataPtr pointer;
  dcl-s jsonPtrLen uns(10);
  dcl-ds creds likeds(MDRDCRED_t);
  dcl-s errMsg varchar(200);
  dcl-s newEpochTime int(20);
  dcl-s baseEpochTime int(20);
  dcl-s success ind;
  dcl-s setSeconds ind;
  dcl-s claimsExpFound ind;
  dcl-s claimsIatFound ind;
  dcl-s LocalIdx1 int(10);
  dcl-s Entrykeyval varchar(1024);
  dcl-s KeyNodePtr pointer;
  dcl-s ExpDuration int(10);

  dcl-ds claims_ds qualified dim(99);
    key varchar(50);
    value varchar(4096);
  end-ds;

  // no of clains in JWT claims payload
  dcl-s claimsCount int(5) inz(1);

    // Clear the credentials data struture first
  clear creds;

  // Fetch the header parameters
  // jwtalgorithm can have below possible values
  // RS256-DCM : Create RS256 token using certificate from DCM application
  //             in digital certificate manager of your IBMi machine
  // RS256-KST: Create RS256 using keystore file. It requires keystore file
  //            library and label
  // RS256-PVT: Create RS256 token using public and private keys. Not yet
  //            implemented
  // HS256:     Create HS256 token. It requires clientSecret as 32 byte key for
  //            encryption
  // UUID:      Create a token as random 36 byte characters
  if MDR_getHeader(handle: 'x-jwtalgo') <> '*NOTFOUND';
    creds.algor = MDR_getHeader(handle: 'x-jwtalgo');
  endif;

  // Client Id and application ID are any unique identifiers for making the
  // DB entry for the token being generated
  if MDR_getHeader(handle: 'x-clientId') <> '*NOTFOUND';
    creds.clientid = MDR_getHeader(handle: 'x-clientId');
  endif;

  if MDR_getHeader(handle: 'x-appId') <> '*NOTFOUND';
    creds.appid = MDR_getHeader(handle: 'x-appId');
  endif;

  // DCM application name from digital certificate manager. Required when
  // RS256-DCM algorithm is used
  if MDR_getHeader(handle: 'x-dcmApp') <> '*NOTFOUND';
    creds.dcmapp = MDR_getHeader(handle: 'x-dcmApp');
  endif;

  // Token type can be JWT: Json Web Token, PAT: Personal Access Token or
  // JWS: Json Web Signature
  if MDR_getHeader(handle: 'x-tokenType') <> '*NOTFOUND';
    creds.tkntyp = MDR_getHeader(handle: 'x-tokenType');
  endif;

  // Client Secret is any 32 byte string required when HS256 algorithm is
  // supplied for token generation
  if MDR_getHeader(handle: 'x-clientSecret') <> '*NOTFOUND';
    creds.cliSecret = MDR_getHeader(handle: 'x-clientSecret');
  endif;

  // Assign the JSON request body as claims
  creds.claims = body;

  // Below three blocks show the retrieval of token expiry unit, auth token
  // expiry and refresh token expiry. You can also supply them fixed values
  // instead requesting from the API user to provide these values

  // Extract expiry unit from the http header. It can be "*seconds" (or "*s"),
  // "*minutes" (or "*m"), "*hours" (or "*h"), "*days" (or "*d")
  creds.expiryUnit = MDR_getHeader(handle:'x-expiryUnit');
  if creds.expiryUnit <> '*seconds' and creds.expiryUnit <> '*minutes' and
     creds.expiryUnit <> '*hours' and creds.expiryUnit <> '*days' and
     creds.expiryUnit <> '*s' and creds.expiryUnit <> '*m' and
     creds.expiryUnit <> '*h' and creds.expiryUnit <> '*d';

    // If invalid token expiry has been supplied, use *seconds. We can
    // also throw an error alternatively depending on requirement
    clear creds.expiryUnit;
  endif;

  // Supply auth token expiry and refresh token expiry. If not supplied,
  // auth token expiry is taken as 180 seconds and refresh token expiry
  // as one day and the unit value is calculated in seconds
  if MDR_getHeader(handle: 'x-authTokenExpiry') <> '*NOTFOUND';
    monitor;
      creds.atokenExp = %dec(MDR_getHeader(handle:'x-authTokenExpiry'):12:0);
    on-error;
      // Just in case non-numeric value is supplied from header, set to 180
      // and likewise, set the expiry unit as "*seconds" even if any other
      // expiryUnit value is supplied from query parameter
      creds.atokenExp = 0;
    endmon;
  else;
    // Query parameter for authTokenExpiry not supplied. Set to some default
    // value which is being set as 180 seconds in this example
    creds.atokenExp = 0;
  endif;

  if MDR_getHeader(handle: 'x-refreshTokenExpiry') <> '*NOTFOUND';
    monitor;
      creds.rtokenExp = %dec(MDR_getHeader(handle:'x-refreshTokenExpiry'):12:0);
    on-error;
      // If non-numeric value is supplied for the refresh token, set as 1800 seconds
      creds.rtokenExp = 0;
    endmon;
  else;
    // Query parameter for refreshTokenExpiry not supplied. Set to some default
    // value which is being set as 1800 seconds in this example
    creds.rtokenExp = 0;
  endif;

  // Fetch data area if the auth or refresh token expiry not supplied or
  // expiry duration is blank
  if creds.atokenExp = *zeros or creds.rtokenExp = *zeros or
     creds.expiryUnit = *blanks;
    In(e) dftExpiry;
    if %error;
      errorMsg = 'MDRTKNEXP data area not found in *LIBL';
      MDR_setError( handle: 'MDRTKNAPI': errorMsg: 166: 30 : 221);
      *inlr = *On;
      return;
    endif;
  endif;

  if creds.expiryUnit = *blanks;
    creds.expiryUnit = dftExpiry.expUnit;
  endif;

  // If for some reason, the expiry unit is still blank, set it to *seconds
  if creds.expiryUnit = *blanks;
    creds.expiryUnit = '*seconds';
  endif;

  if creds.atokenExp = *zeros;
    monitor;
      ExpDuration = %dec(dftExpiry.tokenExpPeriod:10:0);
    on-error;
      ExpDuration = 0;
    endmon;
    creds.atokenExp = ExpDuration;
  endif;

  if creds.rtokenExp = *zeros;
    monitor;
      ExpDuration = %dec(dftExpiry.refreshExpPeriod:10:0);
    on-error;
      ExpDuration= 0;
    endmon;
    creds.rtokenExp = ExpDuration;
  endif;

  // Logic to parse request body. The purpose here is just to make sure
  // its a valid JSON and then extract claims info

  if body <> *blanks;
    docNode = MDR_tree_parse( handle: *omit: *omit: errorMsg);
  endIf;

  if body <> *blanks and docNode = *null;
    MDR_setError( handle: 'MDRTKNAPI': errorMsg: 211: 30 : 433);
    *inlr = *On;
    return;
  endIf;

  // Thsi logic exttrcats all of the name value pairs
  dow Yajl_Object_loop2(docNode:LocalIdx1:Entrykeyval:KeyNodePtr);
    claims_ds(claimsCount).key = entryKeyVal;
    if yajl_is_number(keyNodePtr);
      claims_ds(claimsCount).value = yajl_getNumStr(keyNodePtr);
    elseif yajl_is_string(keyNodePtr);
      claims_ds(claimsCount).value = yajl_get_string(keyNodePtr);
    elseif yajl_is_true(keyNodePtr);
      claims_ds(claimsCount).value = 'true';
    elseif yajl_is_false(keyNodePtr);
      claims_ds(claimsCount).value = 'false';
    elseif yajl_is_null(keyNodePtr);
      claims_ds(claimsCount).value = 'null';
    endif;

    // validate claims exp value to be valid and greater than current
    // epoch timestamp
    if entryKeyVal = 'exp';
      newEpochTime = CvtToEpochTimestamp(%timestamp());
      claimsExpFound = *On;
      monitor;
        creds.claimsExp = %dec(claims_ds(claimsCount).value:10:0);
      on-error;
        creds.claimsExp = *zeros;
        errorMsg = 'Invalid claim value submitted: conversion failed';
        MDR_setError( handle: 'MDRTKNAPI': errorMsg: 237: 30 : 221);
        *inlr = *On;
        return;
      endmon;

      if creds.ClaimsExp < newEpochTime;
        errorMsg = 'Invalid claim value submitted: exp is less than current time';
        MDR_setError( handle: 'MDRTKNAPI': errorMsg: 246: 30 : 221);
        *inlr = *On;
        return;
      endif;
    endif;

    // validate claims iat value to be valida and greater than the
    // timestamp after 1970-01-01-00.00.00.000000
    if entryKeyVal = 'iat';
      baseEpochTime = CvtToEpochTimestamp(%timestamp('1970-01-01-00.00.00.000000'));
      newEpochTime = CvtToEpochTimestamp(%timestamp()+ %years(1));
      claimsIatFound = *On;
      monitor;
        creds.claimsIat = %dec(claims_ds(claimsCount).value:10:0);
      on-error;
        creds.claimsIat = *zeros;
        errorMsg = 'Invalid claim value submitted: iat';
        MDR_setError( handle: 'MDRTKNAPI': errorMsg: 261: 30 : 221);
        *inlr = *On;
        return;
      endmon;
      if creds.ClaimsIat < baseEpochTime or creds.ClaimsIat > newEpochTime or
         %scan('.':claims_ds(claimsCount).value) > *zeros or creds.claimsIat < 0;
        errorMsg = 'Invalid claim value submitted: iat';
        MDR_setError( handle: 'MDRTKNAPI': errorMsg: 269: 30 : 221);
        *inlr = *On;
        return;
      endif;
    endif;
    claimsCount += 1;
  enddo;

  // If both the "iat" and "exp" are not found
  if claimsIatFound = *Off and claimsExpFound = *Off;
    errorMsg = 'Mandatory claims are either: iat OR exp';
    MDR_setError( handle: 'MDRTKNAPI': errorMsg: 281: 30 : 221);
    *inlr = *On;
    return;
  elseif claimsExpFound = *off;
    ExpDuration = creds.atokenExp;

    creds.claimsExp = CalculateNewEpochTime(creds.claimsIat:ExpDuration:
                      creds.expiryUnit);
  endif;

  // At this point, all the values are loaded, proceed to create the token
  success = MDR_createToken(creds:errMsg);

  // Logic to generate response
  // Start writing the JSON using YAJL functions.
  mdr_startJson(*OFF:*OFF);
  mdr_beginObject();
  if success = *On;
    mdr_addChar('authToken':creds.authtoken);
    mdr_addNum('authTokenExpiry':%char(creds.atokenexp));
    mdr_addChar('refreshToken':creds.refreshtkn);
    mdr_addNum('refreshTokenExpiry':%char(creds.rtokenexp));
    mdr_addChar('expiryUnit':%trim(creds.expiryUnit));
  else;
    mdr_addChar('status':'Token generation failed');
    mdr_addChar('message':%trim(errMsg));
  endif;
  mdr_endObject();

  // Retrieve JSON loaded in buffer memory
  mdr_getJsonptr(jsonDataPtr:jsonPtrLen);

  // Write the JSON data to standard output as response
  mdr_len_write(handle:jsonDataPtr:jsonPtrLen);

  // Release the memory allocated by JSON generator
  mdr_endJson();
end-proc;