@dax-crafta/auth
Version:
A powerful, flexible, and secure authentication plugin for the Crafta framework. Supports JWT, social login, 2FA, RBAC, audit logging, and enterprise-grade security features.
442 lines (398 loc) • 14.5 kB
JavaScript
const passport = require('passport');
const cookieParser = require('cookie-parser');
const csrf = require('csurf');
const helmet = require("helmet");
const mongoose = require("mongoose"); // <<< FIX ADDED
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const AuthService = require('./services/auth.service');
const RoleService = require('./services/role.service');
const AuditService = require('./services/audit.service');
const MFAService = require('./utils/mfa');
const PasswordPolicy = require('./utils/password-policy');
const createAuthMiddleware = require('./middlewares/auth.middleware');
const validators = require('./middlewares/validation.middleware');
const { createLogger } = require('./utils/logger');
const ApiError = require('./utils/api-error');
const { runAdaptiveTests, inferFeatureFlags, testCatalog } = require('./testing/adaptive-runner');
const defaultConfig = {
strategy: 'jwt',
fields: ['email', 'password'],
routes: {
register: '/register',
login: '/login',
verify: '/verify',
forgotPassword: '/forgot-password',
resetPassword: '/reset-password',
refreshToken: '/refresh-token',
profile: '/profile',
twoFactor: '/2fa',
roles: '/roles',
permissions: '/permissions'
},
mongoUrl: process.env.MONGODB_URI || 'mongodb://localhost:27017/forge-auth',
maxLoginAttempts: 5,
emailVerification: true,
loginAlerts: true,
passwordPolicy: {
minLength: 8,
requireUppercase: true,
requireNumbers: true,
requireSpecialChars: true,
expiryDays: 90
},
smtp: null,
social: {
google: null,
facebook: null,
github: null
},
features: {
emailVerification: true,
loginAlerts: true,
securityAttempts: true,
rateLimit: true,
auditLogs: true,
twoFactor: true,
csrf: false
},
env: {
JWT_SECRET: process.env.JWT_SECRET || 'crafta-auth-dev-secret'
},
accessTokenExpiry: '1h',
refreshTokenDays: 7
};
function auth(config = {}) {
const finalConfig = {
...defaultConfig,
...config,
routes: { ...defaultConfig.routes, ...(config.routes || {}) },
passwordPolicy: { ...defaultConfig.passwordPolicy, ...(config.passwordPolicy || {}) },
social: { ...defaultConfig.social, ...(config.social || {}) },
limits: { ...(config.limits || {}) },
features: { ...defaultConfig.features, ...(config.features || {}) }
};
finalConfig.env = { ...(defaultConfig.env || {}), ...(config.env || process.env) };
const logger = createLogger(finalConfig.logging !== false);
const featureBools = {
emailVerification: config.emailVerification,
loginAlerts: config.loginAlerts,
csrf: config.enableCSRF
};
Object.keys(featureBools).forEach((key) => {
if (typeof featureBools[key] === 'boolean') {
finalConfig.features[key] = featureBools[key];
}
});
finalConfig.emailVerification = finalConfig.features.emailVerification;
finalConfig.loginAlerts = finalConfig.features.loginAlerts;
finalConfig.enableCSRF = finalConfig.features.csrf;
if (finalConfig.features.securityAttempts === false) {
finalConfig.loginAlerts = false;
finalConfig.features.loginAlerts = false;
}
if (finalConfig.env?.JWT_SECRET === 'crafta-auth-dev-secret') {
logger.logWarn('JWT_SECRET not provided; using development default secret. Set env.JWT_SECRET in production.');
}
const smtpEnabled = !!(
finalConfig.smtp &&
finalConfig.smtp.host &&
finalConfig.smtp.port &&
finalConfig.smtp.auth &&
finalConfig.smtp.auth.user &&
finalConfig.smtp.auth.pass &&
finalConfig.smtp.from
);
if (!smtpEnabled && finalConfig.features.emailVerification) {
logger.logWarn('SMTP not configured; emailVerification auto-disabled.');
finalConfig.features.emailVerification = false;
finalConfig.emailVerification = false;
}
if (!smtpEnabled && finalConfig.features.loginAlerts) {
logger.logWarn('SMTP not configured; loginAlerts auto-disabled.');
finalConfig.features.loginAlerts = false;
finalConfig.loginAlerts = false;
}
// ------------------------------------------
// ✅ Proper MongoDB Connection
// ------------------------------------------
if (mongoose.connection.readyState === 0) {
mongoose.connect(finalConfig.mongoUrl, {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => logger.logSuccess("MongoDB connected"))
.catch(err => logger.logError(`MongoDB connection error: ${err.message}`));
} else {
logger.logInfo("Using existing mongoose connection");
}
// ------------------------------------------
const auditService = finalConfig.features.auditLogs ? new AuditService() : null;
const mfaService = new MFAService();
// Pass shared services into AuthService config so it can log/audit correctly
const authService = new AuthService({
...finalConfig,
auditService,
mfaService
});
const roleService = new RoleService();
const passwordPolicy = new PasswordPolicy(finalConfig.passwordPolicy);
const { rateLimiter, limiterFor, verifyToken, checkRole, checkOwnershipOrAdmin } = createAuthMiddleware(finalConfig);
if (finalConfig.social && finalConfig.social.google) {
passport.use(new GoogleStrategy(finalConfig.social.google,
async (accessToken, refreshToken, profile, done) => {
try {
const user = await authService.handleSocialLogin('google', profile);
done(null, user);
} catch (err) {
done(err);
}
}
));
}
return function (app) {
const safeAudit = async (entry) => {
if (!auditService) return;
try {
await auditService.logActivity(entry);
} catch (err) {
logger.logWarn(`Audit logging failed: ${err.message}`);
}
};
app.use(cookieParser());
if (finalConfig.enableCSRF) {
const csrfProtection = csrf({ cookie: true });
app.use(csrfProtection);
app.get('/csrf-token', (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});
}
app.use(passport.initialize());
app.use(rateLimiter);
app.use(helmet({
contentSecurityPolicy: false,
crossOriginEmbedderPolicy: false,
}));
app.authService = authService;
app.roleService = roleService;
app.auditService = auditService;
app.mfaService = mfaService;
app.passwordPolicy = passwordPolicy;
app.authLogger = logger;
// EMAIL VERIFICATION
app.get(
finalConfig.routes.verify,
async (req, res, next) => {
try {
const token = req.query.token;
const user = await authService.verifyEmail(token);
res.json({
success: true,
message: 'Email verified successfully',
userId: user._id
});
} catch (err) {
next(err);
}
}
);
// REGISTER
app.post(
finalConfig.routes.register,
limiterFor('register'),
validators.registerValidator,
validators.handleValidation,
async (req, res, next) => {
try {
const { isValid, errors } = passwordPolicy.validate(req.body.password, {
email: req.body.email,
name: req.body.name
});
if (!isValid) {
return res.status(400).json({ success: false, error: 'Password policy violation', details: errors });
}
const user = await authService.register(req.body);
await safeAudit({
userId: user._id,
action: 'register',
ipAddress: req.ip,
userAgent: req.headers['user-agent'],
status: 'success'
});
logger.logSuccess('User registered');
res.status(201).json({ success: true, message: 'Registration successful' });
} catch (err) {
logger.logError(`Register failed: ${err.message}`);
next(err);
}
}
);
// LOGIN
app.post(
finalConfig.routes.login,
limiterFor('login'),
validators.loginValidator,
validators.handleValidation,
async (req, res, next) => {
try {
const deviceInfo = { userAgent: req.headers['user-agent'], ip: req.ip };
const result = await authService.login(req.body.email, req.body.password, deviceInfo);
await safeAudit({
userId: result.user?._id,
action: 'login',
ipAddress: req.ip,
userAgent: req.headers['user-agent'],
status: 'success'
});
logger.logSuccess('Login successful');
res.json({ success: true, ...result });
} catch (err) {
await safeAudit({
action: 'login',
ipAddress: req.ip,
userAgent: req.headers['user-agent'],
status: 'failure',
details: { error: err.message }
});
logger.logError(`Login failed: ${err.message}`);
next(err);
}
}
);
// 2FA
if (finalConfig.features.twoFactor !== false) {
app.post(
finalConfig.routes.twoFactor,
limiterFor('2fa'),
validators.twoFAValidator,
validators.handleValidation,
async (req, res, next) => {
try {
const result = await authService.verify2FA(req.body.userId, req.body.code);
await safeAudit({ userId: result.user._id, action: '2fa_verify', status: 'success' });
logger.logSuccess('2FA verified');
res.json({ success: true, ...result });
} catch (err) {
logger.logError(`2FA failed: ${err.message}`);
next(err);
}
}
);
}
// FORGOT PASSWORD
app.post(
finalConfig.routes.forgotPassword,
limiterFor('forgotPassword'),
validators.forgotPasswordValidator,
validators.handleValidation,
async (req, res, next) => {
try {
await authService.forgotPassword(req.body.email);
logger.logInfo('Password reset email triggered');
res.json({ success: true, message: 'Password reset email sent if user exists' });
} catch (err) {
logger.logError(`Forgot password failed: ${err.message}`);
next(err);
}
}
);
// RESET PASSWORD
app.post(
finalConfig.routes.resetPassword,
validators.resetPasswordValidator,
validators.handleValidation,
async (req, res, next) => {
try {
await authService.resetPassword(req.body.token, req.body.newPassword);
await safeAudit({ action: 'password_reset', status: 'success' });
logger.logSuccess('Password reset completed');
res.json({ success: true, message: 'Password reset successful' });
} catch (err) {
logger.logError(`Reset password failed: ${err.message}`);
next(err);
}
}
);
// REFRESH TOKEN
app.post(
finalConfig.routes.refreshToken,
validators.refreshTokenValidator,
validators.handleValidation,
async (req, res, next) => {
try {
const result = await authService.refreshToken(req.body.refreshToken);
logger.logSuccess('Token refreshed');
res.json({ success: true, ...result });
} catch (err) {
logger.logError(`Refresh token failed: ${err.message}`);
next(err);
}
}
);
// UPDATE PROFILE
app.put(
finalConfig.routes.profile,
verifyToken,
checkOwnershipOrAdmin((req) => req.user.id),
validators.profileUpdateValidator,
validators.handleValidation,
async (req, res, next) => {
try {
const user = await authService.updateProfile(req.user.id, req.body);
await safeAudit({ userId: user._id, action: 'profile_update', status: 'success' });
logger.logSuccess('Profile updated');
res.json({ success: true, user });
} catch (err) {
logger.logError(`Profile update failed: ${err.message}`);
next(err);
}
}
);
// CREATE ROLE (ADMIN)
app.post('/roles', verifyToken, checkRole(['admin']), async (req, res, next) => {
try {
const role = await roleService.createRole(req.body);
res.status(201).json({ success: true, role });
} catch (err) {
next(err);
}
});
// GOOGLE OAUTH
if (finalConfig.social && finalConfig.social.google) {
app.get('/auth/google', passport.authenticate('google', { scope: ['profile', 'email'] }));
app.get('/auth/google/callback',
passport.authenticate('google', { session: false }),
async (req, res, next) => {
try {
const tokens = await authService.generateTokens(req.user);
await safeAudit({
userId: req.user._id,
action: 'social_login',
status: 'success',
details: { provider: 'google' }
});
logger.logSuccess('Google OAuth success');
res.json({ success: true, accessToken: tokens.accessToken, refreshToken: tokens.refreshToken, user: req.user });
} catch (err) {
logger.logError(`Google OAuth failed: ${err.message}`);
next(err);
}
});
}
// ERROR HANDLER
app.use((err, req, res, next) => {
console.error(err && err.stack ? err.stack : err);
const status = err.status || (err.name === 'ValidationError' ? 400 : 500);
let message;
if (status >= 400 && status < 500) {
message = err.message || 'Bad request';
} else {
message = process.env.NODE_ENV === 'development'
? (err.message || 'Internal Server Error')
: 'Internal Server Error';
}
logger.logError(`Error ${status}: ${message}`);
res.status(status).json({ success: false, error: message });
});
};
}
module.exports = { auth, ApiError, runAdaptiveTests, inferFeatureFlags, testCatalog };