authwall
v2.0.0
Published
OTP-based authentication and full admin user-management REST API for Express + MongoDB
Downloads
603
Maintainers
Readme
🔐 authwall
OTP-based authentication + full admin user-management REST API for Express + MongoDB
Zero-friction auth for Express apps — OTP login, JWT tokens, RBAC, full admin API, audit logs, and more. Drop it in, configure, ship.
📋 Table of Contents
- Features
- Requirements
- Installation
- Quick Start
- Custom User Model
- Configuration
- API Reference
- Middleware Helpers
- Two-Factor Authentication
- Event System
- Rate Limiting — Multi-Server
- MongoDB Collections
- Security Best Practices
✨ Features
| | Feature | Details |
|---|---|---|
| 📧 | Email OTP login | Auto-registers new users on first login |
| 🔐 | JWT authentication | Configurable expiry, no insecure defaults |
| 👥 | RBAC | Role & permission system, fully configurable |
| 🛡️ | Admin API | Suspend, ban, impersonate, bulk actions |
| 📊 | Audit trail | Login history + admin logs with request IDs |
| 📤 | CSV export | Download full user list |
| 🚦 | Rate limiting | Built-in on OTP endpoints |
| 🌐 | CORS | Configured out of the box |
| ❤️ | Health check | GET /api/health |
| 🎉 | Event system | Hook into every auth action via events |
| 🧩 | Zero conflicts | All collections prefixed otpguard_* |
📦 Requirements
- Node.js
>= 18 - Express
4+ - MongoDB via Mongoose
8+
🚀 Installation
npm install authwall⚡ Quick Start
// app.js
const express = require('express');
const mongoose = require('mongoose');
const { authwall } = require('authwall');
const app = express();
app.use(express.json());
mongoose.connect(process.env.MONGO_URI || 'mongodb://localhost:27017/myapp');
authwall(app, {
seed: true, // seed default roles & permissions on startup
cleanupCron: true, // auto-clean expired OTPs every 15 min
config: {
jwt: {
secret: process.env.JWT_SECRET, // ⚠️ required — set in your .env
},
email: {
from: '[email protected]',
transport: {
host: process.env.MAIL_HOST,
port: 587,
auth: { user: process.env.MAIL_USER, pass: process.env.MAIL_PASS },
},
},
},
});
app.listen(3000, () => console.log('Server running on port 3000'));🧩 Using Your Own User Model
// models/User.js
const mongoose = require('mongoose');
const { addUserAdminFields } = require('authwall');
const userSchema = new mongoose.Schema({
name: String,
email: { type: String, unique: true },
password: String,
}, { timestamps: true });
addUserAdminFields(userSchema); // adds status, roles, OTP helpers, etc.
module.exports = mongoose.model('User', userSchema);// app.js
const User = require('./models/User');
authwall(app, { getUserModel: () => User, config: { ... } });⚙️ Configuration
authwall(app, {
config: {
routePrefix: '/api', // default '/api'
routeMiddleware: [], // global middleware before all routes
otp: {
expiresInMinutes: 5, // auto-set to 15 in development
maxAttempts: 3,
rateLimitCount: 3,
rateLimitMinutes: 5,
codeLength: 6, // auto-set to 4 in development
},
jwt: {
secret: process.env.JWT_SECRET, // required — no insecure default
expiresIn: '7d',
},
defaultRoleSlug: 'user',
adminRoleSlug: 'admin',
autoRegister: true,
perPage: 15,
rateLimit: {
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // max OTP attempts per window
},
cors: {
enabled: true,
origin: process.env.CORS_ORIGIN || '*',
credentials: true,
},
email: {
from: '[email protected]',
transport: { /* nodemailer options */ },
},
},
});📡 API Reference
All routes are mounted under routePrefix (default /api).
General
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | /api/health | Health check — returns service info |
Authentication
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | /api/auth/otp/send | — | Send OTP to email |
| POST | /api/auth/otp/verify | — | Verify OTP, receive JWT |
| POST | /api/auth/logout | Bearer | Logout |
| GET | /api/auth/me | Bearer | Get current user |
Admin — Users
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | /api/admin/users | List users — filters: status, role, search, sort_by |
| POST | /api/admin/users | Create user |
| GET | /api/admin/users/:id | Get user |
| PUT | /api/admin/users/:id | Update user |
| DELETE | /api/admin/users/:id | Permanently delete user |
| DELETE | /api/admin/users/:id/soft | Soft delete user |
| GET | /api/admin/users/export | Download CSV |
Admin — User Actions
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | /api/admin/users/:id/suspend | Suspend user |
| POST | /api/admin/users/:id/unsuspend | Unsuspend user |
| POST | /api/admin/users/:id/temporary-ban | Temporary ban |
| POST | /api/admin/users/:id/force-password-reset | Force password reset (sends email) |
| POST | /api/admin/users/:id/remove-2fa | Remove 2FA |
| POST | /api/admin/users/:id/terminate-sessions | Revoke all sessions |
| GET | /api/admin/users/:id/login-history | Login history |
| POST | /api/admin/users/:id/impersonate | Impersonate user |
| POST | /api/admin/users/stop-impersonation | Stop impersonation |
Admin — Bulk Operations
| Method | Endpoint | Body |
|--------|----------|------|
| POST | /api/admin/users/bulk/suspend | { user_ids: [...] } |
| POST | /api/admin/users/bulk/unsuspend | { user_ids: [...] } |
| POST | /api/admin/users/bulk/assign-role | { user_ids: [...], role: "moderator" } |
| POST | /api/admin/users/bulk/delete | { user_ids: [...] } |
Admin — Stats & Logs
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | /api/admin/statistics | User statistics |
| GET | /api/admin/admin-logs | Admin audit log |
🔧 Middleware Helpers
const { makeAuthMiddleware, makeAdminMiddleware, makeRoleMiddleware } =
require('authwall/src/middleware');
const auth = makeAuthMiddleware(cfg, () => User);
const admin = makeAdminMiddleware(cfg);
const mod = makeRoleMiddleware('moderator');
app.get('/dashboard', auth, admin, handler);
app.get('/mod-panel', auth, mod, handler);🔐 Two-Factor Authentication
When a user has 2FA enabled, POST /api/auth/otp/verify returns:
{
"requires_2fa": true,
"otp_id": "abc123",
"message": "Two-factor authentication required."
}The client then re-submits with a TOTP code or a backup recovery code:
// Option A — TOTP authenticator app
{ "otp_id": "abc123", "code": "381920", "totp_token": "654321" }
// Option B — backup recovery code (one-time use)
{ "otp_id": "abc123", "code": "381920", "backup_code": "ABCD1234" }No separate
/auth/2fa/recoveryendpoint — backup codes work directly through the standard verify flow, keeping the API clean.
🎉 Event System
authwall emits events for every meaningful action. Subscribe to integrate with Slack, Datadog, webhooks, Socket.io, or any custom logic — without touching core code.
const { authwall, events, EVENTS } = require('authwall');
// Log every login
events.on(EVENTS.LOGIN_SUCCESS, (data) => {
console.log(`✅ Login: ${data.email} from ${data.ip}`);
});
// Alert on failed 2FA attempts
events.on(EVENTS.TFA_FAILED, (data) => {
slackAlert(`⚠️ Failed 2FA attempt for ${data.email} from ${data.ip}`);
});
// Trigger onboarding on new user
events.on(EVENTS.USER_CREATED, async (data) => {
await crm.createContact({ email: data.email });
});
// Wildcard — catch everything
events.on('*', (payload) => {
datadogClient.event({ title: payload.event, text: JSON.stringify(payload) });
});Full Event List
| Event | Fired when |
|-------|------------|
| EVENTS.OTP_SENT | OTP email dispatched |
| EVENTS.OTP_VERIFIED | OTP successfully verified |
| EVENTS.OTP_FAILED | Invalid or rate-limited OTP attempt |
| EVENTS.LOGIN_SUCCESS | User logged in |
| EVENTS.LOGIN_FAILED | Failed login (suspended, not found, etc.) |
| EVENTS.LOGOUT | User logged out |
| EVENTS.USER_CREATED | New user registered or created by admin |
| EVENTS.USER_UPDATED | User record updated |
| EVENTS.USER_DELETED | User deleted |
| EVENTS.USER_SUSPENDED | User account suspended |
| EVENTS.USER_UNSUSPENDED | Suspension lifted |
| EVENTS.TFA_ENABLED | 2FA activated |
| EVENTS.TFA_DISABLED | 2FA disabled |
| EVENTS.TFA_FAILED | Invalid TOTP or backup code |
| EVENTS.DEVICE_TRUSTED | Device marked trusted |
| EVENTS.DEVICE_REVOKED | Device or session revoked |
| EVENTS.SESSIONS_TERMINATED | All sessions terminated |
| EVENTS.TOKEN_REFRESHED | Refresh token rotated |
| EVENTS.TOKEN_REUSE_DETECTED | Reuse attack detected — all family revoked |
| EVENTS.ADMIN_ACTION | Any admin action |
| EVENTS.IMPERSONATION_STARTED | Admin begins impersonation |
| EVENTS.BULK_USERS_IMPORTED | CSV/JSON import complete |
| EVENTS.BULK_USERS_SUSPENDED | Bulk suspend complete |
| EVENTS.BULK_USERS_DELETED | Bulk delete complete |
| EVENTS.BULK_ROLE_ASSIGNED | Bulk role assignment complete |
| EVENTS.ACCOUNT_DELETION_REQUESTED | Grace-period deletion scheduled |
| EVENTS.ACCOUNT_DELETION_COMPLETED | Account permanently purged |
| EVENTS.EMAIL_SENT | Email dispatched |
| EVENTS.EMAIL_FAILED | Email failed |
⚖️ Rate Limiting — Multi-Server Note
authwall uses in-memory rate limiting (zero extra dependencies). For multi-instance deployments behind a load balancer, swap to a Redis store:
npm install rate-limit-redis ioredisconst { rateLimit } = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');
const sharedLimiter = rateLimit({
store: new RedisStore({ client: new Redis(process.env.REDIS_URL) }),
windowMs: 15 * 60 * 1000,
max: 5,
});🗄️ MongoDB Collections
| Collection | Description |
|------------|-------------|
| otpguard_roles | Roles |
| otpguard_permissions | Permissions |
| otpguard_otps | One-time passwords — TTL index, auto-deleted on expiry |
| otpguard_login_histories | Login history |
| otpguard_admin_logs | Admin audit trail with request IDs |
| users | Users — your model or the built-in one |
🔒 Security Best Practices
- Always set
JWT_SECRETin your environment — never hardcode it - Use HTTPS in production
- Rate limiting is built-in on OTP endpoints — configure
rateLimitfor your needs - Regularly rotate JWT secrets in high-security environments
- Monitor admin audit logs (
GET /api/admin/admin-logs) for suspicious activity - Admin accounts cannot modify/suspend/delete themselves — built-in protection
- Password reset tokens are never sent in API responses — always emailed to the user
📄 License
MIT © Susheel Kumar
💼 Open to Work
Susheel Kumar is available for freelance & full-time opportunities.
Built this package? He can build yours too.
📧 [email protected] — reach out anytime!
