Skip to content
This repository has been archived by the owner on Apr 11, 2024. It is now read-only.

FixPostgreSQLSessionStorage@hasSessionTable always return false #413

Merged
merged 3 commits into from
Jul 8, 2022

Conversation

chuangbo
Copy link
Contributor

WHY are these changes introduced?

Fixes #412

PostgreSQLSessionStorage@hasSessionTable function calls PostgreSQLSessionStorage@query and check the length of return rows. But the query function return rows directly instead of [rows], so after the array restructuring, rows will always be undefined, which cause function hasSessionTable returns false even the table exists.

WHAT is this pull request doing? 

Remove array destructuring.

Type of change

  • Patch: Bug (non-breaking change which fixes an issue)
  • Minor: New feature (non-breaking change which adds functionality)
  • Major: Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist

  • I have added a changelog entry, prefixed by the type of change noted above
  • I have added/updated tests for this change
  • I have documented new APIs/updated the documentation for modified APIs (for public APIs)

@chuangbo chuangbo requested a review from a team as a code owner June 25, 2022 09:55
@ghost ghost added the cla-needed label Jun 25, 2022
@chuangbo
Copy link
Contributor Author

I have signed the shopify CLA.

@chuangbo
Copy link
Contributor Author

chuangbo commented Jul 7, 2022

@surma Hi, it would be great if you could help review my PR at your convenience?

@ghost ghost removed the cla-needed label Jul 7, 2022
@yoan-elba
Copy link

Thanks for the fix chuangbo, i need it as well 🙏

@Eiles
Copy link

Eiles commented Jul 8, 2022

Looking forward to see this merged, it's blocking my current development as well.
Thanks @chuangbo

@chuangbo
Copy link
Contributor Author

chuangbo commented Jul 8, 2022

@yoan-elba @Eiles We are currently making a local copy with fixes to make it work temporally.

import pg from "pg";

import { SessionInterface } from "@shopify/shopify-api";
import { SessionStorage } from "@shopify/shopify-api/dist/auth/session/session_storage.js";
import {
  sessionEntries,
  sessionFromEntries,
} from "@shopify/shopify-api/dist/auth/session/session-utils.js";

export interface PostgreSQLSessionStorageOptions {
  sessionTableName: string;
  port: number;
}
const defaultPostgreSQLSessionStorageOptions: PostgreSQLSessionStorageOptions =
  {
    sessionTableName: "shopify_sessions",
    port: 3211,
  };

export class PostgreSQLSessionStorage implements SessionStorage {
  static withCredentials(
    host: string,
    dbName: string,
    username: string,
    password: string,
    opts: Partial<PostgreSQLSessionStorageOptions>
  ) {
    return new PostgreSQLSessionStorage(
      new URL(
        `postgres://${encodeURIComponent(username)}:${encodeURIComponent(
          password
        )}@${host}/${encodeURIComponent(dbName)}`
      ),
      opts
    );
  }

  public readonly ready: Promise<void>;
  private options: PostgreSQLSessionStorageOptions;
  private client: pg.Client;

  constructor(
    private dbUrl: URL,
    opts: Partial<PostgreSQLSessionStorageOptions> = {}
  ) {
    if (typeof this.dbUrl === "string") {
      this.dbUrl = new URL(this.dbUrl);
    }
    this.options = { ...defaultPostgreSQLSessionStorageOptions, ...opts };
    // fix: move from init() to constructor as typescript throws error
    this.client = new pg.Client({ connectionString: this.dbUrl.toString() });
    this.ready = this.init();
  }

  public async storeSession(session: SessionInterface): Promise<boolean> {
    await this.ready;

    const entries = sessionEntries(session);
    const query = `
      INSERT INTO ${this.options.sessionTableName}
      (${entries.map(([key]) => key).join(", ")})
      VALUES (${entries.map((_, i) => `$${i + 1}`).join(", ")})
      ON CONFLICT (id) DO UPDATE SET ${entries
        .map(([key]) => `${key} = Excluded.${key}`)
        .join(", ")};
    `;
    await this.query(
      query,
      entries.map(([_key, value]) => value)
    );
    return true;
  }

  public async loadSession(id: string): Promise<SessionInterface | undefined> {
    await this.ready;
    const query = `
      SELECT * FROM ${this.options.sessionTableName}
      WHERE id = $1;
    `;
    const rows = await this.query(query, [id]);
    if (!Array.isArray(rows) || rows?.length !== 1) return undefined;
    const rawResult = rows[0] as any;
    return sessionFromEntries(Object.entries(rawResult));
  }

  public async deleteSession(id: string): Promise<boolean> {
    await this.ready;
    const query = `
      DELETE FROM ${this.options.sessionTableName}
      WHERE id = $1;
    `;
    await this.query(query, [id]);
    return true;
  }

  public disconnect(): Promise<void> {
    return this.client.end();
  }

  private async init() {
    // fix: move this to constructor as typescript throws error
    // this.client = new pg.Client({ connectionString: this.dbUrl.toString() });
    await this.connectClient();
    await this.createTable();
  }

  private async connectClient(): Promise<void> {
    await this.client.connect();
  }

  private async hasSessionTable(): Promise<boolean> {
    const query = `
      SELECT * FROM pg_catalog.pg_tables WHERE tablename = $1
    `;
    // fix: change [rows] to rows
    // see https://github.com/Shopify/shopify-api-node/pull/413
    const rows = await this.query(query, [this.options.sessionTableName]);
    return Array.isArray(rows) && rows.length === 1;
  }

  private async createTable() {
    const hasSessionTable = await this.hasSessionTable();
    if (!hasSessionTable) {
      const query = `
        CREATE TABLE ${this.options.sessionTableName} (
          id varchar(255) NOT NULL PRIMARY KEY,
          shop varchar(255) NOT NULL,
          state varchar(255) NOT NULL,
          isOnline boolean NOT NULL,
          scope varchar(255),
          expires integer,
          onlineAccessInfo varchar(255),
          accessToken varchar(255)
        )
      `;
      await this.query(query);
    }
  }

  private async query(sql: string, params: any[] = []): Promise<any> {
    const result = await this.client.query(sql, params);
    return result.rows;
  }
}

@yoan-elba
Copy link

@chuangbo Thanks for the tips, i will do that for this moment, I hope your PR will be merge soon

Copy link
Contributor

@paulomarg paulomarg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great catch, thank you for the fix! I tested it and it works nicely.

CHANGELOG.md Outdated Show resolved Hide resolved
@chuangbo
Copy link
Contributor Author

chuangbo commented Jul 8, 2022

@paulomarg many thanks to the merge and the fix to my broken English 😃

@paulomarg paulomarg merged commit 56a7092 into Shopify:main Jul 8, 2022
@chuangbo chuangbo deleted the patch-1 branch July 11, 2022 06:58
@shopify-shipit shopify-shipit bot temporarily deployed to production July 14, 2022 18:38 Inactive
@alexissel
Copy link

Hey @paulomarg,

I'm still having this issue on v6.0.1.
Any suggestions?

Thanks

@paulomarg
Copy link
Contributor

It seems this fix wasn't carried over when we separated the package here. PR is up!

@alexissel
Copy link

@paulomarg, when should we expect the release with the Postgres fix?

@alexissel
Copy link

@paulomarg, I don't know if it's just me, but I'm still having the issue on v6.0.2.

Here's the full error message:

/var/www/html/app/web/node_modules/pg-protocol/dist/parser.js:287
        const message = name === 'notice' ? new messages_1.NoticeMessage(length, messageValue) : new messages_1.DatabaseError(messageValue, length, name);
                                                                                                 ^

error: relation "shopify_sessions" already exists
    at Parser.parseErrorMessage (/var/www/html/app/web/node_modules/pg-protocol/dist/parser.js:287:98)
    at Parser.handlePacket (/var/www/html/app/web/node_modules/pg-protocol/dist/parser.js:126:29)
    at Parser.parse (/var/www/html/app/web/node_modules/pg-protocol/dist/parser.js:39:38)
    at Socket.<anonymous> (/var/www/html/app/web/node_modules/pg-protocol/dist/index.js:11:42)
    at Socket.emit (node:events:513:28)
    at Socket.emit (node:domain:489:12)
    at addChunk (node:internal/streams/readable:315:12)
    at readableAddChunk (node:internal/streams/readable:289:9)
    at Socket.Readable.push (node:internal/streams/readable:228:10)
    at TCP.onStreamRead (node:internal/stream_base_commons:190:23) {
  length: 110,
  severity: 'ERROR',
  code: '42P07',
  detail: undefined,
  hint: undefined,
  position: undefined,
  internalPosition: undefined,
  internalQuery: undefined,
  where: undefined,
  schema: undefined,
  table: undefined,
  column: undefined,
  dataType: undefined,
  constraint: undefined,
  file: 'heap.c',
  line: '1202',
  routine: 'heap_create_with_catalog'
}

@SeanMythen
Copy link

Yes, I'm also getting the same error message @alexissel

This error currently prevents my app from re-deploying if the shopify_sessions table already exists

@dani-sanomads
Copy link

I'm also getting the same error.
Screenshot 2023-01-02 at 11 41 19 PM

@dani-sanomads
Copy link

but right now in development this is working for me :)
npm i @shopify/shopify-app-session-storage-postgresql@1.0.0-rc.0

@pateketu
Copy link

pateketu commented Jan 3, 2023

I have ended up monkey-patching 1.0.1 version like below, as anyways I don't want the table to be created automatically, in my case its created alongside other app-related tables via version-controlled schema, also the service account running the app will not enough privileges to create tables only Read/Write access,

PostgreSQLSessionStorage.prototype.hasSessionTable = ()=>true;

@justinhenricks
Copy link

justinhenricks commented Jan 11, 2023

Any updates here? I just received this error using "@shopify/shopify-app-session-storage-sqlite": "^1.0.0",

Is there a workaround?

EDIT: This seems odd looking at the code:
https://github.com/Shopify/shopify-app-js/blob/7657e86d788b954c50f7a7227949abdf6c7d851f/packages/shopify-app-session-storage-postgresql/src/postgresql.ts#L150

Is my version correct?

@justinhenricks
Copy link

but right now in development this is working for me :) npm i @shopify/shopify-app-session-storage-postgresql@1.0.0-rc.0

Can also confirm this seems to be working.. would love for this to be a full release by the time my app hits production :)

# for free to subscribe to this conversation on GitHub. Already have an account? #.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PostgreSQLSessionStorage throw error: relation "shopify_sessions" already exists
9 participants