← back to AbramsOS

server.js

123 lines

require('dotenv').config();
const express = require('express');
const helmet = require('helmet');
const morgan = require('morgan');
const path = require('path');
const cookieParser = require('cookie-parser');

const { loadSessionMiddleware, requireAuth, requireStepUp } = require('./middleware/auth');
const csrf = require('./middleware/csrf');

const home = require('./routes/home');
const health = require('./routes/health');
const oauth = require('./routes/auth');           // Google OAuth (existing)
const authApp = require('./routes/auth-app');     // password + TOTP signup/signin
const connectors = require('./routes/connectors');
const purchases = require('./routes/purchases');
const importRouter = require('./routes/import');
const plaidRouter = require('./routes/plaid');
const documentsRouter = require('./routes/documents');
const remindersRouter = require('./routes/reminders');
const claimsRouter = require('./routes/claims');
const auditRouter = require('./routes/audit');
const recallsRouter = require('./routes/recalls');
const uploadRouter = require('./routes/upload');
const billsRouter = require('./routes/bills');
const reordersRouter = require('./routes/reorders');
const savingsRouter = require('./routes/savings');
const vitalsRouter = require('./routes/vitals').router;
const assetsRouter = require('./routes/assets');
const neighborhoodRouter = require('./routes/neighborhood');
const biometricsRouter = require('./routes/biometrics');
const withingsRouter = require('./routes/withings');
const warrantiesRouter = require('./routes/warranties');
const peopleRouter = require('./routes/people');
const medicationsRouter = require('./routes/medications');
const prescriptionsRouter = require('./routes/prescriptions');

const app = express();
const PORT = parseInt(process.env.PORT || '9931', 10);

app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.set('trust proxy', 'loopback');

app.use(helmet({ contentSecurityPolicy: false, crossOriginEmbedderPolicy: false }));
app.use(morgan('tiny'));
app.use(express.json({ limit: '2mb' }));
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser(process.env.SESSION_SECRET || 'dev-only-rotate-me'));

// Load DB-backed session for every request (no-op if cookie missing)
app.use(loadSessionMiddleware);

// CSRF: token mint + verify (HTML POST forms only; /api/* exempted)
app.use(csrf.ensureToken);
app.use(csrf.verifyToken);

// View locals
app.locals.infoEmail = process.env.INFO_EMAIL || 'info@abramsos.agentabrams.com';
app.use((req, res, next) => {
  res.locals.infoEmail = app.locals.infoEmail;
  res.locals.userId = req.userId || null;
  next();
});

// Defensive 404-guard: never serve backup/snapshot files from /public, even if
// one accidentally lands on disk (*.bak, *.bak.<n>, *.pre-*, *.orig, *~).
app.use((req, res, next) => {
  if (/\.(bak|orig)(\.[^/]+)?$|\.pre-[^/]+$|~$/i.test(req.path)) {
    return res.status(404).type('text/plain').send('Not found');
  }
  next();
});

app.use(express.static(path.join(__dirname, 'public'), { maxAge: '0' }));

// Public routes
app.use(health);                    // /healthz
app.use(authApp);                   // /signup, /signin, /signout, /enroll-totp, /step-up

// Authenticated routes (require login)
app.use(requireAuth);
app.use(home);                      // /
app.use(oauth);                     // /auth/google/start, /auth/google/callback
app.use(connectors);                // /connectors, /api/connectors, sync
app.use(purchases);                 // /purchases, /api/purchases
app.use(plaidRouter);               // /api/plaid/*
app.use(documentsRouter);           // /api/documents, /api/documents/:id/parse
app.use(remindersRouter);           // /api/reminders/upcoming, regenerate, dismiss
app.use(claimsRouter);              // /api/claims, /api/claims/from-reminder/:id
app.use(auditRouter);               // /audit, /api/audit/events
app.use(recallsRouter);             // /recalls, /api/recalls
app.use(uploadRouter);              // /api/upload/csv
app.use(billsRouter);               // /bills, /api/bills*
app.use(reordersRouter);            // /reorders, /api/reorders*
app.use(savingsRouter);             // /savings, /api/savings*, /api/coupons
app.use(vitalsRouter);              // /health, /api/health/readings*
app.use(assetsRouter);              // /assets, /api/assets*
app.use(neighborhoodRouter);        // /neighborhood, /api/neighborhood/geocode
app.use(biometricsRouter);          // /biometrics, /api/biometrics*
app.use(withingsRouter);            // /auth/withings, /api/withings/*
app.use(warrantiesRouter);          // /warranties, /api/warranties*
app.use(peopleRouter);              // /household, /api/people*
app.use(medicationsRouter);         // /medications, /api/medications*
app.use(prescriptionsRouter);       // /prescriptions, /api/prescriptions

// Step-up-required routes (must re-verify TOTP within 60s)
app.use('/import', requireStepUp, importRouter);

app.use((err, _req, res, _next) => {
  console.error('[unhandled]', err);
  res.status(500).render('error', { error: err.message });
});

if (require.main === module) {
  app.listen(PORT, () => {
    console.log(`[abramsos] listening on http://localhost:${PORT}`);
    require('./lib/scheduler').start();
  });
}

module.exports = app;