zapishis-client/apps/web/BAN_CHECK_README.md
Vlad Chikalkin 81fa32c3d2
Release/rc 1 (#87)
* feat(profile): implement local hooks for profile and service data editing

- Added `useProfileEdit` and `useServiceEdit` hooks to manage pending changes and save functionality for profile and service data cards.
- Updated `ProfileDataCard` and `ServiceDataCard` components to utilize these hooks, enhancing user experience with save and cancel options.
- Introduced buttons for saving and canceling changes, improving the overall interactivity of the forms.
- Refactored input handling to use `updateField` for better state management.

* feat(bot): integrate Redis and update bot configuration

- Added Redis service to both docker-compose files for local development and production environments.
- Updated bot configuration to utilize the Grammy framework, replacing Telegraf.
- Implemented graceful shutdown for the bot, ensuring proper resource management.
- Refactored bot commands and removed deprecated message handling logic.
- Enhanced environment variable management for Redis connection settings.
- Updated dependencies in package.json to include new Grammy-related packages.

* fix(registration): improve error handling for customer creation

- Updated error handling in the registration feature to return a generic error message when documentId is not present, enhancing user experience by providing clearer feedback.

* feat(bot): add unhandled command message and integrate unhandled feature

- Introduced a new message for unhandled commands in Russian localization to improve user feedback.
- Integrated the unhandled feature into the bot's middleware for better command handling.

* feat(locales): update Russian localization with additional contact information

- Enhanced the short description in the Russian localization file to include a contact note for user inquiries, improving user support accessibility.

* feat(help): enhance help command with support information

- Updated the help command to include a support message in the Russian localization, providing users with a contact point for inquiries.
- Improved the command response by combining the list of available commands with the new support information, enhancing user experience.

* fix(orders): update default sorting order for orders

- Changed the default sorting order for orders from 'datetime_start:asc' to 'datetime_start:desc' to ensure the most recent orders are displayed first, improving the user experience in order management.

* refactor(orders): remove ClientsOrdersList and streamline OrdersList component

- Eliminated the ClientsOrdersList component to simplify the orders page structure.
- Updated OrdersList to handle both client and master views, enhancing code reusability.
- Improved order fetching logic and UI rendering for better performance and user experience.

* fix(order-form): hide next button on success & error pages

* refactor(bot): streamline bot middleware and improve key generator function

- Removed unused session middleware and sequentialize function from the bot's error boundary.
- Simplified the key generator function for rate limiting by condensing its implementation.
- Enhanced overall code clarity and maintainability in the bot's configuration.

* feat(customer): implement banned customer check and enhance customer data handling

- Added `isCustomerBanned` function to determine if a customer is banned based on the `bannedUntil` field.
- Updated the `BaseService` to throw an error if a banned customer attempts to access certain functionalities.
- Enhanced the GraphQL operations to include the `bannedUntil` field in customer queries and mutations, improving data integrity and user experience.
- Integrated the `CheckBanned` component in the layout to manage banned customer states effectively.

* feat(ban-system): implement multi-level user ban checks across services

- Added a comprehensive ban checking system to prevent access for banned users at multiple levels, including database, API, and client-side.
- Introduced `bannedUntil` field in the customer model to manage temporary and permanent bans effectively.
- Enhanced `BaseService` and various service classes to include ban checks, ensuring that banned users cannot perform actions or access data.
- Updated error handling to provide consistent feedback for banned users across the application.
- Improved user experience with a dedicated ban check component and a user-friendly ban notification page.

* packages(apps/web): upgrade next@15.5.0
2025-08-26 13:23:52 +03:00

9.5 KiB
Raw Blame History

Система проверки бана пользователей

Обзор

Реализована многоуровневая система проверки бана пользователей, которая предотвращает доступ заблокированных пользователей к функциональности приложения.

1. База данных (bannedUntil поле)

В Strapi добавлено поле bannedUntil типа datetime в модель Customer:

  • null = пользователь не забанен
  • дата в будущем = временный бан до указанной даты
  • дата в далеком будущем = постоянный бан

2. Утилита проверки (packages/utils/src/customer.ts)

export function isCustomerBanned(customer: { bannedUntil?: string | null }): boolean {
  return Boolean(customer.bannedUntil && new Date() < new Date(customer.bannedUntil));
}

3. Next Auth проверка (apps/web/config/auth.ts)

В authorize callback добавлена проверка бана:

async authorize(credentials) {
  const { telegramId } = credentials ?? {};
  if (!telegramId) { throw new Error('Invalid Telegram ID'); }
  
  try {
    const { query } = await getClientWithToken();
    const result = await query({
      query: GetCustomerDocument,
      variables: { telegramId: Number(telegramId) },
    });
    const customer = result.data.customers.at(0);
    
    if (!customer || isCustomerBanned(customer)) {
      throw new Error('User is banned or not found');
    }
    return { id: telegramId };
  } catch (error) {
    throw new Error('Authentication failed');
  }
}

4. Универсальная проверка в BaseService (packages/graphql/api/base.ts)

Добавлен метод checkIsBanned() в BaseService:

/**
 * Универсальная проверка статуса бана пользователя
 * Должна вызываться в начале каждого метода сервиса
 */
protected async checkIsBanned() {
  const { query } = await getClientWithToken();
  const result = await query({
    query: GQL.GetCustomerDocument,
    variables: this._user,
  });
  const customer = result.data.customers.at(0);
  
  if (!customer) {
    throw new Error(BASE_ERRORS.NOT_FOUND_CUSTOMER);
  }
  
  if (isCustomerBanned(customer)) {
    throw new Error(ERRORS.NO_PERMISSION);
  }
  
  return { customer };
}

Использование в сервисах:

async someMethod() {
  await this.checkIsBanned(); // Проверка бана в начале метода
  // ... остальная логика
}

Обновленные сервисы:

  • CustomersService - все методы
  • ServicesService - все методы
  • OrdersService - все методы
  • SlotsService - все методы
  • RegistrationService - добавлена собственная проверка

Преимущества:

  • Автоматическая проверка во всех сервисах, наследующих от BaseService
  • Единая точка проверки бана
  • Работает как в веб-приложении, так и в боте
  • Простота добавления в новые методы
  • Защита всех API методов от забаненных пользователей

5. Защита от изменения статуса бана (packages/graphql/api/customers.ts и registration.ts)

Пользователи не могут изменять поле bannedUntil самостоятельно:

// В CustomersService
async updateCustomer(variables: Omit<VariablesOf<typeof GQL.UpdateCustomerDocument>, 'documentId'>) {
  await this.checkBanStatus();
  
  const { customer } = await this._getUser();

  // Проверяем, что пользователь не пытается изменить поле bannedUntil
  if (variables.data.bannedUntil !== undefined) {
    throw new Error(ERRORS.NO_PERMISSION);
  }

  // ... остальная логика обновления
}

// В RegistrationService
async updateCustomer(variables: VariablesOf<typeof GQL.UpdateCustomerDocument>) {
  // Проверяем бан для существующего пользователя
  if (variables.documentId) {
    const { query } = await getClientWithToken();
    const result = await query({
      query: GQL.GetCustomerDocument,
      variables: { documentId: variables.documentId },
    });
    const customer = result.data.customers.at(0);
    if (customer && isCustomerBanned(customer)) {
      throw new Error(ERRORS.NO_PERMISSION);
    }
  }

  if (variables.data.bannedUntil) {
    throw new Error(ERRORS.NO_PERMISSION);
  }

  // ... остальная логика обновления
}

Преимущества:

  • Пользователи не могут снять с себя бан
  • Только администраторы могут изменять статус блокировки
  • Дополнительный уровень безопасности
  • Защита работает во всех сервисах, которые обновляют данные пользователей
  • Единая ошибка NO_PERMISSION для всех случаев отсутствия доступа

6. Client-side Check (components/auth/ban-check.tsx)

React компонент для проверки бана на клиенте:

export function BanCheck({ children }: Readonly<PropsWithChildren>) {
  const { data: session } = useSession();
  const router = useRouter();
  const isBanned = useIsBanned();

  useEffect(() => {
    if (session?.user?.telegramId && isBanned) {
      router.push('/banned');
    }
  }, [session?.user?.telegramId, isBanned, router]);

  if (session?.user?.telegramId && isBanned) {
    return null;
  }

  return <>{children}</>;
}

Использование в layout:

export default function Layout({ children }: Readonly<PropsWithChildren>) {
  return (
    <Provider>
      <BanCheck>
        <UpdateProfile />
        <main className="grow">{children}</main>
        <BottomNav />
      </BanCheck>
    </Provider>
  );
}

7. Hook для проверки бана (hooks/api/customers.ts)

export const useIsBanned = () => {
  const { data: { customer } = {} } = useCustomerQuery();
  if (!customer) return false;
  return isCustomerBanned(customer);
};

8. Страница для забаненных пользователей (apps/web/app/(auth)/banned/page.tsx)

Создана специальная страница с информацией о бане и возможностью выхода из аккаунта.

9. Централизованные ошибки (packages/graphql/constants/errors.ts)

export const ERRORS = {
  NO_PERMISSION: 'Нет доступа',
} as const;

Архитектура системы

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Next Auth     │    │   BaseService   │    │  Client-side    │
│   (авторизация) │    │  (API методы)   │    │   (UI)          │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                       │                       │
         ▼                       ▼                       ▼
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│  isCustomerBanned│    │ checkBanStatus()│    │   BanCheck      │
│   (утилита)     │    │   (метод)       │    │  (компонент)    │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                       │                       │
         └───────────────────────┼───────────────────────┘
                                 ▼
                    ┌─────────────────────────┐
                    │   bannedUntil (DB)      │
                    │   (Strapi/PostgreSQL)   │
                    └─────────────────────────┘

Преимущества системы

Многоуровневая защита - проверка на всех уровнях приложения
Универсальность - работает в веб-приложении и боте
Простота использования - один вызов checkBanStatus() в начале метода
Безопасность - пользователи не могут обойти бан
UX - понятные сообщения и страница для забаненных
DRY принцип - нет дублирования кода
Легкость расширения - просто добавить новые проверки