Shopify / koa-shopify-auth

DEPRECATED Middleware to authenticate a Koa application with Shopify
MIT License
80 stars 63 forks source link

webhookSubscriptions null and broken nextjs embeded app when using prefix #131

Closed djedlajn closed 1 year ago

djedlajn commented 3 years ago

Issue summary

I have various issues with trying to deploy the embedded app from behind a proxy.

The app does install from the generated link. But it can not register webhook upon registration.

All this is based on premise that I have TLD and I want to use a prefix when trying with TLD without prefix everything works as expected.

Here is the general overview of the architecture.

Shopify -> TLD ( traefik proxy ) -> node service

Here is the detailed log from the moment of loading the signed link from the partner's dashboard all the way to trying to load the application on the store.

Initialize the app

  <-- GET /v1/shopify/?hmac=fa6b801ea5555c94e1fe05e4efda72b4bbbff2f41983839424e93c9aeddf4ca7&shop=cybsteststore1.myshopify.com&timestamp=1632318856
SHOP from / cybsteststore1.myshopify.com
  --> GET /v1/shopify/?hmac=fa6b801ea5555c94e1fe05e4efda72b4bbbff2f41983839424e93c9aeddf4ca7&shop=cybsteststore1.myshopify.com&timestamp=1632318856 302 25ms 131b
  <-- GET /v1/shopify/auth?shop=cybsteststore1.myshopify.com
  --> GET /v1/shopify/auth?shop=cybsteststore1.myshopify.com 302 11ms 653b

 <-- GET /v1/shopify/auth/callback?code=3001f924034c1b9142b9439782c043c6&hmac=e34e6d4b49a43ab1ca12ec1e46fdad1c0fbb24619f572e3fa7581e7899cc473c&host=Y3lic3Rlc3RzdG9yZTEubXlzaG9waWZ5LmNvbS9hZG1pbg&shop=cybsteststore1.myshopify.com&state=374022678365205&timestamp=1632319192
  --> GET /v1/shopify/auth/callback?code=3001f924034c1b9142b9439782c043c6&hmac=e34e6d4b49a43ab1ca12ec1e46fdad1c0fbb24619f572e3fa7581e7899cc473c&host=Y3lic3Rlc3RzdG9yZTEubXlzaG9waWZ5LmNvbS9hZG1pbg&shop=cybsteststore1.myshopify.com&state=374022678365205&timestamp=1632319192 302 6ms 131b
  <-- GET /v1/shopify/auth?shop=cybsteststore1.myshopify.com
  --> GET /v1/shopify/auth?shop=cybsteststore1.myshopify.com 200 4ms 1.04kb
  <-- GET /v1/shopify/auth/inline?shop=cybsteststore1.myshopify.com
  --> GET /v1/shopify/auth/inline?shop=cybsteststore1.myshopify.com 302 4ms 653b
  <-- GET /v1/shopify/auth/callback?code=247743ade6be1ebcc5ddd0e8fa752c4c&hmac=52055b299a7e6a1183d081b41ac11e4efbd6e7a92cd5b43729d4318bf7993719&host=Y3lic3Rlc3RzdG9yZTEubXlzaG9waWZ5LmNvbS9hZG1pbg&shop=cybsteststore1.myshopify.com&state=363218581094596&timestamp=1632319193
ACTIVE SHOPS {
  'cybsteststore1.myshopify.com': 'write_products,write_customers,write_draft_orders'
}
Details cybsteststore1.myshopify.com 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
  xxx GET /v1/shopify/auth/callback?code=247743ade6be1ebcc5ddd0e8fa752c4c&hmac=52055b299a7e6a1183d081b41ac11e4efbd6e7a92cd5b43729d4318bf7993719&host=Y3lic3Rlc3RzdG9yZTEubXlzaG9waWZ5LmNvbS9hZG1pbg&shop=cybsteststore1.myshopify.com&state=363218581094596&timestamp=1632319193 500 1,761ms -

  InternalServerError: Cannot read property 'webhookSubscriptions' of null
      at Object.throw (/usr/app/node_modules/koa/lib/context.js:97:11)
      at /usr/app/node_modules/@shopify/koa-shopify-auth/dist/src/auth/index.js:102:42
      at step (/usr/app/node_modules/tslib/tslib.js:141:27)
      at Object.throw (/usr/app/node_modules/tslib/tslib.js:122:57)
      at rejected (/usr/app/node_modules/tslib/tslib.js:113:69)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)

  <-- GET /favicon.ico
SHOP from / undefined
  --> GET /favicon.ico 302 3ms 93b
  <-- GET /v1/shopify/auth?shop=undefined
  --> GET /v1/shopify/auth?shop=undefined 200 2ms 1008b

At this point, because of an internal server error, the app is not redirected but it is marked as installed on the shop admin panel.

Trying to load frontend yields this on the node side

  <-- GET /v1/shopify/?hmac=bf6e343b3ccd502475baae4cb216aec5702bb26b4edded0aeb42a2c9b188d64b&host=Y3lic3Rlc3RzdG9yZTEubXlzaG9waWZ5LmNvbS9hZG1pbg&locale=en&session=b737285cbedbf8e04108f0076fb32fb1bf2ffca357c475018a23e6b12329075d&shop=cybsteststore1.myshopify.com&timestamp=1632319740
SHOP from / cybsteststore1.myshopify.com
  --> GET /v1/shopify/?hmac=bf6e343b3ccd502475baae4cb216aec5702bb26b4edded0aeb42a2c9b188d64b&host=Y3lic3Rlc3RzdG9yZTEubXlzaG9waWZ5LmNvbS9hZG1pbg&locale=en&session=b737285cbedbf8e04108f0076fb32fb1bf2ffca357c475018a23e6b12329075d&shop=cybsteststore1.myshopify.com&timestamp=1632319740 200 18ms -
  <-- GET /v1/shopify?hmac=bf6e343b3ccd502475baae4cb216aec5702bb26b4edded0aeb42a2c9b188d64b&host=Y3lic3Rlc3RzdG9yZTEubXlzaG9waWZ5LmNvbS9hZG1pbg&locale=en&session=b737285cbedbf8e04108f0076fb32fb1bf2ffca357c475018a23e6b12329075d&shop=cybsteststore1.myshopify.com&timestamp=1632319740
SHOP from / cybsteststore1.myshopify.com
  --> GET /v1/shopify?hmac=bf6e343b3ccd502475baae4cb216aec5702bb26b4edded0aeb42a2c9b188d64b&host=Y3lic3Rlc3RzdG9yZTEubXlzaG9waWZ5LmNvbS9hZG1pbg&locale=en&session=b737285cbedbf8e04108f0076fb32fb1bf2ffca357c475018a23e6b12329075d&shop=cybsteststore1.myshopify.com&timestamp=1632319740 200 2,378ms -
  <-- GET /_next/static/XTyLDnnecn7OiXggz97M_/_buildManifest.js
  <-- GET /_next/static/XTyLDnnecn7OiXggz97M_/_ssgManifest.js

And following on the Frontend

Screenshot 2021-09-22 161225

Here is the full server.js


const port = parseInt(process.env.PORT, 10) || 8080;
const dev = process.env.NODE_ENV !== "production";
const app = next({
  dev,
});
const handle = app.getRequestHandler();

Shopify.Context.initialize({
  API_KEY: process.env.SHOPIFY_API_KEY,
  API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
  SCOPES: process.env.SCOPES.split(","),
  HOST_NAME: process.env.HOST.replace(/https:\/\//, ""),
  API_VERSION: ApiVersion.October20,
  IS_EMBEDDED_APP: true,
  SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});

const ACTIVE_SHOPIFY_SHOPS = {};

app.prepare().then(async () => {
  const server = new Koa({ proxy: true });
  const router = new Router();
  server.keys = [Shopify.Context.API_SECRET_KEY];
  server.use(logger())

  server.use(
    createShopifyAuth({
      prefix: "/v1/shopify",
      async afterAuth(ctx) {
        // Access token and shop available in ctx.state.shopify
        const { shop, accessToken, scope } = ctx.state.shopify;
        const host = ctx.query.host;
        ACTIVE_SHOPIFY_SHOPS[shop] = scope;

        console.log('ACTIVE SHOPS', ACTIVE_SHOPIFY_SHOPS)
        console.log('Details', shop, accessToken)

        const response = await Shopify.Webhooks.Registry.register({
          shop,
          accessToken,
          path: "/v1/shopify/webhooks",
          topic: "APP_UNINSTALLED",
          webhookHandler: async (topic, shop, body) =>
            delete ACTIVE_SHOPIFY_SHOPS[shop],
        });

        if (!response.success) {
          console.log(
            `Failed to register APP_UNINSTALLED webhook: ${response.result}`
          );
        }

        await Shopify.Webhooks.Registry.register({
          shop,
          accessToken,
          path: "/v1/shopify/webhooks",
          topic: "ORDERS_CREATE",
          webhookHandler: async (topic, shop, body) => {
            try {
              console.log('BODY', body)
            } catch (e) {

            }
          }
        })

        // Redirect to app with shop parameter upon auth
        ctx.redirect(`/?shop=${shop}&host=${host}`);
      },
    })
  );

  const handleRequest = async (ctx) => {
    await handle(ctx.req, ctx.res);
    ctx.respond = false;
    ctx.res.statusCode = 200;
  };

  router.post("/v1/shopify/webhooks", async (ctx) => {
    try {
      await Shopify.Webhooks.Registry.process(ctx.req, ctx.res);
      console.log(`Webhook processed, returned status code 200`);
    } catch (error) {
      console.log(`Failed to process webhook: ${error}`);
    }
  });

  router.post("/v1/shopify/webhooks/order-created", async (ctx) => {
    try {
      console.log('ORDER CR', ctx.body)
    } catch (error) {
      console.log(`Failed to process webhook: ${error}`);
    }
  });

  router.post(
    "/graphql",
    verifyRequest({ returnHeader: true, authRoute: "/v1/shopify/auth" }),
    async (ctx, next) => {
      await Shopify.Utils.graphqlProxy(ctx.req, ctx.res);
    }
  );

  router.get("(/_next/static/.*)", handleRequest); // Static content is clear
  router.get("/_next/webpack-hmr", handleRequest); // Webpack content is clear

  router.get("(.*)", async (ctx) => {
    const shop = ctx.query.shop;
    console.log('SHOP from /', shop)
    if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
      ctx.redirect(`/v1/shopify/auth?shop=${shop}`);
    } else {
      await handleRequest(ctx);
    }
  });

  server.use(router.allowedMethods());
  server.use(router.routes());
  server.listen(port, () => {
    console.log(`> Ready on http://localhost:${port}`);
  });
});

I have tried putting verifyRequest on the * all route but that ends up with infinite redirects. Note this only happens when I try to use prefix.

github-actions[bot] commented 1 year ago

Note that this repo is no longer maintained and this issue will not be reviewed. Prefer the official JavaScript API library. If you still want to use Koa, see simple-koa-shopify-auth for a potential community solution.