package httpapi import ( "context" "log" "net/http" "strings" "time" "github.com/benya/temporserv/internal/auth" ) type contextKey string const currentUserKey contextKey = "currentUser" func requestLogger(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { startedAt := time.Now() next.ServeHTTP(w, r) log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(startedAt)) }) } func recoverer(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if recover() != nil { http.Error(w, "internal server error", http.StatusInternalServerError) } }() next.ServeHTTP(w, r) }) } func cors(origins string) func(http.Handler) http.Handler { allowed := strings.Split(origins, ",") return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { origin := r.Header.Get("Origin") for _, candidate := range allowed { if strings.TrimSpace(candidate) == origin { w.Header().Set("Access-Control-Allow-Origin", origin) break } } w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") if r.Method == http.MethodOptions { w.WriteHeader(http.StatusNoContent) return } next.ServeHTTP(w, r) }) } } func (a app) requireAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user, err := a.auth.CurrentUser(r.Context(), r.Header.Get("Authorization")) if err != nil { writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "unauthorized"}) return } ctx := context.WithValue(r.Context(), currentUserKey, user) next.ServeHTTP(w, r.WithContext(ctx)) }) } func currentUserFromContext(r *http.Request) auth.User { user, ok := r.Context().Value(currentUserKey).(auth.User) if !ok { return auth.User{} } return user } func (a app) requireSubsonicAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user, err := a.auth.CurrentUserBySubsonicAuth( r.Context(), r.URL.Query().Get("u"), r.URL.Query().Get("p"), r.URL.Query().Get("t"), r.URL.Query().Get("s"), ) if err != nil { writeJSON(w, http.StatusUnauthorized, map[string]any{ "subsonic-response": map[string]any{ "status": "failed", "version": "1.16.1", "type": "temporserv", "serverVersion": "0.1.0", "openSubsonic": true, "error": map[string]any{ "code": 40, "message": "Wrong username or password", }, }, }) return } ctx := context.WithValue(r.Context(), currentUserKey, user) next.ServeHTTP(w, r.WithContext(ctx)) }) }