@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.
- package/bin/cli.js +22 -0
- package/package.json +30 -0
- package/src/detector.js +74 -0
- package/src/generator.js +79 -0
- package/src/index.js +127 -0
- package/src/installer.js +65 -0
- package/src/logger.js +62 -0
- package/src/questions.js +110 -0
- package/src/reporter.js +45 -0
- package/src/scaffolder.js +157 -0
- package/templates/controllers/adminController.js +78 -0
- package/templates/controllers/authController.js +345 -0
- package/templates/database/db.pg.js +127 -0
- package/templates/database/db.sqlite.js +118 -0
- package/templates/middleware/adminMiddleware.js +19 -0
- package/templates/middleware/authMiddleware.js +119 -0
- package/templates/middleware/ddosMiddleware.js +183 -0
- package/templates/models/userModel.pg.js +74 -0
- package/templates/models/userModel.sqlite.js +68 -0
- package/templates/routes/authRoutes.js +68 -0
- package/templates/server.js +93 -0
- package/templates/utils/jwtUtils.js +115 -0
- package/templates/utils/totpUtils.js +52 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
require('dotenv').config();
|
|
2
|
+
const express = require('express');
|
|
3
|
+
const cors = require('cors');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const helmet = require('helmet');
|
|
6
|
+
|
|
7
|
+
const authRoutes = require('./routes/authRoutes');
|
|
8
|
+
const { initDatabase, seedAdminUser, seedDemoUser } = require('./database/db');
|
|
9
|
+
|
|
10
|
+
const app = express();
|
|
11
|
+
const PORT = process.env.PORT || {{PORT}};
|
|
12
|
+
|
|
13
|
+
app.use(helmet({
|
|
14
|
+
contentSecurityPolicy: {
|
|
15
|
+
directives: {
|
|
16
|
+
defaultSrc: ["'self'"],
|
|
17
|
+
styleSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://fonts.googleapis.com"],
|
|
18
|
+
scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://cdn.tailwindcss.com", "https://unpkg.com"],
|
|
19
|
+
fontSrc: ["'self'", "data:", "https://fonts.gstatic.com"],
|
|
20
|
+
imgSrc: ["'self'", "data:", "https:"],
|
|
21
|
+
connectSrc: ["'self'", "http://localhost:*", "https://localhost:*"],
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
hsts: {
|
|
25
|
+
maxAge: 31536000,
|
|
26
|
+
includeSubDomains: true,
|
|
27
|
+
preload: true
|
|
28
|
+
}
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
{{DDOS_MIDDLEWARE}}
|
|
32
|
+
|
|
33
|
+
app.use(cors());
|
|
34
|
+
app.use(express.json({ limit: '100kb' }));
|
|
35
|
+
app.use(express.urlencoded({ extended: true, limit: '100kb' }));
|
|
36
|
+
|
|
37
|
+
app.use(express.static(path.join(__dirname, 'public')));
|
|
38
|
+
app.use('/api', authRoutes);
|
|
39
|
+
|
|
40
|
+
app.get('/api/health', (req, res) => {
|
|
41
|
+
res.status(200).json({
|
|
42
|
+
status: 'ok',
|
|
43
|
+
message: 'SecureAuth API is running',
|
|
44
|
+
timestamp: new Date().toISOString(),
|
|
45
|
+
version: '2.1.0',
|
|
46
|
+
framework: 'Express/Node.js',
|
|
47
|
+
features: [
|
|
48
|
+
'TOTP Two-Factor Authentication',
|
|
49
|
+
'JWT Session Management',
|
|
50
|
+
'Inactivity Auto-Logout',
|
|
51
|
+
'Account Lockout',
|
|
52
|
+
'Login History Tracking',
|
|
53
|
+
'Rate Limiting',
|
|
54
|
+
'bcrypt Password Hashing'
|
|
55
|
+
]
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
app.get('/', (req, res) => {
|
|
60
|
+
res.json({ message: 'SecureAuth API is running', version: '2.1.0' });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
let dbReady = false;
|
|
64
|
+
|
|
65
|
+
initDatabase()
|
|
66
|
+
.then(async () => {
|
|
67
|
+
await seedAdminUser();
|
|
68
|
+
await seedDemoUser();
|
|
69
|
+
dbReady = true;
|
|
70
|
+
console.log('Database initialized and seeded');
|
|
71
|
+
})
|
|
72
|
+
.catch(err => {
|
|
73
|
+
console.error('Database initialization failed:', err.message);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
app.use((req, res, next) => {
|
|
77
|
+
if (dbReady) return next();
|
|
78
|
+
if (req.path.startsWith('/api')) {
|
|
79
|
+
return res.status(503).json({
|
|
80
|
+
error: 'System initializing, please try again in a moment.'
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
next();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (process.env.VERCEL !== '1') {
|
|
87
|
+
app.listen(PORT, () => {
|
|
88
|
+
console.log(`SecureAuth server running on http://localhost:${PORT}`);
|
|
89
|
+
console.log(`Mode: ${process.env.NODE_ENV || 'development'}`);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = app;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const jwt = require('jsonwebtoken');
|
|
2
|
+
|
|
3
|
+
const JWT_SECRET = process.env.JWT_SECRET || 'dev-jwt-secret';
|
|
4
|
+
const JWT_ALGORITHM = process.env.JWT_ALGORITHM || 'HS256';
|
|
5
|
+
const TEMP_JWT_EXPIRY_MINUTES = parseInt(process.env.JWT_TEMP_EXPIRY_MINUTES) || 5;
|
|
6
|
+
const FULL_JWT_EXPIRY_HOURS = parseInt(process.env.JWT_FULL_EXPIRY_HOURS) || 24;
|
|
7
|
+
const INACTIVITY_TIMEOUT_MINUTES = parseInt(process.env.INACTIVITY_TIMEOUT_MINUTES) || 5;
|
|
8
|
+
|
|
9
|
+
function generateTempJWT(userId, email) {
|
|
10
|
+
const payload = {
|
|
11
|
+
user_id: userId,
|
|
12
|
+
email: email,
|
|
13
|
+
'2fa_required': true,
|
|
14
|
+
type: 'temp',
|
|
15
|
+
iat: Math.floor(Date.now() / 1000)
|
|
16
|
+
};
|
|
17
|
+
return jwt.sign(payload, JWT_SECRET, {
|
|
18
|
+
algorithm: JWT_ALGORITHM,
|
|
19
|
+
expiresIn: `${TEMP_JWT_EXPIRY_MINUTES}m`
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function generateFullJWT(userId, email, fullName, role) {
|
|
24
|
+
const payload = {
|
|
25
|
+
user_id: userId,
|
|
26
|
+
email: email,
|
|
27
|
+
full_name: fullName,
|
|
28
|
+
role: role || 'user',
|
|
29
|
+
'2fa_required': false,
|
|
30
|
+
type: 'full',
|
|
31
|
+
last_activity: new Date().toISOString(),
|
|
32
|
+
iat: Math.floor(Date.now() / 1000)
|
|
33
|
+
};
|
|
34
|
+
return jwt.sign(payload, JWT_SECRET, {
|
|
35
|
+
algorithm: JWT_ALGORITHM,
|
|
36
|
+
expiresIn: `${FULL_JWT_EXPIRY_HOURS}h`
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function verifyJWT(token) {
|
|
41
|
+
try {
|
|
42
|
+
const decoded = jwt.verify(token, JWT_SECRET, { algorithms: [JWT_ALGORITHM] });
|
|
43
|
+
return decoded;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (error.name === 'TokenExpiredError') {
|
|
46
|
+
throw new Error('Token has expired');
|
|
47
|
+
} else if (error.name === 'JsonWebTokenError') {
|
|
48
|
+
throw new Error('Invalid token');
|
|
49
|
+
} else {
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function refreshJWT(token) {
|
|
56
|
+
try {
|
|
57
|
+
const decoded = verifyJWT(token);
|
|
58
|
+
const lastActivity = new Date(decoded.last_activity);
|
|
59
|
+
const timeSinceActivity = Date.now() - lastActivity.getTime();
|
|
60
|
+
const inactivityTimeoutMs = INACTIVITY_TIMEOUT_MINUTES * 60 * 1000;
|
|
61
|
+
|
|
62
|
+
if (timeSinceActivity > inactivityTimeoutMs) {
|
|
63
|
+
throw new Error('Session expired due to inactivity');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const newPayload = {
|
|
67
|
+
user_id: decoded.user_id,
|
|
68
|
+
email: decoded.email,
|
|
69
|
+
full_name: decoded.full_name,
|
|
70
|
+
role: decoded.role || 'user',
|
|
71
|
+
'2fa_required': false,
|
|
72
|
+
type: 'full',
|
|
73
|
+
last_activity: new Date().toISOString(),
|
|
74
|
+
iat: decoded.iat,
|
|
75
|
+
exp: decoded.exp
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return jwt.sign(newPayload, JWT_SECRET, {
|
|
79
|
+
algorithm: JWT_ALGORITHM,
|
|
80
|
+
noTimestamp: true
|
|
81
|
+
});
|
|
82
|
+
} catch (error) {
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function checkInactivity(token) {
|
|
88
|
+
try {
|
|
89
|
+
const decoded = verifyJWT(token);
|
|
90
|
+
const lastActivity = new Date(decoded.last_activity);
|
|
91
|
+
const timeSinceActivity = Date.now() - lastActivity.getTime();
|
|
92
|
+
const secondsInactive = Math.floor(timeSinceActivity / 1000);
|
|
93
|
+
const timeoutSeconds = INACTIVITY_TIMEOUT_MINUTES * 60;
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
inactive: secondsInactive > timeoutSeconds,
|
|
97
|
+
seconds_inactive: secondsInactive,
|
|
98
|
+
timeout_seconds: timeoutSeconds
|
|
99
|
+
};
|
|
100
|
+
} catch (error) {
|
|
101
|
+
return { inactive: true, seconds_inactive: 999999 };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function extractTokenFromHeader(authHeader) {
|
|
106
|
+
if (!authHeader) return null;
|
|
107
|
+
const parts = authHeader.split(' ');
|
|
108
|
+
if (parts.length !== 2 || parts[0].toLowerCase() !== 'bearer') return null;
|
|
109
|
+
return parts[1];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = {
|
|
113
|
+
generateTempJWT, generateFullJWT, verifyJWT,
|
|
114
|
+
refreshJWT, checkInactivity, extractTokenFromHeader
|
|
115
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const speakeasy = require('speakeasy');
|
|
2
|
+
const QRCode = require('qrcode');
|
|
3
|
+
|
|
4
|
+
const TOTP_ISSUER = process.env.TOTP_ISSUER || 'SecureAuth';
|
|
5
|
+
|
|
6
|
+
function generateSecret() {
|
|
7
|
+
const secret = speakeasy.generateSecret({
|
|
8
|
+
name: TOTP_ISSUER,
|
|
9
|
+
length: 32
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
base32_secret: secret.base32,
|
|
13
|
+
otpauth_url: secret.otpauth_url
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function generateQR(secret, email) {
|
|
18
|
+
const otpauthUrl = speakeasy.otpauthURL({
|
|
19
|
+
secret: secret,
|
|
20
|
+
label: email,
|
|
21
|
+
issuer: TOTP_ISSUER,
|
|
22
|
+
encoding: 'base32'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const qrCodeDataURL = await QRCode.toDataURL(otpauthUrl);
|
|
27
|
+
return qrCodeDataURL;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error('QR Code generation error:', error);
|
|
30
|
+
throw new Error('Failed to generate QR code');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function verifyCode(secret, token, window = 1) {
|
|
35
|
+
return speakeasy.totp.verify({
|
|
36
|
+
secret: secret,
|
|
37
|
+
encoding: 'base32',
|
|
38
|
+
token: token,
|
|
39
|
+
window: window
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function generateCode(secret) {
|
|
44
|
+
return speakeasy.totp({
|
|
45
|
+
secret: secret,
|
|
46
|
+
encoding: 'base32'
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
generateSecret, generateQR, verifyCode, generateCode
|
|
52
|
+
};
|