NEEDS_AUTHENTICATION_ERR on API call when handling an event

I am — a newbie — creating a basic Forge app for Confluence in order to learn what is going on. And I am now stumped. I was trying to solve this in forge logs:

ERROR   2023-03-19T10:30:04.500Z fc537d6d-529c-46e4-8dcf-c1d11a3f60bc {
  message: 'Authentication required',
  name: 'NEEDS_AUTHENTICATION_ERR',
  stack: ''
}

And in order to do that, I tried to add all kinds of console.log statements. The simple trial app creates a macro with config and it listens to events.

I got this error above when I tried to read the space info when handling an event and I would like to understand why.

manifest.yml:

modules:
  macro:
    - key: dl-spike-one-decision-story
      function: dl-decision-story
      title: DL Decision Story
      description: Inserts 6-row standard Decision Story
      config:
        function: dl-decision-story-edit
  trigger:
    - key: dl-spike-one-trigger-test
      function: dl-trigger-test
      events:
        - avi:confluence:viewed:page
        - avi:confluence:updated:page
        - avi:confluence:created:page
  function:
    - key: dl-decision-story
      handler: index.dl_renderDecisionStory
    - key: dl-decision-story-edit
      handler: index.dl_configDecisionStory
    - key: dl-trigger-test
      handler: index.dl_handleTriggerTest
app:
  id: ari:cloud:ecosystem::app/[snip]
permissions:
  scopes:
    - read:confluence-content.summary
    - read:confluence-content.all
    - read:page:confluence
    - storage:app
    - read:space:confluence

index.jsx:

import api, { route, storage } from "@forge/api";
import ForgeUI, { render, Fragment, Heading, MacroConfig, Text, Macro, Table, Head, Row, Cell, useProductContext, useState, useConfig, TextField, TextArea } from '@forge/ui';

// We are using forge's storage API. This data is only available in this app on this site
const getGlobalDLDict = async ( spaceKey, calledfrom) => {
  console.log( `getGlobalDLDict ${spaceKey} ${calledfrom}`);
  const dlDict = await storage.get( 'DLAppValues');
  if (dlDict === undefined) {
    console.log( `getGlobalDLDict: no storage defined`);
    const space = await fetchSpace(spaceKey, calledfrom);
    console.log(`getGlobalDLDict: All info about my space: ${JSON.stringify(space, null, 2)}`);
    return {test: 'DICT TEST VALUE'};
  }
  else {
    console.log(`getGlobalDLDict exists: All info about my storage: ${JSON.stringify(dlDict, null, 2)}`);
  }
  return dlDict;
};

const fetchSpace = async (spaceKey, calledfrom) => {
  console.log( `fetchSpace ${spaceKey} ${calledfrom}`);
  const res = await api
    .asUser()
    .requestConfluence(route`/wiki/api/v2/spaces?keys=${spaceKey}`);

  const data = await res.json();
  return data.results;
};

const decisionStoryLabels = {
[snip]
};

const defaultDecisionStory = {
[snip]
};

const DL_DecisionStory_Config = () => {
  console.log( `DL_DecisionStory_Config`);
  return (
    <MacroConfig>
[snip]
    </MacroConfig>
  );
};

const DL_DecisionStory_Render = () => {
  console.log(`DL_DecisionStory_Render`);
  const context = useProductContext();
  const decisionStoryConfig = useConfig() || defaultDecisionStory;

  const [space] = useState(async () => await getGlobalDLDict(context.spaceKey, 'render'));
  console.log(`DL_DecisionStory_Render: All info about my dict: ${JSON.stringify(space, null, 2)}`);

  return (
    <Table>
[snip]
    </Table>
  );
};

export const dl_renderDecisionStory = render(<Macro app={<DL_DecisionStory_Render />}/>);
export const dl_configDecisionStory = render(<DL_DecisionStory_Config />);

export async function dl_handleTriggerTest( event, context) {
  console.log( `dl_handleTriggerTest`);
  const myDict = await getGlobalDLDict( event.content.space.key, 'trigger');
  console.log(`All info about my DLDict: ${JSON.stringify(myDict, null, 2)}`);
};

The log says (code colouring is off here):

INFO    2023-03-19T11:16:14.808Z 10971370-8992-4a5b-b52c-7075f0ff92ff DL_DecisionStory_Render
INFO    2023-03-19T11:16:14.809Z 10971370-8992-4a5b-b52c-7075f0ff92ff getGlobalDLDict DLDEV render
INFO    2023-03-19T11:16:14.889Z 10971370-8992-4a5b-b52c-7075f0ff92ff getGlobalDLDict: no storage defined
INFO    2023-03-19T11:16:14.889Z 10971370-8992-4a5b-b52c-7075f0ff92ff fetchSpace DLDEV render
INFO    2023-03-19T11:16:15.094Z 10971370-8992-4a5b-b52c-7075f0ff92ff getGlobalDLDict: All info about my space: [
[snip]
]
INFO    2023-03-19T11:16:15.094Z 10971370-8992-4a5b-b52c-7075f0ff92ff DL_DecisionStory_Render
INFO    2023-03-19T11:16:15.095Z 10971370-8992-4a5b-b52c-7075f0ff92ff DL_DecisionStory_Render: All info about my dict: {
  "test": "DICT TEST VALUE"
}
INFO    2023-03-19T11:16:15.722Z 34630c61-bafa-4b52-a068-6bff42f913ac dl_handleTriggerTest
INFO    2023-03-19T11:16:15.722Z 34630c61-bafa-4b52-a068-6bff42f913ac getGlobalDLDict DLDEV trigger
INFO    2023-03-19T11:16:15.787Z 34630c61-bafa-4b52-a068-6bff42f913ac getGlobalDLDict: no storage defined
INFO    2023-03-19T11:16:15.788Z 34630c61-bafa-4b52-a068-6bff42f913ac fetchSpace DLDEV trigger
ERROR   2023-03-19T11:16:15.790Z 34630c61-bafa-4b52-a068-6bff42f913ac {
  message: 'Authentication required',
  name: 'NEEDS_AUTHENTICATION_ERR',
  stack: ''
}

My next guess is that I cannot use async/await when handling an event, but I wonder how to solve than that in handling the event I need space info.

When coming from a trigger, there is no user, so fetchSpace must be called .asApp()

const fetchSpace = async (spaceKey, calledfrom) => {
  console.log( `fetchSpace ${spaceKey} ${calledfrom}`);
  if (calledfrom === 'trigger') {
    const res = await api
    .asApp()
    .requestConfluence(route`/wiki/api/v2/spaces?keys=${spaceKey}`);

    const data = await res.json();
    console.log( `fetchSpace: will return`);
    return data.results;
  }
  else {
    const res = await api
    .asUser()
    .requestConfluence(route`/wiki/api/v2/spaces?keys=${spaceKey}`);
2 Likes