Auth Provider (#1309)
* auth provider mock interface * Auth Provider Options * Implement API Server Auth Package * Add weh utils * Add Login URL * Auth Provider Options * Add auth provider scope and setting token in cookie * Remove auth_login_url flag Co-authored-by: Asim Aslam <asim@aslam.me> Co-authored-by: Ben Toogood <ben@micro.mu>
This commit is contained in:
		
							
								
								
									
										70
									
								
								api/server/auth/auth.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								api/server/auth/auth.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | |||||||
|  | package auth | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/micro/go-micro/v2/auth" | ||||||
|  | 	"github.com/micro/go-micro/v2/metadata" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // CombinedAuthHandler wraps a server and authenticates requests | ||||||
|  | func CombinedAuthHandler(h http.Handler) http.Handler { | ||||||
|  | 	return authHandler{ | ||||||
|  | 		handler: h, | ||||||
|  | 		auth:    auth.DefaultAuth, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type authHandler struct { | ||||||
|  | 	handler http.Handler | ||||||
|  | 	auth    auth.Auth | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	// BearerScheme is the prefix in the auth header | ||||||
|  | 	BearerScheme = "Bearer " | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { | ||||||
|  | 	loginURL := h.auth.Options().LoginURL | ||||||
|  |  | ||||||
|  | 	// Return if the user disabled auth on this endpoint | ||||||
|  | 	excludes := h.auth.Options().Exclude | ||||||
|  | 	if len(loginURL) > 0 { | ||||||
|  | 		excludes = append(excludes, loginURL) | ||||||
|  | 	} | ||||||
|  | 	for _, e := range excludes { | ||||||
|  | 		if e == req.URL.Path { | ||||||
|  | 			h.handler.ServeHTTP(w, req) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var token string | ||||||
|  | 	if header, ok := metadata.Get(req.Context(), "Authorization"); ok { | ||||||
|  | 		// Extract the auth token from the request | ||||||
|  | 		if strings.HasPrefix(header, BearerScheme) { | ||||||
|  | 			token = header[len(BearerScheme):] | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		// Get the token out the cookies if not provided in headers | ||||||
|  | 		if c, err := req.Cookie(auth.CookieName); err != nil && c != nil { | ||||||
|  | 			token = c.Value | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If the token is valid, allow the request | ||||||
|  | 	if _, err := h.auth.Verify(token); err == nil { | ||||||
|  | 		h.handler.ServeHTTP(w, req) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If there is no auth login url set, 401 | ||||||
|  | 	if loginURL == "" { | ||||||
|  | 		w.WriteHeader(401) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Redirect to the login path | ||||||
|  | 	http.Redirect(w, req, loginURL, http.StatusTemporaryRedirect) | ||||||
|  | } | ||||||
| @@ -8,6 +8,8 @@ import ( | |||||||
| 	"os" | 	"os" | ||||||
| 	"sync" | 	"sync" | ||||||
|  |  | ||||||
|  | 	"github.com/micro/go-micro/v2/api/server/auth" | ||||||
|  |  | ||||||
| 	"github.com/gorilla/handlers" | 	"github.com/gorilla/handlers" | ||||||
| 	"github.com/micro/go-micro/v2/api/server" | 	"github.com/micro/go-micro/v2/api/server" | ||||||
| 	"github.com/micro/go-micro/v2/api/server/cors" | 	"github.com/micro/go-micro/v2/api/server/cors" | ||||||
| @@ -47,6 +49,7 @@ func (s *httpServer) Init(opts ...server.Option) error { | |||||||
|  |  | ||||||
| func (s *httpServer) Handle(path string, handler http.Handler) { | func (s *httpServer) Handle(path string, handler http.Handler) { | ||||||
| 	h := handlers.CombinedLoggingHandler(os.Stdout, handler) | 	h := handlers.CombinedLoggingHandler(os.Stdout, handler) | ||||||
|  | 	h = auth.CombinedAuthHandler(handler) | ||||||
|  |  | ||||||
| 	if s.opts.EnableCORS { | 	if s.opts.EnableCORS { | ||||||
| 		h = cors.CombinedCORSHandler(h) | 		h = cors.CombinedCORSHandler(h) | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ type Role struct { | |||||||
|  |  | ||||||
| // Account provided by an auth provider | // Account provided by an auth provider | ||||||
| type Account struct { | type Account struct { | ||||||
| 	// ID of the account (UUID or email) | 	// ID of the account (UUIDV4, email or username) | ||||||
| 	Id string `json:"id"` | 	Id string `json:"id"` | ||||||
| 	// Token used to authenticate | 	// Token used to authenticate | ||||||
| 	Token string `json:"token"` | 	Token string `json:"token"` | ||||||
| @@ -62,6 +62,9 @@ const ( | |||||||
| 	// MetadataKey is the key used when storing the account | 	// MetadataKey is the key used when storing the account | ||||||
| 	// in metadata | 	// in metadata | ||||||
| 	MetadataKey = "auth-account" | 	MetadataKey = "auth-account" | ||||||
|  | 	// CookieName is the name of the cookie which stores the | ||||||
|  | 	// auth token | ||||||
|  | 	CookieName = "micro-token" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // AccountFromContext gets the account from the context, which | // AccountFromContext gets the account from the context, which | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| package auth | package auth | ||||||
|  |  | ||||||
|  | import "github.com/micro/go-micro/v2/auth/provider" | ||||||
|  |  | ||||||
| type Options struct { | type Options struct { | ||||||
| 	// Token is an auth token | 	// Token is an auth token | ||||||
| 	Token string | 	Token string | ||||||
| @@ -9,6 +11,10 @@ type Options struct { | |||||||
| 	PrivateKey string | 	PrivateKey string | ||||||
| 	// Endpoints to exclude | 	// Endpoints to exclude | ||||||
| 	Exclude []string | 	Exclude []string | ||||||
|  | 	// Provider is an auth provider | ||||||
|  | 	Provider provider.Provider | ||||||
|  | 	// LoginURL is the relative url path where a user can login | ||||||
|  | 	LoginURL string | ||||||
| } | } | ||||||
|  |  | ||||||
| type Option func(o *Options) | type Option func(o *Options) | ||||||
| @@ -41,6 +47,20 @@ func Token(t string) Option { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Provider set the auth provider | ||||||
|  | func Provider(p provider.Provider) Option { | ||||||
|  | 	return func(o *Options) { | ||||||
|  | 		o.Provider = p | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // LoginURL sets the auth LoginURL | ||||||
|  | func LoginURL(url string) Option { | ||||||
|  | 	return func(o *Options) { | ||||||
|  | 		o.LoginURL = url | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| type GenerateOptions struct { | type GenerateOptions struct { | ||||||
| 	// Metadata associated with the account | 	// Metadata associated with the account | ||||||
| 	Metadata map[string]string | 	Metadata map[string]string | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								auth/provider/basic/basic.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								auth/provider/basic/basic.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | package basic | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/micro/go-micro/v2/auth/provider" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // NewProvider returns an initialised basic provider | ||||||
|  | func NewProvider(opts ...provider.Option) provider.Provider { | ||||||
|  | 	var options provider.Options | ||||||
|  | 	for _, o := range opts { | ||||||
|  | 		o(&options) | ||||||
|  | 	} | ||||||
|  | 	return &basic{options} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type basic struct { | ||||||
|  | 	opts provider.Options | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *basic) String() string { | ||||||
|  | 	return "basic" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *basic) Options() provider.Options { | ||||||
|  | 	return b.opts | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *basic) Endpoint() string { | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *basic) Redirect() string { | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
							
								
								
									
										42
									
								
								auth/provider/oauth/oauth.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								auth/provider/oauth/oauth.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | package oauth | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  |  | ||||||
|  | 	"github.com/micro/go-micro/v2/auth/provider" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // NewProvider returns an initialised oauth provider | ||||||
|  | func NewProvider(opts ...provider.Option) provider.Provider { | ||||||
|  | 	var options provider.Options | ||||||
|  | 	for _, o := range opts { | ||||||
|  | 		o(&options) | ||||||
|  | 	} | ||||||
|  | 	return &oauth{options} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type oauth struct { | ||||||
|  | 	opts provider.Options | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *oauth) String() string { | ||||||
|  | 	return "oauth" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *oauth) Options() provider.Options { | ||||||
|  | 	return o.opts | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *oauth) Endpoint() string { | ||||||
|  | 	s := fmt.Sprintf("%v?client_id=%v", o.opts.Endpoint, o.opts.ClientID) | ||||||
|  |  | ||||||
|  | 	if scope := o.opts.Scope; len(scope) > 0 { | ||||||
|  | 		s = fmt.Sprintf("%v&scope=%v", s, scope) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *oauth) Redirect() string { | ||||||
|  | 	return o.opts.Redirect | ||||||
|  | } | ||||||
							
								
								
									
										47
									
								
								auth/provider/options.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								auth/provider/options.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | package provider | ||||||
|  |  | ||||||
|  | // Option returns a function which sets an option | ||||||
|  | type Option func(*Options) | ||||||
|  |  | ||||||
|  | // Options a provider can have | ||||||
|  | type Options struct { | ||||||
|  | 	// ClientID is the application's ID. | ||||||
|  | 	ClientID string | ||||||
|  | 	// ClientSecret is the application's secret. | ||||||
|  | 	ClientSecret string | ||||||
|  | 	// Endpoint for the provider | ||||||
|  | 	Endpoint string | ||||||
|  | 	// Redirect url incase of UI | ||||||
|  | 	Redirect string | ||||||
|  | 	// Scope of the oauth request | ||||||
|  | 	Scope string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Credentials is an option which sets the client id and secret | ||||||
|  | func Credentials(id, secret string) Option { | ||||||
|  | 	return func(o *Options) { | ||||||
|  | 		o.ClientID = id | ||||||
|  | 		o.ClientSecret = secret | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Endpoint sets the endpoint option | ||||||
|  | func Endpoint(e string) Option { | ||||||
|  | 	return func(o *Options) { | ||||||
|  | 		o.Endpoint = e | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Redirect sets the Redirect option | ||||||
|  | func Redirect(r string) Option { | ||||||
|  | 	return func(o *Options) { | ||||||
|  | 		o.Redirect = r | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Scope sets the oauth scope | ||||||
|  | func Scope(s string) Option { | ||||||
|  | 	return func(o *Options) { | ||||||
|  | 		o.Scope = s | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								auth/provider/provider.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								auth/provider/provider.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | // Package provider is an external auth provider e.g oauth | ||||||
|  | package provider | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Provider is an auth provider | ||||||
|  | type Provider interface { | ||||||
|  | 	// String returns the name of the provider | ||||||
|  | 	String() string | ||||||
|  | 	// Options returns the options of a provider | ||||||
|  | 	Options() Options | ||||||
|  | 	// Endpoint for the provider | ||||||
|  | 	Endpoint() string | ||||||
|  | 	// Redirect url incase of UI | ||||||
|  | 	Redirect() string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Grant is a granted authorisation | ||||||
|  | type Grant struct { | ||||||
|  | 	// token for reuse | ||||||
|  | 	Token string | ||||||
|  | 	// Expiry of the token | ||||||
|  | 	Expiry time.Time | ||||||
|  | 	// Scopes associated with grant | ||||||
|  | 	Scopes []string | ||||||
|  | } | ||||||
| @@ -7,6 +7,8 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/micro/go-micro/v2/auth/provider" | ||||||
|  |  | ||||||
| 	"github.com/micro/go-micro/v2/auth" | 	"github.com/micro/go-micro/v2/auth" | ||||||
| 	"github.com/micro/go-micro/v2/broker" | 	"github.com/micro/go-micro/v2/broker" | ||||||
| 	"github.com/micro/go-micro/v2/client" | 	"github.com/micro/go-micro/v2/client" | ||||||
| @@ -70,6 +72,10 @@ import ( | |||||||
| 	jwtAuth "github.com/micro/go-micro/v2/auth/jwt" | 	jwtAuth "github.com/micro/go-micro/v2/auth/jwt" | ||||||
| 	sAuth "github.com/micro/go-micro/v2/auth/service" | 	sAuth "github.com/micro/go-micro/v2/auth/service" | ||||||
| 	storeAuth "github.com/micro/go-micro/v2/auth/store" | 	storeAuth "github.com/micro/go-micro/v2/auth/store" | ||||||
|  |  | ||||||
|  | 	// auth providers | ||||||
|  | 	"github.com/micro/go-micro/v2/auth/provider/basic" | ||||||
|  | 	"github.com/micro/go-micro/v2/auth/provider/oauth" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type Cmd interface { | type Cmd interface { | ||||||
| @@ -269,6 +275,36 @@ var ( | |||||||
| 			EnvVars: []string{"MICRO_AUTH_EXCLUDE"}, | 			EnvVars: []string{"MICRO_AUTH_EXCLUDE"}, | ||||||
| 			Usage:   "Comma-separated list of endpoints excluded from authentication, e.g. Users.ListUsers", | 			Usage:   "Comma-separated list of endpoints excluded from authentication, e.g. Users.ListUsers", | ||||||
| 		}, | 		}, | ||||||
|  | 		&cli.StringFlag{ | ||||||
|  | 			Name:    "auth_provider", | ||||||
|  | 			EnvVars: []string{"MICRO_AUTH_PROVIDER"}, | ||||||
|  | 			Usage:   "Auth provider used to login user", | ||||||
|  | 		}, | ||||||
|  | 		&cli.StringFlag{ | ||||||
|  | 			Name:    "auth_provider_client_id", | ||||||
|  | 			EnvVars: []string{"MICRO_AUTH_PROVIDER_CLIENT_ID"}, | ||||||
|  | 			Usage:   "The client id to be used for oauth", | ||||||
|  | 		}, | ||||||
|  | 		&cli.StringFlag{ | ||||||
|  | 			Name:    "auth_provider_client_secret", | ||||||
|  | 			EnvVars: []string{"MICRO_AUTH_PROVIDER_CLIENT_SECRET"}, | ||||||
|  | 			Usage:   "The client secret to be used for oauth", | ||||||
|  | 		}, | ||||||
|  | 		&cli.StringFlag{ | ||||||
|  | 			Name:    "auth_provider_endpoint", | ||||||
|  | 			EnvVars: []string{"MICRO_AUTH_PROVIDER_ENDPOINT"}, | ||||||
|  | 			Usage:   "The enpoint to be used for oauth", | ||||||
|  | 		}, | ||||||
|  | 		&cli.StringFlag{ | ||||||
|  | 			Name:    "auth_provider_redirect", | ||||||
|  | 			EnvVars: []string{"MICRO_AUTH_PROVIDER_REDIRECT"}, | ||||||
|  | 			Usage:   "The redirect to be used for oauth", | ||||||
|  | 		}, | ||||||
|  | 		&cli.StringFlag{ | ||||||
|  | 			Name:    "auth_provider_scope", | ||||||
|  | 			EnvVars: []string{"MICRO_AUTH_PROVIDER_SCOPE"}, | ||||||
|  | 			Usage:   "The scope to be used for oauth", | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	DefaultBrokers = map[string]func(...broker.Option) broker.Broker{ | 	DefaultBrokers = map[string]func(...broker.Option) broker.Broker{ | ||||||
| @@ -328,6 +364,11 @@ var ( | |||||||
| 		"jwt":     jwtAuth.NewAuth, | 		"jwt":     jwtAuth.NewAuth, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	DefaultAuthProviders = map[string]func(...provider.Option) provider.Provider{ | ||||||
|  | 		"oauth": oauth.NewProvider, | ||||||
|  | 		"basic": basic.NewProvider, | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	DefaultProfiles = map[string]func(...profile.Option) profile.Profile{ | 	DefaultProfiles = map[string]func(...profile.Option) profile.Profile{ | ||||||
| 		"http":  http.NewProfile, | 		"http":  http.NewProfile, | ||||||
| 		"pprof": pprof.NewProfile, | 		"pprof": pprof.NewProfile, | ||||||
| @@ -627,6 +668,32 @@ func (c *cmd) Before(ctx *cli.Context) error { | |||||||
| 		authOpts = append(authOpts, auth.Exclude(ctx.StringSlice("auth_exclude")...)) | 		authOpts = append(authOpts, auth.Exclude(ctx.StringSlice("auth_exclude")...)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if name := ctx.String("auth_provider"); len(name) > 0 { | ||||||
|  | 		p, ok := DefaultAuthProviders[name] | ||||||
|  | 		if !ok { | ||||||
|  | 			return fmt.Errorf("AuthProvider %s not found", name) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		var provOpts []provider.Option | ||||||
|  |  | ||||||
|  | 		clientID := ctx.String("auth_provider_client_id") | ||||||
|  | 		clientSecret := ctx.String("auth_provider_client_secret") | ||||||
|  | 		if len(clientID) > 0 || len(clientSecret) > 0 { | ||||||
|  | 			provOpts = append(provOpts, provider.Credentials(clientID, clientSecret)) | ||||||
|  | 		} | ||||||
|  | 		if e := ctx.String("auth_provider_endpoint"); len(e) > 0 { | ||||||
|  | 			provOpts = append(provOpts, provider.Endpoint(e)) | ||||||
|  | 		} | ||||||
|  | 		if r := ctx.String("auth_provider_redirect"); len(r) > 0 { | ||||||
|  | 			provOpts = append(provOpts, provider.Redirect(r)) | ||||||
|  | 		} | ||||||
|  | 		if s := ctx.String("auth_provider_scope"); len(s) > 0 { | ||||||
|  | 			provOpts = append(provOpts, provider.Scope(s)) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		authOpts = append(authOpts, auth.Provider(p(provOpts...))) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if len(authOpts) > 0 { | 	if len(authOpts) > 0 { | ||||||
| 		if err := (*c.opts.Auth).Init(authOpts...); err != nil { | 		if err := (*c.opts.Auth).Init(authOpts...); err != nil { | ||||||
| 			log.Fatalf("Error configuring auth: %v", err) | 			log.Fatalf("Error configuring auth: %v", err) | ||||||
|   | |||||||
| @@ -1,12 +1,47 @@ | |||||||
| package http | package http | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  |  | ||||||
| 	"github.com/micro/go-micro/v2/client/selector" | 	"github.com/micro/go-micro/v2/client/selector" | ||||||
| 	"github.com/micro/go-micro/v2/registry" | 	"github.com/micro/go-micro/v2/registry" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | // Write sets the status and body on a http ResponseWriter | ||||||
|  | func Write(w http.ResponseWriter, contentType string, status int, body string) { | ||||||
|  | 	w.Header().Set("Content-Length", fmt.Sprintf("%v", len(body))) | ||||||
|  | 	w.Header().Set("Content-Type", contentType) | ||||||
|  | 	w.WriteHeader(status) | ||||||
|  | 	fmt.Fprintf(w, `%v`, body) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // WriteBadRequestError sets a 400 status code | ||||||
|  | func WriteBadRequestError(w http.ResponseWriter, err error) { | ||||||
|  | 	rawBody, err := json.Marshal(map[string]string{ | ||||||
|  | 		"error": err.Error(), | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		WriteInternalServerError(w, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	Write(w, "application/json", 400, string(rawBody)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // WriteInternalServerError sets a 500 status code | ||||||
|  | func WriteInternalServerError(w http.ResponseWriter, err error) { | ||||||
|  | 	rawBody, err := json.Marshal(map[string]string{ | ||||||
|  | 		"error": err.Error(), | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Println(err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	Write(w, "application/json", 500, string(rawBody)) | ||||||
|  | } | ||||||
|  |  | ||||||
| func NewRoundTripper(opts ...Option) http.RoundTripper { | func NewRoundTripper(opts ...Option) http.RoundTripper { | ||||||
| 	options := Options{ | 	options := Options{ | ||||||
| 		Registry: registry.DefaultRegistry, | 		Registry: registry.DefaultRegistry, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user