Skip to content

Commit 89099dc

Browse files
fix: bypassing trial on reload after counting to 0
1 parent 26fb366 commit 89099dc

File tree

4 files changed

+419
-23
lines changed

4 files changed

+419
-23
lines changed

hooks/useSecureTrial.tsx

Lines changed: 91 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,35 @@ export const useSecureTrial = () => {
122122
return sessionId;
123123
};
124124

125+
// Check if device has ever used a trial (additional security layer)
126+
const hasUsedTrialBefore = (): boolean => {
127+
try {
128+
// Check multiple storage mechanisms for trial usage evidence
129+
const localTrialId = localStorage.getItem("currentTrialId");
130+
const sessionId = localStorage.getItem("trial_session_id");
131+
const fallbackId = localStorage.getItem("ip_fallback_id");
132+
133+
// Also check sessionStorage (survives page refresh but not tab close)
134+
const sessionTrialUsed = sessionStorage.getItem("trial_ever_used");
135+
136+
// If any evidence of previous trial exists, consider it used
137+
return !!(localTrialId || sessionId || fallbackId || sessionTrialUsed);
138+
} catch (error) {
139+
// If we can't check, assume trial was used (security-first)
140+
return true;
141+
}
142+
};
143+
144+
// Mark that this device/session has used a trial
145+
const markTrialAsUsed = (): void => {
146+
try {
147+
sessionStorage.setItem("trial_ever_used", "true");
148+
localStorage.setItem("trial_ever_used", Date.now().toString());
149+
} catch (error) {
150+
// Ignore storage errors
151+
}
152+
};
153+
125154
// Check if user already has an active trial
126155
const checkExistingTrial = async (): Promise<TrialRecord | null> => {
127156
try {
@@ -254,10 +283,11 @@ export const useSecureTrial = () => {
254283
},
255284
);
256285

257-
// Store trial ID for later reference
286+
// Store trial ID for later reference and mark trial as used
258287
if (typeof window !== "undefined") {
259288
localStorage.setItem("currentTrialId", trialRecord.$id);
260289
localStorage.removeItem("creating_trial"); // Clear the creation flag
290+
markTrialAsUsed(); // Mark that this device has used a trial
261291
}
262292

263293
return trialRecord as unknown as TrialRecord;
@@ -287,7 +317,7 @@ export const useSecureTrial = () => {
287317
},
288318
);
289319
} catch (error) {
290-
console.error("Error expiring trial:", error);
320+
// Ignore errors
291321
}
292322
};
293323

@@ -306,6 +336,17 @@ export const useSecureTrial = () => {
306336
}
307337

308338
try {
339+
// First check if this device has ever used a trial (additional security)
340+
if (hasUsedTrialBefore()) {
341+
// Device has evidence of previous trial usage - block access immediately
342+
setTrialExpired(true);
343+
setTrialBlocked(true);
344+
setTimeRemaining(0);
345+
setTrialStartTime(null);
346+
setIsLoading(false);
347+
return;
348+
}
349+
309350
// Check if user already has an active trial
310351
const existingTrial = await checkExistingTrial();
311352

@@ -332,28 +373,56 @@ export const useSecureTrial = () => {
332373
}
333374
}
334375
} else {
335-
// No existing trial, create a new one
336-
const newTrial = await createTrial();
337-
338-
if (newTrial) {
339-
setTrialStartTime(newTrial.start_time);
340-
setTimeRemaining(TRIAL_DURATION_MS);
341-
setTrialExpired(false);
342-
setTrialBlocked(false);
376+
// No existing trial found on server
377+
// Check if device has evidence of previous trial usage
378+
if (hasUsedTrialBefore()) {
379+
// Device shows evidence of previous trial - block access for security
380+
setTrialExpired(true);
381+
setTrialBlocked(true);
382+
setTimeRemaining(0);
383+
setTrialStartTime(null);
343384
} else {
344-
// Failed to create trial - allow access but log error
345-
setTrialStartTime(Date.now());
346-
setTimeRemaining(TRIAL_DURATION_MS);
347-
setTrialExpired(false);
348-
setTrialBlocked(false);
385+
// Genuinely new device/session - create a new trial
386+
const newTrial = await createTrial();
387+
388+
if (newTrial) {
389+
setTrialStartTime(newTrial.start_time);
390+
setTimeRemaining(TRIAL_DURATION_MS);
391+
setTrialExpired(false);
392+
setTrialBlocked(false);
393+
} else {
394+
// Failed to create trial - be conservative and block access
395+
setTrialExpired(true);
396+
setTrialBlocked(true);
397+
setTimeRemaining(0);
398+
setTrialStartTime(null);
399+
}
349400
}
350401
}
351402
} catch (error) {
352-
// Don't block access on error - allow trial to proceed
353-
setTrialStartTime(Date.now());
354-
setTimeRemaining(TRIAL_DURATION_MS);
355-
setTrialExpired(false);
356-
setTrialBlocked(false);
403+
setTrialExpired(true);
404+
setTrialBlocked(true);
405+
setTimeRemaining(0);
406+
setTrialStartTime(null);
407+
408+
// Check localStorage for any existing trial state to prevent bypass
409+
const localTrialId = localStorage.getItem("currentTrialId");
410+
const localSessionId = localStorage.getItem("trial_session_id");
411+
412+
// If we have local trial data, assume trial was already used (security-first approach)
413+
if (localTrialId || localSessionId) {
414+
setTrialExpired(true);
415+
setTrialBlocked(true);
416+
setTimeRemaining(0);
417+
setTrialStartTime(null);
418+
} else {
419+
// Only allow new trial if no local evidence of previous trial exists
420+
// This is a fallback for genuine first-time users with network issues
421+
setTrialStartTime(Date.now());
422+
setTimeRemaining(TRIAL_DURATION_MS);
423+
setTrialExpired(false);
424+
setTrialBlocked(false);
425+
}
357426
} finally {
358427
setIsLoading(false);
359428
}
@@ -374,13 +443,14 @@ export const useSecureTrial = () => {
374443
setTrialExpired(true);
375444
setTrialBlocked(true);
376445
setTimeRemaining(0);
377-
// Mark trial as expired in database
446+
// Mark trial as expired in database and locally
378447
if (typeof window !== "undefined") {
379448
const trialId = localStorage.getItem("currentTrialId");
380449
if (trialId) {
381450
expireTrial(trialId);
382451
localStorage.removeItem("currentTrialId");
383452
}
453+
markTrialAsUsed(); // Ensure trial usage is marked locally
384454
}
385455
} else {
386456
setTimeRemaining(remaining);

public/sw.js

Lines changed: 101 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/README.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,35 @@ node scripts/test-ip-flexibility.js
138138

139139
---
140140

141+
### 🔧 `test-trial-bypass-fix.js`
142+
143+
**Purpose**: Verify that trial bypass vulnerabilities have been fixed
144+
145+
**Usage**:
146+
147+
```bash
148+
node scripts/test-trial-bypass-fix.js
149+
```
150+
151+
**What it tests**:
152+
153+
1. Fresh user with no previous trial (should allow trial)
154+
2. User with expired trial ID in localStorage (should block)
155+
3. User with session ID from previous trial (should block)
156+
4. User with fallback IP ID (should block)
157+
5. User with sessionStorage trial marker (should block)
158+
6. User who cleared localStorage but not sessionStorage (should block)
159+
7. User with multiple evidence sources (should block)
160+
161+
**What it validates**:
162+
163+
- The `hasUsedTrialBefore()` function correctly detects previous trial usage
164+
- Multiple storage mechanisms prevent bypass attempts
165+
- Security-first approach blocks suspicious activity
166+
- Only completely fresh devices get trial access
167+
168+
---
169+
141170
## Common Debugging Scenarios
142171

143172
### 🐛 "Trial expired popup showing for all users"
@@ -155,7 +184,8 @@ node scripts/test-ip-flexibility.js
155184
### 🧪 "Testing if system works"
156185

157186
1. Run `test-trial-system.js` to verify all functionality
158-
2. Use `monitor-trials.js` to watch real-time activity
187+
2. Run `test-trial-bypass-fix.js` to verify bypass prevention
188+
3. Use `monitor-trials.js` to watch real-time activity
159189

160190
### 🌐 "Testing IP-based security"
161191

@@ -201,4 +231,5 @@ NEXT_PUBLIC_APPWRITE_PROJECT_ID=your_project_id
201231
- **Be careful with `--all` flag** - it deletes everything!
202232
- **Check the console output** for detailed error messages
203233
- **Run `test-trial-system.js`** after making changes to verify everything works
234+
- **Run `test-trial-bypass-fix.js`** to verify bypass prevention is working
204235
- **Test with different browsers** to ensure IP-based blocking works

0 commit comments

Comments
 (0)