Skip to content

Userauth part4 frontend context#37

Open
parthdude07 wants to merge 7 commits into
genesis-kb:userauthfrom
parthdude07:userauth-part4-frontend-context
Open

Userauth part4 frontend context#37
parthdude07 wants to merge 7 commits into
genesis-kb:userauthfrom
parthdude07:userauth-part4-frontend-context

Conversation

@parthdude07

@parthdude07 parthdude07 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary by cubic

Adds JWT auth and a full notes + whiteboard system integrated into transcripts and the library. Users can register/login, take rich notes with tags/colors, and build concept maps; the API exposes /api/v1/auth routes.

  • New Features

    • Backend auth: POST /auth/register, POST /auth/login, GET /auth/me with JWT via jsonwebtoken, bcrypt hashing via bcryptjs, and requireAuth.
    • Frontend auth: AuthContext with token persistence and auto-validate, useAuth, and a tabbed LoginModal.
    • Notes: localStorage-backed notes (create/edit/pin/tag/color, search/sort), useNotes, NoteEditor/NoteCard/TranscriptNotes, and HighlightToolbar “Add Note” support; library now shows notes.
    • Whiteboard: concept mapping with @xyflow/react (TranscriptWhiteboard, ConceptNode), persisted per transcript; highlights support color/underline and TranscriptChat accepts a prefilled prompt.
  • Migration

    • Add JWT_SECRET (and optional JWT_EXPIRES_IN) to your environment.
    • Apply DB migration 002_auth_users.sql to create the users table.
    • Install client dep @xyflow/react, then restart the backend after setting env vars and running the migration.

Written for commit dbd89e0. Summary will update on new commits.

Review in cubic

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0548d207-6bca-4cee-a6aa-1ce78913beaf

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@cubic-dev-ai cubic-dev-ai 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.

13 issues found across 17 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/src/config/index.js">

<violation number="1" location="backend/src/config/index.js:69">
P1: Auth is forgeable on any non-production deployment lacking `JWT_SECRET`, since this publicly known fallback signs and verifies every token. Require an explicit secret there too, or generate a cryptographically random per-process development secret rather than using a constant.</violation>
</file>

<file name="backend/src/middleware/auth.js">

<violation number="1" location="backend/src/middleware/auth.js:18">
P3: Valid bearer credentials using a lowercase/mixed-case scheme are rejected, although HTTP auth scheme names are case-insensitive. Match the scheme case-insensitively in both middleware paths before verifying the token.</violation>
</file>

<file name="backend/supabase/migrations/002_auth_users.sql">

<violation number="1" location="backend/supabase/migrations/002_auth_users.sql:8">
P0: On Supabase, this leaves every `users` row—including bcrypt password hashes—readable through the Data API to any role with table grants. Enable RLS immediately after creation and add no public policies; backend DB credentials bypass RLS unless forced.</violation>

<violation number="2" location="backend/supabase/migrations/002_auth_users.sql:21">
P3: This adds a duplicate email index: `UNIQUE` already supplies the lookup index used by the auth queries. Omitting `idx_users_email` avoids needless index storage and write maintenance.</violation>
</file>

<file name="src/components/LoginModal.tsx">

<violation number="1" location="src/components/LoginModal.tsx:23">
P1: Opening `isLoginModalOpen` cannot display a dialog because `LoginModal` is never mounted; mounting it as-is would also throw outside `AuthProvider`. Render the provider and modal in the application tree so `openLoginModal()` has a UI target.</violation>

<violation number="2" location="src/components/LoginModal.tsx:104">
P2: Registration controls become unreachable on short viewports because this fixed dialog clips overflow without a maximum height or scroll container. Bound it to the viewport and allow vertical scrolling.</violation>

<violation number="3" location="src/components/LoginModal.tsx:137">
P3: Failed login or registration errors are not announced to screen-reader users because this dynamically inserted banner lacks live-region semantics. Mark it as an alert.</violation>

<violation number="4" location="src/components/LoginModal.tsx:189">
P3: Keyboard and screen-reader users cannot operate either password visibility toggle because both buttons are removed from tab order and have no accessible name. Keep them focusable and add state-dependent `aria-label`s.</violation>
</file>

<file name="backend/src/services/authService.js">

<violation number="1" location="backend/src/services/authService.js:39">
P1: Concurrent registration attempts for the same email can return a server error instead of `EMAIL_EXISTS`. The separate pre-check plus insert leaves a race window; handling conflict at insert time (or `ON CONFLICT`) keeps duplicate-email responses consistently 409.</violation>

<violation number="2" location="backend/src/services/authService.js:49">
P2: Registration/login logs currently store full email addresses, which persists user PII in log sinks. Prefer masked email or stable internal identifiers (for example `user.id`) in auth log messages.</violation>
</file>

<file name="backend/src/controllers/authController.js">

<violation number="1" location="backend/src/controllers/authController.js:19">
P2: Malformed request types can produce 500s because `register`/`login` validate presence but not string type before calling authService. Adding explicit `typeof email/password === 'string'` (and non-empty trimmed email) checks would keep bad input in the 400-validation path.</violation>

<violation number="2" location="backend/src/controllers/authController.js:38">
P2: Registration can fail with a server error when `name` is sent as a non-string because the controller passes it through without validation. Coercing/validating `name` as an optional string before calling `registerUser` avoids that runtime exception.</violation>
</file>

<file name="src/contexts/AuthContext.tsx">

<violation number="1" location="src/contexts/AuthContext.tsx:66">
P0: The `/auth/me` token validation always fails on page reload because the stored JWT is never sent with the request. The `services/api.ts` fetch client has no interceptor to attach `Authorization: Bearer <token>` from localStorage, and `authApi.me()` does not pass the token explicitly. As a result, every refresh will: read the stored token → call `authApi.me()` with no auth header → get a 401 → clear the stored token and user. The user will never stay logged in across page loads.

**Fix option A (API service level, preferred):** Add a header in the API `request()` function that reads the token from localStorage under `btc-auth-token` and sets the `Authorization` header.

**Fix option B (caller level):** Refactor `authApi.me()` (and future authenticated calls) to accept and forward a token header, then pass it in AuthContext from state.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

-- ============================================================

-- Users table
CREATE TABLE IF NOT EXISTS users (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P0: On Supabase, this leaves every users row—including bcrypt password hashes—readable through the Data API to any role with table grants. Enable RLS immediately after creation and add no public policies; backend DB credentials bypass RLS unless forced.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/supabase/migrations/002_auth_users.sql, line 8:

<comment>On Supabase, this leaves every `users` row—including bcrypt password hashes—readable through the Data API to any role with table grants. Enable RLS immediately after creation and add no public policies; backend DB credentials bypass RLS unless forced.</comment>

<file context>
@@ -0,0 +1,36 @@
+-- ============================================================
+
+-- Users table
+CREATE TABLE IF NOT EXISTS users (
+    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+    email       TEXT UNIQUE NOT NULL,
</file context>


try {
// Temporarily inject the token header for this request
const data = await authApi.me();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P0: The /auth/me token validation always fails on page reload because the stored JWT is never sent with the request. The services/api.ts fetch client has no interceptor to attach Authorization: Bearer <token> from localStorage, and authApi.me() does not pass the token explicitly. As a result, every refresh will: read the stored token → call authApi.me() with no auth header → get a 401 → clear the stored token and user. The user will never stay logged in across page loads.

Fix option A (API service level, preferred): Add a header in the API request() function that reads the token from localStorage under btc-auth-token and sets the Authorization header.

Fix option B (caller level): Refactor authApi.me() (and future authenticated calls) to accept and forward a token header, then pass it in AuthContext from state.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/contexts/AuthContext.tsx, line 66:

<comment>The `/auth/me` token validation always fails on page reload because the stored JWT is never sent with the request. The `services/api.ts` fetch client has no interceptor to attach `Authorization: Bearer <token>` from localStorage, and `authApi.me()` does not pass the token explicitly. As a result, every refresh will: read the stored token → call `authApi.me()` with no auth header → get a 401 → clear the stored token and user. The user will never stay logged in across page loads.

**Fix option A (API service level, preferred):** Add a header in the API `request()` function that reads the token from localStorage under `btc-auth-token` and sets the `Authorization` header.

**Fix option B (caller level):** Refactor `authApi.me()` (and future authenticated calls) to accept and forward a token header, then pass it in AuthContext from state.</comment>

<file context>
@@ -0,0 +1,137 @@
+
+      try {
+        // Temporarily inject the token header for this request
+        const data = await authApi.me();
+        setUser(data.user);
+      } catch {
</file context>


// Authentication (JWT)
auth: {
jwtSecret: process.env.JWT_SECRET || 'dev-secret-change-in-production',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Auth is forgeable on any non-production deployment lacking JWT_SECRET, since this publicly known fallback signs and verifies every token. Require an explicit secret there too, or generate a cryptographically random per-process development secret rather than using a constant.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/src/config/index.js, line 69:

<comment>Auth is forgeable on any non-production deployment lacking `JWT_SECRET`, since this publicly known fallback signs and verifies every token. Require an explicit secret there too, or generate a cryptographically random per-process development secret rather than using a constant.</comment>

<file context>
@@ -64,6 +64,13 @@ const config = {
 
+  // Authentication (JWT)
+  auth: {
+    jwtSecret: process.env.JWT_SECRET || 'dev-secret-change-in-production',
+    jwtExpiresIn: process.env.JWT_EXPIRES_IN || '7d',
+    bcryptRounds: 12,
</file context>

import { Label } from '@/components/ui/label';
import { useAuth } from '@/hooks/useAuth';

export function LoginModal() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Opening isLoginModalOpen cannot display a dialog because LoginModal is never mounted; mounting it as-is would also throw outside AuthProvider. Render the provider and modal in the application tree so openLoginModal() has a UI target.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/LoginModal.tsx, line 23:

<comment>Opening `isLoginModalOpen` cannot display a dialog because `LoginModal` is never mounted; mounting it as-is would also throw outside `AuthProvider`. Render the provider and modal in the application tree so `openLoginModal()` has a UI target.</comment>

<file context>
@@ -0,0 +1,324 @@
+import { Label } from '@/components/ui/label';
+import { useAuth } from '@/hooks/useAuth';
+
+export function LoginModal() {
+  const { isLoginModalOpen, closeLoginModal, login, register } = useAuth();
+
</file context>

const hashedPassword = await bcrypt.hash(password, config.auth.bcryptRounds);

// Insert user
const result = await query(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Concurrent registration attempts for the same email can return a server error instead of EMAIL_EXISTS. The separate pre-check plus insert leaves a race window; handling conflict at insert time (or ON CONFLICT) keeps duplicate-email responses consistently 409.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/src/services/authService.js, line 39:

<comment>Concurrent registration attempts for the same email can return a server error instead of `EMAIL_EXISTS`. The separate pre-check plus insert leaves a race window; handling conflict at insert time (or `ON CONFLICT`) keeps duplicate-email responses consistently 409.</comment>

<file context>
@@ -0,0 +1,139 @@
+  const hashedPassword = await bcrypt.hash(password, config.auth.bcryptRounds);
+
+  // Insert user
+  const result = await query(
+    `INSERT INTO users (email, password, name)
+     VALUES ($1, $2, $3)
</file context>

@@ -0,0 +1,84 @@
/**

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Malformed request types can produce 500s because register/login validate presence but not string type before calling authService. Adding explicit typeof email/password === 'string' (and non-empty trimmed email) checks would keep bad input in the 400-validation path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/src/controllers/authController.js, line 19:

<comment>Malformed request types can produce 500s because `register`/`login` validate presence but not string type before calling authService. Adding explicit `typeof email/password === 'string'` (and non-empty trimmed email) checks would keep bad input in the 400-validation path.</comment>

<file context>
@@ -0,0 +1,84 @@
+  const { email, password, name } = req.body;
+
+  // Input validation
+  if (!email || !password) {
+    throw new APIError('Email and password are required', 400, 'VALIDATION_ERROR');
+  }
</file context>

export const requireAuth = (req, _res, next) => {
const authHeader = req.headers.authorization;

if (!authHeader || !authHeader.startsWith('Bearer ')) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Valid bearer credentials using a lowercase/mixed-case scheme are rejected, although HTTP auth scheme names are case-insensitive. Match the scheme case-insensitively in both middleware paths before verifying the token.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/src/middleware/auth.js, line 18:

<comment>Valid bearer credentials using a lowercase/mixed-case scheme are rejected, although HTTP auth scheme names are case-insensitive. Match the scheme case-insensitively in both middleware paths before verifying the token.</comment>

<file context>
@@ -0,0 +1,59 @@
+export const requireAuth = (req, _res, next) => {
+  const authHeader = req.headers.authorization;
+
+  if (!authHeader || !authHeader.startsWith('Bearer ')) {
+    throw new APIError('Authentication required', 401, 'UNAUTHORIZED');
+  }
</file context>

);

-- Indexes
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: This adds a duplicate email index: UNIQUE already supplies the lookup index used by the auth queries. Omitting idx_users_email avoids needless index storage and write maintenance.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/supabase/migrations/002_auth_users.sql, line 21:

<comment>This adds a duplicate email index: `UNIQUE` already supplies the lookup index used by the auth queries. Omitting `idx_users_email` avoids needless index storage and write maintenance.</comment>

<file context>
@@ -0,0 +1,36 @@
+);
+
+-- Indexes
+CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
+CREATE INDEX IF NOT EXISTS idx_users_provider ON users(provider, provider_id);
+
</file context>

{/* Error banner */}
<AnimatePresence mode="wait">
{error && (
<motion.div

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Failed login or registration errors are not announced to screen-reader users because this dynamically inserted banner lacks live-region semantics. Mark it as an alert.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/LoginModal.tsx, line 137:

<comment>Failed login or registration errors are not announced to screen-reader users because this dynamically inserted banner lacks live-region semantics. Mark it as an alert.</comment>

<file context>
@@ -0,0 +1,324 @@
+            {/* Error banner */}
+            <AnimatePresence mode="wait">
+              {error && (
+                <motion.div
+                  initial={{ opacity: 0, y: -8 }}
+                  animate={{ opacity: 1, y: 0 }}
</file context>

type="button"
onClick={() => setShowLoginPassword(!showLoginPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
tabIndex={-1}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Keyboard and screen-reader users cannot operate either password visibility toggle because both buttons are removed from tab order and have no accessible name. Keep them focusable and add state-dependent aria-labels.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/LoginModal.tsx, line 189:

<comment>Keyboard and screen-reader users cannot operate either password visibility toggle because both buttons are removed from tab order and have no accessible name. Keep them focusable and add state-dependent `aria-label`s.</comment>

<file context>
@@ -0,0 +1,324 @@
+                      type="button"
+                      onClick={() => setShowLoginPassword(!showLoginPassword)}
+                      className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
+                      tabIndex={-1}
+                    >
+                      {showLoginPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</file context>

- Create 002_auth_users.sql: users table with email/password,
  provider columns for future GitHub OAuth, auto-update trigger
- Add auth config block (JWT_SECRET, JWT_EXPIRES_IN, bcryptRounds)
- Add JWT_SECRET to production required env vars
- Install jsonwebtoken and bcryptjs dependencies
- Update .env.example with auth variables
- auth.js middleware: requireAuth (hard 401) and optionalAuth (soft attach)
  for JWT verification from Authorization: Bearer header
- authService.js: registerUser, loginUser, getUserById with bcrypt
  hashing, email normalization, and sanitized user output
- authController.js: HTTP handlers with input validation (email format,
  password length 8–128), proper error code propagation
- Export query() from supabaseService.js for use by authService
- Create authRoutes.js: POST /register, POST /login, GET /me
- Mount auth routes at /api/v1/auth in route index
- Add auth endpoints to API documentation endpoint
- Export requireAuth and optionalAuth from middleware index
- Add auth types and API service
- Add AuthContext for managing user state and modal visibility
- Add useAuth hook
- Add LoginModal component with tabs for login and registration
@parthdude07
parthdude07 force-pushed the userauth-part4-frontend-context branch from efc52dc to dbd89e0 Compare July 21, 2026 09:53
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