Shopify / shopify-app-template-node

MIT License
867 stars 391 forks source link

Error while using Redis or MongoDB (TypeError: scopesArray.map is not a function) #1221

Closed fonya2014 closed 1 year ago

fonya2014 commented 1 year ago

# Issue summary

Hello everyone!

I am working with the new app template and the API that was released in January 2023.

I am trying to use Redis or MongoDB as session storage. I use these packages: @shopify/shopify-app-session-storage-redis' @shopify/shopify-app-session-storage-mongodb'

according to these guides: https://github.com/Shopify/shopify-app-js/tree/main/packages/shopify-app-session-storage-redis https://github.com/Shopify/shopify-app-js/tree/main/packages/shopify-app-session-storage-mongodb

And I am getting the following error in the console:

node_modules\@shopify\shopify-api\lib\auth\scopes\index.js:15
.map(function (scope) { return scope.trim(); })
TypeError: scopesArray.map is not a function

If I try to use REST API, I am getting the 503 error on the frontend (browser console).

The session data document is created in MongoDB.

If I use SQLiteSessionStorage, everything works and loads fine.

Could you please help me to solve the issue?

2023-01-11 12:29:31 | backend  | [nodemon] starting `node index.js`
2023-01-11 12:30:57 | backend  | [shopify-app/INFO] Running ensureInstalledOnShop
2023-01-11 12:30:57 | backend  | [shopify-app/DEBUG] Checking if shop has installed the app | {shop: a-store-for-a-test-task.myshopify.com}
2023-01-11 12:30:57 | backend  | [shopify-app/ERROR] Could not check if session was valid: TypeError: scopesArray.map is not a function | {shop: a-store-for-a-test-task.myshopify.com}
2023-01-11 12:30:57 | backend  | [shopify-app/INFO] Found a session, but it is not valid. Redirecting to auth | {shop: a-store-for-a-test-task.myshopify.com} 
2023-01-11 12:30:57 | backend  | [shopify-app/DEBUG] Redirecting to auth at /api/auth, with callback /api/auth/callback | {shop: a-store-for-a-test-task.myshopify.com, isOnline: false}
2023-01-11 12:30:57 | backend  | [shopify-api/INFO] Beginning OAuth | {shop: a-store-for-a-test-task.myshopify.com, isOnline: false, callbackPath: /api/auth/callback}
2023-01-11 12:30:57 | backend  | [shopify-api/DEBUG] OAuth started, redirecting to https://a-store-for-a-test-task.myshopify.com/admin/oauth/authorize?client_id=88ed9b3b95307ea81c67173f944fd
                                 19a&scope=write_themes&redirect_uri=https%3A%2F%2F0ead-188-163-108-143.ngrok.io%2Fapi%2Fauth%2Fcallback&state=439314155827792&grant_options%5B%5D= | {shop:
                                 a-store-for-a-test-task.myshopify.com, isOnline: false}
2023-01-11 12:30:59 | backend  | [shopify-app/INFO] Handling request to complete OAuth process
2023-01-11 12:30:59 | backend  | [shopify-api/INFO] Completing OAuth | {shop: a-store-for-a-test-task.myshopify.com}
2023-01-11 12:30:59 | backend  | [shopify-api/DEBUG] OAuth request is valid, requesting access token | {shop: a-store-for-a-test-task.myshopify.com}
2023-01-11 12:30:59 | backend  | [shopify-app/DEBUG] Registering webhooks | {shop: a-store-for-a-test-task.myshopify.com}
2023-01-11 12:30:59 | backend  | [shopify-api/INFO] Registering webhooks | {shop: a-store-for-a-test-task.myshopify.com}
2023-01-11 12:31:00 | backend  | [shopify-api/DEBUG] Existing topics: [APP_UNINSTALLED] | {shop: a-store-for-a-test-task.myshopify.com}
2023-01-11 12:31:00 | backend  | [shopify-api/DEBUG] Running webhook mutation | {topic: APP_UNINSTALLED, operation: create}
2023-01-11 12:31:01 | backend  | [shopify-api/DEBUG] Running webhook mutation | {topic: APP_UNINSTALLED, operation: delete}
2023-01-11 12:31:01 | backend  | [shopify-app/DEBUG] Completed OAuth callback | {shop: a-store-for-a-test-task.myshopify.com, isOnline: false}
2023-01-11 12:31:01 | backend  | [shopify-app/DEBUG] Redirecting to host at https://a-store-for-a-test-task.myshopify.com/admin/apps/88ed9b3b95307ea81c67173f944fd19a | {shop:
                                 a-store-for-a-test-task.myshopify.com}
2023-01-11 12:31:12 | backend  | [shopify-app/INFO] Running ensureInstalledOnShop
2023-01-11 12:31:12 | backend  | [shopify-app/DEBUG] Checking if shop has installed the app | {shop: a-store-for-a-test-task.myshopify.com}
2023-01-11 12:31:12 | backend  | [shopify-app/INFO] App is installed and ready to load | {shop: a-store-for-a-test-task.myshopify.com}
2023-01-11 12:31:40 | backend  | [shopify-app/INFO] Running validateAuthenticatedSession
2023-01-11 12:31:40 | backend  | [shopify-api/DEBUG] App is embedded, looking for session id in JWT payload | {isOnline: false}
2023-01-11 12:31:40 | backend  | [shopify-api/DEBUG] Found valid JWT payload | {shop: a-store-for-a-test-task.myshopify.com, isOnline: false}
2023-01-11 12:31:40 | backend  | [shopify-app/DEBUG] Request session found and loaded | {shop: a-store-for-a-test-task.myshopify.com}
2023-01-11 12:31:40 | backend  | C:\Users\Sofiia\VSCODEProjects\seo-schema-for-products\node_modules\@shopify\shopify-api\lib\auth\scopes\index.js:15
2023-01-11 12:31:40 | backend  |             .map(function (scope) { return scope.trim(); })
2023-01-11 12:31:40 | backend  |              ^
2023-01-11 12:31:40 | backend  |
2023-01-11 12:31:40 | backend  | TypeError: scopesArray.map is not a function
2023-01-11 12:31:40 | backend  |     at new AuthScopes (C:\Users\Sofiia\VSCODEProjects\seo-schema-for-products\node_modules\@shopify\shopify-api\lib\auth\scopes\index.js:15:14)
2023-01-11 12:31:40 | backend  |     at Session.isActive (C:\Users\Sofiia\VSCODEProjects\seo-schema-for-products\node_modules\@shopify\shopify-api\lib\session\session.js:80:77)
2023-01-11 12:31:40 | backend  |     at C:\Users\Sofiia\VSCODEProjects\seo-schema-for-products\web\node_modules\@shopify\shopify-app-express\build\cjs\middlewares\validate-authenticated-sess
                                 ion.js:69:79
2023-01-11 12:31:40 | backend  |     at processTicksAndRejections (node:internal/process/task_queues:96:5)
2023-01-11 12:31:40 | backend  | [nodemon] app crashed - waiting for file changes before starting...

My shopify.js file:

`

import { BillingInterval, LATEST_API_VERSION, BillingReplacementBehavior, LogSeverity  } from "@shopify/shopify-api";
import { shopifyApp } from "@shopify/shopify-app-express";

let { restResources } = await import(
  `@shopify/shopify-api/rest/admin/${LATEST_API_VERSION}`
);
import {RedisSessionStorage} from '@shopify/shopify-app-session-storage-redis';
import {MongoDBSessionStorage} from '@shopify/shopify-app-session-storage-mongodb';
import { SQLiteSessionStorage } from "@shopify/shopify-app-session-storage-sqlite";

const DB_PATH = `${process.cwd()}/database.sqlite`;

/// The transactions with Shopify will always be marked as test transactions, unless NODE_ENV is production.
// See the ensureBilling helper to learn more about billing in this template.
const billingConfig = {
  "Basic Plan": {
    // This is an example configuration that would do a one-time charge for $5 (only USD is currently supported)
    amount: 4.99,
    currencyCode: "USD",
    interval: BillingInterval.Every30Days,
    trialDays: 3,
    replacementBehavior: BillingReplacementBehavior.ApplyImmediately,
  },
};

const shopify = shopifyApp({
  api: {
    apiVersion: LATEST_API_VERSION,
    restResources,
    billing: billingConfig, // or replace with billingConfig above to enable example billing
    logger: {
      level: LogSeverity.Debug,
      //httpRequests: true, // if the error seems to be related to requests
    },
  },
  auth: {
    path: "/api/auth",
    callbackPath: "/api/auth/callback",
  },
  webhooks: {
    path: "/api/webhooks",
  },
  // This should be replaced with your preferred storage strategy
  //sessionStorage: new RedisSessionStorage("redis://login:pass@host.cloud.redislabs.com:19100"),
  //sessionStorage: new SQLiteSessionStorage(DB_PATH),
  sessionStorage: new MongoDBSessionStorage(
    'mongodb+srv://login:pass@cluster.mongodb.net/?retryWrites=true&w=majority',
    'DBName',
  ),
});

export default shopify;

`

## Expected behavior

The session and scope loads from DB.

## Actual behavior

The following error in the console:

node_modules\@shopify\shopify-api\lib\auth\scopes\index.js:15
.map(function (scope) { return scope.trim(); })
TypeError: scopesArray.map is not a function

## Steps to reproduce the problem

  1. Use Redis or MongoDB according to https://github.com/Shopify/shopify-app-js/tree/main/packages/shopify-app-session-storage-redis https://github.com/Shopify/shopify-app-js/tree/main/packages/shopify-app-session-storage-mongodb

  2. Try to get access to the REST API using the app.

molotow11 commented 1 year ago

Here too

brdsmth commented 1 year ago

I am experiencing this issue, too. Just set up session storage with mongodb using the @shopify/shopify-app-session-storage-mongodb package

amIsmael commented 1 year ago

another one, I want to storage in mongodb

brdsmth commented 1 year ago

I ended up replacing validateAuthenticatedSession() with a custom middleware function to retrieve the session from the shopify_sessions collection. I'm then setting the session in the following responses. Things seem to be working fine and I don't see this problem anymore.

export const customMiddleware = async function (req, res, next) {
    const session = await db.collection('shopify_sessions').findOne({  /** whatever index **/ })
    res.locals.shopify = { session: {} }
    res.locals.shopify.session = session
    next()
}
fonya2014 commented 1 year ago

Hello everyone! I investigated the issue and here is what I discovered:

MongoDB and Redis return the following object:

AuthScopes {
compressedScopes: Set(1) { 'write_themes' },
expandedScopes: Set(2) { 'write_themes', 'read_themes' }
 }

The error TypeError: scopesArray.map is not a function happens because .map doesn't work with objects.

I tried to replace the line: scopesArray = scopes; width this line: scopesArray = [...scopes.expandedScopes]

In this part of the code (In the file node_modules\@shopify\shopify-api\lib\auth\scopes\index.js):

function AuthScopes(scopes) {
        var scopesArray = [];
        if (typeof scopes === 'string') {
            scopesArray = scopes.split(new RegExp("".concat(AuthScopes.SCOPE_DELIMITER, "\\s*")));
        }
        else if (scopes) {
            //scopesArray = scopes;
            scopesArray = [...scopes.expandedScopes]
        }

And everything is working now

doitalldev commented 1 year ago

Hello everyone! I investigated the issue and here is what I discovered:

MongoDB and Redis return the following object:

AuthScopes {
compressedScopes: Set(1) { 'write_themes' },
expandedScopes: Set(2) { 'write_themes', 'read_themes' }
 }

The error TypeError: scopesArray.map is not a function happens because .map doesn't work with objects.

I tried to replace the line: scopesArray = scopes; width this line: scopesArray = [...scopes.expandedScopes]

In this part of the code (In the file node_modules@shopify\shopify-api\lib\auth\scopes\index.js):

function AuthScopes(scopes) {
        var scopesArray = [];
        if (typeof scopes === 'string') {
            scopesArray = scopes.split(new RegExp("".concat(AuthScopes.SCOPE_DELIMITER, "\\s*")));
        }
        else if (scopes) {
            //scopesArray = scopes;
            scopesArray = [...scopes.expandedScopes]
        }

And everything is working now

Had exact same issue, saw the same output for scopesArray. Implemented the fix recommended in that reply and it all works. thanks @fonya2014

doitalldev commented 1 year ago

@paulomarg or @cquemin is this something that could be PR'd? Also, does using the expandedScopes for the scopesArray cause problems or should we be combining the compressedScopes and expandedScopes, then use that array?

molotow11 commented 1 year ago

Try to remove shopify-api dependency as answered here https://github.com/Shopify/shopify-app-template-node/issues/1208#issuecomment-1406999580

mkevinosullivan commented 1 year ago

@fonya2014 I'm having difficulty in reproducing this as you've described above.

  1. What version of MongoDB are you using?
  2. What versions of the following libraries?
    • @shopify-app-express
    • @shopify/shopify-app-session-storage-mongodb
    • @shopify/shopify-api

MongoDB and Redis return the following object:

AuthScopes {
compressedScopes: Set(1) { 'write_themes' },
expandedScopes: Set(2) { 'write_themes', 'read_themes' }
 }

Where did you log this object?

I'm using the basic templates with version 1.0.2 of the mongodb package (which was the version around the time this issue was created), and I've modified the mongodb.js file such that it logs what's returned by findOne() in loadSession() ...

  async loadSession(id) {
    await this.ready;
    const result = await this.collection.findOne({
      id
    });
    console.log('MongoDBSessionStorage.loadSession', id, result); // added this debug line
    return result ? new shopifyApi.Session(result) : undefined;
  }

The output I get is

MongoDBSessionStorage.loadSession offline_testshop.myshopify.com {
  _id: new ObjectId("644933001151c713ce729762"),
  id: 'offline_testshop.myshopify.com',
  shop: 'testshop.myshopify.com',
  state: '424763202963782',
  isOnline: false,
  scope: 'write_products,write_orders',
  accessToken: 'shpat_redacted_access_token'
}

scope is showing as a string, not an AuthScopes object, so I'm curious as to where you logged that return value?

Note that I've also tried this with @shopoify/shopify-app-express@1.0.2 and @shopify/shopify-app-session-storage-mongodb@1.0.1

fonya2014 commented 1 year ago

Hello @mkevinosullivan !

Here are the versions of packages I probably used at that time:

@shopify-app-express 1.0.2 @shopify/shopify-app-session-storage-mongodb 1.0.1 @shopify/shopify-api 6.1.0

I don't remember for sure about the mongodb and shopify-api packages as it was 4 months earlier and I updated all apps to recent versions since then.

I added the console log to this file: node_modules@shopify\shopify-api\lib\auth\scopes\index.js

function AuthScopes(scopes) {
        var scopesArray = [];
    console.log(scopes); // here is my console log
        if (typeof scopes === 'string') {
            scopesArray = scopes.split(new RegExp("".concat(AuthScopes.SCOPE_DELIMITER, "\\s*")));
        }
        else if (scopes) {
            scopesArray = scopes;
        }

And it showed me the following:

AuthScopes {
compressedScopes: Set(1) { 'write_themes' },
expandedScopes: Set(2) { 'write_themes', 'read_themes' }
 }

As I can see, the changes done several days ago in this PR must address the issue: https://github.com/Shopify/shopify-api-js/pull/837

mkevinosullivan commented 1 year ago

Released in @shopify/shopify-api@7.1.0 today.