带有 Discord OAuth 2 Passport 的 Nest.js 不会登录用户

问题描述 投票:0回答:0

我的 Nest.js 代码有问题。我还是 Nest 的新手,所以我可能做错了什么。

这个概念是没有正常的护照验证 - 只有不一致的策略,就是这样。这就是为什么不需要验证密码的原因,因为不会有任何密码。

重定向到不和谐的 OAuth 页面后,我单击登录,它正确地将我重定向到 localhost:3000/profile 尽管 req.user 没有填充用户数据。

以下是控制台日志,证明登录时填充了哪些方面。

Validating Discord user {
  id: '...',
  username: '...',
  global_name: null,
  display_name: null,
  avatar: '...',    
  discriminator: '8629',
  public_flags: 0,
  flags: 0,
  banner: '...',    
  banner_color: '#6902bb',
  accent_color: 6881979,
  locale: 'pl',
  mfa_enabled: false,
  premium_type: 2,
  avatar_decoration: '...',
  email: '[email protected]',
  verified: true,
  provider: 'discord',
  accessToken: '...', 
  fetchedAt: 2023-04-28T16:49:41.974Z
}
Social login {
  discordId: '...',
  email: '[email protected]',
  is_admin: true
}
User already exists {
  discordId: '...',
  email: '[email protected]',
  is_admin: true
} UserEntity {
  discordService: undefined,
  id: 1,
  discord_id: '...',
  email: '[email protected]',
  created_at: 2023-04-21T14:34:30.044Z,
  updated_at: 2023-04-21T14:34:30.044Z,
  is_admin: true
}
Logged user: UserEntity {
  discordService: undefined,
  id: 1,
  discord_id: '...',
  email: '[email protected]',
  created_at: 2023-04-21T14:34:30.044Z,
  updated_at: 2023-04-21T14:34:30.044Z,
  is_admin: true
}
User:  {
  discordId: '...',
  username: '...',
  discriminator: '8629',
  email: '[email protected]',
  avatar: '...',    
  accessToken: '...', 
  refreshToken: '...' 
}
Req user: undefined

当然所有

...
都隐藏适当的内容由于隐私。

让我们从 main.ts 开始:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as session from 'express-session';
import * as passport from 'passport';
import { UserSerializer } from './auth/user.serializer';
import { UserEntity } from './user/user.entity';

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

  app.use(
    session({
      secret: process.env.SESSION_SECRET,
      resave: false,
      saveUninitialized: false,
    }),
  );

  // const userSerializer: UserSerializer = app.get(UserSerializer);

  // userSerializer.deserializeUser = async (
  //   id: number,
  //   done: (err: Error, payload: string | object) => void,
  // ): Promise<any> => {
  //   const userService = app.get('UserService');
  //   const user: UserEntity = await userService.findById(id);
  //   done(null, user);
  // };

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

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

如你所见,我在登录后使用序列化来记住用户,暂时不起作用,因为用户未通过,所以它被注释掉了。

接下来,app.controller.ts:

import { Controller, Get, UseGuards, Req } from '@nestjs/common';
import { AppService } from './app.service';
import { AuthGuard } from '@nestjs/passport';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(@Req() req): string {
    
    this.appService.sendDiscordMessage('Jestem bogiem');

    console.log("Req user:", req.user);

    // Return user data nicely formatted to string
    return JSON.stringify(req.user, null, 4);
  }

  @Get('profile')
  // @UseGuards(AuthGuard('discord'))
  getProfile(@Req() req): string {
    console.log("Req user:", req.user);
    return JSON.stringify(req.user, null, 4);
  }

}

如您所见 - 有一个名为 getProfile 的控制器,应该填充 req.user 但它是 undefined。 我注释掉了 AuthGuard,因为如果我离开它,它会再次将我重定向到 Discord 登录页面(因为我'我没有登录)。再次登录后,它让我进进出出。

接下来我们有 auth.service.ts

import { BadRequestException, Injectable } from "@nestjs/common";
import { UserService } from "src/user/user.service";
import { UserInterface } from "src/user/user.interface";
import { UserEntity } from "src/user/user.entity";

@Injectable()
export class AuthService {
    constructor(
        private userService: UserService,
    ) {}

    async socialLogin(req: { user: any; }): Promise<UserEntity> {

        console.log(`Social login`, req.user);

        // Passport middleware will attach the user object to the request object
        // if the user is authenticated
        if (!req.user) {
            throw new BadRequestException('No account presented');
        }

        const user: UserInterface = {
            ...req.user,
            discord_id: req.user.discordId,
            email: req.user.email,
            is_admin: req.user.is_admin,
        }

        // Check if the user exists in the database
        const existingUser = await this.userService.findByDiscordId(user.discord_id);

        if (existingUser) {
            console.log(`User already exists`, req.user, existingUser);
            return existingUser;
        }

        console.log(`Creating new user`, req.user);

        const newUser = await this.userService.create(user);
        return newUser;
    }

}

因为我希望 Discord 数据始终正确,所以如果用户存在,它不会拒绝任何内容。如果它还没有,它只会创造一个新用户。

下一个 - discord.strategy.ts


import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy } from "passport-discord";
import { AuthService } from "./auth.service";
import { DiscordService } from "src/discord/discord.service";
import { PermissionsBitField } from "discord.js";
import { UserSerializer } from "./user.serializer";

@Injectable()
export class DiscordStrategy extends PassportStrategy(Strategy, 'discord') {
    constructor(
        private readonly authService: AuthService,
        private readonly discordService: DiscordService,
        private readonly userSerializer: UserSerializer,
    ) {
        console.log(
            'Discord env:',
            process.env.DISCORD_CLIENT_ID,
            process.env.DISCORD_CLIENT_SECRET,
            process.env.DISCORD_REDIRECT_URL,
        );
        super({
            clientID: process.env.DISCORD_CLIENT_ID,
            clientSecret: process.env.DISCORD_CLIENT_SECRET,
            callbackURL: process.env.DISCORD_REDIRECT_URL,
            scope: ['identify', 'email'],
        });
    }

    async validate(accessToken: string, refreshToken: string, profile: any, done: (error: any, user?: any, info?: any) => void) {
        
        const { id, username, discriminator, avatar, email } = profile;

        console.log('Validating Discord user', profile);

        // Check if user is an admin on Discord
        const isAdmin = await this.discordService.userHasRights(id, PermissionsBitField.Flags.Administrator);

        const loggedUser = await this.authService.socialLogin({
            user: {
                discordId: id,
                email: email,
                is_admin: isAdmin
            }
        });

        console.log("Logged user:", loggedUser);

        if (!loggedUser) {
            console.log("Logged user:", loggedUser);
            done(null, false);
            return;
        }

        let user = {
            discordId: id,
            username,
            discriminator,
            email,
            avatar,
            accessToken,
            refreshToken,
        }

        // this.userSerializer.serializeUser(user, done);

        console.log('User: ', user);

        done(null, user);

        return user;
    }
}

如您所见,有一部分是关于序列化的,但正如我所说,现在还没有使用。还有一个 Discord 服务,但它是简单的 Discord.js 服务并正确返回所有内容。

接下来我将添加一个 user.service.ts 但只是出于好奇

import { BadRequestException, Injectable, InternalServerErrorException } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { UserEntity } from "./user.entity";
import { Repository } from "typeorm";
import { UserInterface } from "./user.interface";
import { DiscordService } from "src/discord/discord.service";

@Injectable()
export class UserService {

    constructor(
        @InjectRepository(UserEntity)
        private readonly userRepository: Repository<UserEntity>,
        private readonly discordService: DiscordService,
    ) {}

    /**
     * Find a user by their ID
     * @param id The ID of the user
     * @returns The user
     */
    async findById(id: number): Promise<UserEntity> {
        return await this.userRepository.findOneBy({ id });
    }

    /**
     * Find a user by their Discord ID
     * @param discord_id The Discord ID of the user
     * @returns The user
     */
    async findByDiscordId(discord_id: string): Promise<UserEntity> {
        return await this.userRepository.findOneBy({ discord_id });
    }

    /**
     * Find a user by their email address
     * @param email The email address of the user
     * @returns The user
     */
    async findByEmail(email: string): Promise<UserEntity> {
        return await this.userRepository.findOneBy({ email });
    }

    /**
     * Create a new user
     * @param user The user to create
     * @returns The created user
     */
    async create(user: UserInterface): Promise<UserEntity> {

        // If user doesn't exist on Discord, abort
        console.log(`Checking if user ${user.discord_id} exists on Discord`);
        if (!await this.discordService.userExists(user.discord_id)) {
            console.log(`User ${user.discord_id} does not exist on Discord`);
            return null;
        }

        const newUser = await this.userRepository.create({
            discord_id: user.discord_id,
            email: user.email,
            is_admin: user.is_admin || false,
        });

        return await this.userRepository.save(newUser);
    }

    /**
     * Update a user by their ID
     * @param id The ID of the user to update
     * @param properties The properties to update
     * @returns The updated user
     */
    async update(id: number, properties: any): Promise<UserEntity> {
        
        const user = await this.findById(id);

        if (!user) {
            throw new BadRequestException('User not found');
        }

        try {
            Object.assign(user, properties);

            return await this.userRepository.save(user);
        } catch (error) {
            throw new InternalServerErrorException(error);
        }
    }
}

还有 user.entity.ts

import { User } from "discord.js";
import { DiscordService } from "src/discord/discord.service";
import { BaseEntity, Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm";

@Entity()
export class UserEntity extends BaseEntity {

    @PrimaryGeneratedColumn()
    id: number;

    @Column({ unique: true })
    discord_id: string;

    @Column()
    email: string;

    @CreateDateColumn()
    created_at: Date;

    @UpdateDateColumn()
    updated_at: Date;

    @Column()
    is_admin: boolean;

    constructor(
        partial: Partial<UserEntity>,
        private readonly discordService: DiscordService,
    ) {
        super();
        Object.assign(this, partial);
    }

    get hasAvailableDiscordAccount(): boolean {
        return this.discordUser !== null;
    }

    get discordUser(): Promise<User> {
        return this.discordService.getUserById(this.discord_id);
    }

    get username(): Promise<string> {
        return this.discordUser.then(user => user.username);
    }

    get discriminator(): Promise<string> {
        return this.discordUser.then(user => user.discriminator);
    }

    get displayAvatarURL(): Promise<string> {
        return this.discordUser.then(user => user.displayAvatarURL());
    }
}

如果您知道为什么登录不起作用,请告诉我。我会感激它一百次(或一次投票)。

我期待在使用 discord OAuth 2 登录后,Discord 将重定向到 localhost:3000/profile,它将用用户数据填充 req.user。

我试图包含用户序列化方法,但它返回 401 未授权异常,所以我猜它会与填充用户一起工作。

user.serializer.ts 的代码以防万一:

import { Injectable } from "@nestjs/common";
import { UserService } from "src/user/user.service";
import { PassportSerializer } from "@nestjs/passport";


@Injectable()
export class UserSerializer extends PassportSerializer {
    constructor(private readonly userService: UserService) {
        super();
    }

    serializeUser(user: any, done: (err: Error, users: any) => void): any {
        done(null, user.id);
    }

    async deserializeUser(
        id: number,
        done: (err: Error, payload: string | object) => void,
    ): Promise<any> {
        const user = await this.userService.findById(id);
        done(null, user);
    }
}
mysql typescript discord nestjs passport.js
© www.soinside.com 2019 - 2024. All rights reserved.