@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
package/bin/cli.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { init } = require('../src/index');
|
|
4
|
+
|
|
5
|
+
const command = process.argv[2];
|
|
6
|
+
|
|
7
|
+
switch (command) {
|
|
8
|
+
case 'init':
|
|
9
|
+
init();
|
|
10
|
+
break;
|
|
11
|
+
case '--help':
|
|
12
|
+
case '-h':
|
|
13
|
+
default:
|
|
14
|
+
console.log(`
|
|
15
|
+
SecureAuth CLI - Enterprise authentication for Express apps
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
npx secureauth init Add SecureAuth to your project
|
|
19
|
+
npx secureauth --help Show this help message
|
|
20
|
+
`);
|
|
21
|
+
break;
|
|
22
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@metaadri/secureauth",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "1.0.0",
|
|
7
|
+
"description": "SecureAuth CLI - Add enterprise authentication to any Express app in one command",
|
|
8
|
+
"bin": {
|
|
9
|
+
"secureauth": "./bin/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"secureauth",
|
|
13
|
+
"authentication",
|
|
14
|
+
"2fa",
|
|
15
|
+
"totp",
|
|
16
|
+
"jwt",
|
|
17
|
+
"security",
|
|
18
|
+
"express",
|
|
19
|
+
"cli"
|
|
20
|
+
],
|
|
21
|
+
"author": "Bwalya Adrian Mange",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18.0.0"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"chalk": "^4.1.2",
|
|
28
|
+
"enquirer": "^2.4.1"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/detector.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const REQUIRED_DIRS = ['controllers', 'routes', 'middleware', 'models', 'utils', 'database'];
|
|
5
|
+
const SECUREAUTH_DEPS = [
|
|
6
|
+
'express', 'bcryptjs', 'jsonwebtoken', 'helmet',
|
|
7
|
+
'express-rate-limit', 'express-slow-down', 'speakeasy',
|
|
8
|
+
'qrcode', 'dotenv', 'cors', 'uuid'
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
function detect(dir) {
|
|
12
|
+
const report = {
|
|
13
|
+
hasPackageJson: false,
|
|
14
|
+
hasExpress: false,
|
|
15
|
+
hasExpressInstalled: false,
|
|
16
|
+
hasSecureAuthDirs: false,
|
|
17
|
+
hasEnvFile: false,
|
|
18
|
+
existingDirs: [],
|
|
19
|
+
missingDirs: [],
|
|
20
|
+
installedDeps: [],
|
|
21
|
+
missingDeps: [],
|
|
22
|
+
packageJson: null,
|
|
23
|
+
projectName: path.basename(dir)
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
27
|
+
if (fs.existsSync(pkgPath)) {
|
|
28
|
+
report.hasPackageJson = true;
|
|
29
|
+
try {
|
|
30
|
+
report.packageJson = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
31
|
+
} catch {
|
|
32
|
+
return report;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const allDeps = {
|
|
36
|
+
...report.packageJson.dependencies,
|
|
37
|
+
...report.packageJson.devDependencies
|
|
38
|
+
};
|
|
39
|
+
report.installedDeps = Object.keys(allDeps);
|
|
40
|
+
|
|
41
|
+
report.hasExpress = 'express' in allDeps;
|
|
42
|
+
report.hasExpressInstalled = false;
|
|
43
|
+
|
|
44
|
+
const nodeModulesExpress = path.join(dir, 'node_modules', 'express');
|
|
45
|
+
if (fs.existsSync(nodeModulesExpress)) {
|
|
46
|
+
try {
|
|
47
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(nodeModulesExpress, 'package.json'), 'utf-8'));
|
|
48
|
+
report.hasExpressInstalled = true;
|
|
49
|
+
} catch {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
report.missingDeps = SECUREAUTH_DEPS.filter(d => !(d in allDeps));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const existingDirs = [];
|
|
56
|
+
const missingDirs = [];
|
|
57
|
+
for (const d of REQUIRED_DIRS) {
|
|
58
|
+
const full = path.join(dir, d);
|
|
59
|
+
if (fs.existsSync(full) && fs.statSync(full).isDirectory()) {
|
|
60
|
+
existingDirs.push(d);
|
|
61
|
+
} else {
|
|
62
|
+
missingDirs.push(d);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
report.existingDirs = existingDirs;
|
|
66
|
+
report.missingDirs = missingDirs;
|
|
67
|
+
report.hasSecureAuthDirs = missingDirs.length === 0;
|
|
68
|
+
|
|
69
|
+
report.hasEnvFile = fs.existsSync(path.join(dir, '.env'));
|
|
70
|
+
|
|
71
|
+
return report;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { detect };
|
package/src/generator.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const { logger } = require('./logger');
|
|
2
|
+
|
|
3
|
+
function buildEnvContent(answers) {
|
|
4
|
+
const lines = [];
|
|
5
|
+
|
|
6
|
+
lines.push('# SecureAuth Configuration (Node.js/Express)');
|
|
7
|
+
lines.push('# Auto-generated by SecureAuth CLI');
|
|
8
|
+
lines.push('');
|
|
9
|
+
|
|
10
|
+
lines.push('# ── Server Configuration ──');
|
|
11
|
+
lines.push(`PORT=${answers.port}`);
|
|
12
|
+
lines.push('NODE_ENV=development');
|
|
13
|
+
lines.push('');
|
|
14
|
+
|
|
15
|
+
lines.push('# ── JWT Configuration ──');
|
|
16
|
+
lines.push(`JWT_SECRET=${answers.jwtSecret}`);
|
|
17
|
+
lines.push('JWT_ALGORITHM=HS256');
|
|
18
|
+
lines.push('JWT_TEMP_EXPIRY_MINUTES=5');
|
|
19
|
+
lines.push('JWT_FULL_EXPIRY_HOURS=24');
|
|
20
|
+
lines.push('');
|
|
21
|
+
|
|
22
|
+
lines.push('# ── Bcrypt Configuration ──');
|
|
23
|
+
lines.push('BCRYPT_ROUNDS=12');
|
|
24
|
+
lines.push('');
|
|
25
|
+
|
|
26
|
+
lines.push('# ── TOTP Configuration ──');
|
|
27
|
+
lines.push(`TOTP_ISSUER=${answers.projectName || 'SecureAuth-App'}`);
|
|
28
|
+
lines.push('TOTP_WINDOW=1');
|
|
29
|
+
lines.push('');
|
|
30
|
+
|
|
31
|
+
if (answers.features && answers.features.includes('accountLockout')) {
|
|
32
|
+
lines.push('# ── Account Lockout ──');
|
|
33
|
+
lines.push('INACTIVITY_TIMEOUT_MINUTES=5');
|
|
34
|
+
lines.push('MAX_FAILED_ATTEMPTS=5');
|
|
35
|
+
lines.push('LOCKOUT_DURATION_MINUTES=30');
|
|
36
|
+
lines.push('');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
lines.push('# ── Database Configuration ──');
|
|
40
|
+
if (answers.database === 'postgres') {
|
|
41
|
+
lines.push('# For PostgreSQL, provide your connection string below');
|
|
42
|
+
lines.push('# Format: postgresql://user:password@host:5432/database');
|
|
43
|
+
lines.push('DATABASE_URL=postgresql://user:password@localhost:5432/secureauth');
|
|
44
|
+
} else {
|
|
45
|
+
lines.push('# SQLite — local database file (zero config)');
|
|
46
|
+
lines.push('DATABASE_URL=./secureauth.db');
|
|
47
|
+
}
|
|
48
|
+
lines.push('');
|
|
49
|
+
|
|
50
|
+
if (answers.features && answers.features.includes('ddos')) {
|
|
51
|
+
lines.push('# ── DDoS Protection Limits ──');
|
|
52
|
+
lines.push('MAX_REQUEST_SIZE=100kb');
|
|
53
|
+
lines.push('MAX_REQUESTS_PER_MINUTE=60');
|
|
54
|
+
lines.push('AUTO_BAN_THRESHOLD=100');
|
|
55
|
+
lines.push('BAN_DURATION_MINUTES=30');
|
|
56
|
+
lines.push('MAX_CONCURRENT_CONNECTIONS=100');
|
|
57
|
+
lines.push('');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return lines.join('\n');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function writeEnvFile(targetDir, answers) {
|
|
64
|
+
const fs = require('fs');
|
|
65
|
+
const path = require('path');
|
|
66
|
+
const envPath = path.join(targetDir, '.env');
|
|
67
|
+
|
|
68
|
+
if (fs.existsSync(envPath)) {
|
|
69
|
+
logger.warn('.env already exists — skipping (delete it first to regenerate)');
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const content = buildEnvContent(answers);
|
|
74
|
+
fs.writeFileSync(envPath, content, 'utf-8');
|
|
75
|
+
logger.success('.env created with secure defaults');
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = { writeEnvFile, buildEnvContent };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const { logger } = require('./logger');
|
|
4
|
+
const { detect } = require('./detector');
|
|
5
|
+
const { printReport } = require('./reporter');
|
|
6
|
+
const { install } = require('./installer');
|
|
7
|
+
const { writeEnvFile } = require('./generator');
|
|
8
|
+
const { scaffold } = require('./scaffolder');
|
|
9
|
+
const {
|
|
10
|
+
askProjectType,
|
|
11
|
+
askProjectName,
|
|
12
|
+
askDatabase,
|
|
13
|
+
askPort,
|
|
14
|
+
askJwtSecret,
|
|
15
|
+
askFeatures,
|
|
16
|
+
askConfirm
|
|
17
|
+
} = require('./questions');
|
|
18
|
+
|
|
19
|
+
const FEATURE_LABELS = {
|
|
20
|
+
ddos: 'DDoS Protection',
|
|
21
|
+
admin: 'Admin Dashboard',
|
|
22
|
+
accountLockout: 'Account Lockout'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function printSuccess(answers, filesCreated) {
|
|
26
|
+
const features = answers.features && answers.features.length > 0
|
|
27
|
+
? answers.features.map(f => ' ' + logger.bullet() + ' ' + (FEATURE_LABELS[f] || f)).join('\n')
|
|
28
|
+
: ' (none selected — core auth only)';
|
|
29
|
+
|
|
30
|
+
logger.box(`
|
|
31
|
+
${logger.checkmark()} SecureAuth installed successfully!
|
|
32
|
+
|
|
33
|
+
Files generated: ${filesCreated}
|
|
34
|
+
Database: ${answers.database}
|
|
35
|
+
Port: ${answers.port}
|
|
36
|
+
|
|
37
|
+
Features enabled:
|
|
38
|
+
${features}
|
|
39
|
+
|
|
40
|
+
${logger.separator('Quick Start')}
|
|
41
|
+
|
|
42
|
+
${logger.bold('npm start')}
|
|
43
|
+
${logger.arrow()} http://localhost:${answers.port}
|
|
44
|
+
|
|
45
|
+
${logger.separator('API Endpoints')}
|
|
46
|
+
|
|
47
|
+
${logger.dim('Auth')}
|
|
48
|
+
POST /api/register Register + 2FA setup
|
|
49
|
+
POST /api/login Login (step 1 — password)
|
|
50
|
+
POST /api/verify-2fa Login (step 2 — TOTP code)
|
|
51
|
+
POST /api/refresh Refresh access token
|
|
52
|
+
|
|
53
|
+
${logger.dim('Protected')}
|
|
54
|
+
GET /api/dashboard User dashboard
|
|
55
|
+
GET /api/login-history Login history${answers.features && answers.features.includes('admin') ? `
|
|
56
|
+
|
|
57
|
+
${logger.dim('Admin')}
|
|
58
|
+
GET /api/admin/users List all users
|
|
59
|
+
GET /api/admin/logs System audit logs
|
|
60
|
+
GET /api/admin/stats System statistics` : ''}
|
|
61
|
+
|
|
62
|
+
${logger.dim('System')}
|
|
63
|
+
GET /api/health Health check
|
|
64
|
+
|
|
65
|
+
${logger.separator('Demo Credentials')}
|
|
66
|
+
|
|
67
|
+
${logger.bold('User:')} demo@secureauth.com / Demo@123
|
|
68
|
+
${logger.bold('Admin:')} admin@secureauth.com / AdminPassword123!
|
|
69
|
+
|
|
70
|
+
${logger.separator('Next Steps')}
|
|
71
|
+
|
|
72
|
+
1. Review .env and customize settings
|
|
73
|
+
2. Run ${logger.bold('npm start')}
|
|
74
|
+
3. Open ${logger.underline('http://localhost:' + answers.port)}
|
|
75
|
+
4. Register at POST /api/register
|
|
76
|
+
`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function init() {
|
|
80
|
+
logger.banner();
|
|
81
|
+
|
|
82
|
+
const targetDir = process.cwd();
|
|
83
|
+
|
|
84
|
+
const report = detect(targetDir);
|
|
85
|
+
printReport(report);
|
|
86
|
+
logger.info('');
|
|
87
|
+
|
|
88
|
+
const projectType = report.hasPackageJson
|
|
89
|
+
? 'existing'
|
|
90
|
+
: await askProjectType();
|
|
91
|
+
|
|
92
|
+
const answers = { projectName: report.projectName };
|
|
93
|
+
|
|
94
|
+
if (projectType === 'new') {
|
|
95
|
+
answers.projectName = await askProjectName();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
answers.database = await askDatabase();
|
|
99
|
+
answers.port = await askPort(3000);
|
|
100
|
+
answers.jwtSecret = await askJwtSecret();
|
|
101
|
+
answers.features = await askFeatures();
|
|
102
|
+
|
|
103
|
+
logger.info('');
|
|
104
|
+
const confirmed = await askConfirm();
|
|
105
|
+
if (!confirmed) {
|
|
106
|
+
logger.warn('Installation cancelled.');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
logger.success('Starting SecureAuth installation...');
|
|
111
|
+
logger.info(`Project: ${answers.projectName}`);
|
|
112
|
+
logger.info(`Database: ${answers.database}`);
|
|
113
|
+
logger.info(`Port: ${answers.port}`);
|
|
114
|
+
logger.info(`Features: ${answers.features.join(', ') || 'none'}`);
|
|
115
|
+
|
|
116
|
+
logger.info('');
|
|
117
|
+
|
|
118
|
+
await install(targetDir, report.missingDeps, answers);
|
|
119
|
+
|
|
120
|
+
const filesCreated = scaffold(targetDir, answers);
|
|
121
|
+
|
|
122
|
+
writeEnvFile(targetDir, answers);
|
|
123
|
+
|
|
124
|
+
printSuccess(answers, filesCreated);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = { init };
|
package/src/installer.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const { logger } = require('./logger');
|
|
3
|
+
|
|
4
|
+
const DB_DRIVERS = {
|
|
5
|
+
sqlite: 'better-sqlite3',
|
|
6
|
+
postgres: 'pg'
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function install(cwd, missingDeps, answers) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const dbDriver = DB_DRIVERS[answers.database];
|
|
12
|
+
const allDeps = [...missingDeps];
|
|
13
|
+
|
|
14
|
+
if (dbDriver && !allDeps.includes(dbDriver)) {
|
|
15
|
+
allDeps.push(dbDriver);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (allDeps.length === 0) {
|
|
19
|
+
logger.success('All dependencies already installed');
|
|
20
|
+
return resolve();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
logger.info(`Installing ${allDeps.length} packages: ${allDeps.join(', ')}`);
|
|
24
|
+
logger.info('');
|
|
25
|
+
|
|
26
|
+
const args = ['install', ...allDeps, '--save', '--progress=false'];
|
|
27
|
+
|
|
28
|
+
const child = spawn('npm', args, {
|
|
29
|
+
cwd,
|
|
30
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
31
|
+
shell: true
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
let output = '';
|
|
35
|
+
|
|
36
|
+
child.stdout.on('data', data => {
|
|
37
|
+
output += data.toString();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
child.stderr.on('data', data => {
|
|
41
|
+
const line = data.toString().trim();
|
|
42
|
+
if (line && !line.startsWith('npm WARN')) {
|
|
43
|
+
output += line + '\n';
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
child.on('close', code => {
|
|
48
|
+
if (code === 0) {
|
|
49
|
+
logger.success('Dependencies installed successfully');
|
|
50
|
+
resolve();
|
|
51
|
+
} else {
|
|
52
|
+
logger.error('npm install failed');
|
|
53
|
+
logger.error(`Exit code: ${code}`);
|
|
54
|
+
reject(new Error(`npm install exited with code ${code}`));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
child.on('error', err => {
|
|
59
|
+
logger.error(`Failed to start npm: ${err.message}`);
|
|
60
|
+
reject(err);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { install };
|
package/src/logger.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
const logger = {
|
|
4
|
+
info(msg) {
|
|
5
|
+
console.log(chalk.blue('ℹ'), msg);
|
|
6
|
+
},
|
|
7
|
+
success(msg) {
|
|
8
|
+
console.log(chalk.green('✔'), msg);
|
|
9
|
+
},
|
|
10
|
+
warn(msg) {
|
|
11
|
+
console.log(chalk.yellow('⚠'), msg);
|
|
12
|
+
},
|
|
13
|
+
error(msg) {
|
|
14
|
+
console.log(chalk.red('✖'), msg);
|
|
15
|
+
},
|
|
16
|
+
banner() {
|
|
17
|
+
console.log(`
|
|
18
|
+
${chalk.cyan('╔══════════════════════════════════════════╗')}
|
|
19
|
+
${chalk.cyan('║')} ${chalk.bold('SecureAuth CLI')} ${chalk.cyan('║')}
|
|
20
|
+
${chalk.cyan('║')} Enterprise Authentication for Express ${chalk.cyan('║')}
|
|
21
|
+
${chalk.cyan('╚══════════════════════════════════════════╝')}
|
|
22
|
+
`);
|
|
23
|
+
},
|
|
24
|
+
box(content) {
|
|
25
|
+
const lines = content.split('\n');
|
|
26
|
+
const width = 70;
|
|
27
|
+
const top = chalk.cyan('╔' + '═'.repeat(width - 2) + '╗');
|
|
28
|
+
const bottom = chalk.cyan('╚' + '═'.repeat(width - 2) + '╝');
|
|
29
|
+
console.log('\n' + top);
|
|
30
|
+
for (const line of lines) {
|
|
31
|
+
const stripped = line.replace(/\u001b\[[0-9;]*m/g, '');
|
|
32
|
+
const padding = width - 4 - stripped.length;
|
|
33
|
+
console.log(chalk.cyan('║') + ' ' + line + ' '.repeat(Math.max(0, padding)) + chalk.cyan('║'));
|
|
34
|
+
}
|
|
35
|
+
console.log(bottom + '\n');
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
bullet() {
|
|
39
|
+
return chalk.cyan('•');
|
|
40
|
+
},
|
|
41
|
+
checkmark() {
|
|
42
|
+
return chalk.green('✔');
|
|
43
|
+
},
|
|
44
|
+
separator(label) {
|
|
45
|
+
const line = '─'.repeat(30);
|
|
46
|
+
return chalk.dim(line + ' ' + label + ' ' + line);
|
|
47
|
+
},
|
|
48
|
+
bold(text) {
|
|
49
|
+
return chalk.bold(text);
|
|
50
|
+
},
|
|
51
|
+
dim(text) {
|
|
52
|
+
return chalk.dim(text);
|
|
53
|
+
},
|
|
54
|
+
arrow() {
|
|
55
|
+
return chalk.cyan('→');
|
|
56
|
+
},
|
|
57
|
+
underline(text) {
|
|
58
|
+
return chalk.underline(text);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
module.exports = { logger };
|
package/src/questions.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const { prompt } = require('enquirer');
|
|
2
|
+
|
|
3
|
+
async function askProjectType() {
|
|
4
|
+
const { type } = await prompt({
|
|
5
|
+
type: 'select',
|
|
6
|
+
name: 'type',
|
|
7
|
+
message: 'What kind of project is this?',
|
|
8
|
+
choices: [
|
|
9
|
+
{ name: 'existing', message: 'Existing Express project — add SecureAuth to it' },
|
|
10
|
+
{ name: 'new', message: 'New project — scaffold from scratch' }
|
|
11
|
+
]
|
|
12
|
+
});
|
|
13
|
+
return type;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function askProjectName() {
|
|
17
|
+
const { name } = await prompt({
|
|
18
|
+
type: 'input',
|
|
19
|
+
name: 'name',
|
|
20
|
+
message: 'What is your project name?',
|
|
21
|
+
initial: 'my-secure-app',
|
|
22
|
+
required: true,
|
|
23
|
+
validate: v => /^[a-z0-9-]+$/.test(v) || 'Use lowercase letters, numbers, and hyphens only'
|
|
24
|
+
});
|
|
25
|
+
return name;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function askDatabase() {
|
|
29
|
+
const { db } = await prompt({
|
|
30
|
+
type: 'select',
|
|
31
|
+
name: 'db',
|
|
32
|
+
message: 'Which database do you want to use?',
|
|
33
|
+
choices: [
|
|
34
|
+
{ name: 'sqlite', message: 'SQLite — Zero config, best for development' },
|
|
35
|
+
{ name: 'postgres', message: 'PostgreSQL — Production-grade, requires a running server' }
|
|
36
|
+
],
|
|
37
|
+
initial: 0
|
|
38
|
+
});
|
|
39
|
+
return db;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function askPort(defaultPort) {
|
|
43
|
+
const { port } = await prompt({
|
|
44
|
+
type: 'input',
|
|
45
|
+
name: 'port',
|
|
46
|
+
message: 'Which port should the server run on?',
|
|
47
|
+
initial: String(defaultPort),
|
|
48
|
+
required: true,
|
|
49
|
+
validate: v => /^\d+$/.test(v) || 'Enter a valid port number'
|
|
50
|
+
});
|
|
51
|
+
return parseInt(port, 10);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function askJwtSecret() {
|
|
55
|
+
const autoSecret = require('crypto').randomBytes(32).toString('hex');
|
|
56
|
+
const { choice } = await prompt({
|
|
57
|
+
type: 'select',
|
|
58
|
+
name: 'choice',
|
|
59
|
+
message: 'JWT Secret — auto-generate or enter your own?',
|
|
60
|
+
choices: [
|
|
61
|
+
{ name: 'auto', message: `Auto-generate a secure secret` },
|
|
62
|
+
{ name: 'manual', message: 'Enter my own secret' }
|
|
63
|
+
]
|
|
64
|
+
});
|
|
65
|
+
if (choice === 'auto') return autoSecret;
|
|
66
|
+
const { secret } = await prompt({
|
|
67
|
+
type: 'invisible',
|
|
68
|
+
name: 'secret',
|
|
69
|
+
message: 'Enter your JWT secret:',
|
|
70
|
+
required: true,
|
|
71
|
+
validate: v => v.length >= 32 || 'Secret must be at least 32 characters'
|
|
72
|
+
});
|
|
73
|
+
return secret;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function askFeatures() {
|
|
77
|
+
const { features } = await prompt({
|
|
78
|
+
type: 'multiselect',
|
|
79
|
+
name: 'features',
|
|
80
|
+
message: 'Select additional features to enable:',
|
|
81
|
+
choices: [
|
|
82
|
+
{ name: 'ddos', message: 'DDoS Protection — Rate limiting + IP blocking' },
|
|
83
|
+
{ name: 'admin', message: 'Admin Dashboard — User management panel' },
|
|
84
|
+
{ name: 'accountLockout', message: 'Account Lockout — Brute-force protection' }
|
|
85
|
+
],
|
|
86
|
+
initial: [0, 2]
|
|
87
|
+
});
|
|
88
|
+
return features;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function askConfirm() {
|
|
92
|
+
const { confirmed } = await prompt({
|
|
93
|
+
type: 'toggle',
|
|
94
|
+
name: 'confirmed',
|
|
95
|
+
message: 'Ready to install SecureAuth?',
|
|
96
|
+
enabled: 'Yes',
|
|
97
|
+
disabled: 'No'
|
|
98
|
+
});
|
|
99
|
+
return confirmed;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = {
|
|
103
|
+
askProjectType,
|
|
104
|
+
askProjectName,
|
|
105
|
+
askDatabase,
|
|
106
|
+
askPort,
|
|
107
|
+
askJwtSecret,
|
|
108
|
+
askFeatures,
|
|
109
|
+
askConfirm
|
|
110
|
+
};
|
package/src/reporter.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const { logger } = require('./logger');
|
|
2
|
+
|
|
3
|
+
function printReport(report) {
|
|
4
|
+
logger.info(`Project: ${report.projectName}`);
|
|
5
|
+
|
|
6
|
+
if (!report.hasPackageJson) {
|
|
7
|
+
logger.warn('No package.json found — will scaffold from scratch');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (report.hasExpress) {
|
|
12
|
+
logger.success('Express detected in dependencies');
|
|
13
|
+
|
|
14
|
+
if (report.hasExpressInstalled) {
|
|
15
|
+
logger.success('Express is already installed in node_modules');
|
|
16
|
+
} else {
|
|
17
|
+
logger.warn('Express declared but not installed — will install');
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
logger.warn('Express not found — will add it');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (report.missingDeps.length === 0) {
|
|
24
|
+
logger.success('All SecureAuth dependencies already installed');
|
|
25
|
+
} else {
|
|
26
|
+
logger.info(`${report.missingDeps.length} packages to install: ${report.missingDeps.join(', ')}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (report.hasSecureAuthDirs) {
|
|
30
|
+
logger.success('All SecureAuth folders already exist');
|
|
31
|
+
} else {
|
|
32
|
+
if (report.existingDirs.length > 0) {
|
|
33
|
+
logger.info(`Found folders: ${report.existingDirs.join(', ')}`);
|
|
34
|
+
}
|
|
35
|
+
logger.info(`Will create: ${report.missingDirs.join(', ')}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (report.hasEnvFile) {
|
|
39
|
+
logger.warn('.env file exists — will not overwrite');
|
|
40
|
+
} else {
|
|
41
|
+
logger.info('No .env file — will create one');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { printReport };
|