hackx

Backend From Zero

10 / 35

Someone wants in

They type an email and a password. We don't save the password. Hash it, save them as unverified, make a one-time token, send the mail.

src/routes/auth.ts:

import { Router } from "express";
import bcrypt from "bcrypt";
import crypto from "crypto";
import prisma from "../db.ts";
import { sendMail } from "../mail.ts";

const router = Router();

router.post("/register", async (req, res) => {
  try {
    const email = String(req.body.email ?? "").trim().toLowerCase();
    const password = String(req.body.password ?? "");
    const name = String(req.body.name ?? "").trim();

    if (!email || password.length < 8) {
      return res.status(400).json({ error: "email and password (min 8) required" });
    }

    const existing = await prisma.user.findUnique({ where: { email } });
    if (existing) return res.status(409).json({ error: "email already registered" });

    const passwordHash = await bcrypt.hash(password, 10);
    const user = await prisma.user.create({
      data: { email, passwordHash, name: name || null },
    });

    const token = crypto.randomBytes(32).toString("hex");
    await prisma.emailToken.create({
      data: {
        token,
        purpose: "verify",
        expiresAt: new Date(Date.now() + 864e5),
        userId: user.id,
      },
    });

    await sendMail({
      to: email,
      subject: "Verify your account",
      text: `${process.env.APP_URL}/auth/verify?token=${token}`,
    });

    res.status(201).json({
      user: { id: user.id, email: user.email, name: user.name, isVerified: user.isVerified },
    });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: "register failed" });
  }
});

export default router;

Walk it once. Then hit register twice — 409, club already knows you.

Sandbox: you can only mail addresses SES already verified. Use sreecharan309@gmail.com or o210008@rguktong.ac.in. Random Gmail? It'll 500. Verify that inbox first.

This writes Neon on the machine you curled. Laptop .env vs EC2 .env. Deploy API writes the full club secrets onto the box. Empty secrets → job fails → live register 500. Health still looks fine.