Контроль доступа по NFT
Описание
Система контроля доступа на основе владения продуктовыми NFT. Реализована через NestJS Guard (RoleByNftGuard) и декоратор (@RoleByNft), которые проверяют наличие у пользователя NFT определенного типа перед предоставлением доступа к защищенным endpoint'ам.
Модуль находится в shared-пакете @magnetmlm/common-backend и может использоваться во всех backend сервисах проекта.
Архитектура
Компоненты
| Компонент | Тип | Расположение | Описание |
|---|---|---|---|
RoleByNftGuard | NestJS Guard | packages/reusable-magnet-backend | Guard для проверки доступа по NFT |
@RoleByNft | Decorator | packages/reusable-magnet-backend | Декоратор для маркировки защищенных endpoint'ов |
checkProductAccessByNft | Service Method | apps/backend/src/nft/nft.service.ts | Проверка владения NFT через контракт |
Логика работы
1. Общий процесс проверки
Request → JwtAuthGuard → RoleByNftGuard → checkProductAccessByNft → Allow/Deny
Этапы:
- Пользователь отправляет запрос с JWT токеном
JwtAuthGuardпроверяет токен и извлекает данные пользователяRoleByNftGuardпроверяет метаданные endpoint'а (декоратор@RoleByNft)- Если требуется NFT — вызывается
checkProductAccessByNft() - Проверяется владение NFT через контракт и БД
- Доступ разрешен или выбрасывается
ForbiddenException
2. Приоритеты доступа
Guard работает с учетом иерархии ролей:
Автоматический доступ (bypass):
- ✅
UserRole.Admin - ✅
UserRole.UltimateAdmin - ✅ Endpoint без декоратора
@RoleByNft - ✅ Отсутствие
NftProductAccessService(fallback для совместимости)
Требуется проверка NFT:
- Обычные пользователи
- Endpoint с декоратором
@RoleByNft(NftType.SMARTLINK)и т.д.
3. Проверка владения NFT
apps/backend/src/nft/nft.service.ts
/**
* Проверяет доступ пользователя к продукту через владение NFT
* @param uid - ID пользователя из diamond контракта
* @param nftType - Тип NFT (SMARTLINK | TG_SOFT)
* @returns true если у пользователя есть хотя бы один NFT данного типа
*/
public async checkProductAccessByNft(
uid: number,
nftType: NftType,
): Promise<boolean> {
// 1. Найти пользователя по uid
const user = await this.userRepository.findOne({ where: { uid } });
// 2. Получить token IDs из контракта по адресу пользователя
const tokenIds = await this.getUserNftTokenIds(nftType, user.addr);
// 3. Проверить наличие в БД
const count = await this.nftSchema.countDocuments({
nftId: { $in: tokenIds.map(Number) },
});
return count > 0;
}
Процесс проверки:
- Поиск пользователя в MySQL по
uid - Получение списка
tokenIdsNFT с блокчейна (черезgetUserNftTokenIds) - Проверка существования хотя бы одного NFT в MongoDB
- Возврат
trueесли NFT найден, иначеfalse
Использование
Защита контроллера
apps/backend/src/slink/slink.controller.ts
import { UseGuards } from '@nestjs/common';
import {
JwtAuthGuard,
RoleByNftGuard,
RoleByNft,
} from '@magnetmlm/common-backend';
import { NftType } from '@magnetmlm/common';
@Controller('slink')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, RoleByNftGuard)
@RoleByNft(NftType.SMARTLINK)
export class SlinkController {
// Все endpoint'ы требуют наличия SMARTLINK NFT
@Get()
getAllSmartlinks() {
// Доступ только для владельцев SMARTLINK NFT
}
}
Защита отдельного endpoint'а
@Controller('product')
@UseGuards(JwtAuthGuard)
export class ProductController {
@Get('smartlink-feature')
@UseGuards(RoleByNftGuard)
@RoleByNft(NftType.SMARTLINK)
getSmartlinkFeature() {
// Требуется SMARTLINK NFT
}
@Get('tg-soft-feature')
@UseGuards(RoleByNftGuard)
@RoleByNft(NftType.TG_SOFT)
getTgSoftFeature() {
// Требуется TG_SOFT NFT
}
@Get('public-feature')
getPublicFeature() {
// Доступно всем авторизованным пользователям
}
}
Множественные требования NFT
@Get('advanced-feature')
@UseGuards(RoleByNftGuard)
@RoleByNft(NftType.SMARTLINK, NftType.TG_SOFT)
getAdvancedFeature() {
// Требуется наличие ОБОИХ типов NFT
}
Интеграция в модуль
1. Подключение в модуле
apps/backend/src/slink/slink.module.ts
import { Module } from '@nestjs/common';
import { RoleByNftGuard } from '@magnetmlm/common-backend';
import { NftModule } from '../nft/nft.module';
@Module({
imports: [NftModule],
controllers: [SlinkController],
providers: [
SlinkService,
RoleByNftGuard,
{
provide: 'NftProductAccessService',
useExisting: NftService,
},
],
})
export class SlinkModule {}
Важно:
- Импортировать
NftModuleдля доступа кNftService - Зарегистрировать провайдер
'NftProductAccessService'→NftService - Добавить
RoleByNftGuardв providers
2. Реализация интерфейса
NftService должен реализовывать интерфейс INftProductAccessChecker:
packages/reusable-magnet-backend/src/role-by-nft.guard.ts
export interface INftProductAccessChecker {
checkProductAccessByNft(
uid: number,
productType: NftType,
): Promise<boolean>;
}
Обработка ошибок
Ошибки Guard'а
ForbiddenException:
{
"statusCode": 403,
"message": "Access denied. NFT product required: SMARTLINK"
}
Выбрасывается когда:
- Пользователь не владеет требуемым NFT
- У пользователя нет ни одного NFT указанного типа