heyxyz / hey

Hey is a decentralized and permissionless social media app built with Lens Protocol 🌿
https://hey.xyz
GNU Affero General Public License v3.0
23.11k stars 1.52k forks source link

Skip signFrameAction for anonymous frames #5066

Closed defispartan closed 1 month ago

defispartan commented 1 month ago

Is there an existing issue for this?

Current Behavior

A Frame is considered valid to be rendered by a Lens application if it contains of:accepts:lens or of:accepts:anonymous.

Hey currently supports rendering Frame with of:accepts:anonymous but currently performs the same signFrameAction process which is unnecessary.

Expected Behavior

If a Frame specifies of:accepts:anonymous, which is passed through the Hey Frame API endpoint as the unauthenticated field, the signFrameAction step should be skipped before POST request to Frame server.

Steps To Reproduce

Example Frames with of:accepts:anonymous:

What platform(s) does this occur on?

Web, Mobile

What browser(s) does this occur on?

Chrome, Firefox, Brave, Safari, Edge

Anything else?

The of:accepts:anonymous tag was just recently merged to the Open Frames standard so it can be considered as stable tag for Open Frames

bigint commented 1 month ago

@defispartan can you help me with the fix here 🙇🏼

import type { ButtonType } from '@hey/types/misc';
import type { Request, Response } from 'express';

import { IS_MAINNET } from '@hey/data/constants';
import logger from '@hey/helpers/logger';
import parseJwt from '@hey/helpers/parseJwt';
import axios from 'axios';
import { parseHTML } from 'linkedom';
import catchedError from 'src/helpers/catchedError';
import { HEY_USER_AGENT } from 'src/helpers/constants';
import signFrameAction from 'src/helpers/frames/signFrameAction';
import { rateLimiter } from 'src/helpers/middlewares/rateLimiter';
import validateLensAccount from 'src/helpers/middlewares/validateLensAccount';
import getFrame from 'src/helpers/oembed/meta/getFrame';
import { invalidBody, noBody } from 'src/helpers/responses';
import { number, object, string } from 'zod';

type ExtensionRequest = {
  buttonAction?: ButtonType;
  buttonIndex: number;
  inputText?: string;
  postUrl: string;
  pubId: string;
  state?: string;
};

const validationSchema = object({
  buttonAction: string().optional(),
  buttonIndex: number(),
  postUrl: string(),
  pubId: string()
});

export const post = [
  rateLimiter({ requests: 100, within: 1 }),
  validateLensAccount,
  async (req: Request, res: Response) => {
    const { body } = req;

    if (!body) {
      return noBody(res);
    }

    const validation = validationSchema.safeParse(body);

    if (!validation.success) {
      return invalidBody(res);
    }

    const { buttonAction, buttonIndex, inputText, postUrl, pubId, state } =
      body as ExtensionRequest;

    try {
      const accessToken = req.headers['x-access-token'] as string;
      const identityToken = req.headers['x-identity-token'] as string;
      const payload = parseJwt(identityToken);
      const { id } = payload;

      const request = {
        actionResponse: '',
        buttonIndex,
        inputText: inputText || '',
        profileId: id,
        pubId,
        specVersion: '1.0.0',
        state: state || '',
        url: postUrl
      };

      const signature = await signFrameAction(
        request,
        accessToken,
        IS_MAINNET ? 'mainnet' : 'testnet'
      );

      const trustedData = { messageBytes: signature?.signature };
      const untrustedData = {
        identityToken,
        unixTimestamp: Math.floor(Date.now() / 1000),
        ...signature?.signedTypedData.value
      };

      const { data } = await axios.post(
        postUrl,
        { clientProtocol: 'lens@1.0.0', trustedData, untrustedData },
        { headers: { 'User-Agent': HEY_USER_AGENT } }
      );

      logger.info(`Open frame button clicked by ${id} on ${postUrl}`);

      if (buttonAction === 'tx') {
        return res
          .status(200)
          .json({ frame: { transaction: data }, success: true });
      }

      const { document } = parseHTML(data);

      return res
        .status(200)
        .json({ frame: getFrame(document, postUrl), success: true });
    } catch (error) {
      return catchedError(res, error);
    }
  }
];
defispartan commented 1 month ago

@bigint Yes I can submit a PR for this today

github-actions[bot] commented 1 month ago

This issue has been locked since it has been closed for more than 10 days.

If you found a concrete bug or regression related to it, please open a new bug report.