Track scanner status for the web API and Subsonic-compatible scan endpoints, add authenticated cover art serving, and wire album artwork into the web UI. Keep Subsonic auth limited to legacy password mode for now so behavior stays honest with the current bcrypt-based user storage.
113 lines
2.8 KiB
Go
113 lines
2.8 KiB
Go
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))
|
|
})
|
|
}
|