import 'dotenv/config'; import express from 'express'; import cors from 'cors'; import pg from 'pg'; import { fileURLToPath } from 'node:url'; const { Pool } = pg; import { dirname, resolve as resolvePath } from 'node:path'; import { config as loadEnv } from 'dotenv'; import { corsOptions } from '../shared/cors/policy.js'; import { requestLog } from '../shared/logging/request-log.js'; import { logger } from '../shared/logging/logger.js'; import { errorHandler } from '../shared/errors/handler.js'; import { loginLimiter, verifyLimiter } from '../shared/rate-limit/login.js'; import { extractBearerToken, verifyToken, TokenError } from '../shared/auth/verify.js'; import { createAuthRoutes } from './routes.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // auth/ lives at inventory-server/auth/, so .env one level up loadEnv({ path: resolvePath(__dirname, '../.env') }); if (!process.env.JWT_SECRET) { logger.error('JWT_SECRET is not set; refusing to start'); process.exit(1); } logger.info({ host: process.env.DB_HOST, user: process.env.DB_USER, database: process.env.DB_NAME, port: process.env.DB_PORT, auth_port: process.env.AUTH_PORT, }, 'starting auth server'); const app = express(); const port = Number(process.env.AUTH_PORT) || 3011; const pool = new Pool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, port: Number(process.env.DB_PORT) || 5432, }); app.use(requestLog()); app.use(express.json({ limit: '1mb' })); app.use(cors(corsOptions)); // Caddy forward_auth target: JWT signature check only, no DB hit. // Returns 200 with X-User-Id / X-User-Username on success; 401 otherwise. // Per-service middleware re-verifies independently; these headers are informational. app.all('/verify', verifyLimiter, (req, res) => { try { const token = extractBearerToken(req.headers.authorization); const decoded = verifyToken(token, process.env.JWT_SECRET); res.set('X-User-Id', String(decoded.userId)); if (decoded.username) res.set('X-User-Username', decoded.username); res.status(200).end(); } catch (err) { if (err instanceof TokenError) { return res.status(401).json({ error: err.message }); } res.status(401).json({ error: 'Invalid token' }); } }); // Login route gets its own rate limiter to slow credential stuffing. app.use('/login', loginLimiter); // Mount user-management + /login + /me from routes.js app.use('/', createAuthRoutes({ pool })); app.get('/health', (req, res) => res.json({ status: 'healthy' })); app.use(errorHandler); app.listen(port, () => { logger.info({ port }, 'auth server listening'); });