mirror of
				https://github.com/elyby/chrly.git
				synced 2025-05-31 14:11:51 +05:30 
			
		
		
		
	Rework project's structure
This commit is contained in:
		
							
								
								
									
										14
									
								
								internal/di/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								internal/di/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| package di | ||||
|  | ||||
| import ( | ||||
| 	"github.com/defval/di" | ||||
| 	"github.com/spf13/viper" | ||||
| ) | ||||
|  | ||||
| var config = di.Options( | ||||
| 	di.Provide(newConfig), | ||||
| ) | ||||
|  | ||||
| func newConfig() *viper.Viper { | ||||
| 	return viper.GetViper() | ||||
| } | ||||
							
								
								
									
										55
									
								
								internal/di/db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								internal/di/db.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| package di | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/defval/di" | ||||
| 	"github.com/spf13/viper" | ||||
|  | ||||
| 	db2 "github.com/elyby/chrly/internal/db" | ||||
| 	"github.com/elyby/chrly/internal/db/redis" | ||||
| 	es "github.com/elyby/chrly/internal/eventsubscribers" | ||||
| 	"github.com/elyby/chrly/internal/mojang" | ||||
| 	"github.com/elyby/chrly/internal/profiles" | ||||
| ) | ||||
|  | ||||
| // v4 had the idea that it would be possible to separate backends for storing skins and capes. | ||||
| // But in v5 the storage will be unified, so this is just temporary constructors before large reworking. | ||||
| // | ||||
| // Since there are no options for selecting target backends, | ||||
| // all constants in this case point to static specific implementations. | ||||
| var db = di.Options( | ||||
| 	di.Provide(newRedis, | ||||
| 		di.As(new(profiles.ProfilesRepository)), | ||||
| 		di.As(new(profiles.ProfilesFinder)), | ||||
| 		di.As(new(mojang.MojangUuidsStorage)), | ||||
| 	), | ||||
| ) | ||||
|  | ||||
| func newRedis(container *di.Container, config *viper.Viper) (*redis.Redis, error) { | ||||
| 	config.SetDefault("storage.redis.host", "localhost") | ||||
| 	config.SetDefault("storage.redis.port", 6379) | ||||
| 	config.SetDefault("storage.redis.poolSize", 10) | ||||
|  | ||||
| 	conn, err := redis.New( | ||||
| 		context.Background(), | ||||
| 		db2.NewZlibEncoder(&db2.JsonSerializer{}), | ||||
| 		fmt.Sprintf("%s:%d", config.GetString("storage.redis.host"), config.GetInt("storage.redis.port")), | ||||
| 		config.GetInt("storage.redis.poolSize"), | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if err := container.Provide(func() *namedHealthChecker { | ||||
| 		return &namedHealthChecker{ | ||||
| 			Name:    "redis", | ||||
| 			Checker: es.DatabaseChecker(conn), | ||||
| 		} | ||||
| 	}); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return conn, nil | ||||
| } | ||||
							
								
								
									
										22
									
								
								internal/di/di.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								internal/di/di.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| package di | ||||
|  | ||||
| import "github.com/defval/di" | ||||
|  | ||||
| func New() (*di.Container, error) { | ||||
| 	container, err := di.New( | ||||
| 		config, | ||||
| 		dispatcher, | ||||
| 		logger, | ||||
| 		db, | ||||
| 		mojangTextures, | ||||
| 		handlers, | ||||
| 		profilesDi, | ||||
| 		server, | ||||
| 		signer, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return container, nil | ||||
| } | ||||
							
								
								
									
										34
									
								
								internal/di/dispatcher.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								internal/di/dispatcher.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| package di | ||||
|  | ||||
| import ( | ||||
| 	"github.com/defval/di" | ||||
| 	"github.com/mono83/slf" | ||||
|  | ||||
| 	d "github.com/elyby/chrly/internal/dispatcher" | ||||
| 	"github.com/elyby/chrly/internal/eventsubscribers" | ||||
| 	"github.com/elyby/chrly/internal/http" | ||||
| ) | ||||
|  | ||||
| var dispatcher = di.Options( | ||||
| 	di.Provide(newDispatcher, | ||||
| 		di.As(new(d.Emitter)), | ||||
| 		di.As(new(d.Subscriber)), | ||||
| 		di.As(new(http.Emitter)), | ||||
| 		di.As(new(eventsubscribers.Subscriber)), | ||||
| 	), | ||||
| 	di.Invoke(enableEventsHandlers), | ||||
| ) | ||||
|  | ||||
| func newDispatcher() d.Dispatcher { | ||||
| 	return d.New() | ||||
| } | ||||
|  | ||||
| func enableEventsHandlers( | ||||
| 	dispatcher d.Subscriber, | ||||
| 	logger slf.Logger, | ||||
| 	statsReporter slf.StatsReporter, | ||||
| ) { | ||||
| 	// TODO: use idea from https://github.com/defval/di/issues/10#issuecomment-615869852 | ||||
| 	(&eventsubscribers.Logger{Logger: logger}).ConfigureWithDispatcher(dispatcher) | ||||
| 	(&eventsubscribers.StatsReporter{StatsReporter: statsReporter}).ConfigureWithDispatcher(dispatcher) | ||||
| } | ||||
							
								
								
									
										133
									
								
								internal/di/handlers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								internal/di/handlers.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| package di | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/defval/di" | ||||
| 	"github.com/etherlabsio/healthcheck/v2" | ||||
| 	"github.com/gorilla/mux" | ||||
| 	"github.com/spf13/viper" | ||||
|  | ||||
| 	. "github.com/elyby/chrly/internal/http" | ||||
| ) | ||||
|  | ||||
| var handlers = di.Options( | ||||
| 	di.Provide(newHandlerFactory, di.As(new(http.Handler))), | ||||
| 	di.Provide(newSkinsystemHandler, di.WithName("skinsystem")), | ||||
| 	di.Provide(newApiHandler, di.WithName("api")), | ||||
| ) | ||||
|  | ||||
| func newHandlerFactory( | ||||
| 	container *di.Container, | ||||
| 	config *viper.Viper, | ||||
| 	emitter Emitter, | ||||
| ) (*mux.Router, error) { | ||||
| 	enabledModules := config.GetStringSlice("modules") | ||||
|  | ||||
| 	// gorilla.mux has no native way to combine multiple routers. | ||||
| 	// The hack used later in the code works for prefixes in addresses, but leads to misbehavior | ||||
| 	// if you set an empty prefix. Since the main application should be mounted at the root prefix, | ||||
| 	// we use it as the base router | ||||
| 	var router *mux.Router | ||||
| 	if hasValue(enabledModules, "skinsystem") { | ||||
| 		if err := container.Resolve(&router, di.Name("skinsystem")); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} else { | ||||
| 		router = mux.NewRouter() | ||||
| 	} | ||||
|  | ||||
| 	router.StrictSlash(true) | ||||
| 	requestEventsMiddleware := CreateRequestEventsMiddleware(emitter, "skinsystem") | ||||
| 	router.Use(requestEventsMiddleware) | ||||
| 	// NotFoundHandler doesn't call for registered middlewares, so we must wrap it manually. | ||||
| 	// See https://github.com/gorilla/mux/issues/416#issuecomment-600079279 | ||||
| 	router.NotFoundHandler = requestEventsMiddleware(http.HandlerFunc(NotFoundHandler)) | ||||
|  | ||||
| 	if hasValue(enabledModules, "api") { | ||||
| 		var apiRouter *mux.Router | ||||
| 		if err := container.Resolve(&apiRouter, di.Name("api")); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		var authenticator Authenticator | ||||
| 		if err := container.Resolve(&authenticator); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		apiRouter.Use(CreateAuthenticationMiddleware(authenticator)) | ||||
|  | ||||
| 		mount(router, "/api", apiRouter) | ||||
| 	} | ||||
|  | ||||
| 	err := container.Invoke(enableReporters) | ||||
| 	if err != nil && !errors.Is(err, di.ErrTypeNotExists) { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// Resolve health checkers last, because all the services required by the application | ||||
| 	// must first be initialized and each of them can publish its own checkers | ||||
| 	var healthCheckers []*namedHealthChecker | ||||
| 	if has, _ := container.Has(&healthCheckers); has { | ||||
| 		if err := container.Resolve(&healthCheckers); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		checkersOptions := make([]healthcheck.Option, len(healthCheckers)) | ||||
| 		for i, checker := range healthCheckers { | ||||
| 			checkersOptions[i] = healthcheck.WithChecker(checker.Name, checker.Checker) | ||||
| 		} | ||||
|  | ||||
| 		router.Handle("/healthcheck", healthcheck.Handler(checkersOptions...)).Methods("GET") | ||||
| 	} | ||||
|  | ||||
| 	return router, nil | ||||
| } | ||||
|  | ||||
| func newSkinsystemHandler( | ||||
| 	config *viper.Viper, | ||||
| 	profilesProvider ProfilesProvider, | ||||
| 	texturesSigner TexturesSigner, | ||||
| ) *mux.Router { | ||||
| 	config.SetDefault("textures.extra_param_name", "chrly") | ||||
| 	config.SetDefault("textures.extra_param_value", "how do you tame a horse in Minecraft?") | ||||
|  | ||||
| 	return (&Skinsystem{ | ||||
| 		ProfilesProvider:        profilesProvider, | ||||
| 		TexturesSigner:          texturesSigner, | ||||
| 		TexturesExtraParamName:  config.GetString("textures.extra_param_name"), | ||||
| 		TexturesExtraParamValue: config.GetString("textures.extra_param_value"), | ||||
| 	}).Handler() | ||||
| } | ||||
|  | ||||
| func newApiHandler(profilesManager ProfilesManager) *mux.Router { | ||||
| 	return (&Api{ | ||||
| 		ProfilesManager: profilesManager, | ||||
| 	}).Handler() | ||||
| } | ||||
|  | ||||
| func hasValue(slice []string, needle string) bool { | ||||
| 	for _, value := range slice { | ||||
| 		if value == needle { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func mount(router *mux.Router, path string, handler http.Handler) { | ||||
| 	router.PathPrefix(path).Handler( | ||||
| 		http.StripPrefix( | ||||
| 			strings.TrimSuffix(path, "/"), | ||||
| 			handler, | ||||
| 		), | ||||
| 	) | ||||
| } | ||||
|  | ||||
| type namedHealthChecker struct { | ||||
| 	Name    string | ||||
| 	Checker healthcheck.Checker | ||||
| } | ||||
							
								
								
									
										104
									
								
								internal/di/logger.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								internal/di/logger.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| package di | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/defval/di" | ||||
| 	"github.com/getsentry/raven-go" | ||||
| 	"github.com/mono83/slf" | ||||
| 	"github.com/mono83/slf/rays" | ||||
| 	"github.com/mono83/slf/recievers/sentry" | ||||
| 	"github.com/mono83/slf/recievers/statsd" | ||||
| 	"github.com/mono83/slf/recievers/writer" | ||||
| 	"github.com/mono83/slf/wd" | ||||
| 	"github.com/spf13/viper" | ||||
|  | ||||
| 	"github.com/elyby/chrly/internal/eventsubscribers" | ||||
| 	"github.com/elyby/chrly/internal/version" | ||||
| ) | ||||
|  | ||||
| var logger = di.Options( | ||||
| 	di.Provide(newLogger), | ||||
| 	di.Provide(newSentry), | ||||
| 	di.Provide(newStatsReporter), | ||||
| ) | ||||
|  | ||||
| type loggerParams struct { | ||||
| 	di.Inject | ||||
|  | ||||
| 	SentryRaven *raven.Client `di:"" optional:"true"` | ||||
| } | ||||
|  | ||||
| func newLogger(params loggerParams) slf.Logger { | ||||
| 	dispatcher := &slf.Dispatcher{} | ||||
| 	dispatcher.AddReceiver(writer.New(writer.Options{ | ||||
| 		Marker:     false, | ||||
| 		TimeFormat: "15:04:05.000", | ||||
| 	})) | ||||
|  | ||||
| 	if params.SentryRaven != nil { | ||||
| 		sentryReceiver, _ := sentry.NewReceiverWithCustomRaven( | ||||
| 			params.SentryRaven, | ||||
| 			&sentry.Config{ | ||||
| 				MinLevel: "warn", | ||||
| 			}, | ||||
| 		) | ||||
| 		dispatcher.AddReceiver(sentryReceiver) | ||||
| 	} | ||||
|  | ||||
| 	logger := wd.Custom("", "", dispatcher) | ||||
| 	logger.WithParams(rays.Host) | ||||
|  | ||||
| 	return logger | ||||
| } | ||||
|  | ||||
| func newSentry(config *viper.Viper) (*raven.Client, error) { | ||||
| 	sentryAddr := config.GetString("sentry.dsn") | ||||
| 	if sentryAddr == "" { | ||||
| 		return nil, nil | ||||
| 	} | ||||
|  | ||||
| 	ravenClient, err := raven.New(sentryAddr) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	ravenClient.SetEnvironment("production") | ||||
| 	ravenClient.SetDefaultLoggerName("sentry-watchdog-receiver") | ||||
| 	ravenClient.SetRelease(version.Version()) | ||||
|  | ||||
| 	raven.DefaultClient = ravenClient | ||||
|  | ||||
| 	return ravenClient, nil | ||||
| } | ||||
|  | ||||
| func newStatsReporter(config *viper.Viper) (slf.StatsReporter, error) { | ||||
| 	dispatcher := &slf.Dispatcher{} | ||||
|  | ||||
| 	statsdAddr := config.GetString("statsd.addr") | ||||
| 	if statsdAddr != "" { | ||||
| 		hostname, err := os.Hostname() | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		statsdReceiver, err := statsd.NewReceiver(statsd.Config{ | ||||
| 			Address:    statsdAddr, | ||||
| 			Prefix:     "ely.skinsystem." + hostname + ".app.", | ||||
| 			FlushEvery: 1, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		dispatcher.AddReceiver(statsdReceiver) | ||||
| 	} | ||||
|  | ||||
| 	return wd.Custom("", "", dispatcher), nil | ||||
| } | ||||
|  | ||||
| func enableReporters(reporter slf.StatsReporter, factories []eventsubscribers.Reporter) { | ||||
| 	for _, factory := range factories { | ||||
| 		factory.Enable(reporter) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										108
									
								
								internal/di/mojang_textures.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								internal/di/mojang_textures.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | ||||
| package di | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/defval/di" | ||||
| 	"github.com/spf13/viper" | ||||
|  | ||||
| 	"github.com/elyby/chrly/internal/mojang" | ||||
| 	"github.com/elyby/chrly/internal/profiles" | ||||
| ) | ||||
|  | ||||
| var mojangTextures = di.Options( | ||||
| 	di.Provide(newMojangApi), | ||||
| 	di.Provide(newMojangTexturesProviderFactory), | ||||
| 	di.Provide(newMojangTexturesProvider), | ||||
| 	di.Provide(newMojangTexturesUuidsProviderFactory), | ||||
| 	di.Provide(newMojangTexturesBatchUUIDsProvider), | ||||
| 	di.Provide(newMojangSignedTexturesProvider), | ||||
| ) | ||||
|  | ||||
| func newMojangApi(config *viper.Viper) (*mojang.MojangApi, error) { | ||||
| 	batchUuidsUrl := config.GetString("mojang.batch_uuids_url") | ||||
| 	if batchUuidsUrl != "" { | ||||
| 		if _, err := url.ParseRequestURI(batchUuidsUrl); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	profileUrl := config.GetString("mojang.profile_url") | ||||
| 	if profileUrl != "" { | ||||
| 		if _, err := url.ParseRequestURI(batchUuidsUrl); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	httpClient := &http.Client{} // TODO: extract to the singleton dependency | ||||
|  | ||||
| 	return mojang.NewMojangApi(httpClient, batchUuidsUrl, profileUrl), nil | ||||
| } | ||||
|  | ||||
| func newMojangTexturesProviderFactory( | ||||
| 	container *di.Container, | ||||
| 	config *viper.Viper, | ||||
| ) (profiles.MojangProfilesProvider, error) { | ||||
| 	config.SetDefault("mojang_textures.enabled", true) | ||||
| 	if !config.GetBool("mojang_textures.enabled") { | ||||
| 		return &mojang.NilProvider{}, nil | ||||
| 	} | ||||
|  | ||||
| 	var provider *mojang.MojangTexturesProvider | ||||
| 	err := container.Resolve(&provider) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return provider, nil | ||||
| } | ||||
|  | ||||
| func newMojangTexturesProvider( | ||||
| 	uuidsProvider mojang.UuidsProvider, | ||||
| 	texturesProvider mojang.TexturesProvider, | ||||
| ) *mojang.MojangTexturesProvider { | ||||
| 	return &mojang.MojangTexturesProvider{ | ||||
| 		UuidsProvider:    uuidsProvider, | ||||
| 		TexturesProvider: texturesProvider, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func newMojangTexturesUuidsProviderFactory( | ||||
| 	batchProvider *mojang.BatchUuidsProvider, | ||||
| 	uuidsStorage mojang.MojangUuidsStorage, | ||||
| ) mojang.UuidsProvider { | ||||
| 	return &mojang.UuidsProviderWithCache{ | ||||
| 		Provider: batchProvider, | ||||
| 		Storage:  uuidsStorage, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func newMojangTexturesBatchUUIDsProvider( | ||||
| 	mojangApi *mojang.MojangApi, | ||||
| 	config *viper.Viper, | ||||
| ) (*mojang.BatchUuidsProvider, error) { | ||||
| 	config.SetDefault("queue.loop_delay", 2*time.Second+500*time.Millisecond) | ||||
| 	config.SetDefault("queue.batch_size", 10) | ||||
| 	config.SetDefault("queue.strategy", "periodic") | ||||
|  | ||||
| 	// TODO: healthcheck is broken | ||||
|  | ||||
| 	uuidsProvider := mojang.NewBatchUuidsProvider( | ||||
| 		mojangApi.UsernamesToUuids, | ||||
| 		config.GetInt("queue.batch_size"), | ||||
| 		config.GetDuration("queue.loop_delay"), | ||||
| 		config.GetString("queue.strategy") == "full-bus", | ||||
| 	) | ||||
|  | ||||
| 	return uuidsProvider, nil | ||||
| } | ||||
|  | ||||
| func newMojangSignedTexturesProvider(mojangApi *mojang.MojangApi) mojang.TexturesProvider { | ||||
| 	return mojang.NewTexturesProviderWithInMemoryCache( | ||||
| 		&mojang.MojangApiTexturesProvider{ | ||||
| 			MojangApiTexturesEndpoint: mojangApi.UuidToTextures, | ||||
| 		}, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										27
									
								
								internal/di/profiles.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								internal/di/profiles.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| package di | ||||
|  | ||||
| import ( | ||||
| 	"github.com/defval/di" | ||||
|  | ||||
| 	. "github.com/elyby/chrly/internal/http" | ||||
| 	"github.com/elyby/chrly/internal/profiles" | ||||
| ) | ||||
|  | ||||
| var profilesDi = di.Options( | ||||
| 	di.Provide(newProfilesManager, di.As(new(ProfilesManager))), | ||||
| 	di.Provide(newProfilesProvider, di.As(new(ProfilesProvider))), | ||||
| ) | ||||
|  | ||||
| func newProfilesManager(r profiles.ProfilesRepository) *profiles.Manager { | ||||
| 	return profiles.NewManager(r) | ||||
| } | ||||
|  | ||||
| func newProfilesProvider( | ||||
| 	finder profiles.ProfilesFinder, | ||||
| 	mojangProfilesProvider profiles.MojangProfilesProvider, | ||||
| ) *profiles.Provider { | ||||
| 	return &profiles.Provider{ | ||||
| 		ProfilesFinder:         finder, | ||||
| 		MojangProfilesProvider: mojangProfilesProvider, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										79
									
								
								internal/di/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								internal/di/server.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| package di | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"runtime/debug" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/defval/di" | ||||
| 	"github.com/getsentry/raven-go" | ||||
| 	"github.com/spf13/viper" | ||||
|  | ||||
| 	. "github.com/elyby/chrly/internal/http" | ||||
| ) | ||||
|  | ||||
| var server = di.Options( | ||||
| 	di.Provide(newAuthenticator, di.As(new(Authenticator))), | ||||
| 	di.Provide(newServer), | ||||
| ) | ||||
|  | ||||
| func newAuthenticator(config *viper.Viper, emitter Emitter) (*JwtAuth, error) { | ||||
| 	key := config.GetString("chrly.secret") | ||||
| 	if key == "" { | ||||
| 		return nil, errors.New("chrly.secret must be set in order to use authenticator") | ||||
| 	} | ||||
|  | ||||
| 	return &JwtAuth{ | ||||
| 		Key:     []byte(key), | ||||
| 		Emitter: emitter, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| type serverParams struct { | ||||
| 	di.Inject | ||||
|  | ||||
| 	Config  *viper.Viper  `di:""` | ||||
| 	Handler http.Handler  `di:""` | ||||
| 	Sentry  *raven.Client `di:"" optional:"true"` | ||||
| } | ||||
|  | ||||
| func newServer(params serverParams) *http.Server { | ||||
| 	params.Config.SetDefault("server.host", "") | ||||
| 	params.Config.SetDefault("server.port", 80) | ||||
|  | ||||
| 	var handler http.Handler | ||||
| 	if params.Sentry != nil { | ||||
| 		// raven.Recoverer uses DefaultClient and nothing can be done about it | ||||
| 		// To avoid code duplication, if the Sentry service is successfully initiated, | ||||
| 		// it will also replace DefaultClient, so raven.Recoverer will work with the instance | ||||
| 		// created in the application constructor | ||||
| 		handler = raven.Recoverer(params.Handler) | ||||
| 	} else { | ||||
| 		// Raven's Recoverer is prints the stacktrace and sets the corresponding status itself. | ||||
| 		// But there is no magic and if you don't define a panic handler, Mux will just reset the connection | ||||
| 		handler = http.HandlerFunc(func(request http.ResponseWriter, response *http.Request) { | ||||
| 			defer func() { | ||||
| 				if recovered := recover(); recovered != nil { | ||||
| 					debug.PrintStack() // TODO: colorize output | ||||
| 					request.WriteHeader(http.StatusInternalServerError) | ||||
| 				} | ||||
| 			}() | ||||
|  | ||||
| 			params.Handler.ServeHTTP(request, response) | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	address := fmt.Sprintf("%s:%d", params.Config.GetString("server.host"), params.Config.GetInt("server.port")) | ||||
| 	server := &http.Server{ | ||||
| 		Addr:           address, | ||||
| 		ReadTimeout:    5 * time.Second, | ||||
| 		WriteTimeout:   5 * time.Second, | ||||
| 		IdleTimeout:    60 * time.Second, | ||||
| 		MaxHeaderBytes: 1 << 16, | ||||
| 		Handler:        handler, | ||||
| 	} | ||||
|  | ||||
| 	return server | ||||
| } | ||||
							
								
								
									
										49
									
								
								internal/di/signer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								internal/di/signer.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| package di | ||||
|  | ||||
| import ( | ||||
| 	"crypto/x509" | ||||
| 	"encoding/base64" | ||||
| 	"encoding/pem" | ||||
| 	"errors" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/elyby/chrly/internal/http" | ||||
| 	. "github.com/elyby/chrly/internal/signer" | ||||
|  | ||||
| 	"github.com/defval/di" | ||||
| 	"github.com/spf13/viper" | ||||
| ) | ||||
|  | ||||
| var signer = di.Options( | ||||
| 	di.Provide(newTexturesSigner, | ||||
| 		di.As(new(http.TexturesSigner)), | ||||
| 	), | ||||
| ) | ||||
|  | ||||
| func newTexturesSigner(config *viper.Viper) (*Signer, error) { | ||||
| 	keyStr := config.GetString("chrly.signing.key") | ||||
| 	if keyStr == "" { | ||||
| 		return nil, errors.New("chrly.signing.key must be set in order to sign textures") | ||||
| 	} | ||||
|  | ||||
| 	var keyBytes []byte | ||||
| 	if strings.HasPrefix(keyStr, "base64:") { | ||||
| 		base64Value := keyStr[7:] | ||||
| 		decodedKey, err := base64.URLEncoding.DecodeString(base64Value) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		keyBytes = decodedKey | ||||
| 	} else { | ||||
| 		keyBytes = []byte(keyStr) | ||||
| 	} | ||||
|  | ||||
| 	rawPem, _ := pem.Decode(keyBytes) | ||||
| 	key, err := x509.ParsePKCS1PrivateKey(rawPem.Bytes) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &Signer{Key: key}, nil | ||||
| } | ||||
		Reference in New Issue
	
	Block a user