From ac2f82ecc744d2c600a7bad3744ef059b8fe17be Mon Sep 17 00:00:00 2001 From: Aleksandr Fenin Date: Fri, 19 Sep 2025 13:55:51 +0300 Subject: [PATCH 1/2] fix/auth-service-logout-function-naming: integrate HttpOnly cookies and fix function naming conflict --- .cursor/rules/project-structure.mdc | 47 ++ docs/plans/input-fields-components-plan.md | 669 ++++++++++++++++++ docs/plans/number-field-component-plan.md | 665 +++-------------- .../token-validation-in-auth-guard-plan.md | 311 -------- qodana.yaml | 41 ++ src/app/app.component.spec.ts | 7 +- src/app/app.component.ts | 3 - src/app/app.config.ts | 3 +- src/app/interceptors/auth.interceptor.spec.ts | 66 +- src/app/interceptors/auth.interceptor.ts | 59 +- .../interceptors/credentials.interceptor.ts | 25 + .../macronutrients-display.component.spec.ts | 4 +- src/features/auth/models/auth.types.ts | 6 +- .../auth/services/auth-api.service.spec.ts | 15 +- .../auth/services/auth-api.service.ts | 18 +- .../auth/services/auth.service.spec.ts | 432 +---------- src/features/auth/services/auth.service.ts | 118 +-- .../activity-goal-form.component.spec.ts | 12 +- .../activity-goal-form.component.ts | 14 +- .../basic-data-form.component.ts | 35 +- .../results-display.component.html | 2 +- .../form-autosave.directive.spec.ts | 2 +- .../form-autosave/form-autosave.directive.ts | 3 +- src/shared/lib/utils/api-error.utils.spec.ts | 2 +- src/shared/lib/utils/index.ts | 1 - src/shared/lib/utils/jwt.utils.spec.ts | 60 -- src/shared/lib/utils/jwt.utils.ts | 21 - src/shared/services/auth/index.ts | 1 - .../auth/token-storage.service.spec.ts | 65 -- .../services/auth/token-storage.service.ts | 52 -- .../navigation/navigation.component.spec.ts | 11 +- src/test-setup.ts | 4 - 32 files changed, 1044 insertions(+), 1730 deletions(-) create mode 100644 docs/plans/input-fields-components-plan.md delete mode 100644 docs/plans/token-validation-in-auth-guard-plan.md create mode 100644 qodana.yaml create mode 100644 src/app/interceptors/credentials.interceptor.ts delete mode 100644 src/shared/lib/utils/jwt.utils.spec.ts delete mode 100644 src/shared/lib/utils/jwt.utils.ts delete mode 100644 src/shared/services/auth/index.ts delete mode 100644 src/shared/services/auth/token-storage.service.spec.ts delete mode 100644 src/shared/services/auth/token-storage.service.ts diff --git a/.cursor/rules/project-structure.mdc b/.cursor/rules/project-structure.mdc index 29f1823..1dcb2b0 100644 --- a/.cursor/rules/project-structure.mdc +++ b/.cursor/rules/project-structure.mdc @@ -390,6 +390,7 @@ app → pages → widgets → features → entities → shared - **Standalone Components** - No NgModules - **Zoneless Change Detection** - Experimental Angular feature - **Signals** - Primary state management +- **HttpOnly Cookies** - Secure token storage with Fetch API ### Styling - **SCSS** with modular architecture @@ -403,6 +404,12 @@ app → pages → widgets → features → entities → shared - **Prettier** + **Stylelint** for code formatting - **Husky** + **lint-staged** for pre-commit hooks +### Security & Authentication +- **HttpOnly Cookies** - Secure token storage preventing XSS attacks +- **Fetch API** - Modern HTTP client with better cookie support +- **CORS Credentials** - Automatic inclusion of cookies in requests +- **Token Refresh** - Automatic token renewal via HttpOnly cookies + ## Component Structure Pattern Every component follows this structure: @@ -450,6 +457,46 @@ import { SomeService } from '@/shared/services/some'; import { SomeComponent } from '@/pages/some-page'; ``` +## HTTP Client Configuration + +### HttpOnly Cookies & Fetch API + +The application uses HttpOnly cookies for secure token storage with Fetch API for optimal compatibility: + +```typescript +// app.config.ts +provideHttpClient( + withInterceptors([credentialsInterceptor, authInterceptor]), + withFetch() // Critical for HttpOnly cookies support +) +``` + +#### Why withFetch() is Required: + +1. **HttpOnly Cookie Support**: Fetch API has better support for HttpOnly cookies than XMLHttpRequest +2. **CORS Credentials**: Automatic handling of `credentials: 'include'` for cross-origin requests +3. **Modern Standard**: Fetch API is the modern standard for HTTP requests +4. **Security**: Better integration with secure cookie policies + +#### Credentials Interceptor: + +```typescript +// credentials.interceptor.ts +export const credentialsInterceptor: HttpInterceptorFn = (req, next) => { + const modifiedReq = req.clone({ + withCredentials: true, // Automatically includes HttpOnly cookies + }); + return next(modifiedReq); +}; +``` + +#### Key Benefits: + +- **XSS Protection**: Tokens stored in HttpOnly cookies are inaccessible to JavaScript +- **Automatic Inclusion**: Cookies automatically sent with every request +- **Server-Side Control**: Token lifecycle managed entirely by the server +- **No Client Storage**: No need for localStorage or sessionStorage for tokens + ## Routing Standards ### Route Naming Convention diff --git a/docs/plans/input-fields-components-plan.md b/docs/plans/input-fields-components-plan.md new file mode 100644 index 0000000..9ed8148 --- /dev/null +++ b/docs/plans/input-fields-components-plan.md @@ -0,0 +1,669 @@ +# Input Fields Components Implementation Plan + +## Обзор + +Создание специализированных переиспользуемых компонентов для различных типов полей ввода в проекте. Компоненты будут основаны на существующем паттерне `SelectFieldComponent` и интегрированы в `shared/ui` слой согласно Feature-Sliced Design архитектуре. + +## Текущая ситуация + +### Анализ существующего кода + +**Числовые поля (3 поля в basic-data-form):** +```html + +``` + +**Текстовые поля (6 полей в login/register формах):** +```html + +``` + +### Выявленные паттерны +1. Единообразная структура с `tuiLabel` и `tui-textfield` +2. Различные типы input: `text`, `password`, `number` +3. Общие атрибуты: `placeholder`, `formControlName`, `disabled` +4. Специфичные для числовых: `min`, `max` +5. Интеграция с Angular Reactive Forms + +### Приоритеты +1. **Высокий приоритет**: Числовые поля (3 поля, сложная валидация) +2. **Средний приоритет**: Текстовые поля (6 полей, простая структура) +3. **Низкий приоритет**: Телефонные поля (пока не используются) + +## Архитектурное решение + +### Структура компонентов +``` +src/shared/ui/ +├── number-field/ # Специализированный компонент для числовых полей +│ ├── number-field.component.ts +│ ├── number-field.component.html +│ ├── number-field.component.scss +│ ├── number-field.component.spec.ts +│ └── index.ts +├── text-field/ # Специализированный компонент для текстовых полей +│ ├── text-field.component.ts +│ ├── text-field.component.html +│ ├── text-field.component.scss +│ ├── text-field.component.spec.ts +│ └── index.ts +└── phone-field/ # Специализированный компонент для телефонных полей (будущее) + ├── phone-field.component.ts + ├── phone-field.component.html + ├── phone-field.component.scss + ├── phone-field.component.spec.ts + └── index.ts +``` + +### Интеграция в FSD архитектуру +- **Слой**: `shared/ui` (переиспользуемые UI компоненты) +- **Импорт**: Компоненты будут доступны через `@/shared` +- **Зависимости**: Только от Taiga UI и Angular Forms +- **Принцип**: Один компонент = одна ответственность + +## Техническая спецификация + +### 1. NumberFieldComponent + +#### Интерфейс и типы +```typescript +// Типы для числовых полей +export type NumberFieldValue = number | null; + +// Интерфейс для числовых ограничений +interface NumberConstraints { + min?: number; + max?: number; + step?: number; + precision?: number; +} +``` + +#### Input параметры +```typescript +// Обязательные +readonly label = input.required(); +readonly placeholder = input.required(); + +// Опциональные общие +readonly disabled = input(false); +readonly required = input(false); +readonly readonly = input(false); +readonly suffix = input(''); // единицы измерения (kg, cm) + +// Для числовых полей +readonly min = input(); +readonly max = input(); +readonly step = input(1); +readonly precision = input(0); + +// Для стилизации +readonly size = input<'s' | 'm' | 'l'>('m'); +``` + +#### Output события +```typescript +readonly valueChange = output(); +``` + +#### ControlValueAccessor реализация +```typescript +implements ControlValueAccessor { + writeValue(value: number | null): void + registerOnChange(fn: (value: number | null) => void): void + registerOnTouched(fn: () => void): void + setDisabledState?(isDisabled: boolean): void +} +``` + +#### Template структура +```html + +``` + +### 2. TextFieldComponent + +#### Интерфейс и типы +```typescript +// Типы для текстовых полей +export type TextFieldValue = string | null; +export type TextFieldType = 'text' | 'email' | 'password' | 'url'; + +// Интерфейс для текстовых ограничений +interface TextConstraints { + minlength?: number; + maxlength?: number; + pattern?: string; +} +``` + +#### Input параметры +```typescript +// Обязательные +readonly label = input.required(); +readonly placeholder = input.required(); + +// Опциональные общие +readonly disabled = input(false); +readonly required = input(false); +readonly readonly = input(false); + +// Для текстовых полей +readonly type = input('text'); +readonly minlength = input(); +readonly maxlength = input(); +readonly pattern = input(); + +// Для стилизации +readonly size = input<'s' | 'm' | 'l'>('m'); +``` + +#### Output события +```typescript +readonly valueChange = output(); +``` + +#### ControlValueAccessor реализация +```typescript +implements ControlValueAccessor { + writeValue(value: string | null): void + registerOnChange(fn: (value: string | null) => void): void + registerOnTouched(fn: () => void): void + setDisabledState?(isDisabled: boolean): void +} +``` + +#### Template структура +```html + +``` + +## Пошаговый план реализации + +### Этап 1: NumberFieldComponent (Приоритет 1) +**Цель**: Создать специализированный компонент для числовых полей + +**Задачи**: +1. Создать файловую структуру `src/shared/ui/number-field/` +2. Реализовать TypeScript класс с правильным ControlValueAccessor +3. Создать HTML template с `tuiInputNumber` +4. Добавить стили в соответствии с существующим паттерном +5. Создать unit тесты +6. Экспортировать компонент через `index.ts` + +**Критерии готовности**: +- Компонент работает с `formControlName` +- Корректная обработка `min`, `max`, `step`, `precision` +- Поддержка `suffix` для единиц измерения +- Покрытие тестами минимум 80% + +### Этап 2: Интеграция NumberFieldComponent +**Цель**: Заменить числовые поля в basic-data-form + +**Задачи**: +1. Обновить экспорты в `shared/index.ts` +2. Заменить 3 числовых поля в `basic-data-form.component.html`: + - Age (min: 10, max: 120) + - Height (min: 100, max: 250, suffix: 'cm') + - Weight (min: 30, max: 300, suffix: 'kg') +3. Обновить импорты в `basic-data-form.component.ts` +4. Протестировать интеграцию +5. Проверить, что форма работает корректно + +**Критерии готовности**: +- Все числовые поля заменены на `NumberFieldComponent` +- Форма работает без регрессий +- Валидация работает корректно +- Автосохранение формы работает + +### Этап 3: TextFieldComponent (Приоритет 2) +**Цель**: Создать специализированный компонент для текстовых полей + +**Задачи**: +1. Создать файловую структуру `src/shared/ui/text-field/` +2. Реализовать TypeScript класс с поддержкой разных типов +3. Создать HTML template с `tuiTextfield` +4. Добавить стили +5. Создать unit тесты +6. Экспортировать компонент + +**Критерии готовности**: +- Поддержка типов: `text`, `email`, `password`, `url` +- Корректная работа с `minlength`, `maxlength`, `pattern` +- Покрытие тестами минимум 80% + +### Этап 4: Интеграция TextFieldComponent (Опционально) +**Цель**: Заменить текстовые поля в login/register формах + +**Задачи**: +1. Обновить экспорты в `shared/index.ts` +2. Заменить текстовые поля в `login.component.html` +3. Заменить текстовые поля в `register.component.html` +4. Протестировать интеграцию +5. Проверить работу форм + +**Критерии готовности**: +- Текстовые поля заменены на `TextFieldComponent` +- Формы работают без регрессий +- Валидация работает корректно + +### Этап 5: PhoneFieldComponent (Будущее) +**Цель**: Создать компонент для телефонных полей при необходимости + +**Задачи**: +1. Создать компонент с `tuiInputPhone` +2. Добавить поддержку различных форматов телефонов +3. Интегрировать при появлении потребности + +## Детали реализации + +### Imports и Dependencies + +#### NumberFieldComponent +```typescript +import { ChangeDetectionStrategy, Component, input, output, signal, computed, inject } from '@angular/core'; +import { FormsModule, NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; +import { TuiTextfield } from '@taiga-ui/core'; +import { TuiInputNumber } from '@taiga-ui/kit'; +import type { TuiSizeS, TuiSizeM, TuiSizeL } from '@taiga-ui/core'; +``` + +#### TextFieldComponent +```typescript +import { ChangeDetectionStrategy, Component, input, output, signal, computed, inject } from '@angular/core'; +import { FormsModule, NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; +import { TuiTextfield } from '@taiga-ui/core'; +import { TuiInput } from '@taiga-ui/kit'; +import type { TuiSizeS, TuiSizeM, TuiSizeL } from '@taiga-ui/core'; +``` + +### Provider конфигурация +```typescript +providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: NumberFieldComponent, // или TextFieldComponent + multi: true, + }, +] +``` + +### Signals архитектура + +#### NumberFieldComponent +```typescript +private readonly internalValue = signal(null); +private readonly isTouched = signal(false); + +readonly currentValue = computed(() => this.internalValue()); + +private _onChange = (value: number | null): void => { + void value; +}; +private _onTouched = (): void => {}; + +onValueChange(event: Event): void { + const target = event.target as HTMLInputElement; + const value = target.value === '' ? null : Number(target.value); + this.internalValue.set(value); + this._onChange(value); +} + +onTouched(): void { + this.isTouched.set(true); + this._onTouched(); +} +``` + +#### TextFieldComponent +```typescript +private readonly internalValue = signal(null); +private readonly isTouched = signal(false); + +readonly currentValue = computed(() => this.internalValue()); + +private _onChange = (value: string | null): void => { + void value; +}; +private _onTouched = (): void => {}; + +onValueChange(event: Event): void { + const target = event.target as HTMLInputElement; + const value = target.value === '' ? null : target.value; + this.internalValue.set(value); + this._onChange(value); +} + +onTouched(): void { + this.isTouched.set(true); + this._onTouched(); +} +``` + +## Примеры использования + +### NumberFieldComponent +```html + + + + + + + + + + + +``` + +### TextFieldComponent +```html + + + + + + + + +``` + +## Рефакторинг существующего кода + +### 1. basic-data-form.component.html +```html + +
+ +
+ + +
+ +
+``` + +### 2. basic-data-form.component.ts +```typescript +// Добавить импорт +import { NumberFieldComponent } from '@/shared'; + +// Обновить imports +imports: [ + ReactiveFormsModule, + TuiButton, + TuiInputNumber, // Убрать после замены всех полей + TuiTextfield, // Убрать после замены всех полей + NumberFieldComponent, // Добавить + SelectFieldComponent, + SectionBlockComponent, + FormAutosaveDirective, +], +``` + +## Тестирование + +### Unit тесты для NumberFieldComponent +```typescript +describe('NumberFieldComponent', () => { + describe('Component Creation', () => { + it('should create component with required inputs'); + it('should generate unique field ID'); + }); + + describe('Value Handling', () => { + it('should emit valueChange when value changes'); + it('should handle null values correctly'); + it('should convert string input to number'); + it('should respect min/max constraints'); + }); + + describe('ControlValueAccessor', () => { + it('should implement writeValue correctly'); + it('should call onChange when value changes'); + it('should handle disabled state'); + it('should call onTouched on blur'); + }); + + describe('Form Integration', () => { + it('should work with reactive forms'); + it('should maintain form validation state'); + it('should preserve existing form behavior'); + }); + + describe('Number Constraints', () => { + it('should respect min constraint'); + it('should respect max constraint'); + it('should respect step constraint'); + it('should respect precision constraint'); + }); +}); +``` + +### Unit тесты для TextFieldComponent +```typescript +describe('TextFieldComponent', () => { + describe('Component Creation', () => { + it('should create component with required inputs'); + it('should generate unique field ID'); + }); + + describe('Value Handling', () => { + it('should emit valueChange when value changes'); + it('should handle null values correctly'); + it('should handle string values correctly'); + }); + + describe('ControlValueAccessor', () => { + it('should implement writeValue correctly'); + it('should call onChange when value changes'); + it('should handle disabled state'); + it('should call onTouched on blur'); + }); + + describe('Field Types', () => { + it('should support text type'); + it('should support email type'); + it('should support password type'); + it('should support url type'); + }); + + describe('Text Constraints', () => { + it('should respect minlength constraint'); + it('should respect maxlength constraint'); + it('should respect pattern constraint'); + }); +}); +``` + +### Integration тесты +```typescript +describe('Input Fields Form Integration', () => { + it('should integrate NumberFieldComponent with basic-data-form correctly'); + it('should maintain form validation state'); + it('should preserve existing form behavior'); + it('should work with form autosave'); +}); +``` + +## Критерии качества + +### Производительность +- Использование OnPush change detection +- Минимальное количество re-renders +- Efficient signal usage +- Правильная типизация для избежания лишних вычислений + +### Доступность (A11y) +- Корректные ARIA атрибуты +- Уникальные ID для полей +- Поддержка клавиатурной навигации +- Семантически правильная разметка + +### Тестирование +- Покрытие тестами минимум 80% +- Тесты для всех публичных методов +- Тесты интеграции с формами +- Тесты для всех типов полей + +### Совместимость +- Работа во всех поддерживаемых браузерах +- Корректная работа на мобильных устройствах +- Соответствие Angular best practices +- Соответствие Taiga UI guidelines + +## Риски и их митигация + +### Риск 1: Нарушение существующей функциональности +**Митигация**: Пошаговая замена с тщательным тестированием каждого этапа + +### Риск 2: Несовместимость с существующими стилями +**Митигация**: Использование тех же CSS классов и BEM структуры как в SelectFieldComponent + +### Риск 3: Проблемы с типизацией +**Митигация**: Строгая типизация TypeScript, тестирование с различными типами данных + +### Риск 4: Производительность +**Митигация**: Использование OnPush и signals, профилирование производительности + +## Timeline + +### Week 1: NumberFieldComponent +- Создание NumberFieldComponent +- Unit тесты +- Интеграция в basic-data-form + +### Week 2: TextFieldComponent (Опционально) +- Создание TextFieldComponent +- Unit тесты +- Интеграция в login/register формы + +### Week 3: Testing & Documentation +- Integration тесты +- Документация +- Финальное тестирование + +## Заключение + +Данный план обеспечивает создание специализированных, типобезопасных и хорошо протестированных компонентов для полей ввода, которые: + +1. Соответствуют архитектуре FSD +2. Интегрируются с Angular Reactive Forms +3. Используют современные Angular паттерны (signals, standalone components) +4. Обеспечивают единообразный UX +5. Минимизируют дублирование кода +6. Упрощают создание новых форм в будущем + +**Приоритет**: Начать с NumberFieldComponent как наиболее критичного компонента, затем расширять функциональность по мере необходимости. diff --git a/docs/plans/number-field-component-plan.md b/docs/plans/number-field-component-plan.md index fb0b7d4..bae60c7 100644 --- a/docs/plans/number-field-component-plan.md +++ b/docs/plans/number-field-component-plan.md @@ -1,426 +1,120 @@ -# Text Field Component Implementation Plan +# NumberFieldComponent Implementation Plan ## Обзор -Создание универсального переиспользуемого компонента `TextFieldComponent` для унификации всех типов текстовых полей в проекте. Компонент будет основан на существующем паттерне `SelectFieldComponent` и интегрирован в shared/ui слой согласно Feature-Sliced Design архитектуре. +Создание специализированного компонента `NumberFieldComponent` для унификации числовых полей в проекте. Компонент будет основан на существующем паттерне `SelectFieldComponent` и интегрирован в `shared/ui` слой согласно Feature-Sliced Design архитектуре. -## Текущая ситуация +## Проблема -### Проблема -В коде форм повторяется один и тот же паттерн для текстовых полей разных типов: +В `basic-data-form.component.html` повторяется один и тот же паттерн для числовых полей: -**Числовые поля:** ```html -