Skip to content

feat: post-OAuth consent interstitial with evidentiary consent log - #36

Merged
Bonobo791 merged 9 commits into
mainfrom
feat-account-consent
Aug 1, 2026
Merged

feat: post-OAuth consent interstitial with evidentiary consent log#36
Bonobo791 merged 9 commits into
mainfrom
feat-account-consent

Conversation

@Bonobo791

Copy link
Copy Markdown
Owner

What

Account creation no longer happens silently in the Google OAuth callback. Login now parks the identity in an encrypted, 10-minute pending-consent cookie and redirects to /consent, where an unticked 18+ / ToS / Privacy / DPA checkbox (plus a separate, unbundled marketing opt-in) gates a single transaction that creates the user and writes an evidentiary consents row: userId, doc_version, the exact checkbox text shown, IP, user agent, marketing flag, timestamp.

Why

  • The contract forms at the checkbox, not the OAuth click — no account, service, or billing exists before acceptance (CDC Art. 46; YouTube API ToS requires PP agreement before features).
  • The 18+ self-declaration is the documented age gate (Google OAuth is not age verification), so it lives in the checkbox text.
  • LGPD requires marketing consent to be specific and unbundled — it is its own optional box.
  • consents is the evidence table for "I never agreed to that" disputes (CDC Art. 6º, VIII can shift burden of proof to us).
  • LEGAL_VERSION (src/lib/server/legal.ts) routes users whose consent predates a material doc change back through /consent on next login.

Notes

  • The /terms, /privacy, /dpa links resolve against the legal pages merged in Legal pages: Terms, Privacy Policy, DPA (EN) #35 — no merge-order dependency.
  • First-account orphan-channel claiming moved from the OAuth callback into the consent action (same first-user-only semantics; second-user steal test preserved).
  • CONSENT_CHECKBOX_TEXT and the visible sentence on the consent page must stay byte-identical; there is a source-level test pinning this.
  • AGENTS.md "Accounts & Sessions" and the execution plan (new section 5b-2) updated.
  • After merge: apply migration 0007 to prod Turso via npm run db:migrate from the main checkout.

Verification

  • npm run test: 225/225 (31 files), incl. 10 new consent tests + reworked login-callback tests
  • npm run check: 0 errors, 0 warnings
  • npm run build: green (adapter-netlify)

@cla-bot cla-bot Bot added the cla-signed label Aug 1, 2026
@codeant-ai

codeant-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Incremental review completed 5471d9e Aug 01, 2026 · 21:50 21:52
✅ Reviewed your PR 460e1e9 Aug 01, 2026 · 20:49 20:51

@netlify

netlify Bot commented Aug 1, 2026

Copy link
Copy Markdown

Deploy Preview for moderaty ready!

Name Link
🔨 Latest commit 68dcd3e
🔍 Latest deploy log https://app.netlify.com/projects/moderaty/deploys/6a6e6b9093f483000817cf67
😎 Deploy Preview https://deploy-preview-36--moderaty.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 88
Accessibility: 97
Best Practices: 100
SEO: 100
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Bonobo791, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 13 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0be16d01-4062-425e-a10d-55061b494a6a

📥 Commits

Reviewing files that changed from the base of the PR and between 3410533 and 68dcd3e.

📒 Files selected for processing (7)
  • AGENTS.md
  • EXECUTION_PLAN_YouTube_Comment_Moderator.md
  • src/lib/consentText.ts
  • src/lib/server/session.ts
  • src/routes/consent/+page.server.ts
  • src/routes/consent/+page.svelte
  • src/routes/consent/consent.test.ts
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added a consent step after Google sign-in and before account creation.
    • Required legal consent is separated from the optional marketing opt-in.
    • New accounts can claim existing orphaned channels after setup.
    • Existing accounts are prompted to reaccept consent when legal terms change.
    • Consent details are securely recorded for future reference.
  • Documentation

    • Updated account and implementation documentation to describe the consent and account-creation flow.

Walkthrough

The PR adds a consent interstitial to Google account creation. It stores encrypted pending identities, records legal consent evidence, supports legal-version re-consent and marketing opt-in, and moves orphan-channel claiming to completed account creation.

Changes

Consent-gated account creation

Layer / File(s) Summary
Consent storage and pending identity foundation
drizzle/*, src/lib/server/db/schema.ts, src/lib/server/testdb.ts, src/lib/server/legal.ts
Adds the consents table and migration metadata. Adds encrypted, expiring pending-consent cookie helpers and legal consent constants.
OAuth consent gate
src/routes/api/auth/google/login/callback/+server.ts, src/routes/api/auth/google/login/login.test.ts
Redirects new identities and users with outdated consent to /consent. Current-consent users proceed to session creation.
Consent page and account completion
src/routes/consent/*
Validates required consent, records consent evidence and marketing opt-in, creates or locates users, claims orphan channels during first account creation, and creates sessions.
Consent flow requirements and documentation
AGENTS.md, EXECUTION_PLAN_YouTube_Comment_Moderator.md
Documents the consent flow, legal-version re-consent, pending cookie, consent evidence, marketing opt-in, and deferred authorization.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Google
  participant OAuthCallback
  participant ConsentPage
  participant Database
  User->>Google: Complete Google sign-in
  Google->>OAuthCallback: Return OAuth identity
  OAuthCallback->>ConsentPage: Redirect with encrypted pending identity
  User->>ConsentPage: Submit required consent and optional marketing opt-in
  ConsentPage->>Database: Create or locate user and record consent
  Database-->>ConsentPage: Confirm account and consent
  ConsentPage-->>User: Create session and redirect to dashboard
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding a post-OAuth consent interstitial with evidentiary consent logging.
Description check ✅ Passed The description directly explains the consent flow, consent evidence logging, legal-version re-consent, and related implementation changes.
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 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-account-consent

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

@codeant-ai codeant-ai Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files label Aug 1, 2026
@codeant-ai

codeant-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown

User description

What

Account creation no longer happens silently in the Google OAuth callback. Login now parks the identity in an encrypted, 10-minute pending-consent cookie and redirects to /consent, where an unticked 18+ / ToS / Privacy / DPA checkbox (plus a separate, unbundled marketing opt-in) gates a single transaction that creates the user and writes an evidentiary consents row: userId, doc_version, the exact checkbox text shown, IP, user agent, marketing flag, timestamp.

Why

  • The contract forms at the checkbox, not the OAuth click — no account, service, or billing exists before acceptance (CDC Art. 46; YouTube API ToS requires PP agreement before features).
  • The 18+ self-declaration is the documented age gate (Google OAuth is not age verification), so it lives in the checkbox text.
  • LGPD requires marketing consent to be specific and unbundled — it is its own optional box.
  • consents is the evidence table for "I never agreed to that" disputes (CDC Art. 6º, VIII can shift burden of proof to us).
  • LEGAL_VERSION (src/lib/server/legal.ts) routes users whose consent predates a material doc change back through /consent on next login.

Notes

  • The /terms, /privacy, /dpa links resolve against the legal pages merged in Legal pages: Terms, Privacy Policy, DPA (EN) #35 — no merge-order dependency.
  • First-account orphan-channel claiming moved from the OAuth callback into the consent action (same first-user-only semantics; second-user steal test preserved).
  • CONSENT_CHECKBOX_TEXT and the visible sentence on the consent page must stay byte-identical; there is a source-level test pinning this.
  • AGENTS.md "Accounts & Sessions" and the execution plan (new section 5b-2) updated.
  • After merge: apply migration 0007 to prod Turso via npm run db:migrate from the main checkout.

Verification

  • npm run test: 225/225 (31 files), incl. 10 new consent tests + reworked login-callback tests
  • npm run check: 0 errors, 0 warnings
  • npm run build: green (adapter-netlify)

CodeAnt-AI Description

Require explicit consent before creating accounts through Google sign-in

What Changed

  • New Google users are sent to a consent page before an account or session is created
  • Account creation requires confirmation of the 18+ declaration and the Terms of Service, Privacy Policy, and Data Processing Agreement
  • Marketing updates are offered through a separate optional checkbox
  • Each acceptance records the document version, exact consent text, timestamp, IP address, user agent, and marketing choice
  • Existing users must accept again after the legal document version changes
  • Pending sign-ins expire after 10 minutes and invalid or missing sign-ins return to login

Impact

✅ No account creation before consent
✅ Clearer legal acceptance records
✅ Separate control over marketing emails

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@codacy-production

codacy-production Bot commented Aug 1, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 1 high

Alerts:
⚠ 1 issue (≤ 0 issues of at least high severity)

Results:
1 new issue

Category Results
ErrorProne 1 high

View in Codacy

🔴 Metrics 82 complexity · 6 duplication

Metric Results
Complexity 82 (≤ 100 complexity)
Duplication ⚠️ 6 (≤ 1 duplication)

View in Codacy

AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.

Run reviewer

TIP This summary will be updated as you push new changes.

@codeant-ai

codeant-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown

🏁 CodeAnt Quality Gate Results

Commit: 68dcd3e5
Scan Time: 2026-08-01 22:01:24 UTC

✅ Overall Status: PASSED

Quality Gate Details

Quality Gate Status Details
Secrets ✅ PASSED 0 secrets found
Duplicate Code ✅ PASSED 0.0% duplicated
SAST ✅ PASSED No security issues
Bugs ✅ PASSED Rating S: No bugs
IAC ✅ PASSED Rating S: No issues
Antipatterns ✅ PASSED No antipatterns

View Full Results

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Summary

This PR implements a post-OAuth consent interstitial for LGPD/CDC compliance with evidentiary consent logging. The approach is architecturally sound—contracts form at the checkbox, not at OAuth, and the pending-consent cookie pattern is well-designed.

Critical Issues Identified

I've identified 7 blocking issues that must be addressed before merge:

  1. Race condition in orphan channel claiming (lines 74-88 in consent/+page.server.ts)
  2. Input validation gaps on email/displayName fields from pending consent cookie
  3. IP address spoofing risk in getClientAddress() for evidentiary logs
  4. Missing empty string validation in readPendingConsent for email and displayName
  5. Potential XSS via displayName rendering (verify Svelte auto-escaping is active)
  6. Missing database index on (user_id, doc_version) for consent version checks

All issues involve security vulnerabilities or logic errors that could cause incorrect behavior or data integrity problems.

Test Coverage

The test suite is comprehensive (225 passing tests) and covers critical scenarios including concurrent signup, orphan claiming, and re-acceptance flows. However, the identified race condition and validation gaps need corresponding test coverage and fixes.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Comment thread src/routes/consent/+page.server.ts Outdated
Comment on lines +74 to +88
const count = await tx.select({ n: sql<number>`count(*)` }).from(users).get();
await tx
.insert(users)
.values({
id: randomBytes(16).toString('hex'),
googleSub: pending.sub,
email: pending.email,
displayName: pending.displayName
})
.onConflictDoNothing();
const user = await tx.select().from(users).where(eq(users.googleSub, pending.sub)).get();
if (!user) throw error(500, 'account creation failed — please retry');
if (count?.n === 0) {
await tx.update(channels).set({ userId: user.id }).where(isNull(channels.userId));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛑 Race Condition:

The orphan channel claim check on line 74 has a race condition. Multiple concurrent first-user registrations can each see count = 0, then all claim orphan channels. Move the count check inside a SELECT with a lock or use a stronger isolation mechanism.1

Footnotes

  1. CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition') - https://cwe.mitre.org/data/definitions/362.html

Comment on lines +77 to +82
.values({
id: randomBytes(16).toString('hex'),
googleSub: pending.sub,
email: pending.email,
displayName: pending.displayName
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛑 Security Vulnerability:

Missing validation on email and displayName fields from pending. An attacker who can forge or manipulate the pending consent cookie could inject arbitrary strings. Add validation to ensure email format and length constraints before inserting into the database.1

Footnotes

  1. CWE-20: Improper Input Validation - https://cwe.mitre.org/data/definitions/20.html

Comment thread src/routes/consent/+page.server.ts Outdated
userId: user.id,
docVersion: LEGAL_VERSION,
checkboxText: CONSENT_CHECKBOX_TEXT,
ip: getClientAddress(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛑 Security Vulnerability:

getClientAddress() returns IP from X-Forwarded-For by default which can be spoofed. For evidentiary consent records, validate you're behind a trusted proxy or sanitize this value to prevent injection of false IP addresses into the legal consent log.1

Footnotes

  1. CWE-807: Reliance on Untrusted Inputs in a Security Decision - https://cwe.mitre.org/data/definitions/807.html

Comment thread src/lib/server/legal.ts Outdated
Comment on lines +94 to +99
if (
parsed.kind === 'new' &&
typeof parsed.sub === 'string' &&
parsed.sub &&
typeof parsed.email === 'string' &&
typeof parsed.displayName === 'string'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛑 Logic Error:

Missing check for empty string validation on email field. The validation at line 98 only checks typeof parsed.email === 'string' but doesn't verify the email is non-empty. An empty email string would pass validation but likely cause issues downstream. Add && parsed.email to line 98.

Suggested change
if (
parsed.kind === 'new' &&
typeof parsed.sub === 'string' &&
parsed.sub &&
typeof parsed.email === 'string' &&
typeof parsed.displayName === 'string'
parsed.kind === 'new' &&
typeof parsed.sub === 'string' &&
parsed.sub &&
typeof parsed.email === 'string' &&
parsed.email &&
typeof parsed.displayName === 'string'

Comment thread src/lib/server/legal.ts Outdated
Comment on lines +94 to +99
if (
parsed.kind === 'new' &&
typeof parsed.sub === 'string' &&
parsed.sub &&
typeof parsed.email === 'string' &&
typeof parsed.displayName === 'string'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛑 Logic Error:

Missing validation for empty string on displayName field. Add && parsed.displayName to line 99 to prevent empty displayName values from passing validation, consistent with how sub and email are validated.

Suggested change
if (
parsed.kind === 'new' &&
typeof parsed.sub === 'string' &&
parsed.sub &&
typeof parsed.email === 'string' &&
typeof parsed.displayName === 'string'
parsed.kind === 'new' &&
typeof parsed.sub === 'string' &&
parsed.sub &&
typeof parsed.email === 'string' &&
typeof parsed.displayName === 'string' &&
parsed.displayName

<main class="consent-main">
<div class="card consent-card">
{#if data.kind === 'new'}
<h1>Almost there{data.displayName ? `, ${data.displayName}` : ''}</h1>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛑 Security Vulnerability:

Potential XSS vulnerability. The data.displayName at line 32 is rendered without escaping in the template. If displayName contains HTML/script tags, it could execute in the browser context. Svelte normally auto-escapes, but verify this is properly sanitized since it originates from Google OAuth and is user-controlled.1

Footnotes

  1. CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') - https://cwe.mitre.org/data/definitions/79.html

Comment on lines +19 to +31
CREATE TABLE `consents` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`doc_version` text NOT NULL,
`checkbox_text` text NOT NULL,
`ip` text NOT NULL,
`user_agent` text NOT NULL,
`marketing_opt_in` integer DEFAULT 0 NOT NULL,
`created_at` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE INDEX `consents_user_id_idx` ON `consents` (`user_id`); No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛑 Logic Error:

Missing database index on (user_id, doc_version) for the consent version check query at lines 88-92 in callback/+server.ts. The query filters by both userId and docVersion, but only user_id is indexed. Add a composite index to prevent full table scans on the consents table as it grows.

Suggested change
CREATE TABLE `consents` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`doc_version` text NOT NULL,
`checkbox_text` text NOT NULL,
`ip` text NOT NULL,
`user_agent` text NOT NULL,
`marketing_opt_in` integer DEFAULT 0 NOT NULL,
`created_at` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE INDEX `consents_user_id_idx` ON `consents` (`user_id`);
CREATE TABLE `consents` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`doc_version` text NOT NULL,
`checkbox_text` text NOT NULL,
`ip` text NOT NULL,
`user_agent` text NOT NULL,
`marketing_opt_in` integer DEFAULT 0 NOT NULL,
`created_at` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE INDEX `consents_user_id_idx` ON `consents` (`user_id`);
--> statement-breakpoint
CREATE INDEX `consents_user_id_doc_version_idx` ON `consents` (`user_id`, `doc_version`);

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Post-OAuth consent interstitial with evidentiary consent log

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Redirect post-Google sign-in to /consent before account creation or session issuance.
• Log immutable consent evidence (doc version, text, IP/UA, marketing opt-in) per acceptance.
• Add LEGAL_VERSION gating to force re-acceptance after material legal document changes.
Diagram

sequenceDiagram
  participant B as "Browser"
  participant CB as "OAuth callback"
  participant L as "legal.ts helpers"
  participant C as "/consent"
  participant DB as "DB (users/consents)"
  participant S as "session.ts"

  B->>CB: "GET /api/auth/google/login/callback"
  CB->>DB: "Lookup user + consent for LEGAL_VERSION"
  alt "New user OR stale/missing consent"
    CB->>L: "parkPendingConsent() -> encrypted httpOnly cookie"
    CB-->>B: "302 Redirect /consent"
    B->>C: "GET /consent"
    C->>L: "readPendingConsent()"
    B->>C: "POST accept consent (+ optional marketing)"
    C->>DB: "Create user (if new) + insert consent row"
    C->>S: "createSession()"
    C-->>B: "Set session cookie; 302 /dashboard"
  else "Existing user with current consent"
    CB->>S: "createSession()"
    CB-->>B: "Set session cookie; 302 /dashboard"
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Server-side pending-consent store (DB table) instead of encrypted cookie
  • ➕ Revocable/observable pending state (debuggable, auditable, can invalidate centrally)
  • ➕ No dependence on cookie payload size; can store more context safely
  • ➕ Easier to support longer TTLs and multi-step onboarding
  • ➖ Extra DB writes/cleanup for abandoned sign-ins (GC job / TTL cleanup)
  • ➖ More moving parts and failure modes during login peak load
  • ➖ Adds schema and operational complexity compared to stateless cookie
2. Create a provisional user record at OAuth callback (status=pending_consent)
  • ➕ Simplifies later steps (userId exists immediately; fewer branches)
  • ➕ Can attach orphan-channel claim and other one-time init early
  • ➖ Violates the stated requirement: account exists before consent forms
  • ➖ Creates liability/cleanup concerns for unconsented accounts
  • ➖ Harder to argue no service/billing existed pre-acceptance

Recommendation: Keep the current approach: stateless encrypted pending-consent cookie + /consent-gated transaction is the cleanest match for “no account before acceptance” while staying operationally simple. If future requirements include longer-lived onboarding, explicit admin revocation, or richer pre-consent state, revisit a server-side pending store as the next step.

Files changed (13) +1331 / -74

Enhancement (5) +371 / -27
schema.tsAdd consents table to application DB schema +18/-0

Add consents table to application DB schema

• Defines the consents sqliteTable with immutable evidentiary fields and a user_id index. Establishes cascade delete behavior via foreign key reference to users.

src/lib/server/db/schema.ts

legal.tsAdd legal constants and encrypted pending-consent cookie helpers +109/-0

Add legal constants and encrypted pending-consent cookie helpers

• Introduces LEGAL_VERSION and the pinned checkbox text constants. Implements AES-GCM-protected cookie helpers to park/read/clear pending consent payloads with a 10-minute TTL.

src/lib/server/legal.ts

+server.tsGate session creation on consent; redirect to /consent when needed +24/-27

Gate session creation on consent; redirect to /consent when needed

• Stops creating accounts in the OAuth callback. For new users or users missing current-version consent, stores a pending-consent cookie and redirects to /consent; otherwise creates a session immediately.

src/routes/api/auth/google/login/callback/+server.ts

+page.server.tsImplement /consent load + action to create user, log consent, and start session +125/-0

Implement /consent load + action to create user, log consent, and start session

• Adds server-side load that requires a valid pending-consent cookie and provides display text. Adds a single action that validates required consent, optionally records marketing opt-in, writes a consents row, creates the user (if new) in a transaction, then issues a session and clears the pending cookie.

src/routes/consent/+page.server.ts

+page.svelteAdd consent interstitial UI with required and optional checkboxes +95/-0

Add consent interstitial UI with required and optional checkboxes

• Creates the consent page UI with an unticked required 18+/ToS/Privacy/DPA checkbox and a separate marketing opt-in checkbox. Includes linked legal documents and supports error display for action failures.

src/routes/consent/+page.svelte

Tests (3) +259 / -43
testdb.tsExtend test DB bootstrap with consents table +10/-0

Extend test DB bootstrap with consents table

• Adds a consents table definition to the in-memory test database setup so consent logging can be tested end-to-end.

src/lib/server/testdb.ts

login.test.tsRework OAuth callback tests for pending consent and LEGAL_VERSION gating +51/-43

Rework OAuth callback tests for pending consent and LEGAL_VERSION gating

• Updates login tests to seed consented users and assert new identities are not persisted during callback. Adds coverage for stale consent re-routing and ensures sessions are only created when consent is current.

src/routes/api/auth/google/login/login.test.ts

consent.test.tsAdd end-to-end consent tests including text pinning and evidence fields +198/-0

Add end-to-end consent tests including text pinning and evidence fields

• Adds tests for cookie tamper/expiry behavior, required checkbox validation, new-user creation at acceptance, orphan-channel claim semantics, existing-user re-acceptance, and marketing opt-in recording. Pins the visible consent sentence and legal links to avoid drift from CONSENT_CHECKBOX_TEXT.

src/routes/consent/consent.test.ts

Documentation (2) +51 / -4
AGENTS.mdDocument consent interstitial as the only account creation path +17/-4

Document consent interstitial as the only account creation path

• Updates the accounts/sessions guidance to state that OAuth callback no longer creates users. Documents the pending-consent cookie, evidentiary consents row requirements, and LEGAL_VERSION re-consent behavior.

AGENTS.md

EXECUTION_PLAN_YouTube_Comment_Moderator.mdAdd execution-plan section for the legal consent interstitial +34/-0

Add execution-plan section for the legal consent interstitial

• Adds a dedicated section describing the post-OAuth consent flow, required/optional checkboxes, consent evidence logging, and re-acceptance on LEGAL_VERSION bumps.

EXECUTION_PLAN_YouTube_Comment_Moderator.md

Other (3) +650 / -0
0007_curved_blade.sqlIntroduce consents table migration for evidentiary logging +31/-0

Introduce consents table migration for evidentiary logging

• Adds migration 0007 creating the consents table with doc version, checkbox text, IP/UA, marketing opt-in, and timestamp. Includes a user_id index and cascades on user deletion.

drizzle/0007_curved_blade.sql

0007_snapshot.jsonRecord Drizzle snapshot including new consents table +612/-0

Record Drizzle snapshot including new consents table

• Updates Drizzle metadata snapshot to include the new consents table schema, index, and foreign key relationship to users.

drizzle/meta/0007_snapshot.json

_journal.jsonRegister migration 0007 in Drizzle journal +7/-0

Register migration 0007 in Drizzle journal

• Adds journal entry for the new 0007_curved_blade migration so it is tracked/applied in order.

drizzle/meta/_journal.json

@codeant-ai

codeant-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown

PR Code Suggestions ✨

Previous suggestions up to commit 460e1e9
CategorySuggestion                                                                                                                                    SeverityGenerated at (UTC)
Race condition
Concurrent OAuth callbacks can replace one tab's pending identity with another tab's identity

The single pending-consent cookie is overwritten whenever two OAuth callbacks
complete before either consent form is submitted. Because OAuth states support
multiple concurrent tabs, the first tab can submit consent for the identity parked
by the second callback, creating or authenticating the wrong account. Bind the
pending payload to the OAuth state or maintain one pending-consent entry per state.

src/routes/api/auth/google/login/callback/+server.ts [82-86]

Why it matters? 🤔
  • ❌ Concurrent Google tabs can consent for the wrong account.
  • ❌ New-account creation can use another tab’s identity.
  • ⚠️ Evidentiary consent rows can contain mismatched user context.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/routes/api/auth/google/login/callback/+server.ts
**Line:** 82:86
**Comment:**
	*Race Condition: The single pending-consent cookie is overwritten whenever two OAuth callbacks complete before either consent form is submitted. Because OAuth states support multiple concurrent tabs, the first tab can submit consent for the identity parked by the second callback, creating or authenticating the wrong account. Bind the pending payload to the OAuth state or maintain one pending-consent entry per state.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
Major2026-08-01 20:51
Incomplete implementation
Session creation can fail after consent is committed, leaving a replayable pending signup and partial account state

Account and consent persistence completes before createSession runs. If session
cleanup or insertion fails, the pending-consent cookie remains valid while the user
and consent row are already committed; retrying the form can create additional
consent records and leaves the signup partially completed. Keep session creation in
the same transaction or implement a rollback/idempotent recovery path.

src/routes/consent/+page.server.ts [100-114]

Why it matters? 🤔
  • ⚠️ Transient database failures leave pending consent cookies active.
  • ⚠️ Retried signup submissions create duplicate consent evidence rows.
  • ❌ Users cannot reach /dashboard until session creation succeeds.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/routes/consent/+page.server.ts
**Line:** 100:114
**Comment:**
	*Incomplete Implementation: Account and consent persistence completes before `createSession` runs. If session cleanup or insertion fails, the pending-consent cookie remains valid while the user and consent row are already committed; retrying the form can create additional consent records and leaves the signup partially completed. Keep session creation in the same transaction or implement a rollback/idempotent recovery path.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
Major2026-08-01 20:51

Latest suggestions up to commit 5471d9e
CategorySuggestion                                                                                                                                    SeverityGenerated at (UTC)
Race condition
Concurrent cookie updates can overwrite each other and lose an unrelated consent flow

Concurrent OAuth callbacks or consent submissions perform a read-modify-write on the
same cookie. If both requests read the same old entries, each writes its own
appended/removed list and the last response overwrites the other flow, losing a
pending identity despite the state key. Use a server-side store or otherwise
coordinate updates rather than relying on competing Set-Cookie headers.

src/lib/server/legal.ts [107-111]

Why it matters? 🤔
  • ⚠️ Concurrent Google tabs can lose one pending identity.
  • ❌ Lost flows redirect users back to /login.
  • ⚠️ No account or consent row is created for the lost flow.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/lib/server/legal.ts
**Line:** 107:111
**Comment:**
	*Race Condition: Concurrent OAuth callbacks or consent submissions perform a read-modify-write on the same cookie. If both requests read the same old entries, each writes its own appended/removed list and the last response overwrites the other flow, losing a pending identity despite the state key. Use a server-side store or otherwise coordinate updates rather than relying on competing Set-Cookie headers.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
Major2026-08-01 21:52
Concurrent submissions can replay one pending consent flow and create duplicate consent records and sessions

The pending consent entry is read and validated, but it is not claimed or removed
until after all database work and session creation. Two concurrent POST requests
using the same cookie can both pass this check, insert duplicate consent records,
and create separate sessions before either response clears the cookie. Atomically
consume or claim the flow before processing, with a server-side idempotency guard
for the state.

src/routes/consent/+page.server.ts [115-123]

Why it matters? 🤔
  • ⚠️ Duplicate evidentiary consent rows can be recorded.
  • ⚠️ Repeated submissions create unnecessary active sessions.
  • ⚠️ Concurrent account creation can duplicate acceptance events.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/routes/consent/+page.server.ts
**Line:** 115:123
**Comment:**
	*Race Condition: The pending consent entry is read and validated, but it is not claimed or removed until after all database work and session creation. Two concurrent POST requests using the same cookie can both pass this check, insert duplicate consent records, and create separate sessions before either response clears the cookie. Atomically consume or claim the flow before processing, with a server-side idempotency guard for the state.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
Major2026-08-01 21:52

@qodo-code-review

qodo-code-review Bot commented Aug 1, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 56 rules

Grey Divider


Action required

1. Consent cookie tab collision ✓ Resolved 🐞 Bug ≡ Correctness
Description
The pending-consent flow stores exactly one moderaty_consent_pending cookie for all in-progress
consents, so a second OAuth callback in another tab can overwrite the first tab’s parked identity
and cause the first tab’s consent submission to create/consent the wrong identity/user. This
reintroduces the same multi-tab collision class you already solved for OAuth state, but now for
the identity payload itself.
Code

src/lib/server/legal.ts[R52-73]

+export const PENDING_CONSENT_COOKIE = 'moderaty_consent_pending';
+const PENDING_TTL_MS = 10 * 60 * 1000;
+
+/** Identity parked between the OAuth callback and the consent interstitial. */
+export type PendingConsent =
+	| { kind: 'new'; sub: string; email: string; displayName: string }
+	| { kind: 'existing'; userId: string };
+
+/**
+ * Parks a Google-verified identity in a short-lived encrypted httpOnly cookie.
+ * AES-GCM makes the payload tamper-proof and confidential, so account
+ * creation claims cannot be forged client-side.
+ */
+export function parkPendingConsent(cookies: Cookies, payload: PendingConsent): void {
+	const wrapped = JSON.stringify({ ...payload, ts: Date.now() });
+	cookies.set(PENDING_CONSENT_COOKIE, encrypt(wrapped), {
+		path: '/',
+		httpOnly: true,
+		sameSite: 'lax',
+		secure: cookieSecure(),
+		maxAge: PENDING_TTL_MS / 1000
+	});
Relevance

●●● Strong

Team previously accepted fixing OAuth multi-tab collisions by supporting multiple outstanding states
(PR #4). Same collision class.

PR-#4

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
parkPendingConsent always writes a single fixed cookie name, while the login callback redirects to
/consent without passing any per-flow identifier; /consent then reads only that one cookie. This
makes concurrent flows overwrite each other and breaks correctness in multi-tab scenarios (a
previously observed class of bug in this codebase).

src/lib/server/legal.ts[52-73]
src/routes/api/auth/google/login/callback/+server.ts[77-97]
src/routes/consent/+page.server.ts[39-43]
PR-#4

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The app parks pending consent in a single global cookie (`moderaty_consent_pending`). If the user initiates multiple OAuth+consent flows concurrently (multiple tabs, multiple Google accounts), the later callback overwrites the earlier parked identity. When the user submits the consent form in the first tab, the server will read the overwritten cookie and may create/consent the wrong account.

### Issue Context
OAuth `state` was hardened to allow multiple concurrent values, but the pending-consent payload is not similarly scoped.

### Fix Focus Areas
- src/lib/server/legal.ts[52-104]
- src/routes/api/auth/google/login/callback/+server.ts[77-97]
- src/routes/consent/+page.server.ts[39-55]

### Implementation direction
- Bind the pending-consent payload to a unique per-flow identifier (ideally the OAuth `state`):
 - Include `state` in the redirect to `/consent` (e.g., `/consent?state=...`).
 - Store the pending payload keyed by that state (either:
   - separate cookies like `moderaty_consent_pending_<state>`, or
   - a single cookie holding a bounded map/list of `{state, payload}` entries).
 - On `/consent` load/action, read only the payload for the provided `state` and delete only that entry.
- Add tests covering two simultaneous pending consents (two states) to ensure each tab reads/consents the intended identity.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. consents read unguarded 📘 Rule violation ≡ Correctness
Description
The login callback now queries the new consents table without any fallback if the migration has
not been applied, which can break sign-in with a DB error in partially migrated deployments. The
repo scripts also don’t guarantee db:migrate runs before serving traffic, so this read path should
be guarded or deployment should enforce migrations first.
Code

src/routes/api/auth/google/login/callback/+server.ts[R88-92]

+	const consent = await db
+		.select({ id: consents.id })
+		.from(consents)
+		.where(and(eq(consents.userId, user.id), eq(consents.docVersion, LEGAL_VERSION)))
+		.get();
Relevance

●●● Strong

Team has accepted changes preventing missing-schema runtime errors via migrations/tests (PR #3);
likely to guard new-table reads too.

PR-#3

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2407776 requires guarding application reads of newly added DB schema unless
migrations are guaranteed to run before serving traffic. The login callback performs a read from
consents (new in migration 0007), and package.json does not provide any start script that runs
db:migrate first, so this path can fail if deployed before/without applying migrations.

Rule 2407776: Guard reads of newly added database columns until after migrations are applied
src/routes/api/auth/google/login/callback/+server.ts[88-92]
drizzle/0007_curved_blade.sql[19-31]
package.json[10-18]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`src/routes/api/auth/google/login/callback/+server.ts` reads from the newly introduced `consents` table with no guard for the case where migration `0007` hasn’t been applied yet. This can cause runtime failures (e.g., `no such table: consents`) during login.

## Issue Context
The PR adds the `consents` table via `drizzle/0007_curved_blade.sql`, but there is no startup script that ensures migrations run before the app handles requests.

## Fix Focus Areas
- src/routes/api/auth/google/login/callback/+server.ts[88-96]
- package.json[10-18]
- drizzle/0007_curved_blade.sql[19-31]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. LEGAL_VERSION mismatch ✓ Resolved 🐞 Bug ≡ Correctness
Description
The server-side consent gating/logging uses LEGAL_VERSION = 'v1.0', while the legal documents
shown to users use LEGAL_VERSION = '1.0', so the consent evidence version can diverge from the
document bundle’s displayed version. This makes audits/disputes ambiguous and increases the chance
of future version bumps being applied inconsistently.
Code

src/lib/server/legal.ts[34]

+export const LEGAL_VERSION = 'v1.0';
Relevance

●● Moderate

No prior reviews on LEGAL_VERSION string format; team cares about schema/version drift (PR #3) but
no direct precedent.

PR-#3

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR introduces a new server-side LEGAL_VERSION with a different literal value than the existing
legal-docs LEGAL_VERSION, so the version recorded/validated for consent can differ from the
version presented in the legal documents.

src/lib/server/legal.ts[29-35]
src/lib/landing/legal.ts[36-46]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
There are now two different `LEGAL_VERSION` constants:
- `src/lib/server/legal.ts` uses `'v1.0'` for consent gating/logging.
- `src/lib/landing/legal.ts` uses `'1.0'` for the rendered legal documents.

This can cause the recorded consent `doc_version` to not match the version shown on /terms,/privacy,/dpa and invites inconsistent future bumps.

### Issue Context
Consent acceptance stores `docVersion: LEGAL_VERSION` in `consents`, and login callback checks for an exact match against server `LEGAL_VERSION`.

### Fix Focus Areas
- src/lib/server/legal.ts[29-35]
- src/lib/landing/legal.ts[36-47]

### Implementation direction
- Make a single shared source of truth for the legal bundle version (e.g., move the constant to a shared module imported by both server and landing code), or at minimum ensure both constants are identical and documented as such.
- Consider naming to avoid dual exports with the same identifier in different modules (reduces accidental drift).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Consent text not single-sourced ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
The evidentiary checkbox_text is logged from CONSENT_CHECKBOX_TEXT, but the consent page renders
a separately maintained copy of that wording in markup with only a substring-based test, so future
edits can change what users see without updating what gets logged. This undermines the intended
“exact text shown” evidence guarantee even if the wording is currently equivalent.
Code

src/routes/consent/+page.svelte[R43-54]

+		<form method="POST">
+			<!-- The visible sentence must stay byte-identical to CONSENT_CHECKBOX_TEXT
+			     in src/lib/server/legal.ts — that constant is what the consent log
+			     stores as "the exact text shown". Links don't alter the wording. -->
+			<label class="check">
+				<input type="checkbox" name="consent" />
+				<span>
+					I am at least 18 years old and agree to the
+					<a href="/terms" target="_blank" rel="noopener">Terms of Service</a>,
+					<a href="/privacy" target="_blank" rel="noopener">Privacy Policy</a>, and
+					<a href="/dpa" target="_blank" rel="noopener">Data Processing Agreement</a>
+				</span>
Relevance

●● Moderate

Some precedent for strengthening “exact wording” guard tests (PR #33 partially accepted), but no
direct consent-text single-source history.

PR-#33

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The server’s logged checkbox text comes from a constant, but the UI uses a separate hardcoded
sentence; the current test only asserts a substring exists, so it does not prevent divergence
between what is logged and what is displayed.

src/lib/server/legal.ts[36-44]
src/routes/consent/+page.svelte[43-54]
src/routes/consent/consent.test.ts[81-90]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The consent checkbox wording is duplicated:
- The server logs `checkboxText: CONSENT_CHECKBOX_TEXT`.
- The Svelte page manually contains the consent sentence.
The added test only checks for a substring, so drift can pass CI and produce consent rows that don’t match the rendered wording.

### Issue Context
Because the visible sentence includes links, strict byte-for-byte source equality is hard to enforce unless the UI is generated from shared structured data.

### Fix Focus Areas
- src/lib/server/legal.ts[36-44]
- src/routes/consent/+page.svelte[43-54]
- src/routes/consent/consent.test.ts[81-90]

### Implementation direction
- Prefer a single source of truth for the displayed consent text:
 - Option A: define a structured representation in `legal.ts` (e.g., array of text/link segments) and render it in the page, and separately derive the logged plain-text version from the same structure.
 - Option B: enhance the test to reconstruct/normalize the rendered text (e.g., strip tags/normalize whitespace from `+page.svelte` or use a small HTML parser) and compare it to `CONSENT_CHECKBOX_TEXT` exactly.
- Keep the evidence text (`CONSENT_CHECKBOX_TEXT`) and the displayed text synchronized via code, not comments.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment on lines +88 to +92
const consent = await db
.select({ id: consents.id })
.from(consents)
.where(and(eq(consents.userId, user.id), eq(consents.docVersion, LEGAL_VERSION)))
.get();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. consents read unguarded 📘 Rule violation ≡ Correctness

The login callback now queries the new consents table without any fallback if the migration has
not been applied, which can break sign-in with a DB error in partially migrated deployments. The
repo scripts also don’t guarantee db:migrate runs before serving traffic, so this read path should
be guarded or deployment should enforce migrations first.
Agent Prompt
## Issue description
`src/routes/api/auth/google/login/callback/+server.ts` reads from the newly introduced `consents` table with no guard for the case where migration `0007` hasn’t been applied yet. This can cause runtime failures (e.g., `no such table: consents`) during login.

## Issue Context
The PR adds the `consents` table via `drizzle/0007_curved_blade.sql`, but there is no startup script that ensures migrations run before the app handles requests.

## Fix Focus Areas
- src/routes/api/auth/google/login/callback/+server.ts[88-96]
- package.json[10-18]
- drizzle/0007_curved_blade.sql[19-31]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/lib/server/legal.ts
Comment thread src/lib/server/legal.ts Outdated
Comment thread src/routes/consent/+page.svelte Outdated
…g-identity fields, single LEGAL_VERSION source

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 3

🤖 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 `@AGENTS.md`:
- Around line 154-155: Update the orphan-channel ownership wording in
EXECUTION_PLAN_YouTube_Comment_Moderator.md to state that orphan channels are
claimed by the first user to complete account creation, replacing the outdated
“first user to sign in” wording.
- Around line 161-166: Correct the consent requirement wording in AGENTS.md
lines 161-166 and EXECUTION_PLAN_YouTube_Comment_Moderator.md lines 1916-1921:
replace “unticked” with “checked” or “ticked” so both documents state that
account creation requires the 18+/ToS/PP/DPA checkbox to be selected.

In `@src/routes/consent/`+page.svelte:
- Around line 44-59: Eliminate the duplicate consent wording source by deriving
the visible sentence in the consent page template from CONSENT_CHECKBOX_TEXT,
preserving the three document links between the corresponding text segments;
update src/routes/consent/+page.svelte lines 44-59 accordingly. Strengthen the
consent page test at src/routes/consent/consent.test.ts lines 81-91 to compare
the rendered visible text, with markup removed, exactly against
CONSENT_CHECKBOX_TEXT instead of using a partial containment check.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: abf1f910-2c5e-416a-a43a-34321bba0710

📥 Commits

Reviewing files that changed from the base of the PR and between c750637 and 0a2aa84.

📒 Files selected for processing (13)
  • AGENTS.md
  • EXECUTION_PLAN_YouTube_Comment_Moderator.md
  • drizzle/0007_curved_blade.sql
  • drizzle/meta/0007_snapshot.json
  • drizzle/meta/_journal.json
  • src/lib/server/db/schema.ts
  • src/lib/server/legal.ts
  • src/lib/server/testdb.ts
  • src/routes/api/auth/google/login/callback/+server.ts
  • src/routes/api/auth/google/login/login.test.ts
  • src/routes/consent/+page.server.ts
  • src/routes/consent/+page.svelte
  • src/routes/consent/consent.test.ts

Comment thread AGENTS.md
Comment thread AGENTS.md
Comment thread src/routes/consent/+page.svelte Outdated
@codeant-ai codeant-ai Bot added size:XXL This PR changes 1000+ lines, ignoring generated files and removed size:XXL This PR changes 1000+ lines, ignoring generated files labels Aug 1, 2026
@codeant-ai

codeant-ai Bot commented Aug 1, 2026

Copy link
Copy Markdown

User description

What

Account creation no longer happens silently in the Google OAuth callback. Login now parks the identity in an encrypted, 10-minute pending-consent cookie and redirects to /consent, where an unticked 18+ / ToS / Privacy / DPA checkbox (plus a separate, unbundled marketing opt-in) gates a single transaction that creates the user and writes an evidentiary consents row: userId, doc_version, the exact checkbox text shown, IP, user agent, marketing flag, timestamp.

Why

  • The contract forms at the checkbox, not the OAuth click — no account, service, or billing exists before acceptance (CDC Art. 46; YouTube API ToS requires PP agreement before features).
  • The 18+ self-declaration is the documented age gate (Google OAuth is not age verification), so it lives in the checkbox text.
  • LGPD requires marketing consent to be specific and unbundled — it is its own optional box.
  • consents is the evidence table for "I never agreed to that" disputes (CDC Art. 6º, VIII can shift burden of proof to us).
  • LEGAL_VERSION (src/lib/server/legal.ts) routes users whose consent predates a material doc change back through /consent on next login.

Notes

  • The /terms, /privacy, /dpa links resolve against the legal pages merged in Legal pages: Terms, Privacy Policy, DPA (EN) #35 — no merge-order dependency.
  • First-account orphan-channel claiming moved from the OAuth callback into the consent action (same first-user-only semantics; second-user steal test preserved).
  • CONSENT_CHECKBOX_TEXT and the visible sentence on the consent page must stay byte-identical; there is a source-level test pinning this.
  • AGENTS.md "Accounts & Sessions" and the execution plan (new section 5b-2) updated.
  • After merge: apply migration 0007 to prod Turso via npm run db:migrate from the main checkout.

Verification

  • npm run test: 225/225 (31 files), incl. 10 new consent tests + reworked login-callback tests
  • npm run check: 0 errors, 0 warnings
  • npm run build: green (adapter-netlify)

CodeAnt-AI Description

Require explicit consent before creating or continuing an account after Google sign-in

What Changed

  • New Google sign-ins now go to a consent page before an account or session is created.
  • The required checkbox confirms the user is 18+ and agrees to the Terms of Service, Privacy Policy, and Data Processing Agreement; each document is linked for review.
  • Marketing updates use a separate, optional checkbox and are never enabled by default.
  • Each acceptance records the document version, exact displayed text, IP address, browser details, timestamp, and marketing choice.
  • Existing users must re-accept when their consent predates the current legal document version.
  • Concurrent sign-in tabs remain isolated, and expired or invalid consent flows return users to sign-in.

Impact

✅ No account created before required consent
✅ Clear re-consent after legal document updates
✅ Auditable consent records
✅ Separate control over marketing emails

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/routes/consent/+page.server.ts (1)

77-106: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Configure local libSQL contention handling

@libsql/client backs both production and testdb.ts, so the async transaction is valid. For supported file: deployments, configure the client timeout or handle busy errors. No such setting exists. Add a failing concurrent-signup regression test before changing the implementation.

🤖 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/routes/consent/`+page.server.ts around lines 77 - 106, Update the libSQL
client configuration used by production and testdb.ts to set an appropriate
timeout for supported file: deployments, while preserving existing client
behavior elsewhere. Before changing the implementation, add a regression test
that runs concurrent new-user consent transactions and fails under the current
contention handling, then use that test to validate the timeout configuration.
♻️ Duplicate comments (1)
AGENTS.md (1)

163-167: ⚠️ Potential issue | 🟠 Major

Correct the required checkbox state.

Line 166 says an unticked checkbox gates account creation. The consent action rejects the request unless consent is on, so this documentation reverses the required condition. Replace unticked with checked or ticked here and in EXECUTION_PLAN_YouTube_Comment_Moderator.md.

This is the same unresolved finding from the previous review.

🤖 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 `@AGENTS.md` around lines 163 - 167, The consent documentation incorrectly says
an unticked checkbox gates account creation; update the consent flow wording
near the moderaty_consent_pending description in AGENTS.md and the corresponding
section of EXECUTION_PLAN_YouTube_Comment_Moderator.md to say the required
18+/ToS/PP/DPA checkbox must be checked or ticked, matching the consent action’s
consent=on requirement.

Source: Coding guidelines

🤖 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.

Outside diff comments:
In `@src/routes/consent/`+page.server.ts:
- Around line 77-106: Update the libSQL client configuration used by production
and testdb.ts to set an appropriate timeout for supported file: deployments,
while preserving existing client behavior elsewhere. Before changing the
implementation, add a regression test that runs concurrent new-user consent
transactions and fails under the current contention handling, then use that test
to validate the timeout configuration.

---

Duplicate comments:
In `@AGENTS.md`:
- Around line 163-167: The consent documentation incorrectly says an unticked
checkbox gates account creation; update the consent flow wording near the
moderaty_consent_pending description in AGENTS.md and the corresponding section
of EXECUTION_PLAN_YouTube_Comment_Moderator.md to say the required
18+/ToS/PP/DPA checkbox must be checked or ticked, matching the consent action’s
consent=on requirement.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0ab849ca-e6d4-4e0a-9502-7c5a82532774

📥 Commits

Reviewing files that changed from the base of the PR and between 0a2aa84 and 3410533.

📒 Files selected for processing (6)
  • AGENTS.md
  • src/lib/server/legal.ts
  • src/routes/api/auth/google/login/callback/+server.ts
  • src/routes/api/auth/google/login/login.test.ts
  • src/routes/consent/+page.server.ts
  • src/routes/consent/consent.test.ts

@sonarqubecloud

sonarqubecloud Bot commented Aug 1, 2026

Copy link
Copy Markdown

@Bonobo791
Bonobo791 merged commit a2db915 into main Aug 1, 2026
15 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed size:XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant