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 🙏

© 2025 – Pkg Stats / Ryan Hefner

najm-auth

v0.1.12

Published

Authentication and authorization library for najm framework

Readme

najm-auth

Authentication and authorization library for the najm framework.

Features

  • 🔐 JWT Authentication - Access and refresh token management
  • 👤 Role-Based Access Control (RBAC) - Simple role-based guards
  • 🔑 Permission-Based Authorization - Fine-grained permission system with wildcard support
  • 🍪 Cookie Management - Secure refresh token cookie handling
  • 🔒 Password Encryption - bcrypt password hashing
  • 🎯 Decorator-Based - Clean, declarative API using TypeScript decorators
  • 🗄️ Database Agnostic - Implement your own repository interfaces

Installation

npm install najm-auth
# or
bun add najm-auth

Quick Start

1. Environment Variables

Create a .env file:

JWT_ACCESS_SECRET=your-secret-access-key
ACCESS_EXPIRES_IN=15m
JWT_REFRESH_SECRET=your-secret-refresh-key
REFRESH_EXPIRES_IN=1y

2. Database Setup

Create the required database tables:

-- Users table
CREATE TABLE users (
  id UUID PRIMARY KEY,
  email VARCHAR(255) UNIQUE NOT NULL,
  password VARCHAR(255) NOT NULL,
  role_id UUID REFERENCES roles(id),
  status VARCHAR(50),
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

-- Roles table
CREATE TABLE roles (
  id UUID PRIMARY KEY,
  name VARCHAR(100) UNIQUE NOT NULL,
  description TEXT,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

-- Permissions table
CREATE TABLE permissions (
  id UUID PRIMARY KEY,
  name VARCHAR(100) UNIQUE NOT NULL,
  action VARCHAR(50) NOT NULL,
  resource VARCHAR(100) NOT NULL,
  description TEXT,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

-- Role-Permission junction table
CREATE TABLE role_permissions (
  role_id UUID REFERENCES roles(id) ON DELETE CASCADE,
  permission_id UUID REFERENCES permissions(id) ON DELETE CASCADE,
  PRIMARY KEY (role_id, permission_id)
);

-- Tokens table
CREATE TABLE tokens (
  user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
  token TEXT NOT NULL,
  expires_at TIMESTAMP NOT NULL
);

3. Implement Repository Interfaces

You need to implement the repository interfaces for your database/ORM:

import { Repository } from 'najm-api';
import { ITokenRepository, TokenData, AuthUser } from 'najm-auth';
import { db } from './db'; // your database instance

@Repository()
export class TokenRepository implements ITokenRepository {
  declare db: any;

  async storeRefreshToken(tokenData: TokenData): Promise<any> {
    return await this.db
      .insert(tokens)
      .values(tokenData)
      .onConflictDoUpdate({
        target: tokens.userId,
        set: { token: tokenData.token, expiresAt: tokenData.expiresAt }
      });
  }

  async getRefreshToken(userId: string | number): Promise<string | null> {
    const [token] = await this.db
      .select()
      .from(tokens)
      .where(eq(tokens.userId, userId));
    return token?.token || null;
  }

  async revokeToken(userId: string | number): Promise<any> {
    return await this.db.delete(tokens).where(eq(tokens.userId, userId));
  }

  async isUserExists(userId: string | number): Promise<boolean> {
    const [user] = await this.db
      .select({ id: users.id })
      .from(users)
      .where(eq(users.id, userId));
    return !!user;
  }

  async getRoleNameById(userId: string | number): Promise<string | null> {
    const [role] = await this.db
      .select({ roleName: roles.name })
      .from(users)
      .leftJoin(roles, eq(users.roleId, roles.id))
      .where(eq(users.id, userId));
    return role?.roleName || null;
  }

  async getUserPermissions(userId: string | number): Promise<string[]> {
    const [user] = await this.db
      .select({ roleId: users.roleId })
      .from(users)
      .where(eq(users.id, userId));

    if (!user?.roleId) return [];

    const perms = await this.db
      .select({ name: permissions.name })
      .from(rolePermissions)
      .leftJoin(permissions, eq(rolePermissions.permissionId, permissions.id))
      .where(eq(rolePermissions.roleId, user.roleId));

    return perms.map(p => p.name).filter(Boolean);
  }

  async getUser(userId: string | number): Promise<AuthUser | null> {
    const [user] = await this.db
      .select({
        id: users.id,
        email: users.email,
        status: users.status,
        roleId: users.roleId,
        roleName: roles.name,
      })
      .from(users)
      .leftJoin(roles, eq(users.roleId, roles.id))
      .where(eq(users.id, userId));

    return user ? { ...user, role: user.roleName } : null;
  }
}

4. Create Controllers

import { Controller, Get, Post, Body, User, Params } from 'najm-api';
import { isAuth, Role, Permission, AuthService, EncryptionService } from 'najm-auth';

@Controller('/api/auth')
export class AuthController {
  constructor(
    private authService: AuthService,
    private encryptionService: EncryptionService,
    private userService: UserService // Your user service
  ) {}

  @Post('/login')
  async login(@Body() body: { email: string; password: string }) {
    const user = await this.userService.getByEmail(body.email);

    // Verify password
    const isValid = await this.encryptionService.comparePassword(
      body.password,
      user.password
    );

    if (!isValid) {
      throw new Error('Invalid credentials');
    }

    // Generate tokens
    const tokens = await this.authService.loginUser(user.id);

    return {
      data: tokens,
      message: 'Login successful',
    };
  }

  @Get('/refresh')
  async refresh() {
    const tokens = await this.authService.refreshTokens();
    return { data: tokens };
  }

  @Get('/logout/:id')
  async logout(@Params('id') id: string) {
    await this.authService.logoutUser(id);
    return { message: 'Logged out successfully' };
  }

  @Get('/me')
  @isAuth()
  async getProfile(@User() user: any) {
    const profile = await this.authService.getUserProfile(user);
    return { data: profile };
  }
}

Usage Examples

Role-Based Guards

import { Controller, Get } from 'najm-api';
import { isAuth, Role, isAdmin, isTeacher, isStaff } from 'najm-auth';

@Controller('/api/users')
export class UserController {
  // Require authentication
  @Get('/profile')
  @isAuth()
  async getProfile() {
    return { message: 'Authenticated user only' };
  }

  // Require specific role
  @Get('/admin-only')
  @Role('admin')
  async adminRoute() {
    return { message: 'Admin only' };
  }

  // Multiple roles allowed
  @Get('/staff-only')
  @Role('admin', 'principal', 'teacher')
  async staffRoute() {
    return { message: 'Staff members only' };
  }

  // Shorthand decorators
  @Get('/teachers')
  @isTeacher()
  async teachersOnly() {
    return { message: 'Teachers only' };
  }

  @Get('/admins')
  @isAdmin()
  async adminsOnly() {
    return { message: 'Admins only' };
  }

  @Get('/all-staff')
  @isStaff()
  async allStaff() {
    return { message: 'All staff members' };
  }
}

Permission-Based Guards

Permissions follow the action:resource format (e.g., read:users, write:classes).

import { Controller, Get, Post, Put, Delete } from 'najm-api';
import { Permission } from 'najm-auth';

@Controller('/api/users')
export class UserController {
  @Get('/')
  @Permission('read:users')
  async listUsers() {
    return { data: [] };
  }

  @Post('/')
  @Permission('write:users')
  async createUser() {
    return { message: 'User created' };
  }

  @Put('/:id')
  @Permission('update:users')
  async updateUser() {
    return { message: 'User updated' };
  }

  @Delete('/:id')
  @Permission('delete:users')
  async deleteUser() {
    return { message: 'User deleted' };
  }
}

Wildcard Permissions

// Permission patterns:
// "read:*"     - Can read any resource
// "*:users"    - Can perform any action on users
// "*:*"        - Super admin (all permissions)

@Controller('/api/admin')
export class AdminController {
  @Get('/dashboard')
  @Permission('*:*')
  async dashboard() {
    return { message: 'Super admin dashboard' };
  }
}

Password Hashing

import { EncryptionService } from 'najm-auth';

@Service()
export class UserService {
  constructor(private encryptionService: EncryptionService) {}

  async createUser(data: { email: string; password: string }) {
    // Hash password before storing
    const hashedPassword = await this.encryptionService.hashPassword(data.password);

    return await this.userRepository.create({
      email: data.email,
      password: hashedPassword,
    });
  }

  async validatePassword(plainPassword: string, hashedPassword: string) {
    return await this.encryptionService.comparePassword(plainPassword, hashedPassword);
  }
}

Role Management

import { RoleService } from 'najm-auth';

@Service()
export class SetupService {
  constructor(private roleService: RoleService) {}

  async seedRoles() {
    const defaultRoles = [
      { name: 'admin', description: 'Administrator' },
      { name: 'teacher', description: 'Teacher' },
      { name: 'student', description: 'Student' },
    ];

    return await this.roleService.seedDefaultRoles(defaultRoles);
  }
}

Permission Management

import { PermissionService } from 'najm-auth';

@Service()
export class SetupService {
  constructor(private permissionService: PermissionService) {}

  async seedPermissions() {
    const defaultPermissions = [
      { name: 'read:users', action: 'read', resource: 'users' },
      { name: 'write:users', action: 'write', resource: 'users' },
      { name: 'delete:users', action: 'delete', resource: 'users' },
    ];

    await this.permissionService.seedDefaultPermissions(defaultPermissions);

    // Assign permissions to roles
    const rolePermissions = [
      {
        roleName: 'admin',
        permissions: ['*:*'], // Admin gets all permissions
      },
      {
        roleName: 'teacher',
        permissions: ['read:users', 'read:classes'],
      },
    ];

    return await this.permissionService.seedDefaultRolePermissions(rolePermissions);
  }
}

API Reference

Services

AuthService

  • loginUser(userId) - Generate tokens for user
  • refreshTokens() - Refresh access and refresh tokens
  • logoutUser(userId) - Revoke user's refresh token
  • getUserProfile(userData) - Get user profile with language

TokenService

  • generateTokens(userId) - Generate access and refresh tokens
  • refreshTokens() - Refresh both tokens
  • verifyAccessToken(token) - Verify access token
  • verifyRefreshToken(token) - Verify refresh token
  • getUserIdByAccessToken(header) - Extract user ID from token
  • getUser(auth) - Get user from access token
  • getUserRole(auth) - Get user role from token
  • getUserPermissions(auth) - Get user permissions from token

EncryptionService

  • hashPassword(password) - Hash password with bcrypt
  • comparePassword(password, hash) - Compare password with hash

RoleService

  • getAll() - Get all roles
  • getById(id) - Get role by ID
  • getByName(name) - Get role by name
  • create(data) - Create new role
  • update(id, data) - Update role
  • delete(id) - Delete role
  • seedDefaultRoles(roles) - Seed initial roles

PermissionService

  • getAll() - Get all permissions
  • getById(id) - Get permission by ID
  • getByName(name) - Get permission by name
  • create(data) - Create permission
  • update(id, data) - Update permission
  • delete(id) - Delete permission
  • assignPermissionToRole(roleId, permissionId) - Grant permission to role
  • removePermissionFromRole(roleId, permissionId) - Revoke permission from role
  • seedDefaultPermissions(permissions) - Seed initial permissions
  • seedDefaultRolePermissions(mapping) - Assign permissions to roles

Guards

Role Guards

  • @isAuth() - Require authentication
  • @Role(...roles) - Require specific role(s)
  • @isAdmin() - Shorthand for admin role
  • @isTeacher() - Shorthand for teacher role
  • @isStudent() - Shorthand for student role
  • @isStaff() - Allow admin, principal, accounting, secretary, teacher

Permission Guards

  • @Permission(permission) - Require specific permission

Constants

ROLES = {
  ADMIN: 'admin',
  PRINCIPAL: 'principal',
  ACCOUNTING: 'accounting',
  SECRETARY: 'secretary',
  TEACHER: 'teacher',
  STUDENT: 'student',
  PARENT: 'parent',
}

ROLE_GROUPS = {
  ADMINISTRATORS: ['admin', 'principal'],
  FINANCIAL: ['admin', 'accounting'],
  STAFF: ['admin', 'principal', 'accounting', 'secretary', 'teacher'],
  END_USERS: ['student', 'parent'],
  ALL: [...all roles],
}

License

MIT