Я хочу исключить поле пароля из возвращенного JSON. Я использую NestJS и Typeorm.
Решение, представленное на этот вопрос не работает ни у меня, ни в NestJS. При необходимости я могу опубликовать свой код. Есть другие идеи или решения? Спасибо.
Я хочу исключить поле пароля из возвращенного JSON. Я использую NestJS и Typeorm.
Решение, представленное на этот вопрос не работает ни у меня, ни в NestJS. При необходимости я могу опубликовать свой код. Есть другие идеи или решения? Спасибо.
Я бы предложил создать перехватчик, который использует преимущества библиотеки class-transformer:
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
call$: Observable<any>,
): Observable<any> {
return call$.pipe(map(data => classToPlain(data)));
}
}
Затем просто исключите свойства с помощью декоратора @Exclude()
, например:
import { Exclude } from 'class-transformer';
export class User {
id: number;
email: string;
@Exclude()
password: string;
}
ClassSerializerInterceptor
, если я не ошибаюсь
- person Jemar Jones; 30.12.2020
Вы можете перезаписать метод toJSON модели следующим образом.
@Entity()
export class User extends BaseAbstractEntity implements IUser {
static passwordMinLength: number = 7;
@ApiModelProperty({ example: faker.internet.email() })
@IsEmail()
@Column({ unique: true })
email: string;
@IsOptional()
@IsString()
@MinLength(User.passwordMinLength)
@Exclude({ toPlainOnly: true })
@Column({ select: false })
password: string;
@IsOptional()
@IsString()
@Exclude({ toPlainOnly: true })
@Column({ select: false })
passwordSalt: string;
toJSON() {
return classToPlain(this);
}
validatePassword(password: string) {
if (!this.password || !this.passwordSalt) {
return false;
}
return comparedToHashed(password, this.password, this.passwordSalt);
}
}
При использовании метода преобразователя классов plainToClass вместе с @Exclude ({toPlainOnly: true}) пароль будет исключен из ответа JSON, но будет доступен в экземпляре модели. Мне нравится это решение, потому что оно сохраняет всю конфигурацию модели в сущности.
В дополнение к ответу Камила:
Вместо создания собственного перехватчика теперь вы можете использовать встроенный ClassSerializerInterceptor
, см. документацию по сериализации .
@UseInterceptors(ClassSerializerInterceptor)
Вы можете использовать его в классе контроллера или его отдельных методах. Каждая сущность, возвращаемая таким методом, будет преобразована с помощью преобразователя классов и, следовательно, будет учитывать аннотации @Exclude
:
import { Exclude } from 'class-transformer';
export class User {
/** other properties */
@Exclude()
password: string;
}
Вы можете настроить его поведение, определив @SerializeOptions()
на вашем контроллере или его методах:
@SerializeOptions({
excludePrefixes: ['_'],
groups: ['admin']
})
чтобы, например, предоставить определенные поля только определенным пользователям:
@Expose({ groups: ["admin"] })
adminInfo: string;
@Expose({ groups: ["user", "admin"] })
? Я изо всех сил пытался понять, как вы могли бы указать, какую группу следует использовать в контроллере. Из class-transformer
документации я вижу, что он поддерживает classToPlain(user, { groups: ["user"] })
, но я немного запутался, где его вызывает nest, и могу ли я пройти через эти параметры.
- person Jamie Street; 03.04.2019
SerializeOptions
работает, только если groups
статичны для каждой конечной точки. Если вы хотите динамически определять groups
на основе запроса, вы должны реализовать для этого собственный перехватчик.
- person Kim Kern; 26.05.2020
@Expose
не работал должным образом на уровне метода по запросу POST
. Я использую nestjs 6.5.3
- person ddsultan; 21.12.2020
В этой теме много хороших ответов. Чтобы основываться на ответе apun выше, я думаю, что следующий подход с наименьшей вероятностью приведет к случайной утечке поля пароля:
@Column({ select: false })
password: string
Если объект не выбирает это поле по умолчанию, и его можно запрашивать только явно (например, через addSelect()
при использовании построителя запросов), я думаю, что вероятность того, что где-то есть промах, намного меньше, и меньше полагаться на магия фреймворка (которая в конечном итоге является class-transformer
библиотекой) для обеспечения безопасности. На практике во многих проектах единственное место, где вы явно выбираете, - это место проверки учетных данных.
Этот подход также может помочь предотвратить случайное попадание хэша пароля в записи журнала и т. Д., О чем еще не упоминалось. Гораздо безопаснее бросать пользовательский объект, зная, что он не содержит конфиденциальной информации, особенно если он может быть сериализован где-нибудь в записи журнала.
Все сказанное, документированный подход для NestJS заключается в использовании декоратора @Exclude()
, и принятый ответ от основателя проекта.
Я определенно часто использую декоратор Exclude()
, но не обязательно для полей пароля или соли.
Вы можете использовать пакет https://github.com/typestack/class-transformer
Вы можете исключить свойства с помощью декораторов, а также вы можете исключить свойства с помощью групп.
@Column({ select: false })
password: string
О скрытых столбцах можно прочитать здесь
Также - есть документация об этом https://docs.nestjs.com/techniques/serialization.
Это уже старая тема, но я все же хотел бы поделиться своим решением, может быть, это кому-то поможет. Я использую Express, но мой пример, наверное, подходит и для этого случая.
Итак, в своем классе сущности вы просто определяете дополнительный статический метод removePassword()
, который получает экземпляр самой сущности, и вы отправляете объект, созданный этим методом, вместо исходного объекта сущности, полученного из БД:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity({ name: 'users' })
export class User {
@PrimaryGeneratedColumn('uuid')
id: string | undefined;
@Column({ type: 'varchar', length: 100, unique: true })
email: string | undefined;
@Column({ type: 'text' })
password: string | undefined;
static removePassword(userObj: User) {
return Object.fromEntries(
Object.entries(userObj).filter(([key, val]) => key !== 'password')
);
}
}
Это в основном похоже на вызов метода filter()
в массиве, но немного сложнее: вы создаете новый объект (тот, который вы в конечном итоге отправите) из массива записей, созданный путем фильтрации записи пароля из исходного массива записей (с этим точным filter()
метод).
В обработчиках маршрутов вы всегда будете делать что-то вроде этого:
import { Router, Request, Response, NextFunction } from 'express';
import { User } from '../../entity/User';
import { getRepository } from 'typeorm';
const router = Router();
router.post(
'/api/users/signin',
(req: Request, res: Response, next: NextFunction) => {
const { email } = req.body;
getRepository(User)
.findOne({ email })
.then(user =>
user ? res.send(User.removePassword(user)) : res.send('No such user:(')
)
.catch(err => next(new Error(err.message)));
}
);
export { router as signinRouter };
Вы также можете использовать обычный метод:
withoutPassword() {
return Object.fromEntries(
Object.entries(this).filter(([key, val]) => key !== 'password')
);
}
и в вашем обработчике маршрута:
res.send(user.withoutPassword());
Чтобы избежать болей в спине и головных болей, я бы предложил использовать plainToClass
, чтобы обеспечить полную совместимость мангуста / преобразования классов и избежать необходимости делать пользовательские переопределения для преодоления этой проблемы.
Например, добавьте это в свой сервис:
async validateUser(email: string, password: string): Promise<UserWithoutPassword | null> {
const user = await this.usersService.findOne({ email });
if (user && await compare(password, user.password))
{
return plainToClass(UserWithoutPassword, user.toObject());
}
return null;
}
Источник: ответ на Stackoverflow
то, что просто работало для меня, было просто
аннотирование поля с помощью @Exclude и переопределение метода модели toJSON, например
toJSON() { return classToPlain(this); }