Security Question: Confluence Macro with CustomUI and resolver security checks

I have a specific question about security concerning my backend resolver in a CustomUI forge confluence macro.

My CustomUI macro looks like this:

import { invoke } from '@forge/bridge';
...
  useEffect(() => {
    invoke('init', undefined).then((context: any) => setContext(context));
  }, []);


  return (
    <div>{context ? context.foo : 'loading'}</div>>
  );

My backend resolver looks like this:

import Resolver from '@forge/resolver';
import { MyParser  } from './generated-common';

const resolver = new Resolver();

resolver.define('init', async (req) => {
  // FIXME: DO I NEED TO CHECK FOR PAGE PERMISSIONS HERE?
  // FIXME: DO I NEED TO CHECK FOR USER IS LOGGED IN HERE?

  if (req.context && req.context && req.context.extension && req.context.extension.config) {
    const parser = new MyParser();
    return await parser.parseFrooFromContext(req.context); // It basically gets the macroConfig from the context
  }
  return null;
});

export const handler = resolver.getDefinitions();

So the question is, do I need to:

  • (a) Check if the user has page view permissions?
  • (b) Check if the user is logged in?

What is the appropriate security check for a macro backend resolver? Is this somehow directly callable anonymously via some GraphQL endpoint?

Or is this by design only callable when someone actually views the confluence page and is somehow magically secure by default?

I would really love to hear answers on this :slight_smile:

Because when using UI kit or UI Kit2, you simply say “useProductContext” and in the docs are also no additional security guards. …

I am tempted to do something like this (which will make public confluence installations with anonymous user break):

import Resolver from '@forge/resolver';
import { MyParser  } from './generated-common';

const resolver = new Resolver();

resolver.define('init', async (req) => {
  // AUTH GUARD
  const bodyData = `{
    "subject": {
      "type": "user",
      "identifier": req.context.accountId
    },
    "operation": "read"
  }`;
  const response = await api.asUser().requestConfluence(route`/wiki/rest/api/content/${req.context.contentId}/permission/check`, {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    body: bodyData
  });

  // RETURN ERROR IF NO PERMS 
 .....

  if (req.context && req.context && req.context.extension && req.context.extension.config) {
    const parser = new MyParser();
    return await parser.parseFrooFromContext(req.context); // It basically gets the macroConfig from the context
  }
  return null;
});

export const handler = resolver.getDefinitions();
1 Like