Auth stub returns 201 with no user creation — any caller thinks they registered #84

Closed
opened 2026-09-14 04:38:41 +00:00 by Mark · 0 comments
Owner

Where: api/src/index.ts:11-13.

Bug:

app.post('/api/v1/auth/register', (_req: Request, res: Response) => {
  res.status(201).json({ status: 'ok' });
});

Returns HTTP 201 Created with a static { status: 'ok' } body. No body parsing, no zod validation, no bcrypt.hash, no insert into users (api/src/db/schema.ts:3-8). The 201 is a lie: no user was created.

Risk:

  • Any client code that calls this and treats 201 as "registered" gets a green light without a session, password, or account.
  • If login is wired later, the user has no row to authenticate against and gets a confusing 401 on next attempt.
  • This is the kind of stub that survives a deploy because the smoke test doesn't exercise it.

Fix consistent with existing patterns:
Replace the stub with one of two contracts:

  1. Honest 501 if the feature is deferred:
app.post('/api/v1/auth/register', (_req, res) => {
  res.status(501).json({ error: 'not_implemented', message: 'Auth is deferred; tracked in issue #4.' });
});
  1. Real implementation matching the schema (api/src/db/schema.ts:3-8) using the deps already in package.json:
import bcrypt from 'bcrypt';
import { z } from 'zod';
import { users } from './db/schema';
// (db pool instantiated in src/db/client.ts, not yet present)

const Body = z.object({ email: z.string().email(), password: z.string().min(8) });
app.post('/api/v1/auth/register', async (req, res) => {
  const parsed = Body.safeParse(req.body);
  if (!parsed.success) return res.status(400).json({ error: 'invalid_input', details: parsed.error.flatten() });
  const hash = await bcrypt.hash(parsed.data.password, 12);
  try {
    const [u] = await db.insert(users).values({ email: parsed.data.email, passwordHash: hash }).returning({ id: users.id });
    res.status(201).json({ id: u.id });
  } catch (e) {
    if (String(e).includes('unique')) return res.status(409).json({ error: 'email_taken' });
    throw e;
  }
});

Recommended: Path 1 until #4 is staffed; path 2 then supersedes.

**Where:** `api/src/index.ts:11-13`. **Bug:** ```ts app.post('/api/v1/auth/register', (_req: Request, res: Response) => { res.status(201).json({ status: 'ok' }); }); ``` Returns HTTP 201 Created with a static `{ status: 'ok' }` body. No body parsing, no zod validation, no `bcrypt.hash`, no insert into `users` (`api/src/db/schema.ts:3-8`). The 201 is a lie: no user was created. **Risk:** - Any client code that calls this and treats 201 as "registered" gets a green light without a session, password, or account. - If login is wired later, the user has no row to authenticate against and gets a confusing 401 on next attempt. - This is the kind of stub that survives a deploy because the smoke test doesn't exercise it. **Fix consistent with existing patterns:** Replace the stub with one of two contracts: 1. **Honest 501** if the feature is deferred: ```ts app.post('/api/v1/auth/register', (_req, res) => { res.status(501).json({ error: 'not_implemented', message: 'Auth is deferred; tracked in issue #4.' }); }); ``` 2. **Real implementation** matching the schema (`api/src/db/schema.ts:3-8`) using the deps already in `package.json`: ```ts import bcrypt from 'bcrypt'; import { z } from 'zod'; import { users } from './db/schema'; // (db pool instantiated in src/db/client.ts, not yet present) const Body = z.object({ email: z.string().email(), password: z.string().min(8) }); app.post('/api/v1/auth/register', async (req, res) => { const parsed = Body.safeParse(req.body); if (!parsed.success) return res.status(400).json({ error: 'invalid_input', details: parsed.error.flatten() }); const hash = await bcrypt.hash(parsed.data.password, 12); try { const [u] = await db.insert(users).values({ email: parsed.data.email, passwordHash: hash }).returning({ id: users.id }); res.status(201).json({ id: u.id }); } catch (e) { if (String(e).includes('unique')) return res.status(409).json({ error: 'email_taken' }); throw e; } }); ``` **Recommended:** Path 1 until #4 is staffed; path 2 then supersedes.
Mark closed this issue 2026-09-14 04:42:47 +00:00
Sign in to join this conversation.
No description provided.