101 lines
2.7 KiB
Go
101 lines
2.7 KiB
Go
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))
|
|
})
|
|
}
|
|
}
|