Giter Club home page Giter Club logo

lucia-auth-test's Introduction

next-14-lucia-v3-sqlite-drizzle-conform-zod-email-verification-otp-server-actions

Store Environment Variables

  1. Copy .env.example file to .env file
cp .env.example .env # duplicate .env.example & name it .env
  1. Change DATABASE_PATH to the filename of your choice
DATABASE_PATH=sqlite.db

Install the dependencies

pnpm install

Start the server

pnpm start

Push schema changes to database

pnpm db:push

Generate Migrations

pnpm db:generate

Install Redis for the rate-limiting on OTP function

https://redis.io/docs/install/install-redis/

Note: You can disable it by removing Redis Specific Code & middleware.ts

Must-watch videos on Redis & Rate Limiting to understand the basic concepts

  1. https://www.youtube.com/watch?v=MR_BN1Ricjw
  2. https://www.youtube.com/watch?v=qUydEBZmGvU
  3. https://www.youtube.com/watch?v=a4yX7RUgTxI

When to rate-limit API routes?

  1. Sign Up - What if someone brute-forces 100s of requests to fill our database with UGC spam?
  2. Login - What if someone tries every possible combination of email to get the right one?
  3. OTP - What if someone tries to gain access by trying various different OTP combinations to find the best one?

Since it is a simple example, I will go with IP-based blocking for 1 day using browser finderprinting library.

How to test rate-limit API using Postman/Hoppscotch?

Go to https://hoppscotch.io/ (or https://www.postman.com/)

Select POST & enter http://localhost:3000/signup in URL.

Go to Headers & add Next-Action to 948fdf27b221db98253b47aa8f8d1c589c93e063 as well as Origin to localhost:3000 & click on Send 10 times to see the error.

Tried Rate Limits on Next.js 14 Server Actions using 2 methods:

  1. Puppeteer/Playwright

The scripts are in rate-limit folder. Both for some reason send 2 requests & it always gives Email is not unique error since 1st request adds it to the database. Its a nasty bug in either Puppeteer/Playwright or my scripts. But definitely not in my email signup process. It works well manually.

  1. Fetch request using Web API

Tried it with Next-Action as a header but that doesn't work either.

Most of the issues are with Next.js 14 Server Actions that I stumbled upon only while automating rate limits.

I don't think its ready for prime time yet so I'm gonna use API routes on my main project.

If you want to use this repo, check out 2 commits behind this as that works without any errors.

Basic Rate Limit Example that works

src/middleware.ts

import { NextRequest, NextResponse } from 'next/server';
import { RateLimiterMemory } from 'rate-limiter-flexible';

const opts = {
  points: 10,
  duration: 5, // Per second
};

const rateLimiter = new RateLimiterMemory(opts);

export default async function middleware(request: NextRequest) {
  if (request.method === 'POST') {
    let res: any;
    try {
      res = await rateLimiter.consume(2);
      console.log({ res });
    } catch (error) {
      res = error;
    }

    if (res._remainingPoints > 0) {
      return NextResponse.next();
    } else {
      return NextResponse.json(
        {
          error: 'Rate limit exceeded. Please try again later.',
        },
        {
          status: 429,
        }
      );
    }
  }
}

export const config = {
  matcher: ['/api/login'],
};

rate-limit/login.ts

const LOCALHOST_URL = 'http://localhost:3000';

async function main() {
  for (let i = 0; i < 20; i++) {
    const url = `${LOCALHOST_URL}/api/login`;
    const email = `test${i}@example.com`;
    const body = JSON.stringify({ email });

    const response = await fetch(url, { method: 'POST', body });
    const data = await response.json();

    console.log({ data });
  }
}

main();

Run Next.js dev server using pnpm dev in one terminal & brute-force the login api in another using pnpm ratelimit:login & it'll show this output in ratelimit terminal:

{ data: { success: false } }
{ data: { success: false } }
{ data: { success: false } }
{ data: { success: false } }
{ data: { success: false } }
{ data: { success: false } }
{ data: { success: false } }
{ data: { success: false } }
{ data: { success: false } }
{ data: { error: 'Rate limit exceeded. Please try again later.' } }
{ data: { error: 'Rate limit exceeded. Please try again later.' } }
{ data: { error: 'Rate limit exceeded. Please try again later.' } }
{ data: { error: 'Rate limit exceeded. Please try again later.' } }
{ data: { error: 'Rate limit exceeded. Please try again later.' } }
{ data: { error: 'Rate limit exceeded. Please try again later.' } }
{ data: { error: 'Rate limit exceeded. Please try again later.' } }
{ data: { error: 'Rate limit exceeded. Please try again later.' } }
{ data: { error: 'Rate limit exceeded. Please try again later.' } }
{ data: { error: 'Rate limit exceeded. Please try again later.' } }
{ data: { error: 'Rate limit exceeded. Please try again later.' } }

And this output in dev server terminal:

{
  res: RateLimiterRes {
  _remainingPoints: 9,
  _msBeforeNext: 5000,
  _consumedPoints: 1,
  _isFirstInDuration: true
}
}
[19:49:26.652] INFO (9732): 🏁 POST /api/login/route
{
  res: RateLimiterRes {
  _remainingPoints: 8,
  _msBeforeNext: 4940,
  _consumedPoints: 2,
  _isFirstInDuration: false
}
}
[19:49:26.706] INFO (9732): 🏁 POST /api/login/route
{
  res: RateLimiterRes {
  _remainingPoints: 7,
  _msBeforeNext: 4898,
  _consumedPoints: 3,
  _isFirstInDuration: false
}
}
[19:49:26.750] INFO (9732): 🏁 POST /api/login/route
{
  res: RateLimiterRes {
  _remainingPoints: 6,
  _msBeforeNext: 4855,
  _consumedPoints: 4,
  _isFirstInDuration: false
}
}
[19:49:26.793] INFO (9732): 🏁 POST /api/login/route
{
  res: RateLimiterRes {
  _remainingPoints: 5,
  _msBeforeNext: 4810,
  _consumedPoints: 5,
  _isFirstInDuration: false
}
}
[19:49:26.839] INFO (9732): 🏁 POST /api/login/route
{
  res: RateLimiterRes {
  _remainingPoints: 4,
  _msBeforeNext: 4765,
  _consumedPoints: 6,
  _isFirstInDuration: false
}
}
[19:49:26.882] INFO (9732): 🏁 POST /api/login/route
{
  res: RateLimiterRes {
  _remainingPoints: 3,
  _msBeforeNext: 4721,
  _consumedPoints: 7,
  _isFirstInDuration: false
}
}
[19:49:26.931] INFO (9732): 🏁 POST /api/login/route
{
  res: RateLimiterRes {
  _remainingPoints: 2,
  _msBeforeNext: 4672,
  _consumedPoints: 8,
  _isFirstInDuration: false
}
}
[19:49:26.977] INFO (9732): 🏁 POST /api/login/route
{
  res: RateLimiterRes {
  _remainingPoints: 1,
  _msBeforeNext: 4625,
  _consumedPoints: 9,
  _isFirstInDuration: false
}
}
[19:49:27.022] INFO (9732): 🏁 POST /api/login/route
{
  res: RateLimiterRes {
  _remainingPoints: 0,
  _msBeforeNext: 4583,
  _consumedPoints: 10,
  _isFirstInDuration: false
}
}

It only works with API routes. Idk how to make it work for Server Actions. I tried Puppeteer/Playwright but for some reason, it calls login api twice & halts the process because of not unique email.

lucia-auth-test's People

Contributors

deadcoder0904 avatar tefkah avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.