@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,157 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { logger } = require('./logger');
4
+
5
+ const REQUIRED_DIRS = ['controllers', 'routes', 'middleware', 'models', 'utils', 'database'];
6
+
7
+ const LOCKOUT_HELPER = `
8
+ async function checkAccountLockout(user) {
9
+ if (user.locked_until) {
10
+ const lockedUntil = new Date(user.locked_until);
11
+ if (lockedUntil > new Date()) {
12
+ const minutesLeft = Math.ceil((lockedUntil - new Date()) / 60000);
13
+ throw { status: 423, message: \`Account locked due to too many failed attempts. Try again in \${minutesLeft} minute(s).\` };
14
+ }
15
+ }
16
+ }
17
+ `;
18
+
19
+ const LOCKOUT_CHECK_CODE = `
20
+ try {
21
+ await checkAccountLockout(user);
22
+ } catch (lockErr) {
23
+ await insertAuditLog(user.id, 'login_attempt_locked', req.ip || 'unknown', req.get('User-Agent') || 'unknown');
24
+ return res.status(lockErr.status || 423).json({
25
+ error: 'Account locked',
26
+ message: lockErr.message
27
+ });
28
+ }
29
+ `;
30
+
31
+ const LOCKOUT_INCREMENT_CODE = `
32
+ const failedAttempts = await userModel.incrementFailedAttempts(user.id);
33
+ `;
34
+
35
+ const LOCKOUT_TRIGGER_CODE = `
36
+ if (failedAttempts >= MAX_FAILED_ATTEMPTS) {
37
+ await userModel.lockAccount(user.id, LOCKOUT_DURATION_MINUTES);
38
+ await insertAuditLog(user.id, 'account_locked', req.ip || 'unknown', req.get('User-Agent') || 'unknown');
39
+ return res.status(423).json({
40
+ error: 'Account locked',
41
+ message: \`Account locked due to \${MAX_FAILED_ATTEMPTS} failed attempts. Try again in \${LOCKOUT_DURATION_MINUTES} minutes.\`
42
+ });
43
+ }
44
+ `;
45
+
46
+ const LOCKOUT_RESET_CODE = `
47
+ await userModel.resetFailedAttempts(user.id);
48
+ `;
49
+
50
+ const LOCKOUT_MESSAGE_CODE = '`Email or password is incorrect. ${remaining} attempt(s) remaining before account lockout.`';
51
+
52
+ const DDOS_MIDDLEWARE_CODE = `const ddos = require('./middleware/ddosMiddleware');
53
+
54
+ app.use(ddos.trackAndBlockIP);
55
+ app.use(ddos.connectionLimiter);
56
+ app.use(ddos.malformedRequestDetector);
57
+ app.use(ddos.validateRequestSize);
58
+ `;
59
+
60
+ const ADMIN_ROUTES_CODE = `
61
+ const adminController = require('../controllers/adminController');
62
+ const { isAdmin } = require('../middleware/adminMiddleware');
63
+
64
+ router.get('/admin/stats', checkAndRefreshToken, isAdmin, adminController.getSystemStats);
65
+ router.get('/admin/logs', checkAndRefreshToken, isAdmin, adminController.getSystemLogs);
66
+ router.get('/admin/users', checkAndRefreshToken, isAdmin, adminController.getAllUsers);
67
+ router.delete('/admin/users/:userId', checkAndRefreshToken, isAdmin, adminController.deleteUser);
68
+ `;
69
+
70
+ function scaffold(targetDir, answers) {
71
+ const templateDir = path.join(__dirname, '..', 'templates');
72
+ const hasAdmin = answers.features && answers.features.includes('admin');
73
+ const hasDdos = answers.features && answers.features.includes('ddos');
74
+
75
+ const createdDirs = [];
76
+ for (const dir of REQUIRED_DIRS) {
77
+ const dirPath = path.join(targetDir, dir);
78
+ if (!fs.existsSync(dirPath)) {
79
+ fs.mkdirSync(dirPath, { recursive: true });
80
+ createdDirs.push(dir);
81
+ }
82
+ }
83
+ if (createdDirs.length > 0) {
84
+ logger.info(`Created folders: ${createdDirs.join(', ')}`);
85
+ }
86
+
87
+ const templateFiles = collectFiles(templateDir);
88
+ let fileCount = 0;
89
+
90
+ for (const tmplPath of templateFiles) {
91
+ const relative = path.relative(templateDir, tmplPath).replace(/\\/g, '/');
92
+
93
+ if (answers.database === 'sqlite' && relative.includes('.pg.')) continue;
94
+ if (answers.database === 'postgres' && relative.includes('.sqlite.')) continue;
95
+
96
+ if (relative === 'middleware/adminMiddleware.js' && !hasAdmin) continue;
97
+ if (relative === 'controllers/adminController.js' && !hasAdmin) continue;
98
+
99
+ const destRelative = relative.replace('.pg.', '.').replace('.sqlite.', '.');
100
+ const destPath = path.join(targetDir, destRelative);
101
+ const destDir = path.dirname(destPath);
102
+
103
+ if (!fs.existsSync(destDir)) {
104
+ fs.mkdirSync(destDir, { recursive: true });
105
+ }
106
+
107
+ if (fs.existsSync(destPath)) {
108
+ logger.warn(`Skipped ${destRelative} — already exists`);
109
+ continue;
110
+ }
111
+
112
+ let content = fs.readFileSync(tmplPath, 'utf-8');
113
+ content = processContent(content, answers);
114
+ fs.writeFileSync(destPath, content, 'utf-8');
115
+ logger.success(`Created ${destRelative}`);
116
+ fileCount++;
117
+ }
118
+
119
+ return fileCount;
120
+ }
121
+
122
+ function collectFiles(dir) {
123
+ const results = [];
124
+ for (const entry of fs.readdirSync(dir)) {
125
+ const fullPath = path.join(dir, entry);
126
+ if (fs.statSync(fullPath).isDirectory()) {
127
+ results.push(...collectFiles(fullPath));
128
+ } else {
129
+ results.push(fullPath);
130
+ }
131
+ }
132
+ return results;
133
+ }
134
+
135
+ function processContent(content, answers) {
136
+ const hasLockout = answers.features && answers.features.includes('accountLockout');
137
+ const hasDdos = answers.features && answers.features.includes('ddos');
138
+ const hasAdmin = answers.features && answers.features.includes('admin');
139
+
140
+ content = content.replace(/\{\{PORT\}\}/g, String(answers.port));
141
+ content = content.replace(/\{\{TOTP_ISSUER\}\}/g, answers.projectName || 'SecureAuth-App');
142
+
143
+ content = content.replace(/\{\{ACCOUNT_LOCKOUT\}\}/g, hasLockout ? LOCKOUT_HELPER : '');
144
+ content = content.replace(/\{\{LOCKOUT_CHECK\}\}/g, hasLockout ? LOCKOUT_CHECK_CODE : '');
145
+ content = content.replace(/\{\{LOCKOUT_INCREMENT\}\}/g, hasLockout ? LOCKOUT_INCREMENT_CODE : '');
146
+ content = content.replace(/\{\{LOCKOUT_TRIGGER\}\}/g, hasLockout ? LOCKOUT_TRIGGER_CODE : '');
147
+ content = content.replace(/\{\{LOCKOUT_RESET\}\}/g, hasLockout ? LOCKOUT_RESET_CODE : '');
148
+ content = content.replace(/\{\{LOCKOUT_MESSAGE\}\}/g, hasLockout ? LOCKOUT_MESSAGE_CODE : "'Email or password is incorrect'");
149
+
150
+ content = content.replace(/\{\{DDOS_MIDDLEWARE\}\}/g, hasDdos ? DDOS_MIDDLEWARE_CODE : '');
151
+
152
+ content = content.replace(/\{\{ADMIN_ROUTES\}\}/g, hasAdmin ? ADMIN_ROUTES_CODE : '');
153
+
154
+ return content;
155
+ }
156
+
157
+ module.exports = { scaffold };
@@ -0,0 +1,78 @@
1
+ const userModel = require('../models/userModel');
2
+ const { db } = require('../database/db');
3
+
4
+ async function getSystemLogs(req, res) {
5
+ try {
6
+ const query = `
7
+ SELECT al.log_id, al.action, al.ip_address, al.user_agent, al.timestamp, u.email, u.full_name
8
+ FROM audit_logs al
9
+ LEFT JOIN users u ON al.user_id = u.id
10
+ ORDER BY al.timestamp DESC
11
+ LIMIT 100
12
+ `;
13
+ const result = await db.query(query);
14
+ return res.status(200).json({
15
+ logs: result.rows,
16
+ count: result.rows.length
17
+ });
18
+ } catch (error) {
19
+ console.error('Error fetching system logs:', error);
20
+ return res.status(500).json({ error: 'Failed to fetch logs' });
21
+ }
22
+ }
23
+
24
+ async function getAllUsers(req, res) {
25
+ try {
26
+ const users = await userModel.getAllUsers();
27
+ return res.status(200).json({
28
+ users: users,
29
+ count: users.length
30
+ });
31
+ } catch (error) {
32
+ console.error('Error fetching users:', error);
33
+ return res.status(500).json({ error: 'Failed to fetch users' });
34
+ }
35
+ }
36
+
37
+ async function deleteUser(req, res) {
38
+ try {
39
+ const { userId } = req.params;
40
+ if (!userId) {
41
+ return res.status(400).json({ error: 'User ID is required' });
42
+ }
43
+ const deletedUser = await userModel.deleteUser(userId);
44
+ if (!deletedUser) {
45
+ return res.status(404).json({ error: 'User not found' });
46
+ }
47
+ return res.status(200).json({
48
+ message: 'User deleted successfully',
49
+ user: deletedUser
50
+ });
51
+ } catch (error) {
52
+ console.error('Error deleting user:', error);
53
+ return res.status(500).json({ error: 'Failed to delete user' });
54
+ }
55
+ }
56
+
57
+ async function getSystemStats(req, res) {
58
+ try {
59
+ const totalUsers = await userModel.countUsers();
60
+ const logsResult = await db.query('SELECT COUNT(*) as count FROM audit_logs');
61
+ const totalLogs = parseInt(logsResult.rows[0].count);
62
+
63
+ return res.status(200).json({
64
+ stats: {
65
+ totalUsers,
66
+ totalLogs,
67
+ systemHealth: 'Optimal'
68
+ }
69
+ });
70
+ } catch (error) {
71
+ console.error('Error fetching system stats:', error);
72
+ return res.status(500).json({ error: 'Failed to fetch stats' });
73
+ }
74
+ }
75
+
76
+ module.exports = {
77
+ getSystemLogs, getAllUsers, deleteUser, getSystemStats
78
+ };
@@ -0,0 +1,345 @@
1
+ const bcrypt = require('bcryptjs');
2
+ const { v4: uuidv4 } = require('uuid');
3
+ const userModel = require('../models/userModel');
4
+ const totpUtils = require('../utils/totpUtils');
5
+ const jwtUtils = require('../utils/jwtUtils');
6
+ const { insertAuditLog, db } = require('../database/db');
7
+
8
+ const BCRYPT_ROUNDS = parseInt(process.env.BCRYPT_ROUNDS) || 12;
9
+ const MAX_FAILED_ATTEMPTS = parseInt(process.env.MAX_FAILED_ATTEMPTS) || 5;
10
+ const LOCKOUT_DURATION_MINUTES = parseInt(process.env.LOCKOUT_DURATION_MINUTES) || 30;
11
+
12
+ async function register(req, res) {
13
+ try {
14
+ const { fullName, email, password } = req.body;
15
+
16
+ if (!fullName || !email || !password) {
17
+ return res.status(400).json({
18
+ error: 'Missing required fields',
19
+ message: 'Please provide fullName, email, and password'
20
+ });
21
+ }
22
+
23
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
24
+ if (!emailRegex.test(email)) {
25
+ return res.status(400).json({
26
+ error: 'Invalid email format',
27
+ message: 'Please provide a valid email address'
28
+ });
29
+ }
30
+
31
+ if (password.length < 8) {
32
+ return res.status(400).json({
33
+ error: 'Weak password',
34
+ message: 'Password must be at least 8 characters long'
35
+ });
36
+ }
37
+
38
+ const existingUser = await userModel.findByEmail(email);
39
+ if (existingUser) {
40
+ return res.status(400).json({
41
+ error: 'Email already registered',
42
+ message: 'This email address is already associated with an account'
43
+ });
44
+ }
45
+
46
+ const passwordHash = await bcrypt.hash(password, BCRYPT_ROUNDS);
47
+ const secretData = totpUtils.generateSecret();
48
+ const totpSecret = secretData.base32_secret;
49
+ const qrCode = await totpUtils.generateQR(totpSecret, email);
50
+
51
+ const userId = uuidv4();
52
+ const createdAt = new Date().toISOString();
53
+
54
+ await userModel.createUser({
55
+ id: userId,
56
+ full_name: fullName,
57
+ email: email,
58
+ password_hash: passwordHash,
59
+ totp_secret: totpSecret,
60
+ created_at: createdAt
61
+ });
62
+
63
+ const ipAddress = req.ip || req.connection.remoteAddress || 'unknown';
64
+ const userAgent = req.get('User-Agent') || 'unknown';
65
+ await insertAuditLog(userId, 'register', ipAddress, userAgent);
66
+
67
+ return res.status(201).json({
68
+ message: 'Registration successful! Scan the QR code with Google Authenticator to complete setup.',
69
+ qrCode: qrCode,
70
+ userId: userId,
71
+ instructions: [
72
+ '1. Open Google Authenticator on your smartphone',
73
+ '2. Tap the "+" button to add a new account',
74
+ '3. Select "Scan a QR code"',
75
+ '4. Scan the QR code displayed above',
76
+ '5. Your SecureAuth account will appear in the app',
77
+ '6. Use the 6-digit code from the app when logging in'
78
+ ]
79
+ });
80
+
81
+ } catch (error) {
82
+ console.error('Registration error:', error);
83
+ return res.status(500).json({
84
+ error: 'Registration failed',
85
+ message: 'An error occurred during registration. Please try again.'
86
+ });
87
+ }
88
+ }
89
+
90
+ {{ACCOUNT_LOCKOUT}}
91
+
92
+ async function loginStep1(req, res) {
93
+ try {
94
+ const { email, password } = req.body;
95
+
96
+ if (!email || !password) {
97
+ return res.status(400).json({
98
+ error: 'Missing required fields',
99
+ message: 'Please provide email and password'
100
+ });
101
+ }
102
+
103
+ const user = await userModel.findByEmail(email);
104
+
105
+ if (!user) {
106
+ const ipAddress = req.ip || 'unknown';
107
+ const userAgent = req.get('User-Agent') || 'unknown';
108
+ await insertAuditLog(null, 'login_attempt_failed', ipAddress, userAgent);
109
+
110
+ return res.status(401).json({
111
+ error: 'Invalid credentials',
112
+ message: 'Email or password is incorrect'
113
+ });
114
+ }
115
+
116
+ {{LOCKOUT_CHECK}}
117
+
118
+ const isValid = await bcrypt.compare(password, user.password_hash);
119
+
120
+ if (!isValid) {
121
+ {{LOCKOUT_INCREMENT}}
122
+
123
+ const ipAddress = req.ip || 'unknown';
124
+ const userAgent = req.get('User-Agent') || 'unknown';
125
+ await insertAuditLog(user.id, 'login_attempt_failed', ipAddress, userAgent);
126
+
127
+ {{LOCKOUT_TRIGGER}}
128
+
129
+ return res.status(401).json({
130
+ error: 'Invalid credentials',
131
+ message: {{LOCKOUT_MESSAGE}}
132
+ });
133
+ }
134
+
135
+ {{LOCKOUT_RESET}}
136
+
137
+ const tempToken = jwtUtils.generateTempJWT(user.id, user.email);
138
+
139
+ const ipAddress = req.ip || 'unknown';
140
+ const userAgent = req.get('User-Agent') || 'unknown';
141
+ await insertAuditLog(user.id, 'login_step1_success', ipAddress, userAgent);
142
+
143
+ return res.status(200).json({
144
+ message: 'Password verified. Please enter your 6-digit 2FA code.',
145
+ tempToken: tempToken,
146
+ '2fa_required': true,
147
+ next_step: 'POST /api/verify-2fa with the tempToken in Authorization header'
148
+ });
149
+
150
+ } catch (error) {
151
+ console.error('Login Step 1 error:', error);
152
+ return res.status(500).json({
153
+ error: 'Login failed',
154
+ message: 'An error occurred during login. Please try again.'
155
+ });
156
+ }
157
+ }
158
+
159
+ async function verifyTOTP(req, res) {
160
+ try {
161
+ const { token: totpCode, code } = req.body;
162
+ const finalCode = totpCode || code;
163
+
164
+ if (!finalCode) {
165
+ return res.status(400).json({
166
+ error: 'Missing TOTP code',
167
+ message: 'Please provide the 6-digit code from your authenticator app'
168
+ });
169
+ }
170
+
171
+ const authHeader = req.get('Authorization');
172
+ const token = jwtUtils.extractTokenFromHeader(authHeader);
173
+
174
+ if (!token) {
175
+ return res.status(401).json({
176
+ error: 'Missing authorization token',
177
+ message: 'Please provide the tempToken from login step 1 in Authorization header'
178
+ });
179
+ }
180
+
181
+ let payload;
182
+ try {
183
+ payload = jwtUtils.verifyJWT(token);
184
+ } catch (error) {
185
+ return res.status(401).json({
186
+ error: 'Invalid or expired token',
187
+ message: error.message
188
+ });
189
+ }
190
+
191
+ if (!payload['2fa_required']) {
192
+ return res.status(401).json({
193
+ error: 'Invalid token type',
194
+ message: 'This endpoint requires a temporary token from login step 1'
195
+ });
196
+ }
197
+
198
+ const userId = payload.user_id;
199
+ const user = await userModel.findById(userId);
200
+
201
+ if (!user) {
202
+ return res.status(401).json({
203
+ error: 'User not found',
204
+ message: 'Invalid token'
205
+ });
206
+ }
207
+
208
+ const isValid = totpUtils.verifyCode(user.totp_secret, finalCode);
209
+
210
+ const ipAddress = req.ip || 'unknown';
211
+ const userAgent = req.get('User-Agent') || 'unknown';
212
+
213
+ if (!isValid) {
214
+ await insertAuditLog(userId, '2fa_fail', ipAddress, userAgent);
215
+ return res.status(401).json({
216
+ error: 'Invalid TOTP code',
217
+ message: 'The code you entered is invalid or has expired. Please try again.'
218
+ });
219
+ }
220
+
221
+ const fullToken = jwtUtils.generateFullJWT(user.id, user.email, user.full_name, user.role);
222
+
223
+ await insertAuditLog(userId, '2fa_success', ipAddress, userAgent);
224
+ await insertAuditLog(userId, 'login_success', ipAddress, userAgent);
225
+
226
+ return res.status(200).json({
227
+ message: '2FA verification successful. You are now logged in.',
228
+ accessToken: fullToken,
229
+ user: {
230
+ id: user.id,
231
+ email: user.email,
232
+ fullName: user.full_name,
233
+ role: user.role || 'user',
234
+ createdAt: user.created_at
235
+ }
236
+ });
237
+
238
+ } catch (error) {
239
+ console.error('TOTP verification error:', error);
240
+ return res.status(500).json({
241
+ error: '2FA verification failed',
242
+ message: 'An error occurred during 2FA verification. Please try again.'
243
+ });
244
+ }
245
+ }
246
+
247
+ async function getDashboard(req, res) {
248
+ try {
249
+ const userId = req.user.user_id;
250
+ const user = await userModel.findById(userId);
251
+
252
+ if (!user) {
253
+ return res.status(401).json({
254
+ error: 'User not found',
255
+ message: 'Invalid token'
256
+ });
257
+ }
258
+
259
+ const totalUsers = await userModel.countUsers();
260
+
261
+ return res.status(200).json({
262
+ message: 'Welcome to your secure dashboard!',
263
+ user: {
264
+ id: user.id,
265
+ email: user.email,
266
+ fullName: user.full_name,
267
+ totpEnabled: Boolean(user.totp_enabled),
268
+ createdAt: user.created_at
269
+ },
270
+ stats: {
271
+ totalUsers: totalUsers,
272
+ loginTime: new Date().toISOString()
273
+ }
274
+ });
275
+
276
+ } catch (error) {
277
+ console.error('Dashboard error:', error);
278
+ return res.status(500).json({
279
+ error: 'Dashboard access failed',
280
+ message: 'An error occurred. Please try again.'
281
+ });
282
+ }
283
+ }
284
+
285
+ async function getLoginHistory(req, res) {
286
+ try {
287
+ const userId = req.user.user_id;
288
+
289
+ const query = `
290
+ SELECT action, ip_address, user_agent, timestamp
291
+ FROM audit_logs
292
+ WHERE user_id = $1
293
+ AND action IN ('login_success', 'login_attempt_failed', 'login_attempt_locked', '2fa_success', '2fa_fail')
294
+ ORDER BY timestamp DESC
295
+ LIMIT 10
296
+ `;
297
+
298
+ const result = await db.query(query, [userId]);
299
+ const rows = result.rows;
300
+
301
+ const history = rows.map(row => ({
302
+ action: row.action,
303
+ ipAddress: row.ip_address,
304
+ userAgent: row.user_agent,
305
+ timestamp: row.timestamp
306
+ }));
307
+
308
+ return res.status(200).json({
309
+ loginHistory: history,
310
+ count: history.length
311
+ });
312
+
313
+ } catch (error) {
314
+ console.error('Login history error:', error);
315
+ return res.status(500).json({
316
+ error: 'Failed to fetch login history'
317
+ });
318
+ }
319
+ }
320
+
321
+ async function requestDeletion(req, res) {
322
+ try {
323
+ const userId = req.user.user_id;
324
+ const ipAddress = req.ip || 'unknown';
325
+ const userAgent = req.get('User-Agent') || 'unknown';
326
+
327
+ await insertAuditLog(userId, 'deletion_request', ipAddress, userAgent);
328
+
329
+ return res.status(200).json({
330
+ message: 'Deletion request sent successfully'
331
+ });
332
+ } catch (error) {
333
+ console.error('Deletion request error:', error);
334
+ return res.status(500).json({ error: 'Failed to request deletion' });
335
+ }
336
+ }
337
+
338
+ module.exports = {
339
+ register,
340
+ loginStep1,
341
+ verifyTOTP,
342
+ getDashboard,
343
+ getLoginHistory,
344
+ requestDeletion
345
+ };
@@ -0,0 +1,127 @@
1
+ const { Pool } = require('pg');
2
+ const { v4: uuidv4 } = require('uuid');
3
+ const bcrypt = require('bcryptjs');
4
+
5
+ const connectionString = process.env.DATABASE_URL;
6
+
7
+ const pool = new Pool({
8
+ connectionString,
9
+ ssl: {
10
+ rejectUnauthorized: false
11
+ },
12
+ connectionTimeoutMillis: 5000,
13
+ idleTimeoutMillis: 10000,
14
+ max: 3
15
+ });
16
+
17
+ async function initDatabase() {
18
+ try {
19
+ const client = await pool.connect();
20
+
21
+ await client.query(`
22
+ CREATE TABLE IF NOT EXISTS users (
23
+ id TEXT PRIMARY KEY,
24
+ full_name TEXT NOT NULL,
25
+ email TEXT UNIQUE NOT NULL,
26
+ password_hash TEXT NOT NULL,
27
+ totp_secret TEXT NOT NULL,
28
+ totp_enabled BOOLEAN DEFAULT TRUE,
29
+ role TEXT DEFAULT 'user',
30
+ created_at TEXT NOT NULL,
31
+ failed_login_attempts INTEGER DEFAULT 0,
32
+ locked_until TEXT,
33
+ last_failed_login TEXT
34
+ )
35
+ `);
36
+
37
+ try {
38
+ await client.query('ALTER TABLE users ADD COLUMN IF NOT EXISTS role TEXT DEFAULT \'user\'');
39
+ } catch (e) {}
40
+
41
+ await client.query(`
42
+ CREATE TABLE IF NOT EXISTS audit_logs (
43
+ log_id TEXT PRIMARY KEY,
44
+ user_id TEXT REFERENCES users(id),
45
+ action TEXT NOT NULL,
46
+ ip_address TEXT,
47
+ user_agent TEXT,
48
+ timestamp TEXT NOT NULL
49
+ )
50
+ `);
51
+
52
+ client.release();
53
+ console.log('✓ Database initialized successfully (PostgreSQL)');
54
+ } catch (err) {
55
+ console.error('❌ Database initialization failed:', err.message || err);
56
+ }
57
+ }
58
+
59
+ async function seedDemoUser() {
60
+ const demoEmail = 'demo@secureauth.com';
61
+ try {
62
+ const result = await pool.query('SELECT * FROM users WHERE email = $1', [demoEmail]);
63
+ if (result.rows.length === 0) {
64
+ const userId = uuidv4();
65
+ const passwordHash = await bcrypt.hash('Demo@123', 12);
66
+ const totpSecret = 'JBSWY3DPEHPK3PXP';
67
+ const createdAt = new Date().toISOString();
68
+ await pool.query(`
69
+ INSERT INTO users (id, full_name, email, password_hash, totp_secret, role, created_at)
70
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
71
+ `, [userId, 'Demo User', demoEmail, passwordHash, totpSecret, 'admin', createdAt]);
72
+ console.log('✓ Demo user created: demo@secureauth.com / Demo@123');
73
+ }
74
+ } catch (err) {
75
+ console.error('❌ Failed to seed demo user:', err);
76
+ }
77
+ }
78
+
79
+ async function seedAdminUser() {
80
+ const adminEmail = 'admin@secureauth.com';
81
+ try {
82
+ const result = await pool.query('SELECT * FROM users WHERE email = $1', [adminEmail]);
83
+ if (result.rows.length === 0) {
84
+ const userId = uuidv4();
85
+ const passwordHash = await bcrypt.hash('AdminPassword123!', 12);
86
+ const totpSecret = 'KVKFKRCPNZQUYMLXOVZGUYLTKVKFKRCP';
87
+ const createdAt = new Date().toISOString();
88
+ await pool.query(`
89
+ INSERT INTO users (id, full_name, email, password_hash, totp_secret, role, created_at)
90
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
91
+ `, [userId, 'System Administrator', adminEmail, passwordHash, totpSecret, 'admin', createdAt]);
92
+ console.log('✓ Admin user created: admin@secureauth.com / AdminPassword123!');
93
+ }
94
+ } catch (err) {
95
+ console.error('❌ Failed to seed admin user:', err);
96
+ }
97
+ }
98
+
99
+ async function insertAuditLog(userId, action, ipAddress = 'unknown', userAgent = 'unknown') {
100
+ const logId = uuidv4();
101
+ const timestamp = new Date().toISOString();
102
+ try {
103
+ await pool.query(`
104
+ INSERT INTO audit_logs (log_id, user_id, action, ip_address, user_agent, timestamp)
105
+ VALUES ($1, $2, $3, $4, $5, $6)
106
+ `, [logId, userId, action, ipAddress, userAgent, timestamp]);
107
+ return logId;
108
+ } catch (err) {
109
+ console.error('❌ Failed to insert audit log:', err);
110
+ return null;
111
+ }
112
+ }
113
+
114
+ pool.on('error', (err) => {
115
+ console.error('❌ Unexpected Postgres pool error:', err.message);
116
+ });
117
+
118
+ module.exports = {
119
+ initDatabase,
120
+ seedAdminUser,
121
+ seedDemoUser,
122
+ insertAuditLog,
123
+ pool,
124
+ db: {
125
+ query: (text, params) => pool.query(text, params)
126
+ }
127
+ };