npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@moonirdev/zauth

v0.1.2

Published

Opinionated authentication & RBAC engine for **Next.js** — built for **internal tools, admin dashboards, and agency workflows**.

Readme

zauth

Opinionated authentication & RBAC engine for Next.js
built for internal tools, admin dashboards, and agency workflows.

Zauth focuses on:

  • predictable behavior
  • explicit setup
  • clean developer experience
  • zero magic in security-critical paths

Why zauth?

Most auth libraries are either:

  • too simple (no RBAC, no audit), or
  • too complex (UI-heavy, SaaS-first, hard to customize)

zauth sits in the middle.

It gives you:

  • authentication
  • session management
  • role-based access control
  • permission checks
  • audit logging

without forcing:

  • hosted SaaS
  • dashboards
  • hidden conventions

Features

  • Email & password authentication
  • Session-based auth (httpOnly cookie)
  • Role-based access control (RBAC)
  • Wildcard permissions (post.*, *)
  • Declarative RBAC sync (no manual DB seeding)
  • Next.js middleware support
  • Audit logging
  • Prisma-first (no raw SQL)
  • Works with Bun or Node

Requirements

  • Node.js >= 20 or Bun
  • Next.js >= 15
  • Prisma >= 7
  • Any database supported by Prisma (Postgres, MySQL, SQLite, etc.)

Database Schema (Required)

Zauth does not create database tables automatically.

You are expected to define the required tables in your database schema
(using Prisma or any other migration tool).

Below is the minimum required schema for zauth to work properly.


Required Tables

Zauth expects the following entities:

  • User
  • Session
  • Role
  • Permission
  • RolePermission (join table)
  • UserRole (join table)
  • AuditLog

Example Prisma Schema

Below is a reference Prisma schema that works with zauth.

You may rename fields or tables as long as the relations remain equivalent.

model User {
  id        String     @id @default(cuid())
  email     String     @unique
  password  String
  createdAt DateTime   @default(now())

  sessions  Session[]
  roles     UserRole[]
  audits    AuditLog[]
}

model Session {
  id        String   @id @default(cuid())
  userId    String
  expiresAt DateTime
  createdAt DateTime @default(now())

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@index([userId])
}

model Role {
  id    String @id @default(cuid())
  name  String @unique

  permissions RolePermission[]
  users       UserRole[]
}

model Permission {
  id   String @id @default(cuid())
  key  String @unique

  roles RolePermission[]
}

model RolePermission {
  roleId       String
  permissionId String

  role       Role       @relation(fields: [roleId], references: [id], onDelete: Cascade)
  permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)

  @@id([roleId, permissionId])
}

model UserRole {
  userId String
  roleId String

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
  role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)

  @@id([userId, roleId])
}

model AuditLog {
  id        String   @id @default(cuid())
  userId    String?
  action    String
  resource  String
  meta      Json?
  createdAt DateTime @default(now())

  user User? @relation(fields: [userId], references: [id], onDelete: SetNull)

  @@index([userId])
}

Installation

npm install @moonirdev/zauth
# or
bun add @moonirdev/zauth

Basic Setup

Create auth instance

// lib/auth.ts
import { PrismaClient } from "@prisma/client";
import { createAuth } from "@moonirdev/zauth";

const prisma = new PrismaClient();

export const auth = createAuth({
  prisma,

  // optional defaults
  defaults: {
    role: "admin", // default role assigned on register
  },

  session: {
    cookieName: "zauth.session",
    ttlSeconds: 60 * 60 * 24 * 7, // 7 days
  },
});

Authentication

Register user

await auth.auth.register(email, password);

What happens:

  • user is created
  • password is hashed
  • default role is assigned (if configured)

Login user

await auth.auth.login(email, password);

What happens:

  • credentials are verified
  • session is created
  • httpOnly cookie is set

Logout user

await auth.auth.logout();

Session

Require authenticated user

const user = await auth.session.requireUser();
  • throws if unauthenticated
  • returns user with permissions

Get optional user

const user = await auth.session.getUser();
  • returns null if not logged in

RBAC (Roles & Permissions)

Permission check

if (!auth.acl.can(user, "post.create")) {
  throw new Error("Forbidden");
}

Supported patterns:

  • exact: post.create
  • wildcard: post.*
  • super admin: *

RBAC Sync (IMPORTANT)

Zauth does not require manual database inserts for roles or permissions.

RBAC is defined declaratively and synced explicitly.

Example RBAC config

await auth.acl.sync({
  roles: {
    admin: ["*"],
    editor: ["post.create", "post.update", "post.read"],
    viewer: ["post.read"],
  },
});

What acl.sync() does:

  • creates roles if missing
  • creates permissions if missing
  • assigns permissions to roles
  • removes stale permissions
  • safe to run multiple times (idempotent)

Where to run acl.sync()

Run it once during setup or deployment, not per request.

Recommended: Prisma seed

// prisma/seed.ts
import { PrismaClient } from "@prisma/client";
import { createAuth } from "zauth";

const prisma = new PrismaClient();
const auth = createAuth({ prisma });

async function main() {
  await auth.acl.sync({
    roles: {
      admin: ["*"],
      user: ["post.read"],
    },
  });
}

main()
  .catch(console.error)
  .finally(() => prisma.$disconnect());

Run:

bunx prisma db seed

Middleware (Route Protection)

Protect routes using Next.js middleware.

Deprecated:
The following usage is deprecated and will be removed in a future release.
Use the new middleware integration as described below.

// middleware.ts
import { auth } from "@/lib/auth";

export default auth.middleware();

New Middleware Integration

Protect routes using the new proxy/middleware pattern:

// middleware.ts
import { zauth } from "@/lib/auth";
import { NextResponse } from "next/server";

export default function proxy(req: Request) {
  const authResult = zauth.requireAuth(req);
  if (authResult) return authResult;

  return NextResponse.next();
}

export const config = {
  matcher: ["/dashboard/:path*"],
};
  • Use zauth.requireAuth(req) to enforce authentication in middleware/proxy.
  • Adjust the matcher to specify which routes to protect.

Behavior:

  • checks session cookie
  • redirects unauthenticated users
  • works on Edge or Node runtime

Audit Logging

await auth.audit.log({
  userId: user.id,
  action: "post.create",
  resource: "post",
  meta: { postId },
});

Design Philosophy

Zauth is intentionally opinionated:

  • no implicit magic
  • no background sync
  • no hidden side effects
  • explicit lifecycle control
  • easy to reason about

This makes it suitable for:

  • internal tools
  • admin panels
  • agency projects
  • B2B dashboards

Roadmap

  • Multi-tenant / organization support
  • Advanced audit export
  • Admin UI (paid)
  • CLI (npx zauth sync)
  • Policy-based permissions

License

MIT