我的 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);
}
}