Skip to content

Phase 1: server auth, ESLint cleanup, AI adapter, free-tier cleanup#5

Merged
projectamazonph merged 3 commits into
mainfrom
docs/remediation-plan
Jul 17, 2026
Merged

Phase 1: server auth, ESLint cleanup, AI adapter, free-tier cleanup#5
projectamazonph merged 3 commits into
mainfrom
docs/remediation-plan

Conversation

@projectamazonph

@projectamazonph projectamazonph commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Phase 1 — Completed items

P1.1 — Server AI adapter

  • Replaced BrowserLLMIntegration with z-ai-web-dev-sdk in 3 AI routes (cover-letter, resume-review, assessment-score)
  • Removed fake fallback data that previously fabricated experience claims
  • Added input length limits (10K chars for cover letters, 20K for résumés, 50K for assessments)
  • JSON schema validation for structured output
  • Remaining: coach route already used real AI — no change needed

P1.2 — Client auth from server session

  • Added GET /api/auth/session endpoint validating the JWT cookie against the database
  • AuthProvider now validates session via server on mount instead of blindly trusting localStorage
  • localStorage kept as write-through cache with server re-validation on page load
  • Logout properly awaits the server call

P1.3 — ESLint fixes & rule re-enablement

  • Fixed 4 ESLint errors (impure render calls with Date.now(), unescaped entities)
  • Re-enabled high-value rules: no-unused-vars (warn), prefer-const (warn), no-debugger (warn), no-empty (warn), no-fallthrough (warn), no-unreachable (warn)
  • ESLint now runs with 0 errors, 47 warnings (all warnings, mostly pre-existing unused variables)
  • TypeScript check: 0 errors

Free companion cleanup

  • subscription-guard.ts — all guard functions return allowed: true
  • Subscription API endpoints deleted (5 route files)
  • Subscription test files deleted (3 files)
  • PricingPage, UpgradeModal, SubscriptionBanner stubbed as no-ops
  • use-subscription hook returns unlimited limits
  • README pricing table replaced with Free Companion notice
  • README tier-gated description corrected

Summary

Metric Before After
TypeScript errors 13 0
ESLint errors 9 0
ESLint warnings 0 47 (all pre-existing)
Subscription endpoints 5 0
AI routes with fake data 3 0
Files changed 28 files (+396 / -2175)

Summary by CodeRabbit

  • New Features
    • Added a server-backed authenticated session endpoint to restore user state reliably.
    • AI-generated assessment scoring, resume reviews, and cover letters now return strict structured output with input-size validation.
  • Documentation
    • Updated documentation to call out “Download Center” and “Admin Panel,” and to state the product is “Free, always.”
  • Bug Fixes
    • Improved login/registration/logout and cached authentication restoration behavior.
    • Re-enabled additional lint warnings to surface issues earlier.
  • Refactor/Changes
    • Simplified subscription handling and UI to a free-only experience, removing subscription management/webhook/status/usage endpoints and stubbing related pricing/upgrade components.

Documents the remaining 15 items across three priority phases
after the initial critical fixes in PR #4.
P1.1 — Server AI adapter:
- Replaced BrowserLLMIntegration in 3 AI routes (cover-letter,
  resume-review, assessment-score) with z-ai-web-dev-sdk
- Removed fake fallback data that fabricated experience claims
- Added input length limits and JSON schema validation

P1.2 — Client auth from server session:
- Added GET /api/auth/session endpoint (validates JWT cookie against DB)
- AuthProvider now validates session via server on mount instead of
  trusting localStorage blindly
- localStorage kept as write-through cache with server re-validation

P1.3 — ESLint fixes & rule re-enablement:
- Fixed 4 ESLint errors (impure render calls, unescaped entities)
- Re-enabled no-unused-vars, prefer-const, no-debugger, no-empty,
  no-fallthrough, no-unreachable
- Cleaned up unused imports in 5 modified files

Free companion cleanup:
- Subscription-guard returns allowed: true unconditionally
- All subscription API endpoints removed
- PricingPage, UpgradeModal, SubscriptionBanner stubbed as no-ops
- README pricing table replaced with Free Companion notice
@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
interview-lab Ready Ready Preview, Comment Jul 17, 2026 6:22am

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 74eb743f-8ae5-4090-9134-2bb2282a3ebf

📥 Commits

Reviewing files that changed from the base of the PR and between a1f9ad1 and 8699ee8.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • __tests__/lib/rate-limit.test.ts
  • __tests__/setup.ts
  • src/lib/session.ts

📝 Walkthrough

Walkthrough

The change migrates three AI routes to server-side ZAI calls, adds server-backed session restoration, removes tier-based subscription enforcement and UI, adjusts linting and auth behavior, and updates product documentation with a remediation plan.

Changes

Platform transition

Layer / File(s) Summary
Server AI route migration
src/app/api/ai/*
AI routes now use ZAI chat completions, bounded inputs, structured prompts, and JSON parsing.
Session restoration and auth timing
src/app/api/auth/session/route.ts, src/lib/auth-context.tsx, src/lib/session.ts, src/app/register/page.tsx, src/components/interview-lab/AuthScreen.tsx, __tests__/setup.ts, .github/workflows/ci.yml
Session verification uses runtime JWT configuration, auth restoration is server-first, cached-user storage is centralized, logout is awaited, and registration timing initialization is adjusted.
Free-access subscription surface
src/lib/subscription-guard.ts, src/lib/use-subscription.ts, src/components/interview-lab/{PricingPage,SubscriptionBanner,UpgradeModal}.tsx, src/app/api/subscription/*
Subscription checks allow access, the hook returns static free-tier data, pricing components become no-op stubs, and subscription routes are removed.
Lint, rate-limit, and route cleanup
eslint.config.mjs, src/app/api/auth/{login,register}/route.ts, src/app/api/{guides,questions}/route.ts, src/components/interview-lab/LandingPage.tsx, __tests__/lib/rate-limit.test.ts
Selected lint rules become warnings, unused bindings and imports are cleaned up, JSX text is escaped, and rate-limit database failures are tested as deny-by-default.
Product and remediation documentation
README.md, REMEDIATION_PLAN.md
README features and pricing now describe Download Center, Admin Panel, and free access; the remediation plan records completed work and phased remaining tasks.

Estimated code review effort: 5 (Critical) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant NextRoute
  participant ZAI
  Client->>NextRoute: Submit AI task data
  NextRoute->>ZAI: Send system and user messages
  ZAI-->>NextRoute: Return generated content
  NextRoute->>NextRoute: Extract and parse JSON
  NextRoute-->>Client: Return result or error
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the changes but omits the required Problem, Scope, Acceptance criteria, Validation, Risk and rollback, and Documentation sections. Add the missing template sections with problem, scope, acceptance criteria, concrete validation results, risk/rollback notes, and documentation updates.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main changes: server auth, ESLint cleanup, AI adapter, and free-tier cleanup.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/remediation-plan

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (1)
src/lib/auth-context.tsx (1)

48-51: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid fetching the profile twice during restoration.

setUser triggers the effect at Lines 81–85, while Line 51 performs the same request directly. Let the centralized user-change effect own this fetch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/auth-context.tsx` around lines 48 - 51, Remove the direct
fetchUserProfile call from the restoration branch that handles data.user in the
auth initialization flow. Keep setUser(data.user) and localStorage persistence
intact, allowing the user-change effect to perform the profile fetch exactly
once.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@REMEDIATION_PLAN.md`:
- Around line 35-67: The remediation plan’s P1.1–P1.3 status and implementation
details are outdated relative to this PR. Update the relevant Phase 1 sections
to mark completed work appropriately, describe migration of three AI routes to
z-ai-web-dev-sdk instead of four routes with OpenAI/Anthropic, update the
historical PR reference, and explicitly state whether BrowserLLMIntegration
remains for the retained coach flow.

In `@src/app/api/ai/assessment-score/route.ts`:
- Around line 55-72: Runtime-validate parsed AI output before exposing or
persisting it: in src/app/api/ai/assessment-score/route.ts lines 55-72, validate
every required AIAssessmentScore field and ensure score is numeric and
constrained to 0–100; in src/app/api/ai/resume-review/route.ts lines 58-75,
validate every required AIResumeReview field, including improvedVersion. Reject
invalid or incomplete responses through the existing failure path instead of
returning or persisting them.

In `@src/app/api/ai/cover-letter/route.ts`:
- Around line 55-75: Validate the parsed result in the cover-letter route before
returning it, requiring the fields consumed downstream, including draftLetter
and claimsToVerify, with the expected types. Treat missing or malformed data
like JSON parsing failure and return a non-2xx NextResponse error instead of the
current HTTP 200 fallback; preserve the successful return path only for
schema-valid results.

In `@src/app/api/ai/resume-review/route.ts`:
- Around line 8-17: Update the response schema in the resume-review route to be
valid JSON by removing the trailing comma, and add the required improvedVersion
field matching the AIResumeReview contract consumed by the API test and
ResumeLab. Preserve the existing fields and their expected types.

In `@src/app/api/auth/session/route.ts`:
- Around line 29-30: Update the catch block in the session route to return an
HTTP 500 response for database or infrastructure failures instead of returning
200 with { user: null }. Preserve the null-user response only for successfully
processed invalid sessions.

In `@src/components/interview-lab/AuthScreen.tsx`:
- Line 3: Use one consistent clock source for the registration timing flow in
AuthScreen: initialize the timing ref with the current value from the same clock
used when tab switching records timestamps, and calculate the submission elapsed
duration with that clock as well. Update the referenced timing logic near the
ref declaration, tab-switch handler, and submission guard without changing the
three-second rejection behavior.

In `@src/lib/auth-context.tsx`:
- Around line 58-70: The server-validation failure path in the auth
initialization flow must not call setUser with cached localStorage data. Remove
the localStorage fallback inside the catch block while preserving the existing
unauthenticated state until server validation succeeds.
- Around line 133-141: Update the logout function to capture the response from
the POST request and validate res.ok before clearing local state. Treat non-2xx
responses as logout failures and surface the server-side error instead of
reporting success or removing the local user/profile data; preserve the existing
best-effort handling for network failures as appropriate.
- Around line 116-118: Update the form initialization near the honeypot and
_formStart fields so bot detection no longer relies on a client-controlled
timestamp. Use a server-issued challenge or retain the form start time
server-side, and ensure isBot() validates the submission against that trusted
server value while preserving the existing timing check.

In `@src/lib/subscription-guard.ts`:
- Around line 10-45: Update the question response sanitization in the questions
API route so free users retain premium answer fields, matching the
always-allowed contract of checkQuestionBankAccess. Remove the hard-coded
starter/pro tier condition, or replace it with the result of
checkQuestionBankAccess, while preserving all other response behavior.

---

Nitpick comments:
In `@src/lib/auth-context.tsx`:
- Around line 48-51: Remove the direct fetchUserProfile call from the
restoration branch that handles data.user in the auth initialization flow. Keep
setUser(data.user) and localStorage persistence intact, allowing the user-change
effect to perform the profile fetch exactly once.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c379f79-66fe-4356-a1db-c71361cad1f9

📥 Commits

Reviewing files that changed from the base of the PR and between 556f8ff and a1f9ad1.

📒 Files selected for processing (28)
  • README.md
  • REMEDIATION_PLAN.md
  • __tests__/api/subscription-manage.test.ts
  • __tests__/api/subscription-webhook.test.ts
  • __tests__/api/subscription.test.ts
  • eslint.config.mjs
  • src/app/api/ai/assessment-score/route.ts
  • src/app/api/ai/cover-letter/route.ts
  • src/app/api/ai/resume-review/route.ts
  • src/app/api/auth/login/route.ts
  • src/app/api/auth/register/route.ts
  • src/app/api/auth/session/route.ts
  • src/app/api/guides/route.ts
  • src/app/api/questions/route.ts
  • src/app/api/subscription/checkout/route.ts
  • src/app/api/subscription/manage/route.ts
  • src/app/api/subscription/status/route.ts
  • src/app/api/subscription/usage/route.ts
  • src/app/api/subscription/webhook/route.ts
  • src/app/register/page.tsx
  • src/components/interview-lab/AuthScreen.tsx
  • src/components/interview-lab/LandingPage.tsx
  • src/components/interview-lab/PricingPage.tsx
  • src/components/interview-lab/SubscriptionBanner.tsx
  • src/components/interview-lab/UpgradeModal.tsx
  • src/lib/auth-context.tsx
  • src/lib/subscription-guard.ts
  • src/lib/use-subscription.ts
💤 Files with no reviewable changes (8)
  • src/app/api/subscription/webhook/route.ts
  • tests/api/subscription-webhook.test.ts
  • src/app/api/subscription/usage/route.ts
  • tests/api/subscription.test.ts
  • tests/api/subscription-manage.test.ts
  • src/app/api/subscription/status/route.ts
  • src/app/api/subscription/checkout/route.ts
  • src/app/api/subscription/manage/route.ts

Comment thread REMEDIATION_PLAN.md
Comment on lines +35 to +67
## 🔴 Phase 1 — Must fix before public launch

### P1.1 — Server AI adapter
**Files:** `src/lib/browser-llm-integration.ts`, `src/app/api/ai/*/route.ts` (4 routes)
**Problem:** The `BrowserLLMIntegration` module is marked `"use client"` and depends on `window.ai`. Server routes import it and silently fall back to rule-based templates that fabricate experience claims.
**Fix:**
- Create `src/lib/server-ai.ts` with:
- Schema-validated structured output (zod)
- Explicit provider configuration (OpenAI/Anthropic)
- Timeouts and abort handling
- Input length limits
- Per-user quota enforcement
- Truthfulness checks
- Replace all `BrowserLLMIntegration` imports in API routes
- Add privacy/provider disclosure to UI

### P1.2 — Client auth from server session
**Files:** `src/lib/auth-context.tsx`
**Problem:** Auth state restored from `localStorage` (modifiable); no server validation on startup.
**Fix:**
- Add `GET /api/auth/session` endpoint returning authenticated user from cookie
- On app mount, validate session via server endpoint instead of reading localStorage
- Keep localStorage as a cache layer with server re-validation
- Ensure logout clears both cookie and localStorage atomically

### P1.3 — ESLint fixes & re-enablement
**Files:** `eslint.config.mjs`, `src/app/page.tsx`, `src/components/interview-lab/AdminPanel.tsx`, `PricingPage.tsx`, `QuestionBank.tsx`
**Problem:** 35 rules disabled; 9 pre-existing ESLint errors block CI.
**Fix:**
- Fix the 9 ESLint errors across 4 files (setState in effects, hoisting, const reassignment)
- Re-enable important rules incrementally: `no-unused-vars`, `no-console`, `react-hooks/exhaustive-deps`, `no-fallthrough`
- Remove blanket `off` overrides
- Add `lint-staged` pre-commit hook

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Synchronize the remediation plan with this PR.

This PR implements Phase 1, but the document still presents P1.1–P1.3 as launch blockers. It also describes four AI routes and OpenAI/Anthropic integration, whereas this PR migrates three routes to z-ai-web-dev-sdk. Update the status, route/provider scope, historical PR reference, and whether BrowserLLMIntegration remains for the retained coach flow.

Also applies to: 167-180

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@REMEDIATION_PLAN.md` around lines 35 - 67, The remediation plan’s P1.1–P1.3
status and implementation details are outdated relative to this PR. Update the
relevant Phase 1 sections to mark completed work appropriately, describe
migration of three AI routes to z-ai-web-dev-sdk instead of four routes with
OpenAI/Anthropic, update the historical PR reference, and explicitly state
whether BrowserLLMIntegration remains for the retained coach flow.

Comment on lines +55 to 72
let result;
try {
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
if (jsonMatch) {
result = JSON.parse(jsonMatch[0]);
}
} catch {
result = null;
}

if (!result) {
return NextResponse.json(
{ error: 'Failed to score assessment' },
{ status: 500 }
);
}

return NextResponse.json(result);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Runtime-validate AI responses before returning them to consumers.

Both routes treat any parsed object as a valid domain response, allowing incomplete or incorrectly typed model output across the API boundary.

  • src/app/api/ai/assessment-score/route.ts#L55-L72: validate every required AIAssessmentScore field and constrain score to 0–100.
  • src/app/api/ai/resume-review/route.ts#L58-L75: validate every required AIResumeReview field, including improvedVersion, before persistence consumes it.
📍 Affects 2 files
  • src/app/api/ai/assessment-score/route.ts#L55-L72 (this comment)
  • src/app/api/ai/resume-review/route.ts#L58-L75
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/ai/assessment-score/route.ts` around lines 55 - 72,
Runtime-validate parsed AI output before exposing or persisting it: in
src/app/api/ai/assessment-score/route.ts lines 55-72, validate every required
AIAssessmentScore field and ensure score is numeric and constrained to 0–100; in
src/app/api/ai/resume-review/route.ts lines 58-75, validate every required
AIResumeReview field, including improvedVersion. Reject invalid or incomplete
responses through the existing failure path instead of returning or persisting
them.

Comment on lines +55 to 75
let result;
try {
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
if (jsonMatch) {
result = JSON.parse(jsonMatch[0]);
}
} catch {
result = null;
}

if (!result) {
return NextResponse.json({
draftLetter: 'Unable to generate cover letter. Please try again.',
shorterVersion: '',
subjectLine: '',
customizationTips: [],
claimsToVerify: [],
});
}

return NextResponse.json(result);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate the parsed AI response before returning it.

JSON.parse accepts objects missing draftLetter or claimsToVerify, while the downstream consumer immediately persists both. The fallback also returns HTTP 200, causing the failure message to be saved as a generated letter. Validate the schema and return a non-2xx error for invalid output.

Proposed validation
-    if (!result) {
-      return NextResponse.json({
-        draftLetter: 'Unable to generate cover letter. Please try again.',
-        shorterVersion: '',
-        subjectLine: '',
-        customizationTips: [],
-        claimsToVerify: [],
-      });
+    if (
+      !result ||
+      typeof result !== 'object' ||
+      typeof result.draftLetter !== 'string' ||
+      !Array.isArray(result.claimsToVerify) ||
+      !result.claimsToVerify.every((claim: unknown) => typeof claim === 'string')
+    ) {
+      return NextResponse.json(
+        { error: 'AI returned an invalid cover letter response' },
+        { status: 502 }
+      );
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let result;
try {
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
if (jsonMatch) {
result = JSON.parse(jsonMatch[0]);
}
} catch {
result = null;
}
if (!result) {
return NextResponse.json({
draftLetter: 'Unable to generate cover letter. Please try again.',
shorterVersion: '',
subjectLine: '',
customizationTips: [],
claimsToVerify: [],
});
}
return NextResponse.json(result);
let result;
try {
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
if (jsonMatch) {
result = JSON.parse(jsonMatch[0]);
}
} catch {
result = null;
}
if (
!result ||
typeof result !== 'object' ||
typeof result.draftLetter !== 'string' ||
!Array.isArray(result.claimsToVerify) ||
!result.claimsToVerify.every((claim: unknown) => typeof claim === 'string')
) {
return NextResponse.json(
{ error: 'AI returned an invalid cover letter response' },
{ status: 502 }
);
}
return NextResponse.json(result);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/ai/cover-letter/route.ts` around lines 55 - 75, Validate the
parsed result in the cover-letter route before returning it, requiring the
fields consumed downstream, including draftLetter and claimsToVerify, with the
expected types. Treat missing or malformed data like JSON parsing failure and
return a non-2xx NextResponse error instead of the current HTTP 200 fallback;
preserve the successful return path only for schema-valid results.

Comment on lines +8 to +17
Respond in the following JSON format only:
{
"score": <number 0-100>,
"missingKeywords": ["<keyword 1>", "<keyword 2>"],
"weakSections": ["<section 1>", "<section 2>"],
"improvedSummary": "<rewritten professional summary>",
"improvedBullets": ["<bullet 1>", "<bullet 2>"],
"skillsRecommendations": ["<skill 1>", "<skill 2>"],
"truthWarnings": ["<warning if user fabricated experience>"],
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fix the incomplete and invalid response schema.

The trailing comma makes the example invalid JSON, and improvedVersion is omitted even though AIResumeReview, the API test, and ResumeLab require it.

Proposed fix
   "skillsRecommendations": ["<skill 1>", "<skill 2>"],
-  "truthWarnings": ["<warning if user fabricated experience>"],
+  "truthWarnings": ["<warning if user fabricated experience>"],
+  "improvedVersion": "<complete revised resume>"
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Respond in the following JSON format only:
{
"score": <number 0-100>,
"missingKeywords": ["<keyword 1>", "<keyword 2>"],
"weakSections": ["<section 1>", "<section 2>"],
"improvedSummary": "<rewritten professional summary>",
"improvedBullets": ["<bullet 1>", "<bullet 2>"],
"skillsRecommendations": ["<skill 1>", "<skill 2>"],
"truthWarnings": ["<warning if user fabricated experience>"],
}
Respond in the following JSON format only:
{
"score": <number 0-100>,
"missingKeywords": ["<keyword 1>", "<keyword 2>"],
"weakSections": ["<section 1>", "<section 2>"],
"improvedSummary": "<rewritten professional summary>",
"improvedBullets": ["<bullet 1>", "<bullet 2>"],
"skillsRecommendations": ["<skill 1>", "<skill 2>"],
"truthWarnings": ["<warning if user fabricated experience>"],
"improvedVersion": "<complete revised resume>"
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/ai/resume-review/route.ts` around lines 8 - 17, Update the
response schema in the resume-review route to be valid JSON by removing the
trailing comma, and add the required improvedVersion field matching the
AIResumeReview contract consumed by the API test and ResumeLab. Preserve the
existing fields and their expected types.

Comment on lines +29 to +30
} catch {
return NextResponse.json({ user: null });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not report infrastructure failures as logged-out sessions.

A database failure currently returns 200 { user: null }, so session restoration treats a transient outage as invalid authentication. Reserve { user: null } for invalid sessions and return 500 here.

-  } catch {
-    return NextResponse.json({ user: null });
+  } catch (error) {
+    console.error('Session retrieval error:', error);
+    return NextResponse.json(
+      { error: 'Failed to retrieve session' },
+      { status: 500 }
+    );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch {
return NextResponse.json({ user: null });
} catch (error) {
console.error('Session retrieval error:', error);
return NextResponse.json(
{ error: 'Failed to retrieve session' },
{ status: 500 }
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/auth/session/route.ts` around lines 29 - 30, Update the catch
block in the session route to return an HTTP 500 response for database or
infrastructure failures instead of returning 200 with { user: null }. Preserve
the null-user response only for successfully processed invalid sessions.

"use client";

import React, { useState, useRef } from "react";
import React, { useState, useRef, useEffect } from "react";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Use the same initialized clock for registration timing.

The ref starts at 0, and tab switching stores performance.now(), but submission subtracts it from Date.now(). The resulting elapsed value is always far above three seconds, so the timing guard never rejects rapid submissions.

Proposed fix
 const formStartRef = useRef<number>(0);
-  // formStartRef remains at mount timestamp for bot protection
+useEffect(() => {
+  formStartRef.current = Date.now();
+}, []);

 const switchTab = (tab: "login" | "register") => {
   setActiveTab(tab);
   setError("");
   setLoading(false);
-  formStartRef.current = performance.now();
+  formStartRef.current = Date.now();
 };

Also applies to: 30-31, 70-70

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/interview-lab/AuthScreen.tsx` at line 3, Use one consistent
clock source for the registration timing flow in AuthScreen: initialize the
timing ref with the current value from the same clock used when tab switching
records timestamps, and calculate the submission elapsed duration with that
clock as well. Update the referenced timing logic near the ref declaration,
tab-switch handler, and submission guard without changing the three-second
rejection behavior.

Comment thread src/lib/auth-context.tsx
Comment on lines +58 to +70
} catch {
// Server unreachable — fall back to cached localStorage data as best effort
if (!cancelled) {
try {
const stored = localStorage.getItem(LS_KEY);
if (stored) {
const parsed = JSON.parse(stored);
setUser(parsed);
}
} catch {
// ignore corrupt storage
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not restore authenticated state from localStorage after server validation fails.

An outage or modified cache currently sets user without a validated session, rendering the client as authenticated while protected APIs reject it. Keep user unauthenticated until the server confirms the session.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/auth-context.tsx` around lines 58 - 70, The server-validation failure
path in the auth initialization flow must not call setUser with cached
localStorage data. Remove the localStorage fallback inside the catch block while
preserving the existing unauthenticated state until server validation succeeds.

Comment thread src/lib/auth-context.tsx
Comment on lines 116 to 118
honeypot: '',
_formStart: Date.now() - 10000, // Fake old timestamp (real form fills in current time)
_formStart: Date.now() - 10000,
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
ast-grep outline src/app/api/auth/register/route.ts --items all --view expanded
rg -n -C4 '\b(_formStart|honeypot)\b' src/app/api/auth/register/route.ts

Repository: projectamazonph/Interview-lab

Length of output: 1262


Do not trust _formStart from the client
isBot() treats the submitted timestamp as proof of form age, so an attacker can send any old value and skip the < 2s check. Use a server-issued challenge or server-side start time instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/auth-context.tsx` around lines 116 - 118, Update the form
initialization near the honeypot and _formStart fields so bot detection no
longer relies on a client-controlled timestamp. Use a server-issued challenge or
retain the form start time server-side, and ensure isBot() validates the
submission against that trusted server value while preserving the existing
timing check.

Comment thread src/lib/auth-context.tsx
Comment on lines +133 to +141
const logout = async () => {
try {
await fetch('/api/auth/logout', { method: 'POST' });
} catch {
// best effort — cookie will expire naturally
}
setUser(null);
setProfile(null);
localStorage.removeItem('interviewlab_user');
localStorage.removeItem(LS_KEY);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Handle non-2xx logout responses before reporting success.

fetch resolves normally for HTTP 500, after which local state is cleared while the session cookie remains valid. A reload silently authenticates the user again, which is especially unsafe on shared devices. Check res.ok and surface server-side logout failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/auth-context.tsx` around lines 133 - 141, Update the logout function
to capture the response from the POST request and validate res.ok before
clearing local state. Treat non-2xx responses as logout failures and surface the
server-side error instead of reporting success or removing the local
user/profile data; preserve the existing best-effort handling for network
failures as appropriate.

Comment on lines +10 to +45
// Interview Lab is a free companion to Project Amazon PH Academy.
// All features are available to all users — no tier gating.

export function checkInterviewAccess(userTier: string, interviewsThisWeek: number): SubscriptionCheck {
const tier = PRICING_TIERS[userTier as TierKey] ?? PRICING_TIERS.free;
const limit = tier.limits.interviewsPerWeek;
if (limit === -1) return { allowed: true };
if (interviewsThisWeek < limit) return { allowed: true, remaining: limit - interviewsThisWeek };
return {
allowed: false,
reason: `You've used all ${limit} interviews this week on the ${tier.name} plan.`,
remaining: 0,
upgradeTo: userTier === 'free' ? 'starter' : 'pro',
};
void userTier; void interviewsThisWeek;
return { allowed: true };
}

export function checkResumeAccess(userTier: string, reviewsThisMonth: number): SubscriptionCheck {
const tier = PRICING_TIERS[userTier as TierKey] ?? PRICING_TIERS.free;
const limit = tier.limits.resumeReviewsPerMonth;
if (limit === -1) return { allowed: true };
if (reviewsThisMonth < limit) return { allowed: true, remaining: limit - reviewsThisMonth };
return {
allowed: false,
reason: `You've used all ${limit} resume reviews this month on the ${tier.name} plan.`,
remaining: 0,
upgradeTo: 'starter',
};
void userTier; void reviewsThisMonth;
return { allowed: true };
}

export function checkCoverLetterAccess(userTier: string, lettersThisMonth: number): SubscriptionCheck {
const tier = PRICING_TIERS[userTier as TierKey] ?? PRICING_TIERS.free;
const limit = tier.limits.coverLettersPerMonth;
if (limit === -1) return { allowed: true };
if (lettersThisMonth < limit) return { allowed: true, remaining: limit - lettersThisMonth };
return {
allowed: false,
reason: `You've used all ${limit} cover letter generations this month on the ${tier.name} plan.`,
remaining: 0,
upgradeTo: 'starter',
};
void userTier; void lettersThisMonth;
return { allowed: true };
}

export function checkPracticeTestAccess(userTier: string, testsThisMonth: number): SubscriptionCheck {
const tier = PRICING_TIERS[userTier as TierKey] ?? PRICING_TIERS.free;
const limit = tier.limits.practiceTestsPerMonth;
if (limit === -1) return { allowed: true };
if (testsThisMonth < limit) return { allowed: true, remaining: limit - testsThisMonth };
return {
allowed: false,
reason: `You've used all ${limit} practice tests this month on the ${tier.name} plan.`,
remaining: 0,
upgradeTo: userTier === 'free' ? 'starter' : 'pro',
};
void userTier; void testsThisMonth;
return { allowed: true };
}

export function checkQuestionBankAccess(userTier: string, questionDifficulty: string): SubscriptionCheck {
const tier = PRICING_TIERS[userTier as TierKey] ?? PRICING_TIERS.free;
const accessLevel = tier.limits.questionBankAccess;
const hierarchy: Record<string, number> = { beginner: 0, intermediate: 1, advanced: 2 };
const userLevel = hierarchy[accessLevel] ?? 0;
const required = hierarchy[questionDifficulty] ?? 0;
if (userLevel >= required) return { allowed: true };
const upgradeTo: TierKey = accessLevel === 'beginner' ? 'starter' : 'pro';
return {
allowed: false,
reason: `${questionDifficulty} questions require the ${upgradeTo === 'starter' ? 'Starter' : 'Pro'} plan.`,
upgradeTo,
};
void userTier; void questionDifficulty;
return { allowed: true };
}

export function checkDownloadAccess(userTier: string, requiredTier: string): SubscriptionCheck {
if (canAccessTier(userTier, requiredTier)) return { allowed: true };
const upgradeTo: TierKey = requiredTier === 'starter' ? 'starter' : 'pro';
return {
allowed: false,
reason: `This download requires the ${requiredTier === 'starter' ? 'Starter' : 'Pro'} plan.`,
upgradeTo,
};
void userTier; void requiredTier;
return { allowed: true };
}

export function checkGuideAccess(userTier: string, guideLevel: string): SubscriptionCheck {
const tier = PRICING_TIERS[userTier as TierKey] ?? PRICING_TIERS.free;
const accessLevel = tier.limits.guideAccess;
const hierarchy: Record<string, number> = { beginner: 0, intermediate: 1, advanced: 2 };
const userLevel = hierarchy[accessLevel] ?? 0;
const required = hierarchy[guideLevel] ?? 0;
if (userLevel >= required) return { allowed: true };
const upgradeTo: TierKey = accessLevel === 'beginner' ? 'starter' : 'pro';
return {
allowed: false,
reason: `${guideLevel} guides require the ${upgradeTo === 'starter' ? 'Starter' : 'Pro'} plan.`,
upgradeTo,
};
void userTier; void guideLevel;
return { allowed: true };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Complete the free-access transition in downstream sanitizers.

These guards allow free-tier access, but src/app/api/questions/route.ts Lines 76-87 still removes premium answer fields unless the user tier is starter or pro. Free users therefore remain partially gated despite this contract. Remove the tier-based sanitizer or derive it from checkQuestionBankAccess.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/subscription-guard.ts` around lines 10 - 45, Update the question
response sanitization in the questions API route so free users retain premium
answer fields, matching the always-allowed contract of checkQuestionBankAccess.
Remove the hard-coded starter/pro tier condition, or replace it with the result
of checkQuestionBankAccess, while preserving all other response behavior.

- Make JWT secret validation lazy (not at module import time)
- Update rate-limit test to mock $transaction and expect fail-closed
- Set JWT_SECRET in vitest setup for all test environments
- Extend CI JWT_SECRET to 44 chars (was 28, below 32-char minimum)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant