@umituz/react-native-auth 4.3.0 → 4.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/package.json +1 -1
  2. package/src/application/services/ValidationService.ts +44 -0
  3. package/src/index.ts +8 -1
  4. package/src/infrastructure/utils/AuthValidation.ts +17 -14
  5. package/src/infrastructure/utils/authConversionDetector.ts +1 -1
  6. package/src/infrastructure/utils/authStateHandler.ts +8 -14
  7. package/src/infrastructure/utils/error/errorCodeMapping.constants.ts +2 -2
  8. package/src/infrastructure/utils/listener/{authStateHandler.ts → authListenerStateHandler.ts} +2 -13
  9. package/src/infrastructure/utils/listener/listenerLifecycle.util.ts +2 -2
  10. package/src/infrastructure/utils/listener/setupListener.ts +1 -1
  11. package/src/infrastructure/utils/safeCallback.ts +80 -0
  12. package/src/infrastructure/utils/validation/sanitization.ts +5 -30
  13. package/src/infrastructure/utils/validation/types.ts +53 -0
  14. package/src/infrastructure/utils/validation/validationHelpers.ts +70 -0
  15. package/src/presentation/components/PasswordStrengthIndicator.tsx +1 -1
  16. package/src/presentation/hooks/registerForm/registerFormHandlers.ts +1 -3
  17. package/src/presentation/hooks/registerForm/registerFormSubmit.ts +7 -7
  18. package/src/presentation/hooks/registerForm/useRegisterForm.types.ts +4 -6
  19. package/src/presentation/hooks/useAuthErrorHandler.ts +59 -0
  20. package/src/presentation/hooks/useGoogleAuth.ts +0 -6
  21. package/src/presentation/hooks/useLocalError.ts +43 -0
  22. package/src/presentation/hooks/useLoginForm.ts +9 -15
  23. package/src/presentation/hooks/useRegisterForm.ts +7 -15
  24. package/src/presentation/utils/form/formValidation.util.ts +1 -1
  25. package/src/presentation/utils/form/usePasswordValidation.hook.ts +1 -1
  26. package/src/presentation/utils/form/validation/formValidation.types.ts +5 -9
  27. package/src/presentation/utils/form/validation/formValidators.ts +6 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-auth",
3
- "version": "4.3.0",
3
+ "version": "4.3.2",
4
4
  "description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design with dependency injection, configurable validation, and comprehensive error handling.",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Validation Service (Application Layer)
3
+ * Facade for validation functions to be used by presentation layer
4
+ * Follows DDD architecture - presentation imports from application, not infrastructure
5
+ */
6
+
7
+ // Validation functions
8
+ export {
9
+ validateEmail,
10
+ validatePasswordForLogin,
11
+ validatePasswordForRegister,
12
+ validatePasswordConfirmation,
13
+ validateDisplayName,
14
+ } from "../../infrastructure/utils/AuthValidation";
15
+
16
+ // validation types
17
+ export type {
18
+ ValidationResult,
19
+ PasswordStrengthResult,
20
+ PasswordRequirements,
21
+ BaseValidationResult,
22
+ FormValidationError,
23
+ FormValidationResult,
24
+ } from "../../infrastructure/utils/validation/types";
25
+
26
+ // validation helpers
27
+ export {
28
+ isEmpty,
29
+ isEmptyEmail,
30
+ isEmptyPassword,
31
+ isEmptyName,
32
+ isNotEmpty,
33
+ hasContent,
34
+ } from "../../infrastructure/utils/validation/validationHelpers";
35
+
36
+ // Sanitization functions
37
+ export {
38
+ sanitizeEmail,
39
+ sanitizePassword,
40
+ sanitizeName,
41
+ SECURITY_LIMITS,
42
+ } from "../../infrastructure/utils/validation/sanitization";
43
+
44
+ export type { SecurityLimitKey } from "../../infrastructure/utils/validation/sanitization";
package/src/index.ts CHANGED
@@ -32,8 +32,11 @@ export { initializeAuth, isAuthInitialized, resetAuthInitialization } from './in
32
32
  export type { InitializeAuthOptions } from './infrastructure/services/initializeAuth';
33
33
  export { validateEmail, validatePasswordForLogin, validatePasswordForRegister, validatePasswordConfirmation, validateDisplayName } from './infrastructure/utils/AuthValidation';
34
34
  export type { ValidationResult, PasswordStrengthResult, PasswordRequirements } from './infrastructure/utils/AuthValidation';
35
- export { SECURITY_LIMITS, sanitizeWhitespace, sanitizeEmail, sanitizePassword, sanitizeName, sanitizeText, containsDangerousChars, isWithinLengthLimit } from './infrastructure/utils/validation/sanitization';
35
+ export type { BaseValidationResult, FormValidationError, FormValidationResult } from './infrastructure/utils/validation/types';
36
+ export { SECURITY_LIMITS, sanitizeEmail, sanitizePassword, sanitizeName } from './infrastructure/utils/validation/sanitization';
36
37
  export type { SecurityLimitKey } from './infrastructure/utils/validation/sanitization';
38
+ export { isEmpty, isEmptyEmail, isEmptyPassword, isEmptyName, isNotEmpty, hasContent } from './infrastructure/utils/validation/validationHelpers';
39
+ export { safeCallback, safeCallbackSync } from './infrastructure/utils/safeCallback';
37
40
  export { getEffectiveUserId } from './infrastructure/utils/getEffectiveUserId';
38
41
 
39
42
  // =============================================================================
@@ -66,6 +69,10 @@ export { useAuthHandlers } from './presentation/hooks/useAuthHandlers';
66
69
  export type { AuthHandlersAppInfo, AuthHandlersTranslations } from './presentation/hooks/useAuthHandlers';
67
70
  export { usePasswordPromptNavigation } from './presentation/hooks/usePasswordPromptNavigation';
68
71
  export type { UsePasswordPromptNavigationOptions, UsePasswordPromptNavigationReturn } from './presentation/hooks/usePasswordPromptNavigation';
72
+ export { useAuthErrorHandler } from './presentation/hooks/useAuthErrorHandler';
73
+ export type { UseAuthErrorHandlerConfig, UseAuthErrorHandlerResult } from './presentation/hooks/useAuthErrorHandler';
74
+ export { useLocalError } from './presentation/hooks/useLocalError';
75
+ export type { UseLocalErrorResult } from './presentation/hooks/useLocalError';
69
76
 
70
77
  // =============================================================================
71
78
  // PRESENTATION LAYER - Components
@@ -1,14 +1,17 @@
1
1
  import type { PasswordConfig } from "../../domain/value-objects/AuthConfig";
2
+ import { isEmptyEmail, isEmptyPassword, isEmptyName } from "./validation/validationHelpers";
3
+ import type {
4
+ ValidationResult,
5
+ PasswordStrengthResult,
6
+ PasswordRequirements,
7
+ } from "./validation/types";
2
8
 
3
- export interface ValidationResult {
4
- isValid: boolean;
5
- error?: string;
6
- }
7
-
8
- export interface PasswordStrengthResult extends ValidationResult { requirements: PasswordRequirements; }
9
- export interface PasswordRequirements {
10
- hasMinLength: boolean;
11
- }
9
+ // Export validation types
10
+ export type {
11
+ ValidationResult,
12
+ PasswordStrengthResult,
13
+ PasswordRequirements,
14
+ };
12
15
 
13
16
  export interface ValidationConfig {
14
17
  emailRegex: RegExp;
@@ -28,14 +31,14 @@ export function validateEmail(
28
31
  email: string,
29
32
  config: ValidationConfig = DEFAULT_VAL_CONFIG
30
33
  ): ValidationResult {
31
- if (!email || email.trim() === "") return { isValid: false, error: "auth.validation.emailRequired" };
34
+ if (isEmptyEmail(email)) return { isValid: false, error: "auth.validation.emailRequired" };
32
35
  if (!config.emailRegex.test(email.trim())) return { isValid: false, error: "auth.validation.invalidEmail" };
33
36
  return { isValid: true };
34
37
  }
35
38
 
36
39
  export function validatePasswordForLogin(password: string): ValidationResult {
37
40
  // Don't trim passwords - whitespace may be intentional
38
- if (!password || password.length === 0) return { isValid: false, error: "auth.validation.passwordRequired" };
41
+ if (isEmptyPassword(password)) return { isValid: false, error: "auth.validation.passwordRequired" };
39
42
  return { isValid: true };
40
43
  }
41
44
 
@@ -44,7 +47,7 @@ export function validatePasswordForRegister(
44
47
  config: PasswordConfig,
45
48
  ): PasswordStrengthResult {
46
49
  // Don't trim passwords - whitespace may be intentional
47
- if (!password || password.length === 0) {
50
+ if (isEmptyPassword(password)) {
48
51
  return { isValid: false, error: "auth.validation.passwordRequired", requirements: { hasMinLength: false } };
49
52
  }
50
53
 
@@ -59,7 +62,7 @@ export function validatePasswordForRegister(
59
62
 
60
63
  export function validatePasswordConfirmation(password: string, confirm: string): ValidationResult {
61
64
  // Don't trim passwords - whitespace may be intentional
62
- if (!confirm || confirm.length === 0) return { isValid: false, error: "auth.validation.confirmPasswordRequired" };
65
+ if (isEmptyPassword(confirm)) return { isValid: false, error: "auth.validation.confirmPasswordRequired" };
63
66
  if (password !== confirm) return { isValid: false, error: "auth.validation.passwordsDoNotMatch" };
64
67
  return { isValid: true };
65
68
  }
@@ -68,7 +71,7 @@ export function validateDisplayName(
68
71
  name: string,
69
72
  minLength: number = DEFAULT_VAL_CONFIG.displayNameMinLength
70
73
  ): ValidationResult {
71
- if (!name || name.trim() === "") return { isValid: false, error: "auth.validation.nameRequired" };
74
+ if (isEmptyName(name)) return { isValid: false, error: "auth.validation.nameRequired" };
72
75
  if (name.trim().length < minLength) return { isValid: false, error: "auth.validation.nameTooShort" };
73
76
  return { isValid: true };
74
77
  }
@@ -29,7 +29,7 @@ export function detectConversion(
29
29
  const isSameUser =
30
30
  previousUserId === currentUserId && wasAnonymous && !isCurrentlyAnonymous;
31
31
 
32
- // Different UID after anonymous (legacy flow)
32
+ // Different UID after anonymous (account creation with new credentials)
33
33
  const isNewUser =
34
34
  !!previousUserId &&
35
35
  previousUserId !== currentUserId &&
@@ -6,6 +6,7 @@
6
6
  import type { User } from "firebase/auth";
7
7
  import { ensureUserDocument } from "@umituz/react-native-firebase";
8
8
  import { detectConversion, type ConversionState } from "./authConversionDetector";
9
+ import { safeCallback } from "./safeCallback";
9
10
 
10
11
  export interface AuthStateHandlerOptions {
11
12
  onUserConverted?: (anonymousId: string, authenticatedId: string) => void | Promise<void>;
@@ -24,7 +25,7 @@ export function createAuthStateHandler(
24
25
 
25
26
  if (!user) {
26
27
  state.current = { previousUserId: null, wasAnonymous: false };
27
- await onAuthStateChange?.(null);
28
+ await safeCallback(onAuthStateChange, [null], '[AuthStateHandler]');
28
29
  return;
29
30
  }
30
31
 
@@ -34,11 +35,11 @@ export function createAuthStateHandler(
34
35
  const conversion = detectConversion(state.current, currentUserId, isCurrentlyAnonymous);
35
36
 
36
37
  if (conversion.isConversion && onUserConverted && state.current.previousUserId) {
37
- try {
38
- await onUserConverted(state.current.previousUserId, currentUserId);
39
- } catch {
40
- // Silently fail - conversion callback errors are handled elsewhere
41
- }
38
+ await safeCallback(
39
+ onUserConverted,
40
+ [state.current.previousUserId, currentUserId],
41
+ '[AuthStateHandler]'
42
+ );
42
43
  }
43
44
 
44
45
  const extras = conversion.isConversion && state.current.previousUserId
@@ -58,13 +59,6 @@ export function createAuthStateHandler(
58
59
  };
59
60
 
60
61
  // Call user callback with error handling
61
- if (onAuthStateChange) {
62
- try {
63
- await onAuthStateChange(user);
64
- } catch (error) {
65
- console.error('[AuthStateHandler] User callback error:', error);
66
- // Don't propagate user callback errors
67
- }
68
- }
62
+ await safeCallback(onAuthStateChange, [user], '[AuthStateHandler]');
69
63
  };
70
64
  }
@@ -9,8 +9,8 @@ import { CONFIG_ERROR_MAPPINGS } from "./mappings/configErrorMappings";
9
9
  import { NETWORK_ERROR_MAPPINGS } from "./mappings/networkErrorMappings";
10
10
  import { ACTION_CODE_ERROR_MAPPINGS } from "./mappings/actionCodeErrorMappings";
11
11
 
12
- // Re-export types for backward compatibility
13
- export type { ErrorConstructor, ErrorFactory, ErrorMapping } from "./mappings/errorMapping.types";
12
+ // Export types needed by AuthErrorMapper
13
+ export type { ErrorConstructor, ErrorFactory } from "./mappings/errorMapping.types";
14
14
 
15
15
  /**
16
16
  * Combined Firebase error code to domain error mapping
@@ -7,6 +7,7 @@ import type { Auth, User } from "firebase/auth";
7
7
  import type { AuthActions } from "../../../types/auth-store.types";
8
8
  import { completeInitialization } from "./listenerState.util";
9
9
  import { handleAnonymousMode } from "./anonymousHandler";
10
+ import { safeCallbackSync } from "../safeCallback";
10
11
 
11
12
  type Store = AuthActions & { isAnonymous: boolean };
12
13
 
@@ -38,19 +39,7 @@ export function handleAuthStateChange(
38
39
  }
39
40
 
40
41
  // Call user callback with proper error handling for async callbacks
41
- if (onAuthStateChange) {
42
- try {
43
- const result = onAuthStateChange(user);
44
- // If callback returns a promise, catch rejections
45
- if (result && typeof result.then === 'function') {
46
- result.catch((error) => {
47
- console.error("[AuthListener] User callback promise rejected:", error);
48
- });
49
- }
50
- } catch (error) {
51
- console.error("[AuthListener] User callback error:", error);
52
- }
53
- }
42
+ safeCallbackSync(onAuthStateChange, [user], '[AuthListener]');
54
43
  } catch (error) {
55
44
  console.error("[AuthListener] Error handling auth state change:", error);
56
45
  // Ensure we don't leave the app in a bad state
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auth Listener Lifecycle - Main Export
3
- * Re-exports all listener lifecycle utilities from modular files
3
+ * Exports all listener lifecycle utilities from modular files
4
4
  */
5
5
 
6
6
  // Cleanup handlers
@@ -14,7 +14,7 @@ export {
14
14
  export { setupAuthListener } from "./setupListener";
15
15
 
16
16
  // Auth state handler
17
- export { handleAuthStateChange } from "./authStateHandler";
17
+ export { handleAuthStateChange } from "./authListenerStateHandler";
18
18
 
19
19
  // Anonymous mode handler
20
20
  export { handleAnonymousMode } from "./anonymousHandler";
@@ -8,7 +8,7 @@ import { onIdTokenChanged } from "firebase/auth";
8
8
  import type { AuthActions } from "../../../types/auth-store.types";
9
9
  import { getAuthService } from "../../services/AuthService";
10
10
  import { completeInitialization, setUnsubscribe } from "./listenerState.util";
11
- import { handleAuthStateChange } from "./authStateHandler";
11
+ import { handleAuthStateChange } from "./authListenerStateHandler";
12
12
 
13
13
  type Store = AuthActions & { isAnonymous: boolean };
14
14
 
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Safe Callback Wrapper
3
+ * Standardized error handling for user-provided callbacks
4
+ */
5
+
6
+ /**
7
+ * Safely executes a user-provided callback with comprehensive error handling
8
+ * Handles both synchronous errors and promise rejections
9
+ *
10
+ * @param callback - The callback to execute (can be sync or async)
11
+ * @param args - Arguments to pass to the callback
12
+ * @param errorPrefix - Prefix for error logging (for debugging)
13
+ * @returns Promise that resolves when callback completes (or fails safely)
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // With async callback
18
+ * await safeCallback(onAuthStateChange, [user], '[AuthListener]');
19
+ *
20
+ * // With sync callback
21
+ * await safeCallback(onUserConverted, [anonymousId, authenticatedId], '[AuthStateHandler]');
22
+ * ```
23
+ */
24
+ export async function safeCallback<T extends unknown[]>(
25
+ callback: ((...args: T) => void | Promise<void>) | undefined,
26
+ args: T,
27
+ errorPrefix: string = '[Callback]'
28
+ ): Promise<void> {
29
+ if (!callback) return;
30
+
31
+ try {
32
+ const result = callback(...args);
33
+
34
+ // If callback returns a promise, await it and catch rejections
35
+ if (result && typeof result.then === 'function') {
36
+ await result.catch((error) => {
37
+ console.error(`${errorPrefix} User callback promise rejected:`, error);
38
+ });
39
+ }
40
+ } catch (error) {
41
+ // Catch synchronous errors
42
+ console.error(`${errorPrefix} User callback error:`, error);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Synchronously executes a user-provided callback with error handling
48
+ * Use this when you don't want to await the callback (fire-and-forget)
49
+ *
50
+ * @param callback - The callback to execute (can be sync or async)
51
+ * @param args - Arguments to pass to the callback
52
+ * @param errorPrefix - Prefix for error logging (for debugging)
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * // Fire-and-forget pattern
57
+ * safeCallbackSync(onAuthStateChange, [user], '[AuthListener]');
58
+ * ```
59
+ */
60
+ export function safeCallbackSync<T extends unknown[]>(
61
+ callback: ((...args: T) => void | Promise<void>) | undefined,
62
+ args: T,
63
+ errorPrefix: string = '[Callback]'
64
+ ): void {
65
+ if (!callback) return;
66
+
67
+ try {
68
+ const result = callback(...args);
69
+
70
+ // If callback returns a promise, catch rejections but don't await
71
+ if (result && typeof result.then === 'function') {
72
+ result.catch((error) => {
73
+ console.error(`${errorPrefix} User callback promise rejected:`, error);
74
+ });
75
+ }
76
+ } catch (error) {
77
+ // Catch synchronous errors
78
+ console.error(`${errorPrefix} User callback error:`, error);
79
+ }
80
+ }
@@ -13,7 +13,11 @@ export const SECURITY_LIMITS = {
13
13
 
14
14
  export type SecurityLimitKey = keyof typeof SECURITY_LIMITS;
15
15
 
16
- export const sanitizeWhitespace = (input: string): string => {
16
+ /**
17
+ * Internal helper to sanitize whitespace
18
+ * Used by sanitizeName
19
+ */
20
+ const sanitizeWhitespace = (input: string): string => {
17
21
  if (!input) return '';
18
22
  return input.trim().replace(/\s+/g, ' ');
19
23
  };
@@ -37,32 +41,3 @@ export const sanitizeName = (name: string): string => {
37
41
  const noTags = trimmed.replace(/<[^>]*>/g, '');
38
42
  return noTags.substring(0, SECURITY_LIMITS.NAME_MAX_LENGTH);
39
43
  };
40
-
41
- export const sanitizeText = (text: string): string => {
42
- if (!text) return '';
43
- const trimmed = sanitizeWhitespace(text);
44
- const noTags = trimmed.replace(/<[^>]*>/g, '');
45
- return noTags.substring(0, SECURITY_LIMITS.GENERAL_TEXT_MAX_LENGTH);
46
- };
47
-
48
- export const containsDangerousChars = (input: string): boolean => {
49
- if (!input) return false;
50
- const dangerousPatterns = [
51
- /<script/i,
52
- /javascript:/i,
53
- /on\w+\s*=/i,
54
- /<iframe/i,
55
- /eval\(/i,
56
- ];
57
- return dangerousPatterns.some(pattern => pattern.test(input));
58
- };
59
-
60
- export const isWithinLengthLimit = (
61
- input: string,
62
- maxLength: number,
63
- minLength = 0
64
- ): boolean => {
65
- if (!input) return minLength === 0;
66
- const length = input.trim().length;
67
- return length >= minLength && length <= maxLength;
68
- };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Validation Types
3
+ * Shared type definitions for validation results across the application
4
+ */
5
+
6
+ /**
7
+ * Base validation result
8
+ * Common interface for all validation results
9
+ */
10
+ export interface BaseValidationResult {
11
+ isValid: boolean;
12
+ }
13
+
14
+ /**
15
+ * Single field validation result
16
+ * Used for validating individual fields (email, password, etc.)
17
+ */
18
+ export interface ValidationResult extends BaseValidationResult {
19
+ error?: string;
20
+ }
21
+
22
+ /**
23
+ * Form validation error
24
+ * Represents an error for a specific form field
25
+ */
26
+ export interface FormValidationError {
27
+ field: string;
28
+ message: string;
29
+ }
30
+
31
+ /**
32
+ * Multi-field form validation result
33
+ * Used for validating entire forms with multiple fields
34
+ */
35
+ export interface FormValidationResult extends BaseValidationResult {
36
+ errors: FormValidationError[];
37
+ }
38
+
39
+ /**
40
+ * Password requirements
41
+ * Tracks which password requirements are met
42
+ */
43
+ export interface PasswordRequirements {
44
+ hasMinLength: boolean;
45
+ }
46
+
47
+ /**
48
+ * Password strength validation result
49
+ * Extends ValidationResult with password-specific requirements
50
+ */
51
+ export interface PasswordStrengthResult extends ValidationResult {
52
+ requirements: PasswordRequirements;
53
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Validation Helpers
3
+ * Standardized helper functions for common validation checks
4
+ */
5
+
6
+ /**
7
+ * Check if a string is empty or null/undefined
8
+ * Handles null/undefined gracefully
9
+ *
10
+ * @param value - String to check
11
+ * @returns true if empty, null, or undefined
12
+ */
13
+ export function isEmpty(value: string | null | undefined): boolean {
14
+ return !value || value.trim() === '';
15
+ }
16
+
17
+ /**
18
+ * Check if an email is empty or null/undefined
19
+ * Email-specific validation that checks for empty after trimming
20
+ *
21
+ * @param email - Email to check
22
+ * @returns true if empty, null, or undefined
23
+ */
24
+ export function isEmptyEmail(email: string | null | undefined): boolean {
25
+ return !email || email.trim() === '';
26
+ }
27
+
28
+ /**
29
+ * Check if a password is empty or null/undefined
30
+ * Password-specific validation - does NOT trim (whitespace may be intentional)
31
+ *
32
+ * @param password - Password to check
33
+ * @returns true if empty, null, or undefined
34
+ */
35
+ export function isEmptyPassword(password: string | null | undefined): boolean {
36
+ return !password || password.length === 0;
37
+ }
38
+
39
+ /**
40
+ * Check if a name is empty or null/undefined
41
+ * Name-specific validation that checks for empty after trimming
42
+ *
43
+ * @param name - Name to check
44
+ * @returns true if empty, null, or undefined
45
+ */
46
+ export function isEmptyName(name: string | null | undefined): boolean {
47
+ return !name || name.trim() === '';
48
+ }
49
+
50
+ /**
51
+ * Check if a value is not empty
52
+ * Convenience function - inverse of isEmpty
53
+ *
54
+ * @param value - String to check
55
+ * @returns true if not empty
56
+ */
57
+ export function isNotEmpty(value: string | null | undefined): boolean {
58
+ return !isEmpty(value);
59
+ }
60
+
61
+ /**
62
+ * Check if a value has content (not empty and has non-whitespace characters)
63
+ * Stricter than isNotEmpty - ensures actual content exists
64
+ *
65
+ * @param value - String to check
66
+ * @returns true if has actual content
67
+ */
68
+ export function hasContent(value: string | null | undefined): boolean {
69
+ return !!value && value.trim().length > 0;
70
+ }
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import { View, StyleSheet } from "react-native";
3
3
  import { useAppDesignTokens, AtomicText, type ColorVariant } from "@umituz/react-native-design-system";
4
- import type { PasswordRequirements } from "../../infrastructure/utils/AuthValidation";
4
+ import type { PasswordRequirements } from "../../application/services/ValidationService";
5
5
 
6
6
  export interface PasswordStrengthTranslations {
7
7
  minLength: string;
@@ -4,11 +4,9 @@
4
4
  */
5
5
 
6
6
  import { useCallback } from "react";
7
- import type { FieldErrors } from "./useRegisterForm.types";
7
+ import type { FieldErrors, RegisterFieldKey } from "./useRegisterForm.types";
8
8
  import { clearFieldError, clearFieldErrors } from "../../utils/form/formErrorUtils";
9
9
 
10
- type RegisterFieldKey = "displayName" | "email" | "password" | "confirmPassword";
11
-
12
10
  export function useRegisterFormHandlers(
13
11
  updateField: (field: RegisterFieldKey, value: string) => void,
14
12
  setFieldErrors: React.Dispatch<React.SetStateAction<FieldErrors>>,
@@ -10,8 +10,8 @@ import {
10
10
  validateRegisterForm,
11
11
  errorsToFieldErrors,
12
12
  } from "../../utils/form/formValidation.util";
13
- import { getAuthErrorLocalizationKey } from "../../utils/getAuthErrorMessage";
14
13
  import type { FieldErrors, RegisterFormTranslations } from "./useRegisterForm.types";
14
+ import { sanitizeEmail, sanitizeName } from "../../../infrastructure/utils/validation/sanitization";
15
15
 
16
16
  interface RegisterFormFields {
17
17
  displayName: string;
@@ -27,6 +27,7 @@ export function useRegisterFormSubmit(
27
27
  setLocalError: React.Dispatch<React.SetStateAction<string | null>>,
28
28
  clearFormErrors: () => void,
29
29
  getErrorMessage: (key: string) => string,
30
+ handleAuthError: (error: unknown) => string,
30
31
  translations?: RegisterFormTranslations
31
32
  ) {
32
33
  const handleSignUp = useCallback(async () => {
@@ -34,8 +35,8 @@ export function useRegisterFormSubmit(
34
35
 
35
36
  const validation = validateRegisterForm(
36
37
  {
37
- displayName: fields.displayName.trim() || undefined,
38
- email: fields.email.trim(),
38
+ displayName: sanitizeName(fields.displayName) || undefined,
39
+ email: sanitizeEmail(fields.email),
39
40
  password: fields.password,
40
41
  confirmPassword: fields.confirmPassword,
41
42
  },
@@ -49,16 +50,15 @@ export function useRegisterFormSubmit(
49
50
  }
50
51
 
51
52
  try {
52
- await signUp(fields.email.trim(), fields.password, fields.displayName.trim() || undefined);
53
+ await signUp(sanitizeEmail(fields.email), fields.password, sanitizeName(fields.displayName) || undefined);
53
54
 
54
55
  if (translations) {
55
56
  alertService.success(translations.successTitle, translations.signUpSuccess);
56
57
  }
57
58
  } catch (err: unknown) {
58
- const localizationKey = getAuthErrorLocalizationKey(err);
59
- setLocalError(getErrorMessage(localizationKey));
59
+ setLocalError(handleAuthError(err));
60
60
  }
61
- }, [fields, signUp, translations, getErrorMessage, clearFormErrors, setFieldErrors, setLocalError]);
61
+ }, [fields, signUp, translations, handleAuthError, getErrorMessage, clearFormErrors, setFieldErrors, setLocalError]);
62
62
 
63
63
  return { handleSignUp };
64
64
  }
@@ -3,12 +3,10 @@
3
3
  * Type definitions for register form hook
4
4
  */
5
5
 
6
- export type FieldErrors = {
7
- displayName?: string;
8
- email?: string;
9
- password?: string;
10
- confirmPassword?: string;
11
- };
6
+ import type { FieldErrors as GenericFieldErrors } from "../../utils/form/formErrorUtils";
7
+
8
+ export type RegisterFieldKey = "displayName" | "email" | "password" | "confirmPassword";
9
+ export type FieldErrors = GenericFieldErrors<RegisterFieldKey>;
12
10
 
13
11
  export interface RegisterFormTranslations {
14
12
  successTitle: string;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Auth Error Handler Hook
3
+ * Shared hook for handling auth errors with localization
4
+ */
5
+
6
+ import { useCallback } from "react";
7
+ import { getAuthErrorLocalizationKey, resolveErrorMessage } from "../utils/getAuthErrorMessage";
8
+
9
+ export interface UseAuthErrorHandlerConfig {
10
+ translations?: Record<string, string>;
11
+ }
12
+
13
+ export interface UseAuthErrorHandlerResult {
14
+ handleAuthError: (error: unknown) => string;
15
+ getErrorMessage: (key: string) => string;
16
+ }
17
+
18
+ /**
19
+ * Hook to handle auth errors with localization
20
+ * Provides consistent error handling across form hooks
21
+ *
22
+ * @param config - Optional translations configuration
23
+ * @returns Error handler function and error message getter
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const { handleAuthError } = useAuthErrorHandler({ translations });
28
+ *
29
+ * try {
30
+ * await signIn(email, password);
31
+ * } catch (err) {
32
+ * const errorMessage = handleAuthError(err);
33
+ * setLocalError(errorMessage);
34
+ * }
35
+ * ```
36
+ */
37
+ export function useAuthErrorHandler(
38
+ config?: UseAuthErrorHandlerConfig
39
+ ): UseAuthErrorHandlerResult {
40
+ const getErrorMessage = useCallback(
41
+ (key: string): string => {
42
+ return resolveErrorMessage(key, config?.translations);
43
+ },
44
+ [config?.translations]
45
+ );
46
+
47
+ const handleAuthError = useCallback(
48
+ (error: unknown): string => {
49
+ const localizationKey = getAuthErrorLocalizationKey(error);
50
+ return getErrorMessage(localizationKey);
51
+ },
52
+ [getErrorMessage]
53
+ );
54
+
55
+ return {
56
+ handleAuthError,
57
+ getErrorMessage,
58
+ };
59
+ }
@@ -1,14 +1,10 @@
1
1
  /**
2
2
  * useGoogleAuth Hook
3
3
  * Handles Google Sign-In using Firebase auth
4
- *
5
- * This is a re-export wrapper around useGoogleOAuth from Firebase package
6
- * for consistency with the auth package API.
7
4
  */
8
5
 
9
6
  import { useGoogleOAuth } from "@umituz/react-native-firebase";
10
7
 
11
- // Re-export types from firebase
12
8
  export type {
13
9
  GoogleOAuthConfig as GoogleAuthConfig,
14
10
  UseGoogleOAuthResult as UseGoogleAuthResult,
@@ -17,8 +13,6 @@ export type {
17
13
 
18
14
  /**
19
15
  * Hook for Google authentication
20
- * This is a direct re-export of useGoogleOAuth from firebase package
21
16
  */
22
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
23
17
  export const useGoogleAuth = useGoogleOAuth;
24
18
 
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Local Error Hook
3
+ * Shared hook for managing local error state in form components
4
+ */
5
+
6
+ import { useState, useCallback, type Dispatch, type SetStateAction } from "react";
7
+
8
+ export interface UseLocalErrorResult {
9
+ localError: string | null;
10
+ setLocalError: Dispatch<SetStateAction<string | null>>;
11
+ clearLocalError: () => void;
12
+ }
13
+
14
+ /**
15
+ * Hook to manage local error state
16
+ * Provides consistent error state management across form hooks
17
+ *
18
+ * @returns Local error state and handlers
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * const { localError, setLocalError, clearLocalError } = useLocalError();
23
+ *
24
+ * // Set error
25
+ * setLocalError("Invalid email");
26
+ *
27
+ * // Clear error
28
+ * clearLocalError();
29
+ * ```
30
+ */
31
+ export function useLocalError(): UseLocalErrorResult {
32
+ const [localError, setLocalError] = useState<string | null>(null);
33
+
34
+ const clearLocalError = useCallback(() => {
35
+ setLocalError(null);
36
+ }, []);
37
+
38
+ return {
39
+ localError,
40
+ setLocalError,
41
+ clearLocalError,
42
+ };
43
+ }
@@ -1,9 +1,11 @@
1
1
  import { useState, useCallback } from "react";
2
2
  import { useAuth } from "./useAuth";
3
- import { getAuthErrorLocalizationKey, resolveErrorMessage } from "../utils/getAuthErrorMessage";
4
3
  import { validateLoginForm } from "../utils/form/formValidation.util";
5
4
  import { alertService } from "@umituz/react-native-design-system";
6
5
  import { useFormFields } from "../utils/form/useFormField.hook";
6
+ import { sanitizeEmail } from "../../infrastructure/utils/validation/sanitization";
7
+ import { useAuthErrorHandler } from "./useAuthErrorHandler";
8
+ import { useLocalError } from "./useLocalError";
7
9
 
8
10
  export interface LoginFormTranslations {
9
11
  successTitle: string;
@@ -32,14 +34,11 @@ export interface UseLoginFormResult {
32
34
  export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
33
35
  const { signIn, loading, error, continueAnonymously } = useAuth();
34
36
  const translations = config?.translations;
37
+ const { handleAuthError, getErrorMessage } = useAuthErrorHandler({ translations: translations?.errors });
38
+ const { localError, setLocalError, clearLocalError } = useLocalError();
35
39
 
36
40
  const [emailError, setEmailError] = useState<string | null>(null);
37
41
  const [passwordError, setPasswordError] = useState<string | null>(null);
38
- const [localError, setLocalError] = useState<string | null>(null);
39
-
40
- const clearLocalError = useCallback(() => {
41
- setLocalError(null);
42
- }, []);
43
42
 
44
43
  const clearFieldErrorsState = useCallback(() => {
45
44
  setEmailError(null);
@@ -53,10 +52,6 @@ export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
53
52
  { clearLocalError }
54
53
  );
55
54
 
56
- const getErrorMessage = useCallback((key: string) => {
57
- return resolveErrorMessage(key, translations?.errors);
58
- }, [translations]);
59
-
60
55
  const clearErrors = useCallback(() => {
61
56
  clearFieldErrorsState();
62
57
  }, [clearFieldErrorsState]);
@@ -81,7 +76,7 @@ export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
81
76
  clearErrors();
82
77
 
83
78
  const validation = validateLoginForm(
84
- { email: fields.email.trim(), password: fields.password },
79
+ { email: sanitizeEmail(fields.email), password: fields.password },
85
80
  getErrorMessage
86
81
  );
87
82
 
@@ -94,7 +89,7 @@ export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
94
89
  }
95
90
 
96
91
  try {
97
- await signIn(fields.email.trim(), fields.password);
92
+ await signIn(sanitizeEmail(fields.email), fields.password);
98
93
 
99
94
  if (translations) {
100
95
  alertService.success(
@@ -103,10 +98,9 @@ export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
103
98
  );
104
99
  }
105
100
  } catch (err: unknown) {
106
- const localizationKey = getAuthErrorLocalizationKey(err);
107
- setLocalError(getErrorMessage(localizationKey));
101
+ setLocalError(handleAuthError(err));
108
102
  }
109
- }, [fields, signIn, translations, getErrorMessage, clearErrors]);
103
+ }, [fields, signIn, translations, handleAuthError, getErrorMessage, clearErrors]);
110
104
 
111
105
  const handleContinueAnonymously = useCallback(async () => {
112
106
  try {
@@ -6,31 +6,29 @@
6
6
  import { useState, useCallback } from "react";
7
7
  import { DEFAULT_PASSWORD_CONFIG } from "../../domain/value-objects/AuthConfig";
8
8
  import { useAuth } from "./useAuth";
9
- import { resolveErrorMessage } from "../utils/getAuthErrorMessage";
10
9
  import { useFormFields } from "../utils/form/useFormField.hook";
11
10
  import { usePasswordValidation } from "../utils/form/usePasswordValidation.hook";
12
11
  import { useRegisterFormHandlers } from "./registerForm/registerFormHandlers";
13
12
  import { useRegisterFormSubmit } from "./registerForm/registerFormSubmit";
13
+ import { useAuthErrorHandler } from "./useAuthErrorHandler";
14
+ import { useLocalError } from "./useLocalError";
14
15
  import type {
15
16
  FieldErrors,
16
17
  UseRegisterFormConfig,
17
18
  UseRegisterFormResult,
18
19
  } from "./registerForm/useRegisterForm.types";
19
20
 
20
- // Re-export types for backward compatibility
21
- export type { FieldErrors, RegisterFormTranslations, UseRegisterFormConfig, UseRegisterFormResult } from "./registerForm/useRegisterForm.types";
21
+ // Export types for public API
22
+ export type { UseRegisterFormConfig, UseRegisterFormResult } from "./registerForm/useRegisterForm.types";
22
23
 
23
24
  export function useRegisterForm(config?: UseRegisterFormConfig): UseRegisterFormResult {
24
25
  const { signUp, loading, error } = useAuth();
25
26
  const translations = config?.translations;
27
+ const { handleAuthError, getErrorMessage } = useAuthErrorHandler({ translations: translations?.errors });
28
+ const { localError, setLocalError, clearLocalError } = useLocalError();
26
29
 
27
- const [localError, setLocalError] = useState<string | null>(null);
28
30
  const [fieldErrors, setFieldErrors] = useState<FieldErrors>({});
29
31
 
30
- const clearLocalError = useCallback(() => {
31
- setLocalError(null);
32
- }, []);
33
-
34
32
  const clearFormErrors = useCallback(() => {
35
33
  setLocalError(null);
36
34
  setFieldErrors({});
@@ -47,13 +45,6 @@ export function useRegisterForm(config?: UseRegisterFormConfig): UseRegisterForm
47
45
  { clearLocalError }
48
46
  );
49
47
 
50
- const getErrorMessage = useCallback(
51
- (key: string) => {
52
- return resolveErrorMessage(key, translations?.errors);
53
- },
54
- [translations]
55
- );
56
-
57
48
  const { passwordRequirements, passwordsMatch } = usePasswordValidation(
58
49
  fields.password,
59
50
  fields.confirmPassword,
@@ -69,6 +60,7 @@ export function useRegisterForm(config?: UseRegisterFormConfig): UseRegisterForm
69
60
  setLocalError,
70
61
  clearFormErrors,
71
62
  getErrorMessage,
63
+ handleAuthError,
72
64
  translations
73
65
  );
74
66
 
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Form Validation - Main Export
3
- * Re-exports all form validation utilities
3
+ * Exports all form validation utilities
4
4
  */
5
5
 
6
6
  // Types
@@ -9,7 +9,7 @@ import {
9
9
  validatePasswordConfirmation,
10
10
  type PasswordRequirements,
11
11
  type ValidationResult,
12
- } from "../../../infrastructure/utils/AuthValidation";
12
+ } from "../../../application/services/ValidationService";
13
13
  import type { PasswordConfig } from "../../../domain/value-objects/AuthConfig";
14
14
 
15
15
  export interface UsePasswordValidationResult {
@@ -3,15 +3,11 @@
3
3
  * Type definitions for form validation
4
4
  */
5
5
 
6
- export interface FormValidationError {
7
- field: string;
8
- message: string;
9
- }
10
-
11
- export interface FormValidationResult {
12
- isValid: boolean;
13
- errors: FormValidationError[];
14
- }
6
+ // Export shared validation types
7
+ export type {
8
+ FormValidationError,
9
+ FormValidationResult,
10
+ } from "../../../../infrastructure/utils/validation/types";
15
11
 
16
12
  export interface LoginFormValues {
17
13
  email: string;
@@ -8,7 +8,7 @@ import {
8
8
  validatePasswordForLogin,
9
9
  validatePasswordForRegister,
10
10
  validatePasswordConfirmation,
11
- } from "../../../../infrastructure/utils/AuthValidation";
11
+ } from "../../../../application/services/ValidationService";
12
12
  import type { PasswordConfig } from "../../../../domain/value-objects/AuthConfig";
13
13
  import type {
14
14
  FormValidationResult,
@@ -17,6 +17,7 @@ import type {
17
17
  ProfileFormValues,
18
18
  FormValidationError,
19
19
  } from "./formValidation.types";
20
+ import { sanitizeEmail, sanitizeName } from "../../../../infrastructure/utils/validation/sanitization";
20
21
 
21
22
  export function validateLoginForm(
22
23
  values: LoginFormValues,
@@ -24,7 +25,7 @@ export function validateLoginForm(
24
25
  ): FormValidationResult {
25
26
  const errors: FormValidationError[] = [];
26
27
 
27
- const emailResult = validateEmail(values.email.trim());
28
+ const emailResult = validateEmail(sanitizeEmail(values.email));
28
29
  if (!emailResult.isValid && emailResult.error) {
29
30
  errors.push({ field: "email", message: getErrorMessage(emailResult.error) });
30
31
  }
@@ -44,7 +45,7 @@ export function validateRegisterForm(
44
45
  ): FormValidationResult {
45
46
  const errors: FormValidationError[] = [];
46
47
 
47
- const emailResult = validateEmail(values.email.trim());
48
+ const emailResult = validateEmail(sanitizeEmail(values.email));
48
49
  if (!emailResult.isValid && emailResult.error) {
49
50
  errors.push({ field: "email", message: getErrorMessage(emailResult.error) });
50
51
  }
@@ -68,7 +69,7 @@ export function validateProfileForm(
68
69
  ): FormValidationResult {
69
70
  const errors: FormValidationError[] = [];
70
71
 
71
- if (!values.displayName || !values.displayName.trim()) {
72
+ if (!values.displayName || !sanitizeName(values.displayName)) {
72
73
  errors.push({
73
74
  field: "displayName",
74
75
  message: getErrorMessage("auth.validation.displayNameRequired"),
@@ -76,7 +77,7 @@ export function validateProfileForm(
76
77
  }
77
78
 
78
79
  if (values.email) {
79
- const emailResult = validateEmail(values.email.trim());
80
+ const emailResult = validateEmail(sanitizeEmail(values.email));
80
81
  if (!emailResult.isValid && emailResult.error) {
81
82
  errors.push({ field: "email", message: getErrorMessage(emailResult.error) });
82
83
  }