Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Prisma Adapter to Store User Info for Online Tokens #715

Merged
merged 17 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
52 changes: 52 additions & 0 deletions .changeset/rich-panthers-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
"@shopify/shopify-app-session-storage-prisma": major
"@shopify/shopify-app-session-storage-test-utils": patch
---

## Store user information as part of the session

With this change when using online access tokens, the user information is stored as part of the session. Previously only the user ID was stored. This will enable changing of page content or limiting of page visibility by user, as well as unlock logging users actions. This is a breaking change, as the Prisma schema has been updated to include the user information.

For more information review the [migration guide](../packages/apps/session-storage/shopify-app-session-storage-prisma/MIGRATION_V5.md).


<details>
The new session will include the following data:

```ts
{
id: 'online_session_id',
shop: 'online-session-shop',
state: 'online-session-state',
isOnline: true,
scope: 'online-session-scope',
accessToken: 'online-session-token',
expires: 2022-01-01T05:00:00.000Z,
onlineAccessInfo: {
associated_user: {
id: 1,
first_name: 'online-session-first-name'
last_name: 'online-session-last-name',
email: 'online-session-email',
locale: 'online-session-locale',
email_verified: false,
account_owner: true,
collaborator: false,
},
}
}
```

You will be able to access the user information on the Session object:

```ts
const { admin, session } = await authenticate.admin(request);

console.log("user id", session.onlineAccessInfo.associated_user.id);
console.log("user email", session.onlineAccessInfo.associated_user.email);
console.log("account owner", session.onlineAccessInfo.associated_user.account_owner);
```

</details>


Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Migrating to `v.5.0.0`

Version `5.0.0` of the `@shopify/shopify-app-session-storage-prisma` introduces a breaking change to the session storage schema. The user information is now stored as part of the session. Previously only the user ID was stored. This change requires updating the Prisma schema to include the user information.

## Updating the Prisma schema

Update the `Session` model in the Prisma schema to include the user information:

```prisma
model Session {
id String @id
shop String
state String
isOnline Boolean @default(false)
scope String?
expires DateTime?
accessToken String
userId BigInt?
// New fields
firstName String?
lastName String?
email String?
accountOwner Boolean @default(false)
locale String?
collaborator Boolean? @default(false)
emailVerified Boolean? @default(false)
}
```

Run the following prisma commands to update the database schema:

```sh
npx prisma migrate dev
```

### Update Types

Update the generated types to include the new fields:

```ts
npx prisma generate
```

## Using the user information

The user information will now be available on the `Session` object:

```ts
const {admin, session} = await authenticate.admin(request);

console.log('user id', session.onlineAccessInfo.associated_user.id);
console.log('user email', session.onlineAccessInfo.associated_user.email);
console.log(
'account owner',
session.onlineAccessInfo.associated_user.account_owner,
);
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Session" (
"id" TEXT NOT NULL PRIMARY KEY,
"shop" TEXT NOT NULL,
"state" TEXT NOT NULL,
"isOnline" BOOLEAN NOT NULL DEFAULT false,
"scope" TEXT,
"expires" DATETIME,
"accessToken" TEXT NOT NULL,
"userId" BIGINT,
"firstName" TEXT,
"lastName" TEXT,
"email" TEXT,
"accountOwner" BOOLEAN,
"locale" TEXT,
"collaborator" BOOLEAN,
"emailVerified" BOOLEAN
);
INSERT INTO "new_Session" ("accessToken", "expires", "id", "isOnline", "scope", "shop", "state", "userId") SELECT "accessToken", "expires", "id", "isOnline", "scope", "shop", "state", "userId" FROM "Session";
DROP TABLE "Session";
ALTER TABLE "new_Session" RENAME TO "Session";
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_MySession" (
"id" TEXT NOT NULL PRIMARY KEY,
"shop" TEXT NOT NULL,
"state" TEXT NOT NULL,
"isOnline" BOOLEAN NOT NULL DEFAULT false,
"scope" TEXT,
"expires" DATETIME,
"accessToken" TEXT NOT NULL,
"userId" BIGINT,
"firstName" TEXT,
"lastName" TEXT,
"email" TEXT,
"accountOwner" BOOLEAN,
"locale" TEXT,
"collaborator" BOOLEAN,
"emailVerified" BOOLEAN
);
INSERT INTO "new_MySession" ("accessToken", "expires", "id", "isOnline", "scope", "shop", "state", "userId") SELECT "accessToken", "expires", "id", "isOnline", "scope", "shop", "state", "userId" FROM "MySession";
DROP TABLE "MySession";
ALTER TABLE "new_MySession" RENAME TO "MySession";
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,38 @@ datasource db {
}

model Session {
id String @id
shop String
state String
isOnline Boolean @default(false)
scope String?
expires DateTime?
accessToken String
userId BigInt?
id String @id
shop String
state String
isOnline Boolean @default(false)
scope String?
expires DateTime?
accessToken String
userId BigInt?
firstName String?
lastName String?
email String?
accountOwner Boolean?
locale String?
collaborator Boolean?
emailVerified Boolean?
}

// We use this in unit tests to ensure it works with a different model name
model MySession {
id String @id
shop String
state String
isOnline Boolean @default(false)
scope String?
expires DateTime?
accessToken String
userId BigInt?
id String @id
shop String
state String
isOnline Boolean @default(false)
scope String?
expires DateTime?
accessToken String
userId BigInt?
firstName String?
lastName String?
email String?
accountOwner Boolean?
locale String?
collaborator Boolean?
emailVerified Boolean?
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@ describe('PrismaSessionStorage', () => {
});

// Using the default table name
batteryOfTests(async () => new PrismaSessionStorage<PrismaClient>(prisma));
batteryOfTests(
async () => new PrismaSessionStorage<PrismaClient>(prisma),
true,
);

// Using a custom table name
batteryOfTests(
async () =>
new PrismaSessionStorage<PrismaClient>(prisma, {tableName: 'mySession'}),
true,
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,18 @@ export class PrismaSessionStorage<T extends PrismaClient>
userId:
(sessionParams.onlineAccessInfo?.associated_user
.id as unknown as bigint) || null,
firstName:
sessionParams.onlineAccessInfo?.associated_user.first_name || null,
lastName:
sessionParams.onlineAccessInfo?.associated_user.last_name || null,
email: sessionParams.onlineAccessInfo?.associated_user.email || null,
accountOwner:
sessionParams.onlineAccessInfo?.associated_user.account_owner || false,
lizkenyon marked this conversation as resolved.
Show resolved Hide resolved
locale: sessionParams.onlineAccessInfo?.associated_user.locale || null,
collaborator:
sessionParams.onlineAccessInfo?.associated_user.collaborator || false,
emailVerified:
sessionParams.onlineAccessInfo?.associated_user.email_verified || false,
};
}

Expand All @@ -137,8 +149,25 @@ export class PrismaSessionStorage<T extends PrismaClient>
shop: row.shop,
state: row.state,
isOnline: row.isOnline,
userId: String(row.userId),
lizkenyon marked this conversation as resolved.
Show resolved Hide resolved
firstName: String(row.firstName),
lastName: String(row.lastName),
email: String(row.email),
locale: String(row.locale),
};

if (row.accountOwner !== null) {
sessionParams.accountOwner = row.accountOwner;
}

if (row.collaborator !== null) {
sessionParams.collaborator = row.collaborator;
}

if (row.emailVerified !== null) {
sessionParams.emailVerified = row.emailVerified;
}

if (row.expires) {
sessionParams.expires = row.expires.getTime();
}
Expand All @@ -151,11 +180,7 @@ export class PrismaSessionStorage<T extends PrismaClient>
sessionParams.accessToken = row.accessToken;
}

if (row.userId) {
sessionParams.onlineAccessInfo = String(row.userId);
}

return Session.fromPropertyArray(Object.entries(sessionParams));
return Session.fromPropertyArray(Object.entries(sessionParams), true);
}

private getSessionTable(): T['session'] {
Expand Down