* 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
240 lines
9.5 KiB
Markdown
240 lines
9.5 KiB
Markdown
# Система проверки бана пользователей
|
||
|
||
## Обзор
|
||
|
||
Реализована многоуровневая система проверки бана пользователей, которая предотвращает доступ заблокированных пользователей к функциональности приложения.
|
||
|
||
## 1. База данных (`bannedUntil` поле)
|
||
|
||
В Strapi добавлено поле `bannedUntil` типа `datetime` в модель `Customer`:
|
||
- `null` = пользователь не забанен
|
||
- `дата в будущем` = временный бан до указанной даты
|
||
- `дата в далеком будущем` = постоянный бан
|
||
|
||
## 2. Утилита проверки (`packages/utils/src/customer.ts`)
|
||
|
||
```typescript
|
||
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 добавлена проверка бана:
|
||
|
||
```typescript
|
||
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`:
|
||
|
||
```typescript
|
||
/**
|
||
* Универсальная проверка статуса бана пользователя
|
||
* Должна вызываться в начале каждого метода сервиса
|
||
*/
|
||
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 };
|
||
}
|
||
```
|
||
|
||
**Использование в сервисах:**
|
||
```typescript
|
||
async someMethod() {
|
||
await this.checkIsBanned(); // Проверка бана в начале метода
|
||
// ... остальная логика
|
||
}
|
||
```
|
||
|
||
**Обновленные сервисы:**
|
||
- ✅ `CustomersService` - все методы
|
||
- ✅ `ServicesService` - все методы
|
||
- ✅ `OrdersService` - все методы
|
||
- ✅ `SlotsService` - все методы
|
||
- ✅ `RegistrationService` - добавлена собственная проверка
|
||
|
||
**Преимущества:**
|
||
- Автоматическая проверка во всех сервисах, наследующих от BaseService
|
||
- Единая точка проверки бана
|
||
- Работает как в веб-приложении, так и в боте
|
||
- Простота добавления в новые методы
|
||
- Защита всех API методов от забаненных пользователей
|
||
|
||
## 5. Защита от изменения статуса бана (`packages/graphql/api/customers.ts` и `registration.ts`)
|
||
|
||
Пользователи не могут изменять поле `bannedUntil` самостоятельно:
|
||
|
||
```typescript
|
||
// В 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 компонент для проверки бана на клиенте:
|
||
|
||
```typescript
|
||
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:**
|
||
```typescript
|
||
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`)
|
||
|
||
```typescript
|
||
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`)
|
||
|
||
```typescript
|
||
export const ERRORS = {
|
||
NO_PERMISSION: 'Нет доступа',
|
||
} as const;
|
||
```
|
||
|
||
## Архитектура системы
|
||
|
||
```
|
||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||
│ Next Auth │ │ BaseService │ │ Client-side │
|
||
│ (авторизация) │ │ (API методы) │ │ (UI) │
|
||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||
│ │ │
|
||
▼ ▼ ▼
|
||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||
│ isCustomerBanned│ │ checkBanStatus()│ │ BanCheck │
|
||
│ (утилита) │ │ (метод) │ │ (компонент) │
|
||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||
│ │ │
|
||
└───────────────────────┼───────────────────────┘
|
||
▼
|
||
┌─────────────────────────┐
|
||
│ bannedUntil (DB) │
|
||
│ (Strapi/PostgreSQL) │
|
||
└─────────────────────────┘
|
||
```
|
||
|
||
## Преимущества системы
|
||
|
||
✅ **Многоуровневая защита** - проверка на всех уровнях приложения
|
||
✅ **Универсальность** - работает в веб-приложении и боте
|
||
✅ **Простота использования** - один вызов `checkBanStatus()` в начале метода
|
||
✅ **Безопасность** - пользователи не могут обойти бан
|
||
✅ **UX** - понятные сообщения и страница для забаненных
|
||
✅ **DRY принцип** - нет дублирования кода
|
||
✅ **Легкость расширения** - просто добавить новые проверки |