package middleware import ( "context" "net/http" "stocksearch/services" "strings" ) const SessionCookieName = "ss_session" // contextKey 컨텍스트 키 타입 type contextKey string const CtxLoggedIn contextKey = "loggedIn" // IsLoggedIn 요청 컨텍스트에서 로그인 여부 반환 func IsLoggedIn(r *http.Request) bool { v, _ := r.Context().Value(CtxLoggedIn).(bool) return v } // publicPaths 로그인 없이 접근 가능한 경로 (전체 일치 또는 prefix) var publicPaths = []string{ "/login", "/static/", // 공개 페이지 "/", "/theme", "/kospi200", "/stock/", // 공개 API (시세/테마/코스피200 관련) "/api/stock/", "/api/indices", "/api/search", "/api/scanner/", "/api/signal", "/api/watchlist-signal", "/api/themes", "/api/themes/", "/api/kospi200", "/api/news", "/api/disclosure", "/ws", } // isPublic 요청 경로가 공개 경로에 해당하는지 판단 func isPublic(path string) bool { for _, p := range publicPaths { if strings.HasSuffix(p, "/") { if strings.HasPrefix(path, p) || path == p[:len(p)-1] { return true } } else { if path == p { return true } } } return false } // Auth 세션 쿠키 검증 미들웨어 // - 공개 경로: 로그인 없이 접근 허용 // - 인증 필요 경로(자산/주문/자동매매 등): 미인증 시 페이지는 /login 리다이렉트, API는 401 반환 func Auth(sessionSvc *services.SessionService) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 공개 경로: 인증 없이 통과하되 로그인 상태는 컨텍스트에 저장 if isPublic(r.URL.Path) { cookie, err := r.Cookie(SessionCookieName) loggedIn := err == nil && sessionSvc.Validate(cookie.Value) ctx := context.WithValue(r.Context(), CtxLoggedIn, loggedIn) next.ServeHTTP(w, r.WithContext(ctx)) return } // 쿠키에서 세션 ID 조회 cookie, err := r.Cookie(SessionCookieName) loggedIn := err == nil && sessionSvc.Validate(cookie.Value) if !loggedIn { // API 경로는 401 JSON 반환, 페이지는 /login 리다이렉트 if strings.HasPrefix(r.URL.Path, "/api/") { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) _, _ = w.Write([]byte(`{"error":"로그인이 필요합니다"}`)) return } redirectURL := "/login?next=" + r.URL.RequestURI() http.Redirect(w, r, redirectURL, http.StatusFound) return } // 로그인 상태를 컨텍스트에 저장 ctx := context.WithValue(r.Context(), CtxLoggedIn, true) next.ServeHTTP(w, r.WithContext(ctx)) }) } }