@umituz/react-native-auth 4.3.1 → 4.3.3
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.
- package/package.json +1 -1
- package/src/application/services/ValidationService.ts +44 -0
- package/src/index.ts +8 -1
- package/src/infrastructure/utils/AuthValidation.ts +17 -14
- package/src/infrastructure/utils/authConversionDetector.ts +1 -1
- package/src/infrastructure/utils/authStateHandler.ts +8 -14
- package/src/infrastructure/utils/error/errorCodeMapping.constants.ts +2 -2
- package/src/infrastructure/utils/listener/{authStateHandler.ts → authListenerStateHandler.ts} +2 -13
- package/src/infrastructure/utils/listener/listenerLifecycle.util.ts +2 -2
- package/src/infrastructure/utils/listener/setupListener.ts +1 -1
- package/src/infrastructure/utils/safeCallback.ts +80 -0
- package/src/infrastructure/utils/validation/sanitization.ts +5 -30
- package/src/infrastructure/utils/validation/types.ts +53 -0
- package/src/infrastructure/utils/validation/validationHelpers.ts +70 -0
- package/src/presentation/components/PasswordStrengthIndicator.tsx +1 -1
- package/src/presentation/hooks/registerForm/registerFormHandlers.ts +1 -3
- package/src/presentation/hooks/registerForm/registerFormSubmit.ts +7 -7
- package/src/presentation/hooks/registerForm/useRegisterForm.types.ts +4 -6
- package/src/presentation/hooks/useAuthErrorHandler.ts +59 -0
- package/src/presentation/hooks/useGoogleAuth.ts +0 -6
- package/src/presentation/hooks/useLocalError.ts +43 -0
- package/src/presentation/hooks/useLoginForm.ts +9 -15
- package/src/presentation/hooks/useRegisterForm.ts +7 -15
- package/src/presentation/utils/form/formValidation.util.ts +1 -1
- package/src/presentation/utils/form/usePasswordValidation.hook.ts +1 -1
- package/src/presentation/utils/form/validation/formValidation.types.ts +5 -9
- 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.
|
|
3
|
+
"version": "4.3.3",
|
|
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 {
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
13
|
-
export type { ErrorConstructor, ErrorFactory
|
|
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
|
package/src/infrastructure/utils/listener/{authStateHandler.ts → authListenerStateHandler.ts}
RENAMED
|
@@ -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
|
-
|
|
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
|
-
*
|
|
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 "./
|
|
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 "./
|
|
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
|
-
|
|
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 "../../
|
|
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
|
|
38
|
-
email: fields.email
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
21
|
-
export type {
|
|
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
|
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
validatePasswordConfirmation,
|
|
10
10
|
type PasswordRequirements,
|
|
11
11
|
type ValidationResult,
|
|
12
|
-
} from "../../../
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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 "../../../../
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|