@metaadri/secureauth 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,118 @@
1
+ const Database = require('better-sqlite3');
2
+ const path = require('path');
3
+ const { v4: uuidv4 } = require('uuid');
4
+ const bcrypt = require('bcryptjs');
5
+
6
+ const dbPath = process.env.DATABASE_URL || path.join(__dirname, '..', 'secureauth.db');
7
+ const db = new Database(dbPath);
8
+ db.pragma('journal_mode = WAL');
9
+ db.pragma('foreign_keys = ON');
10
+
11
+ function initDatabase() {
12
+ try {
13
+ db.exec(`
14
+ CREATE TABLE IF NOT EXISTS users (
15
+ id TEXT PRIMARY KEY,
16
+ full_name TEXT NOT NULL,
17
+ email TEXT UNIQUE NOT NULL,
18
+ password_hash TEXT NOT NULL,
19
+ totp_secret TEXT NOT NULL,
20
+ totp_enabled INTEGER DEFAULT 1,
21
+ role TEXT DEFAULT 'user',
22
+ created_at TEXT NOT NULL,
23
+ failed_login_attempts INTEGER DEFAULT 0,
24
+ locked_until TEXT,
25
+ last_failed_login TEXT
26
+ )
27
+ `);
28
+
29
+ try {
30
+ db.exec("ALTER TABLE users ADD COLUMN role TEXT DEFAULT 'user'");
31
+ } catch (e) {}
32
+
33
+ db.exec(`
34
+ CREATE TABLE IF NOT EXISTS audit_logs (
35
+ log_id TEXT PRIMARY KEY,
36
+ user_id TEXT,
37
+ action TEXT NOT NULL,
38
+ ip_address TEXT,
39
+ user_agent TEXT,
40
+ timestamp TEXT NOT NULL,
41
+ FOREIGN KEY (user_id) REFERENCES users(id)
42
+ )
43
+ `);
44
+
45
+ console.log('✓ Database initialized successfully (SQLite)');
46
+ } catch (err) {
47
+ console.error('❌ Database initialization failed:', err.message || err);
48
+ }
49
+ }
50
+
51
+ function seedDemoUser() {
52
+ const demoEmail = 'demo@secureauth.com';
53
+ try {
54
+ const existing = db.prepare('SELECT * FROM users WHERE email = ?').get(demoEmail);
55
+ if (!existing) {
56
+ const userId = uuidv4();
57
+ const passwordHash = bcrypt.hashSync('Demo@123', 12);
58
+ const totpSecret = 'JBSWY3DPEHPK3PXP';
59
+ const createdAt = new Date().toISOString();
60
+ db.prepare(`
61
+ INSERT INTO users (id, full_name, email, password_hash, totp_secret, role, created_at)
62
+ VALUES (?, ?, ?, ?, ?, ?, ?)
63
+ `).run(userId, 'Demo User', demoEmail, passwordHash, totpSecret, 'admin', createdAt);
64
+ console.log('✓ Demo user created: demo@secureauth.com / Demo@123');
65
+ }
66
+ } catch (err) {
67
+ console.error('❌ Failed to seed demo user:', err);
68
+ }
69
+ }
70
+
71
+ function seedAdminUser() {
72
+ const adminEmail = 'admin@secureauth.com';
73
+ try {
74
+ const existing = db.prepare('SELECT * FROM users WHERE email = ?').get(adminEmail);
75
+ if (!existing) {
76
+ const userId = uuidv4();
77
+ const passwordHash = bcrypt.hashSync('AdminPassword123!', 12);
78
+ const totpSecret = 'KVKFKRCPNZQUYMLXOVZGUYLTKVKFKRCP';
79
+ const createdAt = new Date().toISOString();
80
+ db.prepare(`
81
+ INSERT INTO users (id, full_name, email, password_hash, totp_secret, role, created_at)
82
+ VALUES (?, ?, ?, ?, ?, ?, ?)
83
+ `).run(userId, 'System Administrator', adminEmail, passwordHash, totpSecret, 'admin', createdAt);
84
+ console.log('✓ Admin user created: admin@secureauth.com / AdminPassword123!');
85
+ }
86
+ } catch (err) {
87
+ console.error('❌ Failed to seed admin user:', err);
88
+ }
89
+ }
90
+
91
+ function insertAuditLog(userId, action, ipAddress = 'unknown', userAgent = 'unknown') {
92
+ const logId = uuidv4();
93
+ const timestamp = new Date().toISOString();
94
+ try {
95
+ db.prepare(`
96
+ INSERT INTO audit_logs (log_id, user_id, action, ip_address, user_agent, timestamp)
97
+ VALUES (?, ?, ?, ?, ?, ?)
98
+ `).run(logId, userId, action, ipAddress, userAgent, timestamp);
99
+ return logId;
100
+ } catch (err) {
101
+ console.error('❌ Failed to insert audit log:', err);
102
+ return null;
103
+ }
104
+ }
105
+
106
+ module.exports = {
107
+ initDatabase,
108
+ seedAdminUser,
109
+ seedDemoUser,
110
+ insertAuditLog,
111
+ db: {
112
+ query: (text, params) => {
113
+ const stmt = db.prepare(text);
114
+ const rows = params ? stmt.all(...params) : stmt.all();
115
+ return { rows };
116
+ }
117
+ }
118
+ };
@@ -0,0 +1,19 @@
1
+ function isAdmin(req, res, next) {
2
+ if (!req.user) {
3
+ return res.status(401).json({
4
+ error: 'Unauthorized',
5
+ message: 'Authentication required'
6
+ });
7
+ }
8
+
9
+ if (req.user.role !== 'admin') {
10
+ return res.status(403).json({
11
+ error: 'Forbidden',
12
+ message: 'Access denied: Administrator privileges required'
13
+ });
14
+ }
15
+
16
+ next();
17
+ }
18
+
19
+ module.exports = { isAdmin };
@@ -0,0 +1,119 @@
1
+ const jwtUtils = require('../utils/jwtUtils');
2
+ const rateLimit = require('express-rate-limit');
3
+
4
+ function verifyToken(req, res, next) {
5
+ try {
6
+ const authHeader = req.get('Authorization');
7
+ const token = jwtUtils.extractTokenFromHeader(authHeader);
8
+
9
+ if (!token) {
10
+ return res.status(401).json({
11
+ error: 'Missing authorization token',
12
+ message: 'Please provide your access token in Authorization: Bearer <token>'
13
+ });
14
+ }
15
+
16
+ let payload;
17
+ try {
18
+ payload = jwtUtils.verifyJWT(token);
19
+ } catch (error) {
20
+ return res.status(401).json({
21
+ error: 'Invalid or expired token',
22
+ message: error.message
23
+ });
24
+ }
25
+
26
+ if (payload['2fa_required']) {
27
+ return res.status(403).json({
28
+ error: 'Insufficient permissions',
29
+ message: 'Please complete 2FA verification first'
30
+ });
31
+ }
32
+
33
+ req.user = payload;
34
+ next();
35
+ } catch (error) {
36
+ return res.status(500).json({
37
+ error: 'Authentication failed',
38
+ message: 'An error occurred during authentication'
39
+ });
40
+ }
41
+ }
42
+
43
+ function checkAndRefreshToken(req, res, next) {
44
+ try {
45
+ const authHeader = req.get('Authorization');
46
+ const token = jwtUtils.extractTokenFromHeader(authHeader);
47
+
48
+ if (!token) {
49
+ return res.status(401).json({
50
+ error: 'Missing authorization token',
51
+ message: 'Please log in again'
52
+ });
53
+ }
54
+
55
+ let payload;
56
+ try {
57
+ payload = jwtUtils.verifyJWT(token);
58
+ } catch (error) {
59
+ return res.status(401).json({
60
+ error: 'Invalid or expired token',
61
+ message: error.message,
62
+ logout_required: true
63
+ });
64
+ }
65
+
66
+ if (payload['2fa_required']) {
67
+ return res.status(403).json({
68
+ error: 'Insufficient permissions',
69
+ message: 'Please complete 2FA verification'
70
+ });
71
+ }
72
+
73
+ const inactivity = jwtUtils.checkInactivity(token);
74
+
75
+ if (inactivity.inactive) {
76
+ return res.status(401).json({
77
+ error: 'Session expired',
78
+ message: 'Your session has expired due to inactivity',
79
+ logout_required: true
80
+ });
81
+ }
82
+
83
+ req.user = payload;
84
+
85
+ if (inactivity.seconds_inactive > 240) {
86
+ req.refresh_warning = {
87
+ seconds_until_logout: 300 - inactivity.seconds_inactive,
88
+ message: 'Session will expire soon due to inactivity'
89
+ };
90
+ }
91
+
92
+ next();
93
+ } catch (error) {
94
+ return res.status(500).json({
95
+ error: 'Authentication failed',
96
+ message: error.message
97
+ });
98
+ }
99
+ }
100
+
101
+ function createRateLimiter(maxRequests = 5, windowMinutes = 15) {
102
+ return rateLimit({
103
+ windowMs: windowMinutes * 60 * 1000,
104
+ max: maxRequests,
105
+ message: {
106
+ error: 'Rate limit exceeded',
107
+ message: `Too many requests. Please try again in ${windowMinutes} minutes.`,
108
+ retry_after: windowMinutes * 60
109
+ },
110
+ standardHeaders: true,
111
+ legacyHeaders: false
112
+ });
113
+ }
114
+
115
+ module.exports = {
116
+ verifyToken,
117
+ checkAndRefreshToken,
118
+ createRateLimiter
119
+ };
@@ -0,0 +1,183 @@
1
+ const slowDown = require('express-slow-down');
2
+
3
+ const MAX_REQUESTS_PER_MINUTE = parseInt(process.env.MAX_REQUESTS_PER_MINUTE) || 60;
4
+ const AUTO_BAN_THRESHOLD = parseInt(process.env.AUTO_BAN_THRESHOLD) || 100;
5
+ const BAN_DURATION_MINUTES = parseInt(process.env.BAN_DURATION_MINUTES) || 30;
6
+ const MAX_CONCURRENT_CONNECTIONS = parseInt(process.env.MAX_CONCURRENT_CONNECTIONS) || 100;
7
+
8
+ const ipRequestCounts = new Map();
9
+ const bannedIPs = new Map();
10
+ const activeConnections = new Map();
11
+
12
+ function trackAndBlockIP(req, res, next) {
13
+ const clientIP = req.ip || req.connection.remoteAddress || 'unknown';
14
+ const now = Date.now();
15
+
16
+ if (bannedIPs.has(clientIP)) {
17
+ const banExpiry = bannedIPs.get(clientIP);
18
+ if (now < banExpiry) {
19
+ const minutesLeft = Math.ceil((banExpiry - now) / 60000);
20
+ return res.status(429).json({
21
+ error: 'IP Banned',
22
+ message: `Your IP has been temporarily banned due to suspicious activity. Ban expires in ${minutesLeft} minutes.`,
23
+ banned_until: new Date(banExpiry).toISOString()
24
+ });
25
+ } else {
26
+ bannedIPs.delete(clientIP);
27
+ }
28
+ }
29
+
30
+ if (!ipRequestCounts.has(clientIP)) {
31
+ ipRequestCounts.set(clientIP, []);
32
+ }
33
+
34
+ const requests = ipRequestCounts.get(clientIP);
35
+ const oneMinuteAgo = now - 60000;
36
+ const recentRequests = requests.filter(timestamp => timestamp > oneMinuteAgo);
37
+ ipRequestCounts.set(clientIP, recentRequests);
38
+ recentRequests.push(now);
39
+
40
+ if (recentRequests.length > AUTO_BAN_THRESHOLD) {
41
+ const banUntil = now + (BAN_DURATION_MINUTES * 60000);
42
+ bannedIPs.set(clientIP, banUntil);
43
+ return res.status(429).json({
44
+ error: 'Too Many Requests - IP Banned',
45
+ message: `Your IP has been automatically banned for ${BAN_DURATION_MINUTES} minutes due to excessive requests.`,
46
+ requests_detected: recentRequests.length,
47
+ threshold: AUTO_BAN_THRESHOLD
48
+ });
49
+ }
50
+
51
+ if (recentRequests.length > MAX_REQUESTS_PER_MINUTE) {
52
+ res.setHeader('X-RateLimit-Warning', 'Approaching request limit');
53
+ }
54
+
55
+ next();
56
+ }
57
+
58
+ const progressiveSlowDown = slowDown({
59
+ windowMs: 15 * 60 * 1000,
60
+ delayAfter: 10,
61
+ delayMs: (hits) => hits * 100,
62
+ maxDelayMs: 5000,
63
+ skipSuccessfulRequests: false,
64
+ skipFailedRequests: false
65
+ });
66
+
67
+ function connectionLimiter(req, res, next) {
68
+ const clientIP = req.ip || req.connection.remoteAddress || 'unknown';
69
+
70
+ if (!activeConnections.has(clientIP)) {
71
+ activeConnections.set(clientIP, 0);
72
+ }
73
+
74
+ const currentConnections = activeConnections.get(clientIP);
75
+
76
+ if (currentConnections >= MAX_CONCURRENT_CONNECTIONS) {
77
+ return res.status(429).json({
78
+ error: 'Too Many Concurrent Connections',
79
+ message: `Maximum ${MAX_CONCURRENT_CONNECTIONS} concurrent connections per IP allowed.`,
80
+ current_connections: currentConnections
81
+ });
82
+ }
83
+
84
+ activeConnections.set(clientIP, currentConnections + 1);
85
+
86
+ res.on('finish', () => {
87
+ const count = activeConnections.get(clientIP) || 0;
88
+ if (count > 0) {
89
+ activeConnections.set(clientIP, count - 1);
90
+ }
91
+ });
92
+
93
+ next();
94
+ }
95
+
96
+ function validateRequestSize(req, res, next) {
97
+ const contentLength = req.headers['content-length'];
98
+ const MAX_SIZE = parseInt(process.env.MAX_REQUEST_SIZE) || 100000;
99
+
100
+ if (contentLength && parseInt(contentLength) > MAX_SIZE) {
101
+ return res.status(413).json({
102
+ error: 'Payload Too Large',
103
+ message: `Request body must be less than ${MAX_SIZE / 1000}KB`,
104
+ received: `${(contentLength / 1000).toFixed(2)}KB`
105
+ });
106
+ }
107
+
108
+ next();
109
+ }
110
+
111
+ function malformedRequestDetector(req, res, next) {
112
+ const userAgent = req.get('User-Agent') || '';
113
+ const clientIP = req.ip || 'unknown';
114
+
115
+ const suspiciousPatterns = [
116
+ /sqlmap/i,
117
+ /nikto/i,
118
+ /nmap/i,
119
+ /masscan/i,
120
+ /<script>/i,
121
+ /\.\.\/\.\.\//,
122
+ /union.*select/i,
123
+ ];
124
+
125
+ for (const pattern of suspiciousPatterns) {
126
+ if (pattern.test(userAgent) || pattern.test(req.url)) {
127
+ const banUntil = Date.now() + (60 * 60000);
128
+ bannedIPs.set(clientIP, banUntil);
129
+ return res.status(403).json({
130
+ error: 'Forbidden',
131
+ message: 'Suspicious request detected. Your IP has been logged and banned.'
132
+ });
133
+ }
134
+ }
135
+
136
+ next();
137
+ }
138
+
139
+ function getProtectionStats() {
140
+ const now = Date.now();
141
+ const activeBans = Array.from(bannedIPs.entries())
142
+ .filter(([ip, expiry]) => expiry > now)
143
+ .length;
144
+
145
+ const totalActiveConnections = Array.from(activeConnections.values())
146
+ .reduce((sum, count) => sum + count, 0);
147
+
148
+ return {
149
+ banned_ips: activeBans,
150
+ active_connections: totalActiveConnections,
151
+ max_connections_per_ip: MAX_CONCURRENT_CONNECTIONS,
152
+ auto_ban_threshold: AUTO_BAN_THRESHOLD,
153
+ ban_duration_minutes: BAN_DURATION_MINUTES,
154
+ tracked_ips: ipRequestCounts.size
155
+ };
156
+ }
157
+
158
+ function cleanupExpiredBans() {
159
+ const now = Date.now();
160
+ let cleaned = 0;
161
+ for (const [ip, expiry] of bannedIPs.entries()) {
162
+ if (expiry < now) {
163
+ bannedIPs.delete(ip);
164
+ cleaned++;
165
+ }
166
+ }
167
+ if (cleaned > 0) {
168
+ console.log(`Cleaned ${cleaned} expired IP bans`);
169
+ }
170
+ }
171
+
172
+ if (process.env.VERCEL !== '1') {
173
+ setInterval(cleanupExpiredBans, 5 * 60 * 1000);
174
+ }
175
+
176
+ module.exports = {
177
+ trackAndBlockIP,
178
+ progressiveSlowDown,
179
+ connectionLimiter,
180
+ validateRequestSize,
181
+ malformedRequestDetector,
182
+ getProtectionStats
183
+ };
@@ -0,0 +1,74 @@
1
+ const { db } = require('../database/db');
2
+
3
+ async function findByEmail(email) {
4
+ const result = await db.query('SELECT * FROM users WHERE email = $1', [email]);
5
+ return result.rows[0];
6
+ }
7
+
8
+ async function findById(userId) {
9
+ const result = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
10
+ return result.rows[0];
11
+ }
12
+
13
+ async function createUser(userData) {
14
+ const query = `
15
+ INSERT INTO users (id, full_name, email, password_hash, totp_secret, role, created_at)
16
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
17
+ RETURNING *
18
+ `;
19
+ const result = await db.query(query, [
20
+ userData.id, userData.full_name, userData.email,
21
+ userData.password_hash, userData.totp_secret,
22
+ userData.role || 'user', userData.created_at
23
+ ]);
24
+ return result.rows[0];
25
+ }
26
+
27
+ async function getAllUsers() {
28
+ const result = await db.query('SELECT id, full_name, email, role, created_at FROM users ORDER BY created_at DESC');
29
+ return result.rows;
30
+ }
31
+
32
+ async function deleteUser(userId) {
33
+ await db.query('DELETE FROM audit_logs WHERE user_id = $1', [userId]);
34
+ const result = await db.query('DELETE FROM users WHERE id = $1 RETURNING *', [userId]);
35
+ return result.rows[0];
36
+ }
37
+
38
+ async function countUsers() {
39
+ const result = await db.query('SELECT COUNT(*) as count FROM users');
40
+ return parseInt(result.rows[0].count);
41
+ }
42
+
43
+ async function incrementFailedAttempts(userId) {
44
+ const result = await db.query(`
45
+ UPDATE users
46
+ SET failed_login_attempts = failed_login_attempts + 1,
47
+ last_failed_login = $1
48
+ WHERE id = $2
49
+ RETURNING failed_login_attempts
50
+ `, [new Date().toISOString(), userId]);
51
+ return result.rows[0] ? result.rows[0].failed_login_attempts : 0;
52
+ }
53
+
54
+ async function resetFailedAttempts(userId) {
55
+ await db.query(`
56
+ UPDATE users
57
+ SET failed_login_attempts = 0,
58
+ locked_until = NULL,
59
+ last_failed_login = NULL
60
+ WHERE id = $1
61
+ `, [userId]);
62
+ }
63
+
64
+ async function lockAccount(userId, lockoutDurationMinutes) {
65
+ const lockedUntil = new Date();
66
+ lockedUntil.setMinutes(lockedUntil.getMinutes() + lockoutDurationMinutes);
67
+ await db.query('UPDATE users SET locked_until = $1 WHERE id = $2', [lockedUntil.toISOString(), userId]);
68
+ }
69
+
70
+ module.exports = {
71
+ findByEmail, findById, createUser, getAllUsers,
72
+ deleteUser, countUsers, incrementFailedAttempts,
73
+ resetFailedAttempts, lockAccount
74
+ };
@@ -0,0 +1,68 @@
1
+ const { db } = require('../database/db');
2
+
3
+ function findByEmail(email) {
4
+ const result = db.query('SELECT * FROM users WHERE email = ?', [email]);
5
+ return result.rows[0];
6
+ }
7
+
8
+ function findById(userId) {
9
+ const result = db.query('SELECT * FROM users WHERE id = ?', [userId]);
10
+ return result.rows[0];
11
+ }
12
+
13
+ function createUser(userData) {
14
+ db.query(`
15
+ INSERT INTO users (id, full_name, email, password_hash, totp_secret, role, created_at)
16
+ VALUES (?, ?, ?, ?, ?, ?, ?)
17
+ `, [userData.id, userData.full_name, userData.email,
18
+ userData.password_hash, userData.totp_secret,
19
+ userData.role || 'user', userData.created_at]);
20
+ }
21
+
22
+ function getAllUsers() {
23
+ const result = db.query('SELECT id, full_name, email, role, created_at FROM users ORDER BY created_at DESC');
24
+ return result.rows;
25
+ }
26
+
27
+ function deleteUser(userId) {
28
+ db.query('DELETE FROM audit_logs WHERE user_id = ?', [userId]);
29
+ db.query('DELETE FROM users WHERE id = ?', [userId]);
30
+ }
31
+
32
+ function countUsers() {
33
+ const result = db.query('SELECT COUNT(*) as count FROM users');
34
+ return result.rows[0].count;
35
+ }
36
+
37
+ function incrementFailedAttempts(userId) {
38
+ const result = db.query(`
39
+ UPDATE users
40
+ SET failed_login_attempts = failed_login_attempts + 1,
41
+ last_failed_login = ?
42
+ WHERE id = ?
43
+ `, [new Date().toISOString(), userId]);
44
+ const row = db.query('SELECT failed_login_attempts FROM users WHERE id = ?', [userId]);
45
+ return row.rows[0] ? row.rows[0].failed_login_attempts : 0;
46
+ }
47
+
48
+ function resetFailedAttempts(userId) {
49
+ db.query(`
50
+ UPDATE users
51
+ SET failed_login_attempts = 0,
52
+ locked_until = NULL,
53
+ last_failed_login = NULL
54
+ WHERE id = ?
55
+ `, [userId]);
56
+ }
57
+
58
+ function lockAccount(userId, lockoutDurationMinutes) {
59
+ const lockedUntil = new Date();
60
+ lockedUntil.setMinutes(lockedUntil.getMinutes() + lockoutDurationMinutes);
61
+ db.query('UPDATE users SET locked_until = ? WHERE id = ?', [lockedUntil.toISOString(), userId]);
62
+ }
63
+
64
+ module.exports = {
65
+ findByEmail, findById, createUser, getAllUsers,
66
+ deleteUser, countUsers, incrementFailedAttempts,
67
+ resetFailedAttempts, lockAccount
68
+ };
@@ -0,0 +1,68 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const authController = require('../controllers/authController');
4
+ const { verifyToken, checkAndRefreshToken, createRateLimiter } = require('../middleware/authMiddleware');
5
+ const jwtUtils = require('../utils/jwtUtils');
6
+
7
+ const loginLimiter = createRateLimiter(5, 15);
8
+
9
+ router.post('/register', authController.register);
10
+ router.post('/login', loginLimiter, authController.loginStep1);
11
+ router.post('/demo/login', authController.demoLogin);
12
+ router.post('/verify-2fa', loginLimiter, authController.verifyTOTP);
13
+ router.get('/dashboard', checkAndRefreshToken, authController.getDashboard);
14
+ router.get('/login-history', checkAndRefreshToken, authController.getLoginHistory);
15
+ router.post('/request-deletion', checkAndRefreshToken, authController.requestDeletion);
16
+
17
+ router.post('/refresh', (req, res) => {
18
+ try {
19
+ const authHeader = req.get('Authorization');
20
+ const token = jwtUtils.extractTokenFromHeader(authHeader);
21
+
22
+ if (!token) {
23
+ return res.status(401).json({ error: 'Missing token' });
24
+ }
25
+
26
+ const inactivity = jwtUtils.checkInactivity(token);
27
+
28
+ if (inactivity.inactive) {
29
+ return res.status(401).json({
30
+ error: 'Session expired',
31
+ message: 'Your session has expired due to inactivity'
32
+ });
33
+ }
34
+
35
+ const newToken = jwtUtils.refreshJWT(token);
36
+
37
+ return res.status(200).json({
38
+ accessToken: newToken,
39
+ message: 'Token refreshed successfully'
40
+ });
41
+
42
+ } catch (error) {
43
+ return res.status(401).json({ error: error.message });
44
+ }
45
+ });
46
+
47
+ router.get('/health', (req, res) => {
48
+ res.status(200).json({
49
+ status: 'ok',
50
+ message: 'SecureAuth API is running',
51
+ timestamp: new Date().toISOString(),
52
+ version: '2.1.0',
53
+ framework: 'Express/Node.js',
54
+ features: [
55
+ 'TOTP Two-Factor Authentication',
56
+ 'JWT Session Management',
57
+ 'Inactivity Auto-Logout',
58
+ 'Account Lockout',
59
+ 'Login History Tracking',
60
+ 'Rate Limiting',
61
+ 'bcrypt Password Hashing'
62
+ ]
63
+ });
64
+ });
65
+
66
+ {{ADMIN_ROUTES}}
67
+
68
+ module.exports = router;