@vfd-baas-engineering/user-management-rbac
v2.2.0
Published
A modular, reusable Node.js/TypeScript user management service with authentication and RBAC
Maintainers
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_userFlag - Distinguish between regular app users and system contributors - 🛠️
createSystemContributor()- New UserService method to easily create contributor accounts - 🔄 Flexible Schema -
emailandpasswordare 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
sequelizeconfig option - Shares connection pool with your application
- Prevents relationship and transaction issues
- ⚠️ Important: Always set
autoSync: falsein production
- Pass your own Sequelize instance via
- 📘 Enhanced TypeScript Support - Exported
AuthRequesttype 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-rbacPeer Dependencies
You'll also need to install these peer dependencies:
npm install express sequelizeAnd a database driver (choose one):
# PostgreSQL
npm install pg pg-hstore
# MySQL
npm install mysql2
# SQLite
npm install sqlite3
# MSSQL
npm install tediousQuick 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: falsewhen 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
AuthRequesttype for authenticated routes in TypeScript - Review the External Sequelize Guide before integration
❌ DON'T:
- Enable
autoSync: truewith 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'
- Format:
jwtRefreshExpiresIn(string, optional, default:'7d'): Refresh token lifetime- Format: Same as
jwtExpiresIn - Should be longer than access token
- Recommended:
'7d'to'30d'
- Format: Same as
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
- Can be simple strings:
Password Configuration
passwordResetExpiresIn(number, optional, default:1): Password reset token expiration in hours- Recommended:
1to24hours - Shorter = more secure
- Recommended:
bcryptRounds(number, optional, default:10): Bcrypt hashing rounds- Higher = more secure but slower
- Recommended:
10to12 - Don't go below
10in production
Database Sync Configuration
autoSync(boolean, optional, default:false): Automatically sync database schema- ⚠️ IMPORTANT: Always set to
falsein production - Set to
trueonly in development for convenience - In production, use proper migration tools
- When using external Sequelize instance, always set to
false
- ⚠️ IMPORTANT: Always set to
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/permissionsYou 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.sqlOption 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
permissionsarray (permission IDs to assign) - Tracks creator via
created_byfield - 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 createdCustom 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=10import 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
Optional Permissions Configuration
- The
permissionsfield inServiceConfigis now optional - You can omit it entirely and manage permissions via API
- The
Enhanced Login Response
- Login now returns
refreshToken,roles, andpermissionsarrays - Update your client code to handle the new response structure:
- Login now returns
// Before
const { token, user } = await login(credentials);
// After
const { token, refreshToken, user, roles, permissions } = await login(
credentials
);Direct Permission Assignment
- New endpoints for assigning permissions directly to users
- New
UserPermissionjunction table (automatically created)
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@latestStep 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 automaticallyStep 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 verificationactive- Active and verified userinactive- Temporarily inactiveflagged- Flagged for reviewblocked- Blocked from accessapproved- Manually approvedrejected- 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 nameDUPLICATE_ROLE_NAME- Role name already existsSYSTEM_ROLE_PROTECTED- Cannot modify/delete system roleINSUFFICIENT_PERMISSIONS- Cannot perform action with current permissionsSUPER_ADMIN_EXISTS- Super-admin already initializedNOT_SUPER_ADMIN- Action requires super-admin roleSELF_TRANSFER_NOT_ALLOWED- Cannot transfer super-admin to selfTARGET_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
- Use Strong JWT Secrets - Minimum 32 characters, use environment variables
- HTTPS Only - Always use HTTPS in production
- Rate Limiting - Implement rate limiting on authentication endpoints
- Password Policies - Enforce strong password requirements
- Token Expiration - Use reasonable token expiration times
- Audit Logging - Monitor audit logs for suspicious activity
- 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:watchContributing
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 deletedadmin- 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_byfield) - ✅ 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-adminoradmin)
Super-Admin Management
Initialization:
# One-time initialization (public endpoint)
POST /api/init-super-adminTransfer:
# Transfer super-admin role to another admin
PATCH /api/super-admin/transferFeatures:
- ✅ 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 = truePassword Change:
# User changes password (automatic flag reset)
POST /api/auth/change-passwordFeatures:
- ✅
must_change_passwordfield tracks password status - ✅ Automatically set to
truefor admin-created users - ✅ Automatically set to
falseafter 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
RoleService Method Signatures:
createRole(data, creatorId?)- Now accepts optionalcreatorIdupdateRole(id, data, updaterId?)- Now accepts optionalupdaterIddeleteRole(id, deleterId?)- Now accepts optionaldeleterId
UserPublicData Interface:
- Now includes
must_change_passwordfield
- Now includes
Error Responses:
- All errors now use standardized format with error codes
Migration Steps
Update Package:
npm install @vfd-baas-engineering/[email protected]Run Database Migrations:
# See DATABASE-MIGRATIONS.sql for complete scriptInitialize 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!"}'Update Error Handling:
// Old way if (error.message.includes('not found')) { ... } // New way if (error.code === 'NOT_FOUND') { ... }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
successanderrorstructure
Changelog
2.1.0 - 2025-10-24
External Sequelize Instance Support 🔌
- Added
sequelizeoption toServiceConfigfor using existing Sequelize instances - Prevents "multiple Sequelize instances" errors in integrated applications
- Shares connection pool with host application
- Made
databaseUrloptional whensequelizeis provided - Updated
close()method to handle external instances properly (doesn't close external connections) - Added comprehensive External Sequelize Guide with risks and mitigations
- Added
TypeScript Improvements 📘
- Explicitly exported
AuthRequesttype for use in consumer applications - Added comprehensive TypeScript documentation with usage examples
- Documented all available types and interfaces
- Added type-safe service usage patterns
- Explicitly exported
Example Files 📝
- Added
examples/external-sequelize-example.tsdemonstrating external instance usage - Fixed TypeScript errors in example files
- Updated examples to use
AuthRequesttype correctly - Added proper audit logging examples with correct method signatures
- Added
Bug Fixes 🐛
- Fixed
AuditService.log()method usage in examples (was incorrectly calledlogAction) - Fixed TypeScript type errors in route handlers
- Corrected
AuditLogDatastructure in documentation
- Fixed
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_byfield - 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_passwordfield 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
UserPermissionjunction 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_resetstable 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/roles2. 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 === true3. 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
- 📖 ROLE-MANAGEMENT-GUIDE.md - Complete role management API guide
- 🔑 PASSWORD-MANAGEMENT.md - Password management guide
- 📊 IMPLEMENTATION-SUMMARY.md - Technical implementation details
- 🗄️ DATABASE-MIGRATIONS.sql - SQL migration scripts
- 📝 CHANGELOG.md - Full version history
