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

@vfd-baas-engineering/user-management-rbac

v2.2.0

Published

A modular, reusable Node.js/TypeScript user management service with authentication and RBAC

Readme

User Management RBAC Service

A modular, reusable Node.js/TypeScript user management service that provides authentication, role-based access control (RBAC), and comprehensive user management capabilities. Built with Express and Sequelize, this package can be easily integrated into any Node.js application.

🆕 What's New in v2.2.0

New in This Release (v2.2.0)

  • 👤 System Contributors - Support for non-login accounts (no email or password required)
  • 🚩 is_app_user Flag - Distinguish between regular app users and system contributors
  • 🛠️ createSystemContributor() - New UserService method to easily create contributor accounts
  • 🔄 Flexible Schema - email and password are now optional at the database level to support various account types
  • 📝 Migration Scripts - Included SQL for updating existing databases to v2.2.0

Previous Releases (v2.1.0)

  • 🔌 External Sequelize Instance Support - Use your existing Sequelize instance to prevent "multiple instances" errors
    • Pass your own Sequelize instance via sequelize config option
    • Shares connection pool with your application
    • Prevents relationship and transaction issues
    • ⚠️ Important: Always set autoSync: false in production
  • 📘 Enhanced TypeScript Support - Exported AuthRequest type for type-safe route handlers
  • 📝 Comprehensive Documentation - Detailed risks, mitigations, and best practices
  • 🐛 Bug Fixes - Fixed audit logging examples and TypeScript type errors

From Previous Releases (v2.0.0)

  • Enhanced Role Management - System-defined roles (super-admin, admin) with custom role creation
  • 👑 Super-Admin Management - One-time initialization and transfer capabilities
  • 🔑 Password Management - Temporary password tracking with forced password changes
  • ⚠️ Standardized Error Handling - Consistent error responses with error codes
  • 🔒 Permission Scope Validation - Admins can only grant permissions they possess
  • 📊 Creator Tracking - Track who created each custom role

See full changelog | Migration guide | External Sequelize Guide

Features

  • 🔐 JWT-based Authentication - Secure token-based authentication with refresh tokens
  • 👥 User Management - Complete user lifecycle management with status tracking
  • 🎭 Role-Based Access Control - Flexible RBAC with roles and permissions
  • 👑 Super-Admin System - Protected super-admin role with transfer mechanism
  • 🔑 Password Management - Temporary passwords with automatic change tracking
  • 📝 Audit Logging - Comprehensive audit trails for compliance and security
  • 🛡️ Security Best Practices - Bcryptjs password hashing, input validation, and more
  • 🚀 Ready-to-Use - Pre-built Express middleware and API routes
  • 📘 TypeScript Support - Full type definitions included
  • 🔌 Database Agnostic - Works with PostgreSQL, MySQL, SQLite, and more via Sequelize
  • ⚠️ Standardized Errors - Consistent error responses with error codes

Installation

From Private npm Registry

npm install @vfd-baas-engineering/user-management-rbac

Peer Dependencies

You'll also need to install these peer dependencies:

npm install express sequelize

And a database driver (choose one):

# PostgreSQL
npm install pg pg-hstore

# MySQL
npm install mysql2

# SQLite
npm install sqlite3

# MSSQL
npm install tedious

Quick Start

Option 1: Using Database URL (Standalone)

import express from "express";
import { UserManagementService } from "@vfd-baas-engineering/user-management-rbac";

const app = express();
app.use(express.json());

// Initialize the service
const userService = new UserManagementService({
  databaseUrl: "postgresql://user:password@localhost:5432/mydb",
  jwtSecret: "your-secret-key-min-32-chars",
  permissions: ["users.read", "users.write", "users.delete", "admin.access"],
  autoSync: true,
});

// Initialize and mount routes
await userService.initialize();
app.use("/api", userService.getRouter());

app.listen(3000, () => {
  console.log("Server running on port 3000");
});

Option 2: Using External Sequelize Instance (Recommended for existing apps)

⚠️ New in v2.1.0 - Prevents "multiple Sequelize instances" errors!

import express from "express";
import { Sequelize } from "sequelize";
import { UserManagementService } from "@vfd-baas-engineering/user-management-rbac";

const app = express();
app.use(express.json());

// Your existing Sequelize instance
const sequelize = new Sequelize(
  "postgresql://user:password@localhost:5432/mydb"
);

// Initialize the service with your Sequelize instance
const userService = new UserManagementService({
  sequelize: sequelize, // Pass your existing instance
  jwtSecret: "your-secret-key-min-32-chars",
  permissions: ["users.read", "users.write", "users.delete", "admin.access"],
  autoSync: false, // Recommended: manage migrations yourself
});

// Initialize and mount routes
await userService.initialize();
app.use("/api", userService.getRouter());

app.listen(3000, () => {
  console.log("Server running on port 3000");
});

📖 Read the full External Sequelize Guide for detailed information on benefits, drawbacks, and best practices.

⚠️ Important: External Sequelize Risks & Mitigations

When using the external Sequelize instance option (v2.1.0+), be aware of these critical considerations:

Critical Risks

| Risk | Impact | Mitigation | | ------------------------------ | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | Model Namespace Conflicts | High - Runtime errors if you have models named User, Role, Permission, etc. | Access RBAC models via userService.getModels() and namespace them separately | | Auto-Sync in Production | High - Can alter/corrupt existing schema | Always set autoSync: false with external instances | | Database Schema Pollution | Medium - Adds 9 RBAC tables to your database | Review table names before integration; consider using separate schema (PostgreSQL) | | Sequelize Version Mismatch | High - Initialization failures | Ensure Sequelize ^6.35.0 in your package.json | | Connection Lifecycle | Low - Memory leaks if not managed | You must close your Sequelize instance; service only clears reference |

Best Practices Checklist

✅ DO:

  • Set autoSync: false when using external Sequelize instance in production
  • Test thoroughly in staging environment before production deployment
  • Document which models belong to RBAC vs your application
  • Keep Sequelize version at ^6.35.0 or compatible
  • Use AuthRequest type for authenticated routes in TypeScript
  • Review the External Sequelize Guide before integration

❌ DON'T:

  • Enable autoSync: true with external instances in production
  • Mix internal and external Sequelize instances in the same application
  • Assume RBAC operations automatically join your transactions
  • Close the Sequelize instance before closing the service
  • Modify RBAC tables directly - always use the service APIs

Quick Safety Check

// ✅ SAFE Configuration
const userService = new UserManagementService({
  sequelize: existingSequelize,
  jwtSecret: process.env.JWT_SECRET!,
  autoSync: false,  // Critical for production!
  permissions: [...],
});

// ❌ UNSAFE Configuration
const userService = new UserManagementService({
  sequelize: existingSequelize,
  jwtSecret: process.env.JWT_SECRET!,
  autoSync: true,  // DANGER: Can alter existing schema!
  permissions: [...],
});

🚀 Getting Started with v2.0.0

Step 1: Initialize Super-Admin

After deploying your application, initialize the super-admin user (one-time only):

curl -X POST http://localhost:3000/api/init-super-admin \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "firstname": "Super",
    "lastname": "Admin",
    "password": "SecurePassword123!"
  }'

Step 2: Login and Check Password Status

const response = await fetch("/api/auth/login", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    email: "[email protected]",
    password: "SecurePassword123!",
  }),
});

const { data } = await response.json();

// Check if password change is required
if (data.user.must_change_password) {
  // Redirect to password change page
  console.log("User must change password");
}

Step 3: Create Custom Roles

// Create a custom role with specific permissions
const response = await fetch("/api/roles", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${token}`,
  },
  body: JSON.stringify({
    name: "content-manager",
    description: "Manages content and publications",
    permissions: [1, 2, 5], // Permission IDs
  }),
});

Configuration

ServiceConfig Options

interface ServiceConfig {
  // Database Configuration (choose one)
  databaseUrl?: string; // Sequelize connection URL (for internal instance)
  sequelize?: Sequelize; // External Sequelize instance (NEW in v2.1.0)

  // JWT Configuration (required)
  jwtSecret: string; // Secret key for JWT signing (min 32 chars recommended)
  jwtExpiresIn?: string; // Access token expiration (default: '24h')
  jwtRefreshExpiresIn?: string; // Refresh token expiration (default: '7d')

  // Permission Configuration (optional)
  permissions?: string[] | PermissionConfig[]; // Initial permissions to create

  // Password Configuration (optional)
  passwordResetExpiresIn?: number; // Password reset token expiration in hours (default: 1)
  bcryptRounds?: number; // Bcrypt salt rounds (default: 10)

  // Database Sync Configuration (optional)
  autoSync?: boolean; // Auto-sync database schema (default: false)
}

Configuration Options Explained

Database Configuration

You must provide either databaseUrl OR sequelize, but not both:

  • databaseUrl (string, optional): PostgreSQL/MySQL/SQLite connection string
    • Use this for standalone applications
    • The service creates its own Sequelize instance
    • Example: "postgresql://user:password@localhost:5432/mydb"
  • sequelize (Sequelize instance, optional): Your existing Sequelize instance
    • NEW in v2.1.0 - Prevents "multiple Sequelize instances" errors
    • Use this when integrating with existing applications
    • Shares connection pool with your app
    • See External Sequelize Guide for details

JWT Configuration

  • jwtSecret (string, required): Secret key for signing JWT tokens

    • Minimum 32 characters recommended for security
    • Use environment variables in production
    • Example: process.env.JWT_SECRET
  • jwtExpiresIn (string, optional, default: '24h'): Access token lifetime

    • Format: '15m', '1h', '24h', '7d', '30d'
    • Shorter = more secure, longer = better UX
    • Recommended: '1h' to '24h'
  • jwtRefreshExpiresIn (string, optional, default: '7d'): Refresh token lifetime

    • Format: Same as jwtExpiresIn
    • Should be longer than access token
    • Recommended: '7d' to '30d'

Permission Configuration

  • permissions (array, optional): Initial permissions to create on startup
    • Can be simple strings: ['users.read', 'users.write']
    • Or objects with descriptions: [{ name: 'users.read', description: 'View users' }]
    • Permissions can also be created via API after initialization
    • See Permission Management section

Password Configuration

  • passwordResetExpiresIn (number, optional, default: 1): Password reset token expiration in hours

    • Recommended: 1 to 24 hours
    • Shorter = more secure
  • bcryptRounds (number, optional, default: 10): Bcrypt hashing rounds

    • Higher = more secure but slower
    • Recommended: 10 to 12
    • Don't go below 10 in production

Database Sync Configuration

  • autoSync (boolean, optional, default: false): Automatically sync database schema
    • ⚠️ IMPORTANT: Always set to false in production
    • Set to true only in development for convenience
    • In production, use proper migration tools
    • When using external Sequelize instance, always set to false

Token Expiration Configuration:

  • jwtExpiresIn - Controls how long access tokens are valid (default: 24 hours)
  • jwtRefreshExpiresIn - Controls how long refresh tokens are valid (default: 7 days)
  • passwordResetExpiresIn - Controls how long password reset tokens are valid in hours (default: 1 hour)
  • JWT formats: '15m' (15 minutes), '1h' (1 hour), '24h' (24 hours), '7d' (7 days), '30d' (30 days)
  • Password reset: numeric hours (e.g., 1, 2, 24)

Note: The permissions field is now optional. You can choose to initialize permissions through configuration or create them manually via the API.

Permission Management

The service now supports two modes for managing permissions:

1. Configuration-Based Initialization (Traditional)

Initialize permissions when the service starts using the configuration:

const userService = new UserManagementService({
  databaseUrl: process.env.DATABASE_URL,
  jwtSecret: process.env.JWT_SECRET,
  permissions: ["users.read", "users.write", "users.delete"],
});

2. Manual Permission Management (New)

Skip configuration-based initialization and create permissions dynamically via API:

const userService = new UserManagementService({
  databaseUrl: process.env.DATABASE_URL,
  jwtSecret: process.env.JWT_SECRET,
  // No permissions array - create them manually via API
});

await userService.initialize();

// Later, create permissions via API
// POST /api/permissions

You can also use both approaches together. Permissions defined in configuration will be created on startup, and you can add more permissions via the API later.

Permission Configuration

You can configure permissions in two ways:

Simple String Array

const userService = new UserManagementService({
  databaseUrl: process.env.DATABASE_URL,
  jwtSecret: process.env.JWT_SECRET,
  permissions: [
    "users.read",
    "users.write",
    "users.delete",
    "roles.manage",
    "admin.access",
  ],
});

Object Array with Descriptions

const userService = new UserManagementService({
  databaseUrl: process.env.DATABASE_URL,
  jwtSecret: process.env.JWT_SECRET,
  permissions: [
    { name: "users.read", description: "View user information" },
    { name: "users.write", description: "Create and update users" },
    { name: "users.delete", description: "Delete users" },
    { name: "roles.read", description: "View role information" },
    { name: "roles.write", description: "Create and update roles" },
    { name: "roles.delete", description: "Delete roles" },
    { name: "permissions.read", description: "View permissions" },
    { name: "permissions.write", description: "Create and update permissions" },
    { name: "permissions.delete", description: "Delete permissions" },
  ],
});

Required Permissions

The application uses the following hardcoded permissions in route middleware. You must create these permissions for the system to work properly:

User Management Permissions

| Permission | Description | Used In | | -------------- | ------------------------------------------------- | ------------------------------------------------------------------------------ | | users.read | View user information and list users | GET /api/users, GET /api/users/:id | | users.write | Create and update users, assign roles/permissions | PUT /api/users/:id, POST /api/users/:id/roles, POST /api/users/:id/permissions | | users.delete | Delete users from the system | DELETE /api/users/:id |

Role Management Permissions

| Permission | Description | Used In | | -------------- | ------------------------------------------- | -------------------------------------------------------------------- | | roles.read | View role information and list roles | GET /api/roles, GET /api/roles/:id | | roles.write | Create and update roles, assign permissions | POST /api/roles, PUT /api/roles/:id, POST /api/roles/:id/permissions | | roles.delete | Delete custom roles from the system | DELETE /api/roles/:id |

Permission Management Permissions

| Permission | Description | Used In | | -------------------- | ------------------------------------------------ | ----------------------------------------------- | | permissions.read | View permission information and list permissions | GET /api/permissions, GET /api/permissions/:id | | permissions.write | Create and update permissions | POST /api/permissions, PUT /api/permissions/:id | | permissions.delete | Delete permissions from the system | DELETE /api/permissions/:id |

Total: 9 Required Permissions

Creating Permissions

Option 1: Via Configuration (Recommended)

const userService = new UserManagementService({
  databaseUrl: process.env.DATABASE_URL,
  jwtSecret: process.env.JWT_SECRET,
  permissions: [
    { name: "users.read", description: "View user information and list users" },
    {
      name: "users.write",
      description: "Create and update users, assign roles and permissions",
    },
    { name: "users.delete", description: "Delete users from the system" },
    { name: "roles.read", description: "View role information and list roles" },
    {
      name: "roles.write",
      description: "Create and update roles, assign permissions to roles",
    },
    {
      name: "roles.delete",
      description: "Delete custom roles from the system",
    },
    {
      name: "permissions.read",
      description: "View permission information and list permissions",
    },
    { name: "permissions.write", description: "Create and update permissions" },
    {
      name: "permissions.delete",
      description: "Delete permissions from the system",
    },
  ],
});

Option 2: Via SQL Script

Run the PERMISSIONS-SEED.sql script (included in the package) to create all required permissions and assign them to super-admin and admin roles:

psql -U username -d database_name -f PERMISSIONS-SEED.sql

Option 3: Via API

After initializing super-admin, create permissions via API:

# Create each permission
curl -X POST http://localhost:3000/api/permissions \
  -H "Authorization: Bearer $SUPER_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"users.read","description":"View user information and list users"}'

API Documentation

🆕 New Endpoints in v2.0.0

Initialize Super-Admin (Public, One-Time Only)

POST /api/init-super-admin
Content-Type: application/json

{
  "email": "[email protected]",
  "firstname": "Super",
  "lastname": "Admin",
  "password": "SecurePassword123!"
}

Note: This endpoint only works when no super-admin exists in the system.

Transfer Super-Admin Role

PATCH /api/super-admin/transfer
Authorization: Bearer <super-admin-token>
Content-Type: application/json

{
  "targetUserId": 5
}

Requires: super-admin role. Transfers super-admin role to another admin user.

Change Password

POST /api/auth/change-password
Authorization: Bearer <token>
Content-Type: application/json

{
  "currentPassword": "OldPassword123!",
  "newPassword": "NewSecurePassword456!"
}

Behavior: Automatically sets must_change_password to false after successful password change.

Authentication Routes

Register User

POST /api/auth/register
Content-Type: application/json

{
  "email": "[email protected]",
  "firstname": "John",
  "lastname": "Doe",
  "password": "SecurePassword123!"
}

Login

POST /api/auth/login
Content-Type: application/json

{
  "email": "[email protected]",
  "password": "SecurePassword123!"
}

Response (v2.0.0 includes must_change_password):

{
  "success": true,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "user": {
      "id": 1,
      "email": "[email protected]",
      "firstname": "John",
      "lastname": "Doe",
      "status": "active",
      "must_change_password": false,
      "email_verified_at": "2025-10-10T09:00:00.000Z",
      "created_at": "2025-10-10T08:00:00.000Z",
      "updated_at": "2025-10-10T09:00:00.000Z"
    },
    "roles": [
      {
        "id": 1,
        "name": "user",
        "description": "Standard user role"
      }
    ],
    "permissions": [
      {
        "id": 1,
        "name": "users.read",
        "description": "View user information"
      }
    ]
  }
}

Refresh Token

POST /api/auth/refresh
Authorization: Bearer <token>

User Management Routes

All user routes require authentication.

List Users

GET /api/users?page=1&limit=10
Authorization: Bearer <token>

Get User by ID

GET /api/users/:id
Authorization: Bearer <token>

Update User

PUT /api/users/:id
Authorization: Bearer <token>
Content-Type: application/json

{
  "firstname": "Jane",
  "status": "active"
}

Delete User

DELETE /api/users/:id
Authorization: Bearer <token>

Assign Role to User

POST /api/users/:id/roles
Authorization: Bearer <token>
Content-Type: application/json

{
  "roleId": 2
}

Remove Role from User

DELETE /api/users/:id/roles/:roleId
Authorization: Bearer <token>

Get User Permissions

GET /api/users/:id/permissions
Authorization: Bearer <token>

Role Management Routes

Create Custom Role (v2.0.0 Enhanced)

POST /api/roles
Authorization: Bearer <token>
Content-Type: application/json

{
  "name": "content-manager",
  "description": "Manages content and publications",
  "permissions": [1, 2, 5, 8]
}

v2.0.0 Features:

  • Accepts permissions array (permission IDs to assign)
  • Tracks creator via created_by field
  • Validates permission scope (admins can only grant permissions they have)
  • Prevents using reserved names (super-admin, admin)
  • Returns role with full permission details

Response:

{
  "success": true,
  "data": {
    "id": 5,
    "name": "content-manager",
    "description": "Manages content and publications",
    "created_by": 2,
    "permissions": [
      {
        "id": 1,
        "name": "content.read",
        "description": "Read content"
      },
      {
        "id": 2,
        "name": "content.write",
        "description": "Write content"
      }
    ],
    "created_at": "2025-10-10T09:00:00.000Z",
    "updated_at": "2025-10-10T09:00:00.000Z"
  }
}

List Roles

GET /api/roles
Authorization: Bearer <token>

Get Role by ID

GET /api/roles/:id
Authorization: Bearer <token>

Update Role

PUT /api/roles/:id
Authorization: Bearer <token>
Content-Type: application/json

{
  "description": "Updated description"
}

Delete Role

DELETE /api/roles/:id
Authorization: Bearer <token>

Assign Permission to Role

POST /api/roles/:id/permissions
Authorization: Bearer <token>
Content-Type: application/json

{
  "permissionId": 3
}

Remove Permission from Role

DELETE /api/roles/:id/permissions/:permissionId
Authorization: Bearer <token>

Permission Management Routes

Create Permission Manually

POST /api/permissions
Authorization: Bearer <token>
Content-Type: application/json

{
  "name": "content.publish",
  "description": "Publish content to production"
}

List All Permissions

Returns ALL permissions in the system, including those from configuration and manually created ones.

GET /api/permissions
Authorization: Bearer <token>

Note: This endpoint returns every permission in the system:

  • Permissions defined in the initial ServiceConfig
  • Permissions created manually via POST /api/permissions
  • Permissions created programmatically through the service

Update Permission

PUT /api/permissions/:id
Authorization: Bearer <token>
Content-Type: application/json

{
  "description": "Updated description"
}

Direct Permission Assignment

You can now assign permissions directly to users without requiring role creation. This is useful for exceptional cases or temporary access.

Assign Permission Directly to User

POST /api/users/:id/permissions
Authorization: Bearer <token>
Content-Type: application/json

{
  "permissionId": 5
}

Remove Direct Permission from User

DELETE /api/users/:id/permissions/:permissionId
Authorization: Bearer <token>

Note: Removing a direct permission only removes the direct assignment. If the user has the same permission through a role, they will still have access.

Get Categorized Permissions

View which permissions come from roles vs direct assignment:

GET /api/users/:id/permissions/categorized
Authorization: Bearer <token>

Response:

{
  "roleBased": [
    {
      "permission": {
        "id": 1,
        "name": "users.read",
        "description": "View user information"
      },
      "roles": [
        {
          "id": 1,
          "name": "admin",
          "description": "Administrator role"
        }
      ]
    }
  ],
  "direct": [
    {
      "id": 5,
      "name": "special.access",
      "description": "Special temporary access"
    }
  ]
}

Middleware

Authentication Middleware

Verifies JWT token and attaches user to request object.

const { authenticate } = userService.getMiddleware();

app.get("/api/protected", authenticate, (req, res) => {
  res.json({ user: req.user });
});

Authorization Middleware

Require Single Permission

const { authenticate, requirePermission } = userService.getMiddleware();

app.delete(
  "/api/users/:id",
  authenticate,
  requirePermission("users.delete"),
  (req, res) => {
    // Only users with 'users.delete' permission can access
  }
);

Require Role

const { authenticate, requireRole } = userService.getMiddleware();

app.get("/api/admin", authenticate, requireRole("admin"), (req, res) => {
  // Only users with 'admin' role can access
});

Require Any Permission

const { authenticate, requireAnyPermission } = userService.getMiddleware();

app.get(
  "/api/content",
  authenticate,
  requireAnyPermission(["content.read", "content.write"]),
  (req, res) => {
    // Users with either permission can access
  }
);

Require All Permissions

const { authenticate, requireAllPermissions } = userService.getMiddleware();

app.post(
  "/api/sensitive",
  authenticate,
  requireAllPermissions(["admin.access", "sensitive.write"]),
  (req, res) => {
    // Users must have both permissions
  }
);

Service Methods

Access service instances for programmatic use:

const services = userService.getServices();

// AuthService
await services.auth.register(userData);
await services.auth.login({ email, password });
await services.auth.verifyToken(token);
await services.auth.refreshToken(token);

// UserService
await services.user.createUser(userData);
await services.user.getUserById(id);
await services.user.getUserByEmail(email);
await services.user.updateUser(id, updateData);
await services.user.deleteUser(id);
await services.user.assignRole(userId, roleId);
await services.user.removeRole(userId, roleId);
await services.user.assignPermission(userId, permissionId); // New
await services.user.removePermission(userId, permissionId); // New
await services.user.getUserPermissions(userId);
await services.user.getUserPermissionsCategorized(userId); // New
await services.user.getUserRoles(userId); // New

// RoleService
await services.role.createRole({ name, description });
await services.role.getRoleById(id);
await services.role.getRoleByName(name);
await services.role.updateRole(id, updateData);
await services.role.deleteRole(id);
await services.role.assignPermission(roleId, permissionId);
await services.role.removePermission(roleId, permissionId);

// PermissionService
await services.permission.createPermission(name, description);
await services.permission.createPermissionWithValidation(name, description); // New
await services.permission.getPermissionById(id);
await services.permission.getPermissionByName(name);
await services.permission.getAllPermissions();
await services.permission.getAllPermissionsSorted(); // New
await services.permission.updatePermission(id, updateData); // New
await services.permission.deletePermission(id);
await services.permission.initializePermissions(permissions); // Now optional parameter

// AuditService
await services.audit.log(auditData);
await services.audit.getUserAudits(userId);
await services.audit.getAuditsByEvent(eventName);

Advanced Usage

Custom Routes with Middleware

import express from "express";
import { UserManagementService } from "@vfd-baas-engineering/user-management-rbac";

const app = express();
const userService = new UserManagementService(config);
await userService.initialize();

// Get middleware
const { authenticate, requirePermission } = userService.getMiddleware();

// Get services for custom logic
const services = userService.getServices();

// Custom route with authentication and authorization
app.post(
  "/api/custom/action",
  authenticate,
  requirePermission("custom.action"),
  async (req, res, next) => {
    try {
      // Access authenticated user
      const user = req.user;

      // Use services
      const permissions = await services.user.getUserPermissions(user.id);

      // Log audit
      await services.audit.log({
        user_id: user.id,
        event: "custom.action",
        action: "performed",
        ip_address: req.ip,
        user_agent: req.get("user-agent"),
      });

      res.json({ success: true });
    } catch (error) {
      next(error);
    }
  }
);

Manual Database Sync

const userService = new UserManagementService({
  ...config,
  autoSync: false, // Don't auto-sync
});

await userService.initialize();

// Manually sync when ready
// This gives you control over when tables are created

Custom Token Expiry Configuration

Control how long access and refresh tokens remain valid:

const userService = new UserManagementService({
  databaseUrl: process.env.DATABASE_URL,
  jwtSecret: process.env.JWT_SECRET,
  jwtExpiresIn: "1h", // Access tokens expire in 1 hour
  jwtRefreshExpiresIn: "30d", // Refresh tokens expire in 30 days
  permissions: ["users.read", "users.write", "users.delete"],
});

Common Configurations:

// High security (short-lived tokens)
{
  jwtExpiresIn: "15m",        // 15 minutes
  jwtRefreshExpiresIn: "1d"   // 1 day
}

// Balanced (default)
{
  jwtExpiresIn: "24h",        // 24 hours
  jwtRefreshExpiresIn: "7d"   // 7 days
}

// Extended sessions
{
  jwtExpiresIn: "7d",         // 7 days
  jwtRefreshExpiresIn: "90d"  // 90 days
}

// Custom password reset expiry
{
  jwtExpiresIn: "24h",
  jwtRefreshExpiresIn: "7d",
  passwordResetExpiresIn: 2   // 2 hours for password reset
}

Environment Variables

# .env file
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
JWT_SECRET=your-super-secret-key-at-least-32-characters-long
JWT_EXPIRES_IN=24h
JWT_REFRESH_EXPIRES_IN=7d
PASSWORD_RESET_EXPIRES_IN=1
BCRYPT_ROUNDS=10
import dotenv from "dotenv";
dotenv.config();

const userService = new UserManagementService({
  databaseUrl: process.env.DATABASE_URL!,
  jwtSecret: process.env.JWT_SECRET!,
  jwtExpiresIn: process.env.JWT_EXPIRES_IN || "24h",
  jwtRefreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN || "7d",
  passwordResetExpiresIn: parseInt(
    process.env.PASSWORD_RESET_EXPIRES_IN || "1"
  ),
  bcryptRounds: parseInt(process.env.BCRYPT_ROUNDS || "10"),
  permissions: ["users.read", "users.write", "users.delete"],
});

Migration Guide

Upgrading from Config-Only Permissions

If you're upgrading from an earlier version that only supported config-based permissions, here's what you need to know:

Breaking Changes

None! The upgrade is fully backward compatible. Your existing code will continue to work without any changes.

What's New

  1. Optional Permissions Configuration

    • The permissions field in ServiceConfig is now optional
    • You can omit it entirely and manage permissions via API
  2. Enhanced Login Response

    • Login now returns refreshToken, roles, and permissions arrays
    • Update your client code to handle the new response structure:
// Before
const { token, user } = await login(credentials);

// After
const { token, refreshToken, user, roles, permissions } = await login(
  credentials
);
  1. Direct Permission Assignment

    • New endpoints for assigning permissions directly to users
    • New UserPermission junction table (automatically created)
  2. Multiple Roles Per User

    • Users can now have multiple roles
    • Permissions are aggregated from all roles

Migration Steps

Step 1: Update Dependencies

npm install @vfd-baas-engineering/user-management-rbac@latest

Step 2: Database Migration

The service will automatically create the new user_permissions table when you run with autoSync: true or manually sync:

const userService = new UserManagementService(config);
await userService.initialize();
// New table is created automatically

Step 3: Update Client Code (Optional)

Update your login handler to use the new response fields:

// Old code still works
const response = await fetch("/api/auth/login", {
  method: "POST",
  body: JSON.stringify({ email, password }),
});
const { token, user } = await response.json();

// New code can access additional fields
const { token, refreshToken, user, roles, permissions } = await response.json();

// Store permissions for client-side authorization
localStorage.setItem("userPermissions", JSON.stringify(permissions));

Step 4: Adopt New Features (Optional)

Start using the new features at your own pace:

// Create permissions dynamically
await fetch("/api/permissions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "content.publish",
    description: "Publish content",
  }),
});

// Assign permissions directly to users
await fetch(`/api/users/${userId}/permissions`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ permissionId: 5 }),
});

Rollback

If you need to rollback, simply downgrade the package version. The new user_permissions table won't affect the existing functionality.

Database Schema

The service automatically creates the following tables:

  • users - User accounts with authentication details
  • roles - Role definitions
  • permissions - Permission definitions
  • users_has_roles - User-role associations (many-to-many)
  • role_has_permissions - Role-permission associations (many-to-many)
  • user_permissions - Direct user-permission associations (many-to-many) [New]
  • user_tokens - User verification and authentication tokens
  • password_resets - Password reset tokens [New]
  • audits - Audit log entries

Token Types (user_tokens table)

The user_tokens table supports multiple token types for different purposes:

  • email_verification - Email verification tokens (24 hours expiry)
  • password_reset - Password reset tokens (1 hour expiry)
  • refresh_token - JWT refresh tokens (7 days expiry)
  • api_token - API access tokens (configurable expiry)
  • magic_link - Passwordless login tokens (15 minutes expiry)

Each token includes:

  • Unique token ID (UUID)
  • Cryptographically secure token value
  • Expiry timestamp
  • Revocation status
  • User association

User Status Values

  • pending - Newly registered, awaiting verification
  • active - Active and verified user
  • inactive - Temporarily inactive
  • flagged - Flagged for review
  • blocked - Blocked from access
  • approved - Manually approved
  • rejected - Registration rejected

Error Handling

v2.0.0 Standardized Error Responses

The service now provides consistent, structured error responses with error codes for programmatic handling:

{
  "success": false,
  "error": {
    "message": "Human-readable error message",
    "code": "ERROR_CODE",
    "statusCode": 400
  }
}

Error Types

  • 400 Bad Request - Validation errors (VALIDATION_ERROR)
  • 401 Unauthorized - Authentication failures (AUTHENTICATION_ERROR, INVALID_CREDENTIALS)
  • 403 Forbidden - Authorization failures (AUTHORIZATION_ERROR, INSUFFICIENT_PERMISSIONS)
  • 404 Not Found - Resource not found (NOT_FOUND)
  • 409 Conflict - Duplicate resources (CONFLICT, DUPLICATE_ROLE_NAME)
  • 500 Internal Server Error - Server errors (INTERNAL_ERROR)

Role-Specific Error Codes (v2.0.0)

  • RESERVED_ROLE_NAME - Attempting to use system role name
  • DUPLICATE_ROLE_NAME - Role name already exists
  • SYSTEM_ROLE_PROTECTED - Cannot modify/delete system role
  • INSUFFICIENT_PERMISSIONS - Cannot perform action with current permissions
  • SUPER_ADMIN_EXISTS - Super-admin already initialized
  • NOT_SUPER_ADMIN - Action requires super-admin role
  • SELF_TRANSFER_NOT_ALLOWED - Cannot transfer super-admin to self
  • TARGET_NOT_ADMIN - Transfer target must be admin

Using Error Codes

import {
  ValidationError,
  AuthorizationError,
} from "@vfd-baas-engineering/user-management-rbac";

try {
  await roleService.createRole({ name: "super-admin" });
} catch (error) {
  if (error.code === "RESERVED_ROLE_NAME") {
    console.error("Cannot use system role name");
  } else if (error.code === "INSUFFICIENT_PERMISSIONS") {
    console.error("You lack required permissions");
  }
}

Security Best Practices

  1. Use Strong JWT Secrets - Minimum 32 characters, use environment variables
  2. HTTPS Only - Always use HTTPS in production
  3. Rate Limiting - Implement rate limiting on authentication endpoints
  4. Password Policies - Enforce strong password requirements
  5. Token Expiration - Use reasonable token expiration times
  6. Audit Logging - Monitor audit logs for suspicious activity
  7. Input Validation - The service validates inputs, but add additional validation as needed

Swagger/OpenAPI Documentation

This service includes a complete OpenAPI 3.0 specification for easy integration with Swagger UI or other API documentation tools.

Quick Setup with Swagger UI

import express from "express";
import swaggerUi from "swagger-ui-express";
import YAML from "yamljs";
import { UserManagementService } from "@vfd-baas-engineering/user-management-rbac";

const app = express();
const userService = new UserManagementService(config);
await userService.initialize();

// Load OpenAPI spec
const swaggerDocument = YAML.load(
  "./node_modules/@vfd-baas-engineering/user-management-rbac/openapi.yaml"
);

// Serve Swagger UI
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));

// Mount user management routes
app.use("/api", userService.getRouter());

app.listen(3000, () => {
  console.log("API docs available at http://localhost:3000/api-docs");
});

Features in the OpenAPI Spec

  • Complete endpoint documentation with request/response schemas
  • Required permissions clearly documented for each endpoint
  • Authentication flow with JWT bearer tokens
  • Example requests and responses
  • Error response schemas

See openapi.yaml for the complete specification.

TypeScript Support

Full TypeScript definitions are included with the package.

Available Types and Interfaces

import {
  // Main Service
  UserManagementService,

  // Configuration Types
  ServiceConfig,
  PermissionConfig,

  // Model Attributes
  UserAttributes,
  RoleAttributes,
  PermissionAttributes,
  AuditAttributes,

  // Request/Response Types
  AuthRequest, // Extended Express Request with user property
  CreateUserData,
  UpdateUserData,
  UserPublicData,
  LoginCredentials,
  AuthResponse,
  TokenPayload,
  CreateRoleData,
  AuditLogData,
  PaginationOptions,
  PaginatedResponse,
  CreatePermissionData,
  UpdatePermissionData,
  PermissionData,
  RoleData,
  CategorizedPermissionsResponse,

  // Model Classes
  User,
  Role,
  Permission,
  Audit,
  UserRole,
  RolePermission,
  UserPermission,
  UserToken,
  PasswordReset,

  // Services
  Services,
  Middleware,

  // Error Classes
  AppError,
  ValidationError,
  AuthenticationError,
  AuthorizationError,
  NotFoundError,
  ConflictError,
  InternalError,
  formatErrorResponse,
} from "@vfd-baas-engineering/user-management-rbac";

Using AuthRequest in Your Routes

When using the authentication middleware, use the AuthRequest type instead of Express's default Request type to access the authenticated user:

import express, { Request, Response } from "express";
import {
  UserManagementService,
  AuthRequest,
} from "@vfd-baas-engineering/user-management-rbac";

const app = express();
const userService = new UserManagementService(config);
await userService.initialize();

const { authenticate, requirePermission } = userService.getMiddleware();

// ✅ Correct: Use AuthRequest for authenticated routes
app.get(
  "/api/profile",
  authenticate,
  async (req: AuthRequest, res: Response) => {
    const userId = req.user!.id; // TypeScript knows req.user exists
    const user = await userService.getServices().user.getUserById(userId);
    res.json({ user });
  }
);

// ✅ Correct: Use Request for public routes
app.get("/api/health", (req: Request, res: Response) => {
  res.json({ status: "ok" });
});

// ❌ Wrong: Using Request for authenticated routes
app.get("/api/profile", authenticate, async (req: Request, res: Response) => {
  const userId = req.user!.id; // TypeScript error: Property 'user' does not exist
  // ...
});

Type-Safe Service Usage

import {
  UserManagementService,
  Services,
  AuditLogData,
} from "@vfd-baas-engineering/user-management-rbac";

const userService = new UserManagementService(config);
await userService.initialize();

// Get typed services
const services: Services = userService.getServices();

// Type-safe audit logging
const auditData: AuditLogData = {
  user_id: 123,
  event: "user_login",
  action: "login",
  ip_address: "192.168.1.1",
  user_agent: "Mozilla/5.0...",
};

await services.audit.log(auditData);

Custom Type Declarations

If you need to extend the types in your application:

// types/express.d.ts
import { UserAttributes } from "@vfd-baas-engineering/user-management-rbac";

declare global {
  namespace Express {
    interface Request {
      user?: UserAttributes;
      token?: string;
    }
  }
}

Testing

# Run tests
npm test

# Run tests in watch mode
npm run test:watch

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT License - see LICENSE file for details

Support

For issues, questions, or contributions, please visit the GitHub repository.

🆕 New Features in v2.0.0

Enhanced Role Management System

System-Defined Roles:

  • super-admin - Full system access, cannot be modified or deleted
  • admin - Can create custom roles and manage users within permission scope

Custom Role Creation:

// Create role with specific permissions
await roleService.createRole(
  {
    name: "content-manager",
    description: "Manages content",
    permissions: [1, 2, 5], // Permission IDs
  },
  creatorId
);

Key Features:

  • ✅ Role creator tracking (created_by field)
  • ✅ Permission scope validation (admins can only grant permissions they have)
  • ✅ Creator-based access control (only creator or super-admin can modify/delete)
  • ✅ Reserved name protection (cannot use super-admin or admin)

Super-Admin Management

Initialization:

# One-time initialization (public endpoint)
POST /api/init-super-admin

Transfer:

# Transfer super-admin role to another admin
PATCH /api/super-admin/transfer

Features:

  • ✅ Only one super-admin exists at any time
  • ✅ Super-admin cannot be deleted, only transferred
  • ✅ Target must be an admin user
  • ✅ Prevents self-transfer

Password Management

Temporary Password Tracking:

// Create user with temporary password
const user = await userService.createUserWithTemporaryPassword(
  {
    email: "[email protected]",
    firstname: "John",
    lastname: "Doe",
  },
  "TempPass123!"
);

// user.must_change_password = true

Password Change:

# User changes password (automatic flag reset)
POST /api/auth/change-password

Features:

  • must_change_password field tracks password status
  • ✅ Automatically set to true for admin-created users
  • ✅ Automatically set to false after password change
  • ✅ Included in login response for client-side handling
  • ✅ Super-admin exception (no forced password change)

Standardized Error Handling

Error Classes:

import {
  ValidationError,
  AuthenticationError,
  AuthorizationError,
  NotFoundError,
  ConflictError,
  InternalError,
  formatErrorResponse,
} from "@vfd-baas-engineering/user-management-rbac";

Features:

  • ✅ Consistent error response structure
  • ✅ Error codes for programmatic handling
  • ✅ Automatic Sequelize error handling
  • ✅ All endpoints use standardized errors

Migration to v2.0.0

Database Migrations Required

-- Add must_change_password to users table
ALTER TABLE users
ADD COLUMN must_change_password BOOLEAN NOT NULL DEFAULT true;

-- Add created_by to roles table
ALTER TABLE roles
ADD COLUMN created_by INTEGER NULL;

-- Add indexes for performance
CREATE INDEX idx_roles_created_by ON roles(created_by);
CREATE INDEX idx_users_must_change_password ON users(must_change_password);

Breaking Changes

  1. RoleService Method Signatures:

    • createRole(data, creatorId?) - Now accepts optional creatorId
    • updateRole(id, data, updaterId?) - Now accepts optional updaterId
    • deleteRole(id, deleterId?) - Now accepts optional deleterId
  2. UserPublicData Interface:

    • Now includes must_change_password field
  3. Error Responses:

    • All errors now use standardized format with error codes

Migration Steps

  1. Update Package:

    npm install @vfd-baas-engineering/[email protected]
  2. Run Database Migrations:

    # See DATABASE-MIGRATIONS.sql for complete script
  3. Initialize Super-Admin:

    curl -X POST http://localhost:3000/api/init-super-admin \
      -H "Content-Type: application/json" \
      -d '{"email":"[email protected]","firstname":"Admin","lastname":"User","password":"SecurePass123!"}'
  4. Update Error Handling:

    // Old way
    if (error.message.includes('not found')) { ... }
    
    // New way
    if (error.code === 'NOT_FOUND') { ... }
  5. Handle Password Change Requirement:

    // Check must_change_password in login response
    if (loginResponse.user.must_change_password) {
      navigate("/change-password");
    }

Backward Compatibility

  • ✅ Existing role operations continue to work
  • ✅ Optional parameters maintain backward compatibility
  • ✅ Database schema changes are additive (no data loss)
  • ✅ Error responses maintain success and error structure

Changelog

2.1.0 - 2025-10-24

  • External Sequelize Instance Support 🔌

    • Added sequelize option to ServiceConfig for using existing Sequelize instances
    • Prevents "multiple Sequelize instances" errors in integrated applications
    • Shares connection pool with host application
    • Made databaseUrl optional when sequelize is provided
    • Updated close() method to handle external instances properly (doesn't close external connections)
    • Added comprehensive External Sequelize Guide with risks and mitigations
  • TypeScript Improvements 📘

    • Explicitly exported AuthRequest type for use in consumer applications
    • Added comprehensive TypeScript documentation with usage examples
    • Documented all available types and interfaces
    • Added type-safe service usage patterns
  • Example Files 📝

    • Added examples/external-sequelize-example.ts demonstrating external instance usage
    • Fixed TypeScript errors in example files
    • Updated examples to use AuthRequest type correctly
    • Added proper audit logging examples with correct method signatures
  • Bug Fixes 🐛

    • Fixed AuditService.log() method usage in examples (was incorrectly called logAction)
    • Fixed TypeScript type errors in route handlers
    • Corrected AuditLogData structure in documentation
  • Documentation 📚

    • Expanded configuration section with detailed option explanations
    • Added risks and mitigations for external Sequelize usage
    • Added TypeScript usage guide with AuthRequest examples
    • Updated README with v2.1.0 features and breaking changes
    • Added migration path from internal to external instances

2.0.0 - 2025-10-10

  • Enhanced Role Management System

    • System-defined roles (super-admin, admin) with protected status
    • Custom role creation with permission validation
    • Role creator tracking via created_by field
    • Permission scope validation
    • Creator-based access control
    • Reserved role name validation
  • Super-Admin Management

    • One-time initialization endpoint
    • Super-admin transfer mechanism
    • Single super-admin constraint
  • Password Management

    • must_change_password field added to User model
    • Password change endpoint with automatic flag reset
    • Temporary password support
    • Password change tracking in login response
  • Standardized Error Handling

    • Custom error classes
    • Consistent error responses with error codes
    • Automatic Sequelize error handling
  • Documentation

    • Complete role management guide
    • Password management guide
    • Implementation summary
    • Database migration scripts

1.0.1

  • Enhanced Permission Management
    • Optional permissions configuration (can now be omitted)
    • Manual permission creation via API endpoints
    • Direct user-permission assignments (bypassing roles)
    • Enhanced login response with roles and permissions
    • Multiple roles per user support
    • Categorized permissions endpoint (role-based vs direct)
    • New UserPermission junction table
    • New permission management routes (POST, GET, PUT, DELETE)
    • New user permission routes (assign, remove, categorized view)
    • Configurable refresh token expiry (jwtRefreshExpiresIn)
    • Configurable password reset token expiry (passwordResetExpiresIn)
    • Dedicated password_resets table for password reset tokens
    • Complete OpenAPI/Swagger specification
    • Backward compatible with existing implementations

1.0.0

  • Initial release
  • JWT-based authentication
  • Role-based access control
  • User management
  • Audit logging
  • Express middleware and routes
  • TypeScript support

Quick Reference

v2.0.0 New Endpoints

| Endpoint | Method | Auth | Description | | --------------------------- | ------ | ----------------- | ----------------------------------- | | /api/init-super-admin | POST | Public | Initialize super-admin (one-time) | | /api/super-admin/transfer | PATCH | Super-Admin | Transfer super-admin role | | /api/auth/change-password | POST | Authenticated | Change password | | /api/roles (enhanced) | POST | Admin/Super-Admin | Create custom role with permissions |

Key Features Summary

| Feature | Description | | ------------------------- | ---------------------------------------------- | | System Roles | super-admin and admin are protected | | Custom Roles | Create roles with specific permissions | | Permission Validation | Admins can only grant permissions they have | | Password Tracking | must_change_password field in login response | | Error Codes | Standardized error codes for all endpoints | | Creator Tracking | Track who created each custom role |

Common Use Cases

1. First-Time Setup:

# Initialize super-admin
POST /api/init-super-admin

# Login as super-admin
POST /api/auth/login

# Create permissions
POST /api/permissions

# Create custom roles
POST /api/roles

2. Admin Creates User:

// Create user with temporary password
const user = await userService.createUserWithTemporaryPassword(
  {
    email: "[email protected]",
    firstname: "John",
    lastname: "Doe",
  },
  "TempPass123!"
);

// User must change password on first login
// user.must_change_password === true

3. User First Login:

// Login
const { data } = await login(email, password);

// Check password change requirement
if (data.user.must_change_password) {
  // Redirect to password change page
  navigate("/change-password");
}

4. Create Custom Role:

// Admin creates content manager role
await fetch("/api/roles", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "content-manager",
    description: "Manages content",
    permissions: [1, 2, 5], // Only permissions admin has
  }),
});

Additional Documentation