Ctrl K
ComunidadGuías…

configEditor_general.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/gofrs/uuid/v3"
	"github.com/gorilla/mux"
	log "github.com/sirupsen/logrus"

	"github.com/Luzifer/twitch-bot/v3/plugins"
)

type (
	configEditorGeneralConfig struct {
		BotEditors      []string            `json:"bot_editors"`
		BotName         *string             `json:"bot_name,omitempty"`
		Channels        []string            `json:"channels"`
		ChannelScopes   map[string][]string `json:"channel_scopes"`
		ChannelHasToken map[string]bool     `json:"channel_has_token"`
	}
)

func registerEditorGeneralConfigRoutes() {
	for _, rd := range []plugins.HTTPRouteRegistrationArgs{
		{
			Description:         "Add new authorization token",
			HandlerFunc:         configEditorHandleGeneralAddAuthToken,
			Method:              http.MethodPost,
			Module:              moduleConfigEditor,
			Name:                "Add authorization token",
			Path:                "/auth-tokens",
			RequiresEditorsAuth: true,
			ResponseType:        plugins.HTTPRouteResponseTypeJSON,
		},
		{
			Description:         "Delete authorization token",
			HandlerFunc:         configEditorHandleGeneralDeleteAuthToken,
			Method:              http.MethodDelete,
			Module:              moduleConfigEditor,
			Name:                "Delete authorization token",
			Path:                "/auth-tokens/{handle}",
			RequiresEditorsAuth: true,
			ResponseType:        plugins.HTTPRouteResponseTypeTextPlain,
			RouteParams: []plugins.HTTPRouteParamDocumentation{
				{
					Description: "UUID of the auth-token to delete",
					Name:        "handle",
					Required:    true,
					Type:        "string",
				},
			},
		},
		{
			Description:         "List authorization tokens",
			HandlerFunc:         configEditorHandleGeneralListAuthTokens,
			Method:              http.MethodGet,
			Module:              moduleConfigEditor,
			Name:                "List authorization tokens",
			Path:                "/auth-tokens",
			RequiresEditorsAuth: true,
			ResponseType:        plugins.HTTPRouteResponseTypeJSON,
		},
		{
			Description:         "Returns the current general config",
			HandlerFunc:         configEditorHandleGeneralGet,
			Method:              http.MethodGet,
			Module:              moduleConfigEditor,
			Name:                "Get general config",
			Path:                "/general",
			RequiresEditorsAuth: true,
			ResponseType:        plugins.HTTPRouteResponseTypeJSON,
		},
		{
			Description:         "Updates the general config",
			HandlerFunc:         configEditorHandleGeneralUpdate,
			Method:              http.MethodPut,
			Module:              moduleConfigEditor,
			Name:                "Update general config",
			Path:                "/general",
			RequiresEditorsAuth: true,
			ResponseType:        plugins.HTTPRouteResponseTypeTextPlain,
		},
		{
			Description:         "Get Bot-Auth URLs for updating bot token and channel scopes",
			HandlerFunc:         configEditorHandleGeneralAuthURLs,
			Method:              http.MethodGet,
			Module:              moduleConfigEditor,
			Name:                "Get Bot-Auth-URLs",
			Path:                "/auth-urls",
			RequiresEditorsAuth: true,
			ResponseType:        plugins.HTTPRouteResponseTypeJSON,
		},
	} {
		if err := registerRoute(rd); err != nil {
			log.WithError(err).Fatal("Unable to register config editor route")
		}
	}
}

func configEditorHandleGeneralAddAuthToken(w http.ResponseWriter, r *http.Request) {
	user, _, err := getAuthorizedUserFromRequest(r)
	if err != nil {
		http.Error(w, fmt.Errorf("getting authorized user: %w", err).Error(), http.StatusInternalServerError)
		return
	}

	var payload configAuthToken
	if err = json.NewDecoder(r.Body).Decode(&payload); err != nil {
		http.Error(w, fmt.Errorf("reading payload: %w", err).Error(), http.StatusBadRequest)
		return
	}

	if err = fillAuthToken(&payload); err != nil {
		http.Error(w, fmt.Errorf("hashing token: %w", err).Error(), http.StatusInternalServerError)
		return
	}

	if err := patchConfig(cfg.Config, user, "", "Add auth-token", func(cfg *configFile) error {
		cfg.AuthTokens[uuid.Must(uuid.NewV4()).String()] = payload
		return nil
	}); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	if err = json.NewEncoder(w).Encode(payload); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func configEditorHandleGeneralAuthURLs(w http.ResponseWriter, _ *http.Request) {
	var out struct {
		AvailableExtendedScopes map[string]string `json:"available_extended_scopes"`
		UpdateBotToken          string            `json:"update_bot_token"`
		UpdateChannelScopes     string            `json:"update_channel_scopes"`
	}

	out.AvailableExtendedScopes = channelExtendedScopes

	params := make(url.Values)
	params.Set("client_id", cfg.TwitchClient)
	params.Set("redirect_uri", strings.Join([]string{
		strings.TrimRight(cfg.BaseURL, "/"),
		"auth", "update-bot-token",
	}, "/"))
	params.Set("response_type", "code")
	params.Set("scope", strings.Join(botDefaultScopes, " "))
	params.Set("state", instanceState)

	out.UpdateBotToken = fmt.Sprintf("https://id.twitch.tv/oauth2/authorize?%s", params.Encode())

	params.Set("redirect_uri", strings.Join([]string{
		strings.TrimRight(cfg.BaseURL, "/"),
		"auth", "update-channel-scopes",
	}, "/"))
	params.Set("scope", "")

	out.UpdateChannelScopes = fmt.Sprintf("https://id.twitch.tv/oauth2/authorize?%s", params.Encode())

	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(out); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func configEditorHandleGeneralDeleteAuthToken(w http.ResponseWriter, r *http.Request) {
	user, _, err := getAuthorizedUserFromRequest(r)
	if err != nil {
		http.Error(w, fmt.Errorf("getting authorized user: %w", err).Error(), http.StatusInternalServerError)
		return
	}

	if err := patchConfig(cfg.Config, user, "", "Delete auth-token", func(cfg *configFile) error {
		delete(cfg.AuthTokens, mux.Vars(r)["handle"])

		return nil
	}); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

func configEditorHandleGeneralGet(w http.ResponseWriter, _ *http.Request) {
	resp := configEditorGeneralConfig{
		BotEditors:      config.BotEditors,
		Channels:        config.Channels,
		ChannelHasToken: make(map[string]bool),
		ChannelScopes:   make(map[string][]string),
	}

	channels, err := accessService.ListPermittedChannels()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	for _, ch := range channels {
		if resp.ChannelScopes[ch], err = accessService.GetChannelPermissions(ch); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		if resp.ChannelHasToken[ch], err = accessService.HasTokensForChannel(ch); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}

	uName, err := accessService.GetBotUsername()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	resp.BotName = &uName

	w.Header().Set("Content-Type", "application/json")
	if err = json.NewEncoder(w).Encode(resp); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func configEditorHandleGeneralListAuthTokens(w http.ResponseWriter, _ *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(config.AuthTokens); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func configEditorHandleGeneralUpdate(w http.ResponseWriter, r *http.Request) {
	user, _, err := getAuthorizedUserFromRequest(r)
	if err != nil {
		http.Error(w, fmt.Errorf("getting authorized user: %w", err).Error(), http.StatusInternalServerError)
		return
	}

	var payload configEditorGeneralConfig

	if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	for i := range payload.BotEditors {
		usr, err := twitchClient.GetUserInformation(r.Context(), payload.BotEditors[i])
		if err != nil {
			http.Error(w, fmt.Errorf("getting bot editor profile: %w", err).Error(), http.StatusInternalServerError)
			return
		}

		payload.BotEditors[i] = usr.ID
	}

	if err := patchConfig(cfg.Config, user, "", "Update general config", func(cfg *configFile) error {
		cfg.Channels = payload.Channels
		cfg.BotEditors = payload.BotEditors

		return nil
	}); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}