M1/#293: RequireAdmin middleware + UserFromContext
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
||||
)
|
||||
|
||||
type ctxKey int
|
||||
|
||||
const userCtxKey ctxKey = 1
|
||||
|
||||
// RequireAdmin gates a handler on X-API-Token matching an admin user. This is
|
||||
// the Minstrel-native token path (`/api/*`); Subsonic-compatible auth under
|
||||
// `/rest/*` lands with the Subsonic server.
|
||||
func RequireAdmin(pool *pgxpool.Pool) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.Header.Get("X-API-Token")
|
||||
if token == "" {
|
||||
http.Error(w, "missing X-API-Token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
q := dbq.New(pool)
|
||||
user, err := q.GetUserByAPIToken(r.Context(), token)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
http.Error(w, "invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
http.Error(w, "auth lookup failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if !user.IsAdmin {
|
||||
http.Error(w, "admin required", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), userCtxKey, user)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// UserFromContext returns the authenticated user when the request passed
|
||||
// through RequireAdmin (or a future RequireUser middleware).
|
||||
func UserFromContext(ctx context.Context) (dbq.User, bool) {
|
||||
u, ok := ctx.Value(userCtxKey).(dbq.User)
|
||||
return u, ok
|
||||
}
|
||||
Reference in New Issue
Block a user