🏡 index : quine-global/quintodrome.git

package nativeapi

import (
	"encoding/json"
	"errors"
	"net/http"

	"github.com/go-chi/chi/v5"
	"github.com/navidrome/navidrome/conf"
	"github.com/navidrome/navidrome/core/quickconnect"
	"github.com/navidrome/navidrome/log"
	"github.com/navidrome/navidrome/model"
	"github.com/navidrome/navidrome/model/request"
	"github.com/navidrome/navidrome/server"
)

type quickConnectDevice struct {
	AppName    string `json:"appName"`
	AppVersion string `json:"appVersion"`
	DeviceName string `json:"deviceName"`
}

func (api *Router) addQuickConnectRoute(r chi.Router) {
	if !quickconnect.Enabled() {
		return
	}
	r.Route("/quickconnect", func(r chi.Router) {
		// Throttled like login so a signed-in user cannot enumerate other people's pending codes.
		if conf.Server.AuthRequestLimit > 0 {
			r.Use(server.ClientIPRateLimiter(conf.Server.AuthRequestLimit, conf.Server.AuthWindowLength))
		}
		r.Get("/", lookupQuickConnect(api.quickConnect))
		r.Post("/authorize", authorizeQuickConnect(api.quickConnect))
	})
}

func lookupQuickConnect(qc quickconnect.QuickConnect) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		req, err := qc.Lookup(r.URL.Query().Get("code"))
		writeQuickConnectResult(w, r, req, err)
	}
}

func authorizeQuickConnect(qc quickconnect.QuickConnect) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var body struct {
			Code string `json:"code"`
		}
		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
			http.Error(w, "Bad Request", http.StatusBadRequest)
			return
		}
		ctx := r.Context()
		user, _ := request.UserFrom(ctx)
		req, err := qc.Authorize(body.Code, user.ID)
		if err == nil {
			log.Info(ctx, "Quick Connect sign-in approved", "username", user.UserName, "client", req.Device.App, "device", req.Device.Name)
		}
		writeQuickConnectResult(w, r, req, err)
	}
}

func writeQuickConnectResult(w http.ResponseWriter, r *http.Request, req quickconnect.Request, err error) {
	switch {
	case errors.Is(err, model.ErrNotFound):
		http.Error(w, "Unknown code", http.StatusNotFound)
	case errors.Is(err, quickconnect.ErrAlreadyAuthorized):
		http.Error(w, "Code already used", http.StatusConflict)
	case err != nil:
		log.Error(r.Context(), "Quick Connect failed", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
	default:
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(quickConnectDevice{AppName: req.Device.App, AppVersion: req.Device.AppVersion, DeviceName: req.Device.Name})
	}
}