Module 4: User Authentication (Signup & Login)

අපේ Notes App එකට පරිශීලකයින්ට ලියාපදිංචි වීමට සහ ඇතුල් වීමට අවශ්‍ය system එක ගොඩනගමු.

1. Passwords Hash කිරීම සහ bcrypt

පරිශීලකයින්ගේ passwords කිසිවිටකවත් එලෙසම database එකේ ගබඩා කරන්නේ නැත. එය ඉතාමත් අනාරක්ෂිතයි. ඒ වෙනුවට, අපි password එක සංකීර්ණ, ආපසු හැරවිය නොහැකි අකුරු සහ ඉලක්කම් එකතුවක් (a "hash") බවට පත් කර database එකේ ගබඩා කරමු. මේ සඳහා bcrypt නමැති ජනප්‍රිය library එක භාවිතා කරමු.

පළමුව, terminal එකේ පහත command එක run කර bcrypt library එක install කරගන්න.

npm install bcrypt

2. Signup (ලියාපදිංචි වීමේ) ක්‍රියාවලිය සෑදීම

Signup ක්‍රියාවලියට කොටස් දෙකක් ඇත: Backend API Route එක (දත්ත ලබාගෙන database එකට ඇතුළත් කරන) සහ Frontend Form එක (පරිශීලකයාට දත්ත ඇතුළත් කිරීමට).

➡️ Signup API Route (Backend)

Next.js වලදී, app directory එක තුළ api නමින් folder එකක් සාදා, ඒ තුළ අපේ backend logic එක ලිවිය හැක. app/api/auth/register/route.ts යන path එකට අලුත් file එකක් සාදන්න.

දැන්, එම route.ts file එකට පහත කේතය ඇතුළත් කරන්න.

import { NextResponse } from 'next/server';
import db from '@/lib/db'; // Assuming db is in lib folder
import bcrypt from 'bcrypt';

export async function POST(request: Request) {
  try {
    const { username, password } = await request.json();

    // Check if user already exists
    const existingUser = db.prepare('SELECT * FROM users WHERE username = ?').get(username);
    if (existingUser) {
      return NextResponse.json({ error: 'Username already taken' }, { status: 409 });
    }

    // Hash the password
    const hashedPassword = await bcrypt.hash(password, 10); // 10 is the salt rounds

    // Insert user into the database
    const stmt = db.prepare('INSERT INTO users (username, password_hash) VALUES (?, ?)');
    stmt.run(username, hashedPassword);

    return NextResponse.json({ message: 'User created successfully' }, { status: 201 });
  } catch (error) {
    console.error(error);
    return NextResponse.json({ error: 'An error occurred' }, { status: 500 });
  }
}

කේතය පැහැදිලි කිරීම:

➡️ Signup Form (Frontend)

දැන් app/auth/register/page.tsx යන path එකට file එකක් සාදා, user ට register වීමට අවශ්‍ය form එක නිර්මාණය කරමු. මෙය user සමග interact කරන නිසා, "use client" ලෙස සඳහන් කළ යුතුය.

'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';

export default function RegisterPage() {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');
  const router = useRouter();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setError('');

    const res = await fetch('/api/auth/register', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username, password }),
    });

    if (res.ok) {
      router.push('/auth/login'); // Redirect to login page on success
    } else {
      const data = await res.json();
      setError(data.error || 'Registration failed.');
    }
  };

  return (
    <div className="row justify-content-center">
      <div className="col-md-6">
        <h1>Register</h1>
        <form onSubmit={handleSubmit}>
          <div className="mb-3">
            <label className="form-label">Username</label>
            <input
              type="text"
              className="form-control"
              value={username}
              onChange={(e) => setUsername(e.target.value)}
              required
            />
          </div>
          <div className="mb-3">
            <label className="form-label">Password</label>
            <input
              type="password"
              className="form-control"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              required
            />
          </div>
          {error && <p className="text-danger">{error}</p>}
          <button type="submit" className="btn btn-primary">Register</button>
        </form>
      </div>
    </div>
  );
}

3. Login (ඇතුල් වීමේ) ක්‍රියාවලිය සෑදීම

Login ක්‍රියාවලියද signup මෙන්ම backend API route එකක් සහ frontend form එකකින් සමන්විත වේ.

➡️ Login API Route (Backend)

app/api/auth/login/route.ts යන path එකට අලුත් file එකක් සාදා පහත කේතය ඇතුළත් කරන්න.

import { NextResponse } from 'next/server';
import db from '@/lib/db';
import bcrypt from 'bcrypt';

export async function POST(request: Request) {
  try {
    const { username, password } = await request.json();

    // Find the user by username
    const user = db.prepare('SELECT * FROM users WHERE username = ?').get(username) as any;
    if (!user) {
      return NextResponse.json({ error: 'Invalid username or password' }, { status: 401 });
    }

    // Compare the provided password with the stored hash
    const isValid = await bcrypt.compare(password, user.password_hash);
    if (!isValid) {
      return NextResponse.json({ error: 'Invalid username or password' }, { status: 401 });
    }

    // --- IMPORTANT ---
    // In the next module, we will create a session (e.g., using JWT) here.
    // For now, we just return a success message.
    
    return NextResponse.json({ message: 'Logged in successfully' }, { status: 200 });
  } catch (error) {
    console.error(error);
    return NextResponse.json({ error: 'An error occurred' }, { status: 500 });
  }
}

කේතය පැහැදිලි කිරීම:

➡️ Login Form (Frontend)

දැන් app/auth/login/page.tsx යන path එකට file එකක් සාදා login form එක නිර්මාණය කරමු.

'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';

export default function LoginPage() {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');
  const router = useRouter();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setError('');

    const res = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username, password }),
    });

    if (res.ok) {
      router.push('/notes'); // Redirect to notes dashboard on success
    } else {
      const data = await res.json();
      setError(data.error || 'Login failed.');
    }
  };

  return (
    <div className="row justify-content-center">
      <div className="col-md-6">
        <h1>Login</h1>
        <form onSubmit={handleSubmit}>
          <div className="mb-3">
            <label className="form-label">Username</label>
            <input
              type="text"
              className="form-control"
              value={username}
              onChange={(e) => setUsername(e.target.value)}
              required
            />
          </div>
          <div className="mb-3">
            <label className="form-label">Password</label>
            <input
              type="password"
              className="form-control"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              required
            />
          </div>
          {error && <p className="text-danger">{error}</p>}
          <button type="submit" className="btn btn-primary">Login</button>
        </form>
      </div>
    </div>
  );
}