kleydon / prisma-session-store

Express session store for Prisma
MIT License
116 stars 18 forks source link

Exception thrown for NestJS/Prisma implementation #99

Closed SmilingJoe closed 1 year ago

SmilingJoe commented 1 year ago

(After investigating, appears that this may be related to Issue #91)

Working on integrating the PrismaSessionStore into a NestJS/Prisma project.

Running into a problem where an exception is being thrown after the first session is stored to the MySQL database, where no session was previously recorded.

With passing prismaService as the PrismaClient argument into PrismaSessionStore() the code compiles and runs. However when I attempt to execute the login route I see that a new session record has been created in the DB, but the server throws the following exception:

[Nest] 16436  - 07/17/2022, 9:51:38 AM   ERROR [ExceptionsHandler] 
Invalid `this.prisma[this.sessionModelName].delete()` invocation in
D:\Projects\Testing\nestjs-prisma-sessions\node_modules\@quixo3\prisma-session-store\dist\lib\prisma-session-store.js:266:91

  263 case 3:
  264     _a.sent();
  265     return [3 /*break*/, 6];
→ 266 case 4: return [4 /*yield*/, this.prisma[this.sessionModelName].delete(
  An operation failed because it depends on one or more records that were required but not found. Record to delete does not exist.

Initially I thought it might be a case-sensitivity issue, but after setting the session table to lowercase and still seeing the error, I ruled that out.

This appears to happen only during the first login attempt, when the session table is empty. Upon submitting the login request a second time in Postman, the request succeeds without an exception and the user data is returned.

I modified the node_modules/@quix03/prisma-session-store/dist/lib/prisma-session-store.js file to include console.log lines at the beginning of the _this.destroy() method, and for the case 4 option.

        _this.destroy = function (sid, callback) { return __awaiter(_this, void 0, void 0, function () {
            var e_3;
            var _this = this;
            return __generator(this, function (_a) {
                console.log(`destroy() sid=[${sid}] label=[${_a.label}]`);
                switch (_a.label) {
                    ....
                    ....
                    case 4:
                        console.log(`Attempting to delete session record with sid [${sid}] from [${this.sessionModelName}]`);
                        return [4 /*yield*/, this.prisma[this.sessionModelName].delete({ where: { sid: sid } })];
                   ....
                   ....

On first pass through the login route, I get this output:

destroy() sid=[nDp2LNSbzv4zo6KX4BtqcNlelNJaEX2Z] label=[0]
destroy() sid=[nDp2LNSbzv4zo6KX4BtqcNlelNJaEX2Z] label=[1]
destroy() sid=[nDp2LNSbzv4zo6KX4BtqcNlelNJaEX2Z] label=[4]
Attempting to delete session record with sid [nDp2LNSbzv4zo6KX4BtqcNlelNJaEX2Z] from [session]
destroy() sid=[nDp2LNSbzv4zo6KX4BtqcNlelNJaEX2Z] label=[5]
destroy() sid=[nDp2LNSbzv4zo6KX4BtqcNlelNJaEX2Z] label=[7]
[Nest] 33316  - 07/17/2022, 10:56:24 AM   ERROR [ExceptionsHandler]
Invalid `this.prisma[this.sessionModelName].delete()` invocation in
D:\Projects\Testing\YouTube-Anson-NestJS-Tutorial\nestjs-tutorial-postman-sessions\node_modules\@quixo3\prisma-session-store\dist\lib\prisma-session-store.js:269:87

  266     return [3 /*break*/, 6];
  267 case 4:
  268     console.log(`Attempting to delete session record with sid [${sid}] from [${this.sessionModelName}]`);
→ 269     return [4 /*yield*/, this.prisma[this.sessionModelName].delete(
  An operation failed because it depends on one or more records that were required but not found. Record to delete does not exist.

If I attempt to execute the login route again with the same credentials in Postman, I don't get the exception and the user details are returned as expected.

destroy() sid=[NNrOJWyyBNAeuo2-kn9TpHx_ehLm-_b3] label=[0]
destroy() sid=[NNrOJWyyBNAeuo2-kn9TpHx_ehLm-_b3] label=[1]
destroy() sid=[NNrOJWyyBNAeuo2-kn9TpHx_ehLm-_b3] label=[4]
Attempting to delete session record with sid [NNrOJWyyBNAeuo2-kn9TpHx_ehLm-_b3] from [session]
destroy() sid=[NNrOJWyyBNAeuo2-kn9TpHx_ehLm-_b3] label=[5]
destroy() sid=[NNrOJWyyBNAeuo2-kn9TpHx_ehLm-_b3] label=[8]

Not sure why it's attempting to delete the first session record it just created? Confused as to why the exception only occurs when the session table is completely empty. Obviously I'm missing something here.

Any help would be greatly appreciated.

Packages

@nestjs/common: ^8.0.0
@nestjs/config: ^2.2.0
@nestjs/core: ^8.0.0
@nestjs/passport: ^9.0.0
@nestjs/platform-express: ^8.0.0
@prisma/client: ^4.0.0
@quixo3/prisma-session-store: ^3.1.8
express-session: ^1.17.3
passport: ^0.6.0
passport-local: ^1.0.0
prisma: ^4.0.0

References:

NestJS: https://docs.nestjs.com/recipes/prisma#use-prisma-client-in-your-nestjs-services Prisma: https://www.prisma.io/nestjs (About halfway down the page)

prisma.service.ts

import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }

  async enableShutdownHooks(app: INestApplication) {
    this.$on('beforeExit', async () => {
      await app.close();
    });
  }
}

main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { PrismaService } from './prisma.service';
import { PrismaSessionStore } from '@quixo3/prisma-session-store';
import * as session from 'express-session';
import * as passport from 'passport';

declare const module: any;

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const prismaService: PrismaService = app.get(PrismaService);
  prismaService.enableShutdownHooks(app);

  app.use(
    session({
      name: 'NESTJS-SESSION-ID',
      secret: 'FHSDOWOEIHFSOIDHAOSIHZDFJNNDDOQIWNDWOIHFSRLIFHLA',
      resave: false,
      saveUninitialized: false,
      cookie: {
        maxAge: 7 * 24 * 60 * 60 * 1000,
      },
      store: new PrismaSessionStore(
        prismaService,
        {
            checkPeriod: 2 * 60 * 1000,  //ms
            dbRecordIdIsSessionId: true,
            dbRecordIdFunction: undefined,
          }
      )
    }),
  );

  app.use(passport.initialize());
  app.use(passport.session());

  await app.listen(3000);
}
bootstrap();

My prisma.schema file

Migration completed successfully, table exists in DB

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model session {
    id        String   @id
    sid       String   @unique
    data      String   @db.VarChar(500)
    expiresAt DateTime
}

model Users {
    id        String   @id @default(uuid())
    createdAt DateTime @default(now())
    updatedAt DateTime @updatedAt
    email     String?  @unique
    username  String   @unique
    password  String   @db.VarChar(500)
    salt      String   @db.VarChar(500)
}
SmilingJoe commented 1 year ago

As a temporary workaround, if I downgrade and pin passport to version 0.5.3 the problem does not occur.

Going with this solution for the time being.

kleydon commented 1 year ago

@SmilingJoe Thanks for logging this, and sorry nobody responded yet.

Curious: Are you sure that an exception was occurring, causing a crash or an actual problem? Is it possible that a warning log message is being output, but that no crash was actually occurring?

I think (based on the thread in issue #91, as you referenced) that the issue may now be fixed - but I'm not 100% certain.