0

I'm building a Discord bot dashboard API using NestJS, Passport, and TypeORM for session management. When a user attempts to log in through Discord, I encounter two errors:

[Nest] 19524  - 09.08.2024 15:03:10   ERROR [ExceptionsHandler] Duplicate entry '[access_token]' for key 'sessions.PRIMARY'
QueryFailedError: Duplicate entry 'mBU1jFgl3Q8crGxFJbuYrImdz3tk3cj6' for key 'sessions.PRIMARY'
    at Query.onResult (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\typeorm\driver\src\driver\mysql\MysqlQueryRunner.ts:246:33)
    at Query.execute (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\mysql2\lib\commands\command.js:36:14)
    at PoolConnection.handlePacket (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\mysql2\lib\connection.js:481:34)
    at PacketParser.onPacket (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\mysql2\lib\connection.js:97:12)
    at PacketParser.executeStart (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\mysql2\lib\packet_parser.js:75:16)
    at Socket.<anonymous> (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\mysql2\lib\connection.js:104:25)
    at Socket.emit (node:events:518:28)
    at addChunk (node:internal/streams/readable:559:12)
    at readableAddChunkPushByteMode (node:internal/streams/readable:510:3)
    at Readable.push (node:internal/streams/readable:390:5)
[Nest] 19524  - 09.08.2024 15:03:18   ERROR [ExceptionsHandler] Login sessions require session support. Did you forget to use `express-session` middleware?
Error: Login sessions require session support. Did you forget to use `express-session` middleware?
    at SessionStrategy.authenticate (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\passport\lib\strategies\session.js:97:41)
    at attempt (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\passport\lib\middleware\authenticate.js:378:16)
    at authenticate (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\passport\lib\middleware\authenticate.js:379:7)
    at Layer.handle [as handle_request] (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\express\lib\router\layer.js:95:5)
    at trim_prefix (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\express\lib\router\index.js:328:13)
    at C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\express\lib\router\index.js:286:9
    at Function.process_params (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\express\lib\router\index.js:346:12)
    at next (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\express\lib\router\index.js:280:10)
    at initialize (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\passport\lib\middleware\initialize.js:98:5)
    at Layer.handle [as handle_request] (C:\Users\pc\Desktop\starjin-client\starjin-api\node_modules\express\lib\router\layer.js:95:5)

I've implemented a session entity using TypeORM:

import { ISession } from 'connect-typeorm';
import {
  Column,
  DeleteDateColumn,
  Entity,
  Index,
  PrimaryColumn,
} from 'typeorm';

@Entity('sessions')
export class Session implements ISession {
  @Index()
  @Column('bigint')
  expiredAt: number;

  @PrimaryColumn('varchar', { length: 255 })
  id: string;

  @Column('text')
  json: string;

  @DeleteDateColumn()
  deletedAt?: Date;
}

main.ts:

import 'reflect-metadata';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as session from 'express-session';
import * as passport from 'passport';
import { config } from './config';
import { TypeormStore } from 'connect-typeorm';
import { SessionService } from './session/session.service';

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

  const sessionService = app.get(SessionService);

  app.use(
    session({
      secret: config.sessionSecret,
      resave: false,
      saveUninitialized: false,
      cookie: {
        maxAge: 1 * 60 * 60 * 1000,
      },
      store: new TypeormStore().connect(sessionService.getSessionRepository()),
    }),
  );

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

  app.enableCors({
    origin: 'http://localhost:3000',
    credentials: true,
  });

  await app.listen(process.env.PORT);
}
bootstrap();

discord.strategy.ts:

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Profile, Strategy } from 'passport-discord';
import { AuthService } from '../auth.service';
import { config } from '../../config';

@Injectable()
export class DiscordStrategy extends PassportStrategy(Strategy, 'discord') {
  constructor(private readonly authService: AuthService) {
    super({
      clientID: config.discordClientId,
      clientSecret: config.discordClientSecret,
      callbackURL: config.discordCallbackUrl,
      scope: ['identify', 'email', 'guilds'],
    });
  }

  async validate(
    accessToken: string,
    refreshToken: string,
    profile: Profile,
    done: (error: any, user?: any) => void,
  ) {
    const user = await this.authService.validateUser({
      avatar: profile.avatar,
      email: profile.email,
      global_name: profile.global_name,
      id: profile.id,
      username: profile.username,
      access_token: accessToken,
      refresh_token: refreshToken,
    });
    if (user) {
      done(null, user);
    } else {
      done(new Error('User not found'), null);
    }
  }
}

session.serializer.ts:

import { PassportSerializer } from '@nestjs/passport';
import { AuthService } from '../auth.service';
import { Inject } from '@nestjs/common';
import { SERVICES } from '../../utils/constants';

export class SessionSerializer extends PassportSerializer {
  constructor(
    @Inject(SERVICES.AUTH) private readonly authService: AuthService,
  ) {
    super();
  }

  serializeUser(user: any, done: (error: any, user?: any) => void) {
    console.log('serialize');
    done(null, user);
  }

  async deserializeUser(payload: any, done: (error: any, user?: any) => void) {
    console.log('deserialize');
    const user = await this.authService.findUserById(payload.id);
    return user ? done(null, user) : done(null, null);
  }
}

auth.service.ts:

import { Injectable } from '@nestjs/common';
import { IUser } from './dtos/user.type';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from '../utils/typeorm/entities/user.entity';
import { Repository } from 'typeorm';

@Injectable()
export class AuthService {
  constructor(
    @InjectRepository(User) private readonly userRepository: Repository<User>,
  ) {}

  async validateUser(profile: IUser): Promise<User> {
    const user = await this.userRepository.findOne({
      where: { id: profile.id },
    });
    if (user) {
      return user;
    }

    // Create a new user if not found
    const newUser = this.userRepository.create(profile);
    return await this.userRepository.save(newUser);
  }

  async findUserById(id: string): Promise<any> {
    return this.userRepository.findOne({ where: { id } });
  }
}

auth.module.ts:

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { DiscordStrategy } from './utils/discord.strategy';
import { AuthController } from './auth.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from '../utils/typeorm/entities/user.entity';
import { SessionSerializer } from './utils/session.serializer';
import { SERVICES } from '../utils/constants';
import { Session } from '../utils/typeorm/entities/session.entity';

@Module({
  imports: [TypeOrmModule.forFeature([User, Session])],
  providers: [
    DiscordStrategy,
    SessionSerializer,
    { provide: SERVICES.AUTH, useClass: AuthService },
  ],
  controllers: [AuthController],
})
export class AuthModule {}

I've tried ensuring correct configuration of express-session and Passport middleware. I expected the session to be created successfully and the user to be authenticated. However, the duplicate session entry and missing session support errors prevent successful login.

7
  • can you add the passportjs cconfiguration Commented Aug 10, 2024 at 6:55
  • @DileepaMabulage I added. Commented Aug 10, 2024 at 8:42
  • and the auth service code Commented Aug 10, 2024 at 9:35
  • @DileepaMabulage Added. To mention, when I try to authorize through Discord, it saves the session data (access_token etc.) to database (though it still throws Duplicate entry error), and I can see the cookie on my browser. However when I try to access to session data, it throws the [Nest] 17548 - 10.08.2024 16:44:51 ERROR [ExceptionsHandler] Login sessions require session support. Did you forget to use express-session middleware? error. Commented Aug 10, 2024 at 13:47
  • Your session configs seem correct, I doubt that validateUser function in AuthService. Can you check every time is the user getting null Commented Aug 10, 2024 at 15:45

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.