const express = require('express'); const cookieParser = require('cookie-parser'); const morgan = require('morgan'); const path = require('path'); const session = require('express-session'); const dotenv = require('dotenv'); dotenv.config(); const bodyParser = require('body-parser'); const dbConnect = require('./db/index'); const cors = require('cors'); const passport = require('passport'); // const FileStore = require('session-file-store')(session); const helmet = require('helmet'); const mainRouter = require('./routes/mainRouter'); const archiveRouter = require('./routes/archiveRouter'); const authRouter = require('./routes/authRouter.js'); const passportConfig = require('./passport/index.js'); const commonRouter = require('./routes/commonRouter.js'); const officialDocRouter = require('./routes/officialDocRouter.js'); const overviewRouter = require('./routes/overviewRouter.js'); const bullBoardRouter = require('./routes/bullBoardRouter.js'); const gsimRouter = require('./routes/gsimRouter.js'); //test const oauthRouter = require('./oauth/oauthRouter.js'); const { isLoggedIn, deserializeUser } = require('./oauth/oauthController'); const logger = require('./logger'); const app = express(); const env = process.env.NODE_ENV; dbConnect(); // DB 연결 passportConfig(); // passport 설정 // app.use( // helmet({ // contentSecurityPolicy: false, // CSP는 비활성화 // crossOriginEmbedderPolicy: false, // 외부 CDN 자원 쓰면 충돌 날 수 있음 // }) // ); const ALLOWED_PARENTS = [ "http://bcmf.hanmaceng.co.kr", "https://bcmf.hanmaceng.co.kr", "http://*.hanmaceng.co.kr", "https://*.hanmaceng.co.kr", ]; app.use(helmet({ frameguard: false, // X-Frame-Options 제거 contentSecurityPolicy: false, // CSP는 직접 세팅 crossOriginEmbedderPolicy: false, })); app.use((req, res, next) => { res.setHeader( "Content-Security-Policy", `frame-ancestors 'self' ${ALLOWED_PARENTS.join(' ')}` ); next(); }); //페이로드 크기 설정 => upload check할때 json 용량때문에 사용 app.use(bodyParser.json({limit : '50mb'})); app.use(bodyParser.urlencoded({limit : '50mb', extended:true})); app.use(cors()); app.use(morgan('dev')); app.use('/node_modules', express.static(path.join(__dirname, 'node_modules'))); app.use('/libs', express.static(path.join(__dirname, 'libs'))); app.use('/', express.static(path.join(__dirname, 'views'))); app.use(express.static(__dirname + '/node_modules/socket.io/client-dist')); app.use('/:projectId',express.static(__dirname + '/node_modules/socket.io/client-dist')); // app.use('/PM_ver4', express.static(env != 'production'?'D:\\PM_ver4':'D:\\PM_ver4')); // D드라이브 폴더 사용 // 동적 API 및 라우트 캐싱 방지 설정 (304 Cache로 인한 이전 에러 응답 물림 및 리다이렉트 캐싱 방지) app.use((req, res, next) => { if (req.method === 'GET' && !req.path.includes('.') && !req.path.startsWith('/node_modules') && !req.path.startsWith('/libs')) { res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); res.setHeader('Pragma', 'no-cache'); res.setHeader('Expires', '0'); } next(); }); app.use(express.json()); app.use(express.urlencoded({extended:false})); app.use(cookieParser(process.env.COOKIE_SECRET)); // const fileStoreOptions = { // path:`C:\\develop\\session`, // ttl:1000*60*60*24, // logFn: function(){}, // retries:5 // } // app.use(session({ // store: new FileStore(fileStoreOptions), // FileStore를 사용하도록 설정 // secret: 'testkey', // 세션 ID를 서명하는 데 사용되는 비밀 키 (필수) // resave: false, // 세션 데이터가 변경되지 않아도 세션을 다시 저장할지 여부 // saveUninitialized: false, // 초기화되지 않은 세션을 저장소에 저장할지 여부 // cookie: { // maxAge: 1000 * 60 * 60 * 24 // 24시간 후 쿠키 만료 // } // })); app.use(session({ resave: false, saveUninitialized: false, secret: process.env.COOKIE_SECRET, cookie: { httpOnly: true, secure: false, // maxAge: (24 * 60 * 60 * 1000) + (9 * 3600 * 1000) } })); //passport 초기화 app.use(passport.initialize()); // passport 설정을 심음 app.use(passport.session()); // req.session에 passport 정보를 저장 app.post('/log-client-error', express.json(), (req, res) => { const { message, source, lineno, colno, stack } = req.body; console.error('🚨 [CLIENT ERROR]:', message); console.error(` at ${source}:${lineno}:${colno}`); if (stack) console.error(stack); res.sendStatus(200); }); // 라우터 app.use(`/oauth`, oauthRouter); app.use('/login', (req, res) => { res.redirect('/user/login'); }); app.use('/user/login', (req, res, next) =>{res.sendFile(path.join(process.cwd()+`/views/login/login.html`))}); // app.use('/popup' ,(req,res,next)=>{res.sendFile(path.join(process.cwd()+`/views/main/popup.html`))}) // 글로벌 사용자 세션 역직렬화 (로그인 상태 복원) app.use(deserializeUser); // 공공 라우트 및 인증 API app.use('/auth', authRouter); // 어드민 화면 서빙 및 권한 통제 const isAdminLocal = (req, res, next) => { const userGroup = req.user?.group; if (req.user && (userGroup === 'USER_GROUP_super' || userGroup === 'dev' || userGroup === 'super')) { return next(); } return res.status(403).send("어드민(super) 권한이 필요합니다."); }; app.get('/admin', isLoggedIn, isAdminLocal, (req, res) => { res.sendFile(path.join(process.cwd(), 'views/admin/dashboard.html')); }); // 로그인 보호 장벽 적용 (이하 라우트는 로그인 세션 필수) app.use(isLoggedIn); app.use('/', mainRouter); app.use('/:projectId/archive', archiveRouter); app.use('/:projectId/overview', overviewRouter); app.use('/:projectId/officialDoc', officialDocRouter); app.use('/common', commonRouter); app.use('/gsim', gsimRouter); // 어드민 전용 REST API app.use('/api/admin', require('./routes/admin/adminRouter')); // BullMQ 모니터링 대시보드 app.use('/admin/queues', bullBoardRouter); // 404응답 미들웨어 app.use((req, res, next) => { const error = new Error(`${req.method} ${req.url} 라우터가 없어요`); error.status = 404; logger.error(error.message); next(error); }); // 에러 처리 미들웨어 app.use((err, req, res, next) => { console.error(err.stack); logger.error(err.message); res.status(err.status || 500).send(`status code: ${err.status || 500} 에러가 났어요`) }); // 자동 보관 및 자동 삭제 스케줄러 가동 const scheduler = require('./libs/scheduler'); scheduler.start(); module.exports = app;