Solved

CustomSessionStorage using mysql, error missing access token

EugeneCXY
Excursionist
16 3 4

Hey,

Currently trying to implement a custom session storage using MySQL. 

After I add the app it throws a missing access token when creating graphQL client

InternalServerError: Missing access token when creating GraphQL client

Here is my custom session code

import { Session} from '@shopify/shopify-api/dist/auth/session'

const mysql = require('mysql');

const connection = mysql.createConnection({
  host:'[host]',
  user:'[user]',
  password:'[password]',
  database:'[database]'

})
console.log('is connecting')
connection.connect();

let domain_id = '';
export async function storeCallback(session){
  try{
    console.log("calls storeCallback")
      let data = session;
      console.log("data " +JSON.stringify(data))
      data.onlineAccessInfo = JSON.stringify(session.onlineAccessInfo)

      if(data.id.indexOf(`${data.shop}`)> -1){
        console.log("data.id got called")
        domain_id = data.id
      }
      let connectionQuery = new Promise((resolve, reject) =>{
        connection.query(`INSERT INTO shop (shop_url, session_id, domain_id, access_token, state, isOnline, onlineAccessinfo, scope) VALUES ('${data.shop}','${data.id}','${domain_id}','${data.accessToken}','${data.state}','${data.isOnline}','${data.onlineAccessInfo}','${data.scope}') ON DUPLICATE KEY UPDATE access_token='${data.accessToken}', state='${data.state}', session_id='${data.id}',domain_id='${domain_id}',scope='${data.scope}',onlineAccessInfo='${data.onlineAccessInfo}'`,
            function(error, results, fields){
              if(error) throw error;

            resolve();
            }
        );
      });
      await connectionQuery
      return true;

    }catch(e){
      console.log("got this error")
    throw new Error(e);
  }
}
export async function loadCallback(id){
  try{
      let session = new Session(id);

      let query = new Promise((resolve, reject) =>{
        connection.query(`SELECT * FROM shop WHERE session_id ='${id}' OR domain_id='${id}' LIMIT 1`,
          function(error, results, fields){
            if (error) throw error;


            session.shop = results[0].shop_url;
            session.state = results[0].state;
            session.scope = results[0].scope;
            session.isOnline = results[0].isOnline == 'true' ? true:false;
            session.onlineAccessInfo = results[0].onlineAccessInfo;
            session.accessToken = results[0].accessToken;


            const date = new Date();
            date.setDate(date.getDate() + 1);
            session.expires = date;

            if(session.expires && typeof session.exipres === 'string'){
              session.expires = new Date(session.expires);
            }

            resolve();
          }
        );

      });


      await query;
      return session;

  }catch(err){
    throw new Error(err)
  }

}
export async function deleteCallback(id){
  try{
    return false;
  }catch(e){
    throw new Error(e)
  }
}

 

And here is my server.js

import "@babel/polyfill";
import dotenv from "dotenv";
import "isomorphic-fetch";
import createShopifyAuth, { verifyRequest } from "@shopify/koa-shopify-auth";
import Shopify, { ApiVersion } from "@shopify/shopify-api";
import Koa from "koa";
import next from "next";
import Router from "koa-router";
import {storeCallback,deleteCallback,loadCallback} from './sessiondb.js'


dotenv.config();
const port = parseInt(process.env.PORT, 10) || 8081;
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,
  // This should be replaced with your preferred storage strategy
  SESSION_STORAGE: new Shopify.Session.CustomSessionStorage(
    storeCallback,
    loadCallback,
    deleteCallback

  ),

});

// Storing the currently active shops in memory will force them to re-login when your server restarts. You should
// persist this object in your app.
const ACTIVE_SHOPIFY_SHOPS = {};

app.prepare().then(async () => {
  const server = new Koa();
  const router = new Router();
  server.keys = [Shopify.Context.API_SECRET_KEY];
  server.use(
    createShopifyAuth({
      async afterAuth(ctx) {
        // Access token and shop available in ctx.state.shopify
        const { shop, accessToken, scope } = ctx.state.shopify;
        console.log("IM TESTING THIS"+ shop,accessToken,scope);
        const host = ctx.query.host;
        ACTIVE_SHOPIFY_SHOPS[shop] = scope;

        const response = await Shopify.Webhooks.Registry.register({
          shop,
          accessToken,
          path: "/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}`
          );
        }

        // 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("/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(
    "/graphql",
    verifyRequest({ returnHeader: true }),
    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("(.*)", verifyRequest(),handleRequest);
  router.get("(.*)", async (ctx) => {
    const shop = ctx.query.shop;
    console.log("get request "+ shop)
    // This shop hasn't been seen yet, go through OAuth to create a session
    if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
      console.log("its getting redirected")
      ctx.redirect(`/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}`);
  });
});

 

What am I missing?  

Accepted Solution (1)

EugeneCXY
Excursionist
16 3 4

This is an accepted solution.

My bad, it was just a naming thing.

accessToken was named access_token in my db

View solution in original post

Replies 2 (2)

EugeneCXY
Excursionist
16 3 4

This is an accepted solution.

My bad, it was just a naming thing.

accessToken was named access_token in my db

DavidT
Shopify Partner
23 1 6

I think your app could be vulnerable to SQL injection because you are using string concatenation to construct the query. If the shop URL contained a single quote you could escape out of the query.

QuickEdit - Bulk Product Edit - Quick and easy bulk editor for products and variants.
SafeShip - Address Validator - International address validation and PO box blocking at checkout for Shopify Plus merchants.