@croacroa/react-native-template 2.0.1 → 3.2.0
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/.env.example +5 -0
- package/.eslintrc.js +8 -0
- package/.github/workflows/ci.yml +187 -187
- package/.github/workflows/eas-build.yml +55 -55
- package/.github/workflows/eas-update.yml +50 -50
- package/.github/workflows/npm-publish.yml +57 -0
- package/CHANGELOG.md +195 -106
- package/CONTRIBUTING.md +377 -377
- package/LICENSE +21 -0
- package/README.md +446 -399
- package/__tests__/accessibility/components.test.tsx +285 -0
- package/__tests__/components/Button.test.tsx +2 -4
- package/__tests__/components/__snapshots__/snapshots.test.tsx.snap +512 -0
- package/__tests__/components/snapshots.test.tsx +131 -131
- package/__tests__/helpers/a11y.ts +54 -0
- package/__tests__/hooks/useAnalytics.test.ts +100 -0
- package/__tests__/hooks/useAnimations.test.ts +70 -0
- package/__tests__/hooks/useAuth.test.tsx +71 -28
- package/__tests__/hooks/useMedia.test.ts +318 -0
- package/__tests__/hooks/usePayments.test.tsx +307 -0
- package/__tests__/hooks/usePermission.test.ts +230 -0
- package/__tests__/hooks/useWebSocket.test.ts +329 -0
- package/__tests__/integration/auth-api.test.tsx +224 -227
- package/__tests__/performance/VirtualizedList.perf.test.tsx +385 -362
- package/__tests__/services/api.test.ts +24 -6
- package/app/(auth)/home.tsx +11 -9
- package/app/(auth)/profile.tsx +8 -6
- package/app/(auth)/settings.tsx +11 -9
- package/app/(public)/forgot-password.tsx +25 -15
- package/app/(public)/login.tsx +48 -12
- package/app/(public)/onboarding.tsx +5 -5
- package/app/(public)/register.tsx +24 -15
- package/app/_layout.tsx +6 -3
- package/app.config.ts +27 -2
- package/assets/images/.gitkeep +7 -7
- package/assets/images/adaptive-icon.png +0 -0
- package/assets/images/favicon.png +0 -0
- package/assets/images/icon.png +0 -0
- package/assets/images/notification-icon.png +0 -0
- package/assets/images/splash.png +0 -0
- package/components/ErrorBoundary.tsx +73 -28
- package/components/auth/SocialLoginButtons.tsx +168 -0
- package/components/forms/FormInput.tsx +5 -3
- package/components/onboarding/OnboardingScreen.tsx +370 -370
- package/components/onboarding/index.ts +2 -2
- package/components/providers/AnalyticsProvider.tsx +67 -0
- package/components/providers/SuspenseBoundary.tsx +359 -357
- package/components/providers/index.ts +24 -21
- package/components/ui/AnimatedButton.tsx +1 -9
- package/components/ui/AnimatedList.tsx +98 -0
- package/components/ui/AnimatedScreen.tsx +89 -0
- package/components/ui/Avatar.tsx +319 -316
- package/components/ui/Badge.tsx +416 -416
- package/components/ui/BottomSheet.tsx +307 -307
- package/components/ui/Button.tsx +11 -3
- package/components/ui/Checkbox.tsx +261 -261
- package/components/ui/FeatureGate.tsx +57 -0
- package/components/ui/ForceUpdateScreen.tsx +108 -0
- package/components/ui/ImagePickerButton.tsx +180 -0
- package/components/ui/Input.stories.tsx +2 -10
- package/components/ui/Input.tsx +2 -10
- package/components/ui/OptimizedImage.tsx +369 -369
- package/components/ui/Paywall.tsx +253 -0
- package/components/ui/PermissionGate.tsx +155 -0
- package/components/ui/PurchaseButton.tsx +84 -0
- package/components/ui/Select.tsx +240 -240
- package/components/ui/Skeleton.tsx +3 -1
- package/components/ui/Toast.tsx +427 -0
- package/components/ui/UploadProgress.tsx +189 -0
- package/components/ui/VirtualizedList.tsx +288 -285
- package/components/ui/index.ts +28 -23
- package/constants/config.ts +135 -97
- package/docs/adr/001-state-management.md +79 -79
- package/docs/adr/002-styling-approach.md +130 -130
- package/docs/adr/003-data-fetching.md +155 -155
- package/docs/adr/004-auth-adapter-pattern.md +144 -144
- package/docs/adr/README.md +78 -78
- package/docs/guides/analytics-posthog.md +121 -0
- package/docs/guides/auth-supabase.md +162 -0
- package/docs/guides/feature-flags-launchdarkly.md +150 -0
- package/docs/guides/payments-revenuecat.md +169 -0
- package/docs/plans/2026-02-22-phase6-implementation.md +3222 -0
- package/docs/plans/2026-02-22-phase6-template-completion-design.md +196 -0
- package/docs/plans/2026-02-23-npm-publish-design.md +31 -0
- package/docs/plans/2026-02-23-phase7-polish-documentation-design.md +79 -0
- package/docs/plans/2026-02-23-phase8-additional-features-design.md +136 -0
- package/eas.json +2 -1
- package/hooks/index.ts +70 -27
- package/hooks/useAnimatedEntry.ts +204 -0
- package/hooks/useApi.ts +64 -4
- package/hooks/useAuth.tsx +7 -3
- package/hooks/useBiometrics.ts +295 -295
- package/hooks/useChannel.ts +111 -0
- package/hooks/useDeepLinking.ts +256 -256
- package/hooks/useExperiment.ts +36 -0
- package/hooks/useFeatureFlag.ts +59 -0
- package/hooks/useForceUpdate.ts +91 -0
- package/hooks/useImagePicker.ts +281 -0
- package/hooks/useInAppReview.ts +64 -0
- package/hooks/useMFA.ts +509 -499
- package/hooks/useParallax.ts +142 -0
- package/hooks/usePerformance.ts +434 -434
- package/hooks/usePermission.ts +190 -0
- package/hooks/usePresence.ts +129 -0
- package/hooks/useProducts.ts +36 -0
- package/hooks/usePurchase.ts +103 -0
- package/hooks/useRateLimit.ts +70 -0
- package/hooks/useSubscription.ts +49 -0
- package/hooks/useTrackEvent.ts +52 -0
- package/hooks/useTrackScreen.ts +40 -0
- package/hooks/useUpdates.ts +358 -358
- package/hooks/useUpload.ts +165 -0
- package/hooks/useWebSocket.ts +111 -0
- package/i18n/index.ts +197 -194
- package/i18n/locales/ar.json +170 -101
- package/i18n/locales/de.json +170 -101
- package/i18n/locales/en.json +170 -101
- package/i18n/locales/es.json +170 -101
- package/i18n/locales/fr.json +170 -101
- package/jest.config.js +1 -1
- package/maestro/README.md +113 -113
- package/maestro/config.yaml +35 -35
- package/maestro/flows/login.yaml +62 -62
- package/maestro/flows/mfa-login.yaml +92 -92
- package/maestro/flows/mfa-setup.yaml +86 -86
- package/maestro/flows/navigation.yaml +68 -68
- package/maestro/flows/offline-conflict.yaml +101 -101
- package/maestro/flows/offline-sync.yaml +128 -128
- package/maestro/flows/offline.yaml +60 -60
- package/maestro/flows/register.yaml +94 -94
- package/package.json +188 -175
- package/scripts/generate-placeholders.js +38 -0
- package/services/analytics/adapters/console.ts +50 -0
- package/services/analytics/analytics-adapter.ts +94 -0
- package/services/analytics/types.ts +73 -0
- package/services/analytics.ts +428 -428
- package/services/api.ts +419 -340
- package/services/auth/social/apple.ts +110 -0
- package/services/auth/social/google.ts +159 -0
- package/services/auth/social/social-auth.ts +100 -0
- package/services/auth/social/types.ts +80 -0
- package/services/authAdapter.ts +333 -333
- package/services/backgroundSync.ts +652 -626
- package/services/feature-flags/adapters/mock.ts +108 -0
- package/services/feature-flags/feature-flag-adapter.ts +174 -0
- package/services/feature-flags/types.ts +79 -0
- package/services/force-update.ts +140 -0
- package/services/index.ts +116 -54
- package/services/media/compression.ts +91 -0
- package/services/media/media-picker.ts +151 -0
- package/services/media/media-upload.ts +160 -0
- package/services/payments/adapters/mock.ts +159 -0
- package/services/payments/payment-adapter.ts +118 -0
- package/services/payments/types.ts +131 -0
- package/services/permissions/permission-manager.ts +284 -0
- package/services/permissions/types.ts +104 -0
- package/services/realtime/types.ts +100 -0
- package/services/realtime/websocket-manager.ts +441 -0
- package/services/security.ts +289 -286
- package/services/sentry.ts +4 -4
- package/stores/appStore.ts +9 -0
- package/stores/notificationStore.ts +3 -1
- package/tailwind.config.js +47 -47
- package/tsconfig.json +37 -13
- package/types/user.ts +1 -1
- package/utils/accessibility.ts +446 -446
- package/utils/animations/presets.ts +182 -0
- package/utils/animations/transitions.ts +62 -0
- package/utils/index.ts +63 -52
- package/utils/toast.ts +9 -2
- package/utils/validation.ts +4 -1
- package/utils/withAccessibility.tsx +272 -272
package/services/security.ts
CHANGED
|
@@ -1,286 +1,289 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Security utilities for enhanced app protection
|
|
3
|
-
* Provides SSL pinning validation, request signing, and security checks.
|
|
4
|
-
* @module services/security
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { SECURITY, IS_DEV } from "@/constants/config";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Validate SSL certificate pins for a given hostname.
|
|
11
|
-
* Note: In React Native, actual certificate pinning requires native module configuration.
|
|
12
|
-
* This service provides the configuration and validation helpers.
|
|
13
|
-
*
|
|
14
|
-
* For full SSL pinning, configure your native modules:
|
|
15
|
-
* - iOS: Use TrustKit or configure ATS in Info.plist
|
|
16
|
-
* - Android: Configure network_security_config.xml
|
|
17
|
-
*
|
|
18
|
-
* @param hostname - The hostname to validate pins for
|
|
19
|
-
* @returns Array of certificate pins for the hostname, or empty array if not configured
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```ts
|
|
23
|
-
* const pins = getCertificatePins('api.yourapp.com');
|
|
24
|
-
* if (pins.length > 0) {
|
|
25
|
-
* // Use pins for validation
|
|
26
|
-
* }
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
export function getCertificatePins(hostname: string): string[] {
|
|
30
|
-
if (!SECURITY.ENABLE_SSL_PINNING) {
|
|
31
|
-
return [];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return SECURITY.SSL_PINS[hostname] || [];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Check if SSL pinning is enabled for a hostname
|
|
39
|
-
*/
|
|
40
|
-
export function isSslPinningEnabled(hostname: string): boolean {
|
|
41
|
-
if (!SECURITY.ENABLE_SSL_PINNING) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const pins = SECURITY.SSL_PINS[hostname];
|
|
46
|
-
return Array.isArray(pins) && pins.length > 0;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Generate a request signature for API requests.
|
|
51
|
-
* Useful for preventing request tampering and replay attacks.
|
|
52
|
-
*
|
|
53
|
-
* @param method - HTTP method
|
|
54
|
-
* @param url - Request URL
|
|
55
|
-
* @param body - Request body (optional)
|
|
56
|
-
* @param timestamp - Request timestamp
|
|
57
|
-
* @returns Signature string or null if signing is disabled
|
|
58
|
-
*
|
|
59
|
-
* @example
|
|
60
|
-
* ```ts
|
|
61
|
-
* const signature = generateRequestSignature('POST', '/api/users', { name: 'John' }, Date.now());
|
|
62
|
-
* if (signature) {
|
|
63
|
-
* headers['X-Request-Signature'] = signature;
|
|
64
|
-
* }
|
|
65
|
-
* ```
|
|
66
|
-
*/
|
|
67
|
-
export function generateRequestSignature(
|
|
68
|
-
method: string,
|
|
69
|
-
url: string,
|
|
70
|
-
body: unknown,
|
|
71
|
-
timestamp: number
|
|
72
|
-
): string | null {
|
|
73
|
-
if (!SECURITY.REQUEST_SIGNING.ENABLED) {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Create payload to sign
|
|
78
|
-
const payload = [
|
|
79
|
-
method.toUpperCase(),
|
|
80
|
-
url,
|
|
81
|
-
body ? JSON.stringify(body) : "",
|
|
82
|
-
timestamp.toString(),
|
|
83
|
-
].join(":");
|
|
84
|
-
|
|
85
|
-
// In production, use a proper HMAC implementation with a secret key
|
|
86
|
-
// This is a placeholder - implement actual signing based on your backend requirements
|
|
87
|
-
if (IS_DEV) {
|
|
88
|
-
console.log("[Security] Would sign payload:", payload);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// TODO: Implement actual HMAC signing
|
|
92
|
-
// const hmac = crypto.createHmac(SECURITY.REQUEST_SIGNING.ALGORITHM, SECRET_KEY);
|
|
93
|
-
// return hmac.update(payload).digest('base64');
|
|
94
|
-
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Security headers to add to API requests
|
|
100
|
-
*/
|
|
101
|
-
export function getSecurityHeaders(): Record<string, string> {
|
|
102
|
-
const headers: Record<string, string> = {};
|
|
103
|
-
|
|
104
|
-
// Add timestamp for request signing/replay protection
|
|
105
|
-
if (SECURITY.REQUEST_SIGNING.ENABLED) {
|
|
106
|
-
headers["X-Request-Timestamp"] = Date.now().toString();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return headers;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Validate that a URL is allowed (not blocked by security policy)
|
|
114
|
-
* Useful for preventing open redirects and SSRF
|
|
115
|
-
*
|
|
116
|
-
* @param url - URL to validate
|
|
117
|
-
* @param allowedHosts - List of allowed hostnames
|
|
118
|
-
* @returns True if URL is allowed
|
|
119
|
-
*/
|
|
120
|
-
export function isUrlAllowed(url: string, allowedHosts: string[]): boolean {
|
|
121
|
-
try {
|
|
122
|
-
const parsed = new URL(url);
|
|
123
|
-
return allowedHosts.some((host) => {
|
|
124
|
-
// Support wildcards (e.g., *.yourapp.com)
|
|
125
|
-
if (host.startsWith("*.")) {
|
|
126
|
-
const domain = host.slice(2);
|
|
127
|
-
return (
|
|
128
|
-
parsed.hostname === domain || parsed.hostname.endsWith(`.${domain}`)
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
return parsed.hostname === host;
|
|
132
|
-
});
|
|
133
|
-
} catch {
|
|
134
|
-
// Invalid URL
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Sanitize user input to prevent XSS
|
|
141
|
-
* Basic sanitization - use a proper library for comprehensive XSS protection
|
|
142
|
-
*/
|
|
143
|
-
export function sanitizeInput(input: string): string {
|
|
144
|
-
return input
|
|
145
|
-
.replace(/&/g, "&")
|
|
146
|
-
.replace(/</g, "<")
|
|
147
|
-
.replace(/>/g, ">")
|
|
148
|
-
.replace(/"/g, """)
|
|
149
|
-
.replace(/'/g, "'");
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Check if the app is running in a potentially compromised environment
|
|
154
|
-
* (rooted/jailbroken device, debugger attached, etc.)
|
|
155
|
-
*
|
|
156
|
-
* Note: This requires additional native modules for full detection:
|
|
157
|
-
* - iOS: Consider using a jailbreak detection library
|
|
158
|
-
* - Android: Consider using SafetyNet Attestation or Play Integrity API
|
|
159
|
-
*/
|
|
160
|
-
export async function checkSecurityEnvironment(): Promise<{
|
|
161
|
-
isSecure: boolean;
|
|
162
|
-
warnings: string[];
|
|
163
|
-
}> {
|
|
164
|
-
const warnings: string[] = [];
|
|
165
|
-
|
|
166
|
-
// Check for development mode
|
|
167
|
-
if (IS_DEV) {
|
|
168
|
-
warnings.push("Running in development mode");
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Check for debugger
|
|
172
|
-
// Note: __DEV__ is automatically true when debugger is attached
|
|
173
|
-
if (__DEV__) {
|
|
174
|
-
warnings.push("Debugger may be attached");
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Additional checks would require native modules:
|
|
178
|
-
// - Jailbreak/root detection
|
|
179
|
-
// - Frida/debugging tools detection
|
|
180
|
-
// - Emulator detection
|
|
181
|
-
// - Hook detection
|
|
182
|
-
|
|
183
|
-
return {
|
|
184
|
-
isSecure: warnings.length === 0,
|
|
185
|
-
warnings,
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* SSL Pinning configuration for native modules.
|
|
191
|
-
* Export this configuration to use with native SSL pinning libraries.
|
|
192
|
-
*
|
|
193
|
-
* For iOS (TrustKit), add to Info.plist or configure programmatically.
|
|
194
|
-
* For Android, use network_security_config.xml.
|
|
195
|
-
*/
|
|
196
|
-
export const SSL_PINNING_CONFIG = {
|
|
197
|
-
enabled: SECURITY.ENABLE_SSL_PINNING,
|
|
198
|
-
pins: SECURITY.SSL_PINS,
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Generate Android network_security_config.xml content
|
|
202
|
-
* Save this to android/app/src/main/res/xml/network_security_config.xml
|
|
203
|
-
* and reference it in AndroidManifest.xml:
|
|
204
|
-
* <application android:networkSecurityConfig="@xml/network_security_config">
|
|
205
|
-
*/
|
|
206
|
-
getAndroidConfig(): string {
|
|
207
|
-
const pinEntries = Object.entries(SECURITY.SSL_PINS)
|
|
208
|
-
.map(([domain, pins]) => {
|
|
209
|
-
const pinElements = pins
|
|
210
|
-
.map(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
</
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
</
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Security utilities for enhanced app protection
|
|
3
|
+
* Provides SSL pinning validation, request signing, and security checks.
|
|
4
|
+
* @module services/security
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { SECURITY, IS_DEV } from "@/constants/config";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Validate SSL certificate pins for a given hostname.
|
|
11
|
+
* Note: In React Native, actual certificate pinning requires native module configuration.
|
|
12
|
+
* This service provides the configuration and validation helpers.
|
|
13
|
+
*
|
|
14
|
+
* For full SSL pinning, configure your native modules:
|
|
15
|
+
* - iOS: Use TrustKit or configure ATS in Info.plist
|
|
16
|
+
* - Android: Configure network_security_config.xml
|
|
17
|
+
*
|
|
18
|
+
* @param hostname - The hostname to validate pins for
|
|
19
|
+
* @returns Array of certificate pins for the hostname, or empty array if not configured
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const pins = getCertificatePins('api.yourapp.com');
|
|
24
|
+
* if (pins.length > 0) {
|
|
25
|
+
* // Use pins for validation
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function getCertificatePins(hostname: string): string[] {
|
|
30
|
+
if (!SECURITY.ENABLE_SSL_PINNING) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return SECURITY.SSL_PINS[hostname] || [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if SSL pinning is enabled for a hostname
|
|
39
|
+
*/
|
|
40
|
+
export function isSslPinningEnabled(hostname: string): boolean {
|
|
41
|
+
if (!SECURITY.ENABLE_SSL_PINNING) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const pins = SECURITY.SSL_PINS[hostname];
|
|
46
|
+
return Array.isArray(pins) && pins.length > 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Generate a request signature for API requests.
|
|
51
|
+
* Useful for preventing request tampering and replay attacks.
|
|
52
|
+
*
|
|
53
|
+
* @param method - HTTP method
|
|
54
|
+
* @param url - Request URL
|
|
55
|
+
* @param body - Request body (optional)
|
|
56
|
+
* @param timestamp - Request timestamp
|
|
57
|
+
* @returns Signature string or null if signing is disabled
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* const signature = generateRequestSignature('POST', '/api/users', { name: 'John' }, Date.now());
|
|
62
|
+
* if (signature) {
|
|
63
|
+
* headers['X-Request-Signature'] = signature;
|
|
64
|
+
* }
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export function generateRequestSignature(
|
|
68
|
+
method: string,
|
|
69
|
+
url: string,
|
|
70
|
+
body: unknown,
|
|
71
|
+
timestamp: number
|
|
72
|
+
): string | null {
|
|
73
|
+
if (!SECURITY.REQUEST_SIGNING.ENABLED) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Create payload to sign
|
|
78
|
+
const payload = [
|
|
79
|
+
method.toUpperCase(),
|
|
80
|
+
url,
|
|
81
|
+
body ? JSON.stringify(body) : "",
|
|
82
|
+
timestamp.toString(),
|
|
83
|
+
].join(":");
|
|
84
|
+
|
|
85
|
+
// In production, use a proper HMAC implementation with a secret key
|
|
86
|
+
// This is a placeholder - implement actual signing based on your backend requirements
|
|
87
|
+
if (IS_DEV) {
|
|
88
|
+
console.log("[Security] Would sign payload:", payload);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// TODO: Implement actual HMAC signing
|
|
92
|
+
// const hmac = crypto.createHmac(SECURITY.REQUEST_SIGNING.ALGORITHM, SECRET_KEY);
|
|
93
|
+
// return hmac.update(payload).digest('base64');
|
|
94
|
+
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Security headers to add to API requests
|
|
100
|
+
*/
|
|
101
|
+
export function getSecurityHeaders(): Record<string, string> {
|
|
102
|
+
const headers: Record<string, string> = {};
|
|
103
|
+
|
|
104
|
+
// Add timestamp for request signing/replay protection
|
|
105
|
+
if (SECURITY.REQUEST_SIGNING.ENABLED) {
|
|
106
|
+
headers["X-Request-Timestamp"] = Date.now().toString();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return headers;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Validate that a URL is allowed (not blocked by security policy)
|
|
114
|
+
* Useful for preventing open redirects and SSRF
|
|
115
|
+
*
|
|
116
|
+
* @param url - URL to validate
|
|
117
|
+
* @param allowedHosts - List of allowed hostnames
|
|
118
|
+
* @returns True if URL is allowed
|
|
119
|
+
*/
|
|
120
|
+
export function isUrlAllowed(url: string, allowedHosts: string[]): boolean {
|
|
121
|
+
try {
|
|
122
|
+
const parsed = new URL(url);
|
|
123
|
+
return allowedHosts.some((host) => {
|
|
124
|
+
// Support wildcards (e.g., *.yourapp.com)
|
|
125
|
+
if (host.startsWith("*.")) {
|
|
126
|
+
const domain = host.slice(2);
|
|
127
|
+
return (
|
|
128
|
+
parsed.hostname === domain || parsed.hostname.endsWith(`.${domain}`)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return parsed.hostname === host;
|
|
132
|
+
});
|
|
133
|
+
} catch {
|
|
134
|
+
// Invalid URL
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Sanitize user input to prevent XSS
|
|
141
|
+
* Basic sanitization - use a proper library for comprehensive XSS protection
|
|
142
|
+
*/
|
|
143
|
+
export function sanitizeInput(input: string): string {
|
|
144
|
+
return input
|
|
145
|
+
.replace(/&/g, "&")
|
|
146
|
+
.replace(/</g, "<")
|
|
147
|
+
.replace(/>/g, ">")
|
|
148
|
+
.replace(/"/g, """)
|
|
149
|
+
.replace(/'/g, "'");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Check if the app is running in a potentially compromised environment
|
|
154
|
+
* (rooted/jailbroken device, debugger attached, etc.)
|
|
155
|
+
*
|
|
156
|
+
* Note: This requires additional native modules for full detection:
|
|
157
|
+
* - iOS: Consider using a jailbreak detection library
|
|
158
|
+
* - Android: Consider using SafetyNet Attestation or Play Integrity API
|
|
159
|
+
*/
|
|
160
|
+
export async function checkSecurityEnvironment(): Promise<{
|
|
161
|
+
isSecure: boolean;
|
|
162
|
+
warnings: string[];
|
|
163
|
+
}> {
|
|
164
|
+
const warnings: string[] = [];
|
|
165
|
+
|
|
166
|
+
// Check for development mode
|
|
167
|
+
if (IS_DEV) {
|
|
168
|
+
warnings.push("Running in development mode");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Check for debugger
|
|
172
|
+
// Note: __DEV__ is automatically true when debugger is attached
|
|
173
|
+
if (__DEV__) {
|
|
174
|
+
warnings.push("Debugger may be attached");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Additional checks would require native modules:
|
|
178
|
+
// - Jailbreak/root detection
|
|
179
|
+
// - Frida/debugging tools detection
|
|
180
|
+
// - Emulator detection
|
|
181
|
+
// - Hook detection
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
isSecure: warnings.length === 0,
|
|
185
|
+
warnings,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* SSL Pinning configuration for native modules.
|
|
191
|
+
* Export this configuration to use with native SSL pinning libraries.
|
|
192
|
+
*
|
|
193
|
+
* For iOS (TrustKit), add to Info.plist or configure programmatically.
|
|
194
|
+
* For Android, use network_security_config.xml.
|
|
195
|
+
*/
|
|
196
|
+
export const SSL_PINNING_CONFIG = {
|
|
197
|
+
enabled: SECURITY.ENABLE_SSL_PINNING,
|
|
198
|
+
pins: SECURITY.SSL_PINS,
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Generate Android network_security_config.xml content
|
|
202
|
+
* Save this to android/app/src/main/res/xml/network_security_config.xml
|
|
203
|
+
* and reference it in AndroidManifest.xml:
|
|
204
|
+
* <application android:networkSecurityConfig="@xml/network_security_config">
|
|
205
|
+
*/
|
|
206
|
+
getAndroidConfig(): string {
|
|
207
|
+
const pinEntries = Object.entries(SECURITY.SSL_PINS)
|
|
208
|
+
.map(([domain, pins]) => {
|
|
209
|
+
const pinElements = pins
|
|
210
|
+
.map(
|
|
211
|
+
(pin) =>
|
|
212
|
+
` <pin digest="SHA-256">${pin.replace("sha256/", "")}</pin>`
|
|
213
|
+
)
|
|
214
|
+
.join("\n");
|
|
215
|
+
|
|
216
|
+
return ` <domain-config cleartextTrafficPermitted="false">
|
|
217
|
+
<domain includeSubdomains="true">${domain}</domain>
|
|
218
|
+
<pin-set expiration="2026-12-31">
|
|
219
|
+
${pinElements}
|
|
220
|
+
</pin-set>
|
|
221
|
+
</domain-config>`;
|
|
222
|
+
})
|
|
223
|
+
.join("\n");
|
|
224
|
+
|
|
225
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
226
|
+
<network-security-config>
|
|
227
|
+
<base-config cleartextTrafficPermitted="false">
|
|
228
|
+
<trust-anchors>
|
|
229
|
+
<certificates src="system" />
|
|
230
|
+
</trust-anchors>
|
|
231
|
+
</base-config>
|
|
232
|
+
${pinEntries}
|
|
233
|
+
</network-security-config>`;
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Generate iOS TrustKit configuration dictionary
|
|
238
|
+
* Add this to your AppDelegate.mm or use expo-build-properties plugin
|
|
239
|
+
*
|
|
240
|
+
* For Expo managed workflow, add to app.config.ts:
|
|
241
|
+
* ```
|
|
242
|
+
* plugins: [
|
|
243
|
+
* ["expo-build-properties", {
|
|
244
|
+
* ios: {
|
|
245
|
+
* infoPlist: SSL_PINNING_CONFIG.getIOSInfoPlist()
|
|
246
|
+
* }
|
|
247
|
+
* }]
|
|
248
|
+
* ]
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
getIOSConfig(): Record<string, unknown> {
|
|
252
|
+
const pinnedDomains: Record<string, unknown> = {};
|
|
253
|
+
|
|
254
|
+
Object.entries(SECURITY.SSL_PINS).forEach(([domain, pins]) => {
|
|
255
|
+
pinnedDomains[domain] = {
|
|
256
|
+
TSKIncludeSubdomains: true,
|
|
257
|
+
TSKEnforcePinning: true,
|
|
258
|
+
TSKPublicKeyHashes: pins.map((pin) => pin.replace("sha256/", "")),
|
|
259
|
+
};
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
TSKSwizzleNetworkDelegates: true,
|
|
264
|
+
TSKPinnedDomains: pinnedDomains,
|
|
265
|
+
};
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Generate iOS Info.plist entries for SSL pinning
|
|
270
|
+
*/
|
|
271
|
+
getIOSInfoPlist(): Record<string, unknown> {
|
|
272
|
+
return {
|
|
273
|
+
NSAppTransportSecurity: {
|
|
274
|
+
NSAllowsArbitraryLoads: false,
|
|
275
|
+
NSPinnedDomains: Object.fromEntries(
|
|
276
|
+
Object.entries(SECURITY.SSL_PINS).map(([domain, pins]) => [
|
|
277
|
+
domain,
|
|
278
|
+
{
|
|
279
|
+
NSIncludesSubdomains: true,
|
|
280
|
+
NSPinnedLeafIdentities: pins.map((pin) => ({
|
|
281
|
+
"SPKI-SHA256-BASE64": pin.replace("sha256/", ""),
|
|
282
|
+
})),
|
|
283
|
+
},
|
|
284
|
+
])
|
|
285
|
+
),
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
},
|
|
289
|
+
};
|
package/services/sentry.ts
CHANGED
|
@@ -32,9 +32,7 @@ export function initSentry() {
|
|
|
32
32
|
// replaysOnErrorSampleRate: 1.0,
|
|
33
33
|
|
|
34
34
|
// Integrations
|
|
35
|
-
integrations: [
|
|
36
|
-
Sentry.reactNativeTracingIntegration(),
|
|
37
|
-
],
|
|
35
|
+
integrations: [Sentry.reactNativeTracingIntegration()],
|
|
38
36
|
|
|
39
37
|
// Filter out certain errors
|
|
40
38
|
beforeSend(event) {
|
|
@@ -89,7 +87,9 @@ export function captureMessage(
|
|
|
89
87
|
/**
|
|
90
88
|
* Set user context for error tracking
|
|
91
89
|
*/
|
|
92
|
-
export function setUser(
|
|
90
|
+
export function setUser(
|
|
91
|
+
user: { id: string; email?: string; name?: string } | null
|
|
92
|
+
) {
|
|
93
93
|
if (user) {
|
|
94
94
|
Sentry.setUser({
|
|
95
95
|
id: user.id,
|
package/stores/appStore.ts
CHANGED
|
@@ -15,6 +15,10 @@ interface AppState {
|
|
|
15
15
|
featureFlags: Record<string, boolean>;
|
|
16
16
|
setFeatureFlag: (key: string, value: boolean) => void;
|
|
17
17
|
|
|
18
|
+
// Session tracking
|
|
19
|
+
sessionCount: number;
|
|
20
|
+
incrementSessionCount: () => void;
|
|
21
|
+
|
|
18
22
|
// Reset all state
|
|
19
23
|
reset: () => void;
|
|
20
24
|
}
|
|
@@ -23,6 +27,7 @@ const initialState = {
|
|
|
23
27
|
isLoading: false,
|
|
24
28
|
hasCompletedOnboarding: false,
|
|
25
29
|
featureFlags: {},
|
|
30
|
+
sessionCount: 0,
|
|
26
31
|
};
|
|
27
32
|
|
|
28
33
|
export const useAppStore = create<AppState>()(
|
|
@@ -40,6 +45,9 @@ export const useAppStore = create<AppState>()(
|
|
|
40
45
|
featureFlags: { ...state.featureFlags, [key]: value },
|
|
41
46
|
})),
|
|
42
47
|
|
|
48
|
+
incrementSessionCount: () =>
|
|
49
|
+
set((state) => ({ sessionCount: state.sessionCount + 1 })),
|
|
50
|
+
|
|
43
51
|
reset: () => set(initialState),
|
|
44
52
|
}),
|
|
45
53
|
{
|
|
@@ -48,6 +56,7 @@ export const useAppStore = create<AppState>()(
|
|
|
48
56
|
partialize: (state) => ({
|
|
49
57
|
hasCompletedOnboarding: state.hasCompletedOnboarding,
|
|
50
58
|
featureFlags: state.featureFlags,
|
|
59
|
+
sessionCount: state.sessionCount,
|
|
51
60
|
}),
|
|
52
61
|
}
|
|
53
62
|
)
|
|
@@ -10,7 +10,9 @@ interface NotificationState {
|
|
|
10
10
|
setPushToken: (token: string | null) => void;
|
|
11
11
|
setIsEnabled: (enabled: boolean) => void;
|
|
12
12
|
toggleNotifications: () => void;
|
|
13
|
-
setLastNotification: (
|
|
13
|
+
setLastNotification: (
|
|
14
|
+
notification: Notifications.Notification | null
|
|
15
|
+
) => void;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export const useNotificationStore = create<NotificationState>()(
|