diff --git a/security/hierarchy/README.md b/security/hierarchy/README.md index 50be12e6b..6979addea 100644 --- a/security/hierarchy/README.md +++ b/security/hierarchy/README.md @@ -10,6 +10,8 @@ of the security DSLs: security scheme that uses an API key. * [BasicAuthSecurity](https://pkg.go.dev/goa.design/goa/v3/dsl#BasicAuthSecurity) defines a security scheme that uses basic auth. +* [BearerSecurity](https://pkg.go.dev/goa.design/goa/v3/dsl#BearerSecurity) defines a + security scheme that uses bearer tokens. * [JWTSecurity](https://pkg.go.dev/goa.design/goa/v3/dsl#JWTSecurity) defines a security scheme that uses JWT tokens. * [OAuth2Security](https://pkg.go.dev/goa.design/goa/v3/dsl#OAuth2Security) defines a @@ -129,6 +131,7 @@ shows the basic auth security scheme defined at the API level being applied. The [generated endpoints code](https://github.com/goadesign/examples/tree/master/security/hierarchy/gen/api_key_service/endpoints.go) code the `api_key_service` shows the API key scheme applied the default -endpoint (using the security scheme defined at the service leve), the JWT +endpoint (using the security scheme defined at the service level), the JWT scheme used by the `secure` method that overrides the default set at the -service level and the `unsecure` method with no security scheme applied. +service level, the bearer scheme used by the `bearer_secure` method and the +`unsecure` method with no security scheme applied. diff --git a/security/hierarchy/api_key_service.go b/security/hierarchy/api_key_service.go index e7df9c535..7ec49f274 100644 --- a/security/hierarchy/api_key_service.go +++ b/security/hierarchy/api_key_service.go @@ -60,6 +60,26 @@ func (s *apiKeyServicesrvc) JWTAuth(ctx context.Context, token string, scheme *s return ctx, fmt.Errorf("not implemented") } +// BearerAuth implements the authorization logic for service "api_key_service" +// for the "bearer" security scheme. +func (s *apiKeyServicesrvc) BearerAuth(ctx context.Context, token string, scheme *security.BearerScheme) (context.Context, error) { + // + // TBD: add authorization logic. + // + // In case of authorization failure this function should return + // one of the generated error structs, e.g.: + // + // return ctx, myservice.MakeUnauthorizedError("invalid token") + // + // Alternatively this function may return an instance of + // goa.ServiceError with a Name field value that matches one of + // the design error names, e.g: + // + // return ctx, goa.PermanentError("unauthorized", "invalid token") + // + return ctx, fmt.Errorf("not implemented") +} + // Default implements default. func (s *apiKeyServicesrvc) Default(ctx context.Context, p *apikeyservice.DefaultPayload) (err error) { s.logger.Print("apiKeyService.default") @@ -72,6 +92,12 @@ func (s *apiKeyServicesrvc) Secure(ctx context.Context, p *apikeyservice.SecureP return } +// This method requires a bearer token. +func (s *apiKeyServicesrvc) BearerSecure(ctx context.Context, p *apikeyservice.BearerSecurePayload) (err error) { + s.logger.Print("apiKeyService.bearer_secure") + return +} + // This method is not secured. func (s *apiKeyServicesrvc) Unsecure(ctx context.Context) (err error) { s.logger.Print("apiKeyService.unsecure") diff --git a/security/hierarchy/design/README.md b/security/hierarchy/design/README.md index 388e01a66..8442346ae 100644 --- a/security/hierarchy/design/README.md +++ b/security/hierarchy/design/README.md @@ -10,6 +10,8 @@ of the security DSLs: security scheme that uses an API key. * [BasicAuthSecurity](https://pkg.go.dev/goa.design/goa/v3/dsl#BasicAuthSecurity) defines a security scheme that uses basic auth. +* [BearerSecurity](https://pkg.go.dev/goa.design/goa/v3/dsl#BearerSecurity) defines a + security scheme that uses bearer tokens. * [JWTSecurity](https://pkg.go.dev/goa.design/goa/v3/dsl#JWTSecurity) defines a security scheme that uses JWT tokens. * [OAuth2Security](https://pkg.go.dev/goa.design/goa/v3/dsl#OAuth2Security) defines a @@ -129,6 +131,7 @@ shows the basic auth security scheme defined at the API level being applied. The [generated endpoints code](https://github.com/goadesign/examples/tree/master/security/hierarchy/gen/api_key_service/endpoints.go) code the `api_key_service` shows the API key scheme applied the default -endpoint (using the security scheme defined at the service leve), the JWT +endpoint (using the security scheme defined at the service level), the JWT scheme used by the `secure` method that overrides the default set at the -service level and the `unsecure` method with no security scheme applied. \ No newline at end of file +service level, the bearer scheme used by the `bearer_secure` method and the +`unsecure` method with no security scheme applied. diff --git a/security/hierarchy/design/design.go b/security/hierarchy/design/design.go index cd3dac888..8515308e4 100644 --- a/security/hierarchy/design/design.go +++ b/security/hierarchy/design/design.go @@ -19,6 +19,11 @@ var JWTAuth = JWTSecurity("jwt", func() { Description(`Secures endpoint by requiring a valid JWT token.`) }) +// BearerAuth defines a security scheme that uses bearer tokens. +var BearerAuth = BearerSecurity("bearer", func() { + Description(`Secures endpoint by requiring a bearer token.`) +}) + var _ = API("hierarchy", func() { Title("Security Example API") Description("This API demonstrates the effect of using Security at the API, Service or Method levels") @@ -69,6 +74,20 @@ var _ = Service("api_key_service", func() { HTTP(func() { GET("/secure") }) }) + Method("bearer_secure", func() { + Security(BearerAuth) + Description("This method requires a bearer token.") + + Payload(func() { + BearerToken("bearer_token", String, func() { + Description("Bearer token used for authentication") + }) + Required("bearer_token") + }) + + HTTP(func() { GET("/bearer") }) + }) + Method("unsecure", func() { Description("This method is not secured.") NoSecurity() diff --git a/security/hierarchy/gen/api_key_service/client.go b/security/hierarchy/gen/api_key_service/client.go index c5d8860e9..f22e7f8c5 100644 --- a/security/hierarchy/gen/api_key_service/client.go +++ b/security/hierarchy/gen/api_key_service/client.go @@ -15,17 +15,19 @@ import ( // Client is the "api_key_service" service client. type Client struct { - DefaultEndpoint goa.Endpoint - SecureEndpoint goa.Endpoint - UnsecureEndpoint goa.Endpoint + DefaultEndpoint goa.Endpoint + SecureEndpoint goa.Endpoint + BearerSecureEndpoint goa.Endpoint + UnsecureEndpoint goa.Endpoint } // NewClient initializes a "api_key_service" service client given the endpoints. -func NewClient(default_, secure, unsecure goa.Endpoint) *Client { +func NewClient(default_, secure, bearerSecure, unsecure goa.Endpoint) *Client { return &Client{ - DefaultEndpoint: default_, - SecureEndpoint: secure, - UnsecureEndpoint: unsecure, + DefaultEndpoint: default_, + SecureEndpoint: secure, + BearerSecureEndpoint: bearerSecure, + UnsecureEndpoint: unsecure, } } @@ -41,6 +43,13 @@ func (c *Client) Secure(ctx context.Context, p *SecurePayload) (err error) { return } +// BearerSecure calls the "bearer_secure" endpoint of the "api_key_service" +// service. +func (c *Client) BearerSecure(ctx context.Context, p *BearerSecurePayload) (err error) { + _, err = c.BearerSecureEndpoint(ctx, p) + return +} + // Unsecure calls the "unsecure" endpoint of the "api_key_service" service. func (c *Client) Unsecure(ctx context.Context) (err error) { _, err = c.UnsecureEndpoint(ctx, nil) diff --git a/security/hierarchy/gen/api_key_service/endpoints.go b/security/hierarchy/gen/api_key_service/endpoints.go index ed1a074f0..82e8a8bcb 100644 --- a/security/hierarchy/gen/api_key_service/endpoints.go +++ b/security/hierarchy/gen/api_key_service/endpoints.go @@ -16,9 +16,10 @@ import ( // Endpoints wraps the "api_key_service" service endpoints. type Endpoints struct { - Default goa.Endpoint - Secure goa.Endpoint - Unsecure goa.Endpoint + Default goa.Endpoint + Secure goa.Endpoint + BearerSecure goa.Endpoint + Unsecure goa.Endpoint } // NewEndpoints wraps the methods of the "api_key_service" service with @@ -27,9 +28,10 @@ func NewEndpoints(s Service) *Endpoints { // Casting service to Auther interface a := s.(Auther) return &Endpoints{ - Default: NewDefaultEndpoint(s, a.APIKeyAuth), - Secure: NewSecureEndpoint(s, a.JWTAuth), - Unsecure: NewUnsecureEndpoint(s), + Default: NewDefaultEndpoint(s, a.APIKeyAuth), + Secure: NewSecureEndpoint(s, a.JWTAuth), + BearerSecure: NewBearerSecureEndpoint(s, a.BearerAuth), + Unsecure: NewUnsecureEndpoint(s), } } @@ -38,6 +40,7 @@ func NewEndpoints(s Service) *Endpoints { func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) { e.Default = m(e.Default) e.Secure = m(e.Secure) + e.BearerSecure = m(e.BearerSecure) e.Unsecure = m(e.Unsecure) } @@ -79,6 +82,25 @@ func NewSecureEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { } } +// NewBearerSecureEndpoint returns an endpoint function that calls the method +// "bearer_secure" of service "api_key_service". +func NewBearerSecureEndpoint(s Service, authBearerFn security.AuthBearerFunc) goa.Endpoint { + return func(ctx context.Context, req any) (any, error) { + p := req.(*BearerSecurePayload) + var err error + sc := security.BearerScheme{ + Name: "bearer", + Scopes: []string{}, + RequiredScopes: []string{}, + } + ctx, err = authBearerFn(ctx, p.BearerToken, &sc) + if err != nil { + return nil, err + } + return nil, s.BearerSecure(ctx, p) + } +} + // NewUnsecureEndpoint returns an endpoint function that calls the method // "unsecure" of service "api_key_service". func NewUnsecureEndpoint(s Service) goa.Endpoint { diff --git a/security/hierarchy/gen/api_key_service/service.go b/security/hierarchy/gen/api_key_service/service.go index 16e8f5da2..319bacce0 100644 --- a/security/hierarchy/gen/api_key_service/service.go +++ b/security/hierarchy/gen/api_key_service/service.go @@ -19,6 +19,8 @@ type Service interface { Default(context.Context, *DefaultPayload) (err error) // This method requires a valid JWT token. Secure(context.Context, *SecurePayload) (err error) + // This method requires a bearer token. + BearerSecure(context.Context, *BearerSecurePayload) (err error) // This method is not secured. Unsecure(context.Context) (err error) } @@ -29,6 +31,8 @@ type Auther interface { APIKeyAuth(ctx context.Context, key string, schema *security.APIKeyScheme) (context.Context, error) // JWTAuth implements the authorization logic for the JWT security scheme. JWTAuth(ctx context.Context, token string, schema *security.JWTScheme) (context.Context, error) + // BearerAuth implements the authorization logic for the Bearer security scheme. + BearerAuth(ctx context.Context, token string, schema *security.BearerScheme) (context.Context, error) } // APIName is the name of the API as defined in the design. @@ -45,7 +49,14 @@ const ServiceName = "api_key_service" // MethodNames lists the service method names as defined in the design. These // are the same values that are set in the endpoint request contexts under the // MethodKey key. -var MethodNames = [3]string{"default", "secure", "unsecure"} +var MethodNames = [4]string{"default", "secure", "bearer_secure", "unsecure"} + +// BearerSecurePayload is the payload type of the api_key_service service +// bearer_secure method. +type BearerSecurePayload struct { + // Bearer token used for authentication + BearerToken string +} // DefaultPayload is the payload type of the api_key_service service default // method. diff --git a/security/hierarchy/gen/http/api_key_service/client/cli.go b/security/hierarchy/gen/http/api_key_service/client/cli.go index d4c57fc8f..22a7d6649 100644 --- a/security/hierarchy/gen/http/api_key_service/client/cli.go +++ b/security/hierarchy/gen/http/api_key_service/client/cli.go @@ -36,3 +36,16 @@ func BuildSecurePayload(apiKeyServiceSecureToken string) (*apikeyservice.SecureP return v, nil } + +// BuildBearerSecurePayload builds the payload for the api_key_service +// bearer_secure endpoint from CLI flags. +func BuildBearerSecurePayload(apiKeyServiceBearerSecureBearerToken string) (*apikeyservice.BearerSecurePayload, error) { + var bearerToken string + { + bearerToken = apiKeyServiceBearerSecureBearerToken + } + v := &apikeyservice.BearerSecurePayload{} + v.BearerToken = bearerToken + + return v, nil +} diff --git a/security/hierarchy/gen/http/api_key_service/client/client.go b/security/hierarchy/gen/http/api_key_service/client/client.go index f218f7330..60e1ff940 100644 --- a/security/hierarchy/gen/http/api_key_service/client/client.go +++ b/security/hierarchy/gen/http/api_key_service/client/client.go @@ -24,6 +24,10 @@ type Client struct { // Secure Doer is the HTTP client used to make requests to the secure endpoint. SecureDoer goahttp.Doer + // BearerSecure Doer is the HTTP client used to make requests to the + // bearer_secure endpoint. + BearerSecureDoer goahttp.Doer + // RestoreResponseBody controls whether the response bodies are reset after // decoding so they can be read again. RestoreResponseBody bool @@ -47,6 +51,7 @@ func NewClient( return &Client{ DefaultDoer: doer, SecureDoer: doer, + BearerSecureDoer: doer, RestoreResponseBody: restoreBody, scheme: scheme, host: host, @@ -102,3 +107,27 @@ func (c *Client) Secure() goa.Endpoint { return decodeResponse(resp) } } + +// BearerSecure returns an endpoint that makes HTTP requests to the +// api_key_service service bearer_secure server. +func (c *Client) BearerSecure() goa.Endpoint { + var ( + encodeRequest = EncodeBearerSecureRequest(c.encoder) + decodeResponse = DecodeBearerSecureResponse(c.decoder, c.RestoreResponseBody) + ) + return func(ctx context.Context, v any) (any, error) { + req, err := c.BuildBearerSecureRequest(ctx, v) + if err != nil { + return nil, err + } + err = encodeRequest(req, v) + if err != nil { + return nil, err + } + resp, err := c.BearerSecureDoer.Do(req) + if err != nil { + return nil, goahttp.ErrRequestError("api_key_service", "bearer_secure", err) + } + return decodeResponse(resp) + } +} diff --git a/security/hierarchy/gen/http/api_key_service/client/encode_decode.go b/security/hierarchy/gen/http/api_key_service/client/encode_decode.go index 711c922df..f9744b906 100644 --- a/security/hierarchy/gen/http/api_key_service/client/encode_decode.go +++ b/security/hierarchy/gen/http/api_key_service/client/encode_decode.go @@ -138,3 +138,65 @@ func DecodeSecureResponse(decoder func(*http.Response) goahttp.Decoder, restoreB } } } + +// BuildBearerSecureRequest instantiates a HTTP request object with method and +// path set to call the "api_key_service" service "bearer_secure" endpoint +func (c *Client) BuildBearerSecureRequest(ctx context.Context, v any) (*http.Request, error) { + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: BearerSecureAPIKeyServicePath()} + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, goahttp.ErrInvalidURL("api_key_service", "bearer_secure", u.String(), err) + } + if ctx != nil { + req = req.WithContext(ctx) + } + + return req, nil +} + +// EncodeBearerSecureRequest returns an encoder for requests sent to the +// api_key_service bearer_secure server. +func EncodeBearerSecureRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { + return func(req *http.Request, v any) error { + p, ok := v.(*apikeyservice.BearerSecurePayload) + if !ok { + return goahttp.ErrInvalidType("api_key_service", "bearer_secure", "*apikeyservice.BearerSecurePayload", v) + } + { + head := p.BearerToken + if !strings.Contains(head, " ") { + req.Header.Set("Authorization", "Bearer "+head) + } else { + req.Header.Set("Authorization", head) + } + } + return nil + } +} + +// DecodeBearerSecureResponse returns a decoder for responses returned by the +// api_key_service bearer_secure endpoint. restoreBody controls whether the +// response body should be restored after having been read. +func DecodeBearerSecureResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { + return func(resp *http.Response) (any, error) { + if restoreBody { + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + defer func() { + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + }() + } else { + defer resp.Body.Close() + } + switch resp.StatusCode { + case http.StatusNoContent: + return nil, nil + default: + body, _ := io.ReadAll(resp.Body) + return nil, goahttp.ErrInvalidResponse("api_key_service", "bearer_secure", resp.StatusCode, string(body)) + } + } +} diff --git a/security/hierarchy/gen/http/api_key_service/client/paths.go b/security/hierarchy/gen/http/api_key_service/client/paths.go index fec87f748..1f1107309 100644 --- a/security/hierarchy/gen/http/api_key_service/client/paths.go +++ b/security/hierarchy/gen/http/api_key_service/client/paths.go @@ -16,3 +16,8 @@ func DefaultAPIKeyServicePath() string { func SecureAPIKeyServicePath() string { return "/svc/secure" } + +// BearerSecureAPIKeyServicePath returns the URL path to the api_key_service service bearer_secure HTTP endpoint. +func BearerSecureAPIKeyServicePath() string { + return "/svc/bearer" +} diff --git a/security/hierarchy/gen/http/api_key_service/server/encode_decode.go b/security/hierarchy/gen/http/api_key_service/server/encode_decode.go index 8d68df91b..aa3f73412 100644 --- a/security/hierarchy/gen/http/api_key_service/server/encode_decode.go +++ b/security/hierarchy/gen/http/api_key_service/server/encode_decode.go @@ -88,3 +88,39 @@ func DecodeSecureRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp. return payload, nil } } + +// EncodeBearerSecureResponse returns an encoder for responses returned by the +// api_key_service bearer_secure endpoint. +func EncodeBearerSecureResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { + return func(ctx context.Context, w http.ResponseWriter, v any) error { + w.WriteHeader(http.StatusNoContent) + return nil + } +} + +// DecodeBearerSecureRequest returns a decoder for requests sent to the +// api_key_service bearer_secure endpoint. +func DecodeBearerSecureRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (*apikeyservice.BearerSecurePayload, error) { + return func(r *http.Request) (*apikeyservice.BearerSecurePayload, error) { + var payload *apikeyservice.BearerSecurePayload + var ( + bearerToken string + err error + ) + bearerToken = r.Header.Get("Authorization") + if bearerToken == "" { + err = goa.MergeErrors(err, goa.MissingFieldError("bearer_token", "header")) + } + if err != nil { + return payload, err + } + payload = NewBearerSecurePayload(bearerToken) + if strings.Contains(payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(payload.BearerToken, " ", 2)[1] + payload.BearerToken = cred + } + + return payload, nil + } +} diff --git a/security/hierarchy/gen/http/api_key_service/server/paths.go b/security/hierarchy/gen/http/api_key_service/server/paths.go index 872ce81ca..3c6984e2d 100644 --- a/security/hierarchy/gen/http/api_key_service/server/paths.go +++ b/security/hierarchy/gen/http/api_key_service/server/paths.go @@ -16,3 +16,8 @@ func DefaultAPIKeyServicePath() string { func SecureAPIKeyServicePath() string { return "/svc/secure" } + +// BearerSecureAPIKeyServicePath returns the URL path to the api_key_service service bearer_secure HTTP endpoint. +func BearerSecureAPIKeyServicePath() string { + return "/svc/bearer" +} diff --git a/security/hierarchy/gen/http/api_key_service/server/server.go b/security/hierarchy/gen/http/api_key_service/server/server.go index ce5aa7574..ff547a043 100644 --- a/security/hierarchy/gen/http/api_key_service/server/server.go +++ b/security/hierarchy/gen/http/api_key_service/server/server.go @@ -18,9 +18,10 @@ import ( // Server lists the api_key_service service endpoint HTTP handlers. type Server struct { - Mounts []*MountPoint - Default http.Handler - Secure http.Handler + Mounts []*MountPoint + Default http.Handler + Secure http.Handler + BearerSecure http.Handler } // MountPoint holds information about the mounted endpoints. @@ -52,9 +53,11 @@ func New( Mounts: []*MountPoint{ {"Default", "GET", "/svc/default"}, {"Secure", "GET", "/svc/secure"}, + {"BearerSecure", "GET", "/svc/bearer"}, }, - Default: NewDefaultHandler(e.Default, mux, decoder, encoder, errhandler, formatter), - Secure: NewSecureHandler(e.Secure, mux, decoder, encoder, errhandler, formatter), + Default: NewDefaultHandler(e.Default, mux, decoder, encoder, errhandler, formatter), + Secure: NewSecureHandler(e.Secure, mux, decoder, encoder, errhandler, formatter), + BearerSecure: NewBearerSecureHandler(e.BearerSecure, mux, decoder, encoder, errhandler, formatter), } } @@ -65,6 +68,7 @@ func (s *Server) Service() string { return "api_key_service" } func (s *Server) Use(m func(http.Handler) http.Handler) { s.Default = m(s.Default) s.Secure = m(s.Secure) + s.BearerSecure = m(s.BearerSecure) } // MethodNames returns the methods served. @@ -74,6 +78,7 @@ func (s *Server) MethodNames() []string { return apikeyservice.MethodNames[:] } func Mount(mux goahttp.Muxer, h *Server) { MountDefaultHandler(mux, h.Default) MountSecureHandler(mux, h.Secure) + MountBearerSecureHandler(mux, h.BearerSecure) } // Mount configures the mux to serve the api_key_service endpoints. @@ -186,3 +191,56 @@ func NewSecureHandler( } }) } + +// MountBearerSecureHandler configures the mux to serve the "api_key_service" +// service "bearer_secure" endpoint. +func MountBearerSecureHandler(mux goahttp.Muxer, h http.Handler) { + f, ok := h.(http.HandlerFunc) + if !ok { + f = func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + } + } + mux.Handle("GET", "/svc/bearer", f) +} + +// NewBearerSecureHandler creates a HTTP handler which loads the HTTP request +// and calls the "api_key_service" service "bearer_secure" endpoint. +func NewBearerSecureHandler( + endpoint goa.Endpoint, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(ctx context.Context, err error) goahttp.Statuser, +) http.Handler { + var ( + decodeRequest = DecodeBearerSecureRequest(mux, decoder) + encodeResponse = EncodeBearerSecureResponse(encoder) + encodeError = goahttp.ErrorEncoder(encoder, formatter) + ) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) + ctx = context.WithValue(ctx, goa.MethodKey, "bearer_secure") + ctx = context.WithValue(ctx, goa.ServiceKey, "api_key_service") + payload, err := decodeRequest(r) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + res, err := endpoint(ctx, payload) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + if err := encodeResponse(ctx, w, res); err != nil { + if errhandler != nil { + errhandler(ctx, w, err) + } + } + }) +} diff --git a/security/hierarchy/gen/http/api_key_service/server/types.go b/security/hierarchy/gen/http/api_key_service/server/types.go index 65f640395..1089ea7c5 100644 --- a/security/hierarchy/gen/http/api_key_service/server/types.go +++ b/security/hierarchy/gen/http/api_key_service/server/types.go @@ -26,3 +26,12 @@ func NewSecurePayload(token string) *apikeyservice.SecurePayload { return v } + +// NewBearerSecurePayload builds a api_key_service service bearer_secure +// endpoint payload. +func NewBearerSecurePayload(bearerToken string) *apikeyservice.BearerSecurePayload { + v := &apikeyservice.BearerSecurePayload{} + v.BearerToken = bearerToken + + return v +} diff --git a/security/hierarchy/gen/http/cli/hierarchy/cli.go b/security/hierarchy/gen/http/cli/hierarchy/cli.go index 0882c71f8..81013be6d 100644 --- a/security/hierarchy/gen/http/cli/hierarchy/cli.go +++ b/security/hierarchy/gen/http/cli/hierarchy/cli.go @@ -25,14 +25,14 @@ import ( func UsageCommands() []string { return []string{ "default-service default", - "api-key-service (default|secure)", + "api-key-service (default|secure|bearer-secure)", } } // UsageExamples produces an example of a valid invocation of the CLI tool. func UsageExamples() string { - return os.Args[0] + " " + "default-service default --username \"Sit doloremque inventore eius.\" --password \"Culpa alias.\"" + "\n" + - os.Args[0] + " " + "api-key-service default --key \"Aut delectus ipsam.\"" + "\n" + + return os.Args[0] + " " + "default-service default --username \"Voluptatem et.\" --password \"Libero unde.\"" + "\n" + + os.Args[0] + " " + "api-key-service default --key \"Quasi ex.\"" + "\n" + "" } @@ -59,6 +59,9 @@ func ParseEndpoint( apiKeyServiceSecureFlags = flag.NewFlagSet("secure", flag.ExitOnError) apiKeyServiceSecureTokenFlag = apiKeyServiceSecureFlags.String("token", "REQUIRED", "") + + apiKeyServiceBearerSecureFlags = flag.NewFlagSet("bearer-secure", flag.ExitOnError) + apiKeyServiceBearerSecureBearerTokenFlag = apiKeyServiceBearerSecureFlags.String("bearer-token", "REQUIRED", "") ) defaultServiceFlags.Usage = defaultServiceUsage defaultServiceDefaultFlags.Usage = defaultServiceDefaultUsage @@ -66,6 +69,7 @@ func ParseEndpoint( apiKeyServiceFlags.Usage = apiKeyServiceUsage apiKeyServiceDefaultFlags.Usage = apiKeyServiceDefaultUsage apiKeyServiceSecureFlags.Usage = apiKeyServiceSecureUsage + apiKeyServiceBearerSecureFlags.Usage = apiKeyServiceBearerSecureUsage if err := flag.CommandLine.Parse(os.Args[1:]); err != nil { return nil, nil, err @@ -116,6 +120,9 @@ func ParseEndpoint( case "secure": epf = apiKeyServiceSecureFlags + case "bearer-secure": + epf = apiKeyServiceBearerSecureFlags + } } @@ -154,6 +161,9 @@ func ParseEndpoint( case "secure": endpoint = c.Secure() data, err = apikeyservicec.BuildSecurePayload(*apiKeyServiceSecureTokenFlag) + case "bearer-secure": + endpoint = c.BearerSecure() + data, err = apikeyservicec.BuildBearerSecurePayload(*apiKeyServiceBearerSecureBearerTokenFlag) } } } @@ -192,7 +202,7 @@ func defaultServiceDefaultUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "default-service default --username \"Sit doloremque inventore eius.\" --password \"Culpa alias.\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "default-service default --username \"Voluptatem et.\" --password \"Libero unde.\"") } // apiKeyServiceUsage displays the usage of the api-key-service command and its @@ -203,6 +213,7 @@ func apiKeyServiceUsage() { fmt.Fprintln(os.Stderr, "COMMAND:") fmt.Fprintln(os.Stderr, ` default: Default implements default.`) fmt.Fprintln(os.Stderr, ` secure: This method requires a valid JWT token.`) + fmt.Fprintln(os.Stderr, ` bearer-secure: This method requires a bearer token.`) fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Additional help:") fmt.Fprintf(os.Stderr, " %s api-key-service COMMAND --help\n", os.Args[0]) @@ -222,7 +233,7 @@ func apiKeyServiceDefaultUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "api-key-service default --key \"Aut delectus ipsam.\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "api-key-service default --key \"Quasi ex.\"") } func apiKeyServiceSecureUsage() { @@ -240,5 +251,23 @@ func apiKeyServiceSecureUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "api-key-service secure --token \"Quasi ex.\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "api-key-service secure --token \"Sit doloremque inventore eius.\"") +} + +func apiKeyServiceBearerSecureUsage() { + // Header with flags + fmt.Fprintf(os.Stderr, "%s [flags] api-key-service bearer-secure", os.Args[0]) + fmt.Fprint(os.Stderr, " -bearer-token STRING") + fmt.Fprintln(os.Stderr) + + // Description + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, `This method requires a bearer token.`) + + // Flags list + fmt.Fprintln(os.Stderr, ` -bearer-token STRING: `) + + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "Example:") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "api-key-service bearer-secure --bearer-token \"Culpa alias.\"") } diff --git a/security/hierarchy/gen/http/openapi.json b/security/hierarchy/gen/http/openapi.json index 1a54ffbb0..36fbe7bf0 100644 --- a/security/hierarchy/gen/http/openapi.json +++ b/security/hierarchy/gen/http/openapi.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"Security Example API","description":"This API demonstrates the effect of using Security at the API, Service or Method levels","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/default":{"get":{"tags":["default_service"],"summary":"default default_service","description":"The default service default_method is secured with basic authentication","operationId":"default_service#default","responses":{"204":{"description":"No Content response."}},"schemes":["http"],"security":[{"basic_header_Authorization":null}]}},"/svc/default":{"get":{"tags":["api_key_service"],"summary":"default api_key_service","operationId":"api_key_service#default","responses":{"204":{"description":"No Content response."}},"schemes":["http"],"security":[{"api_key_header_Authorization":null}]}},"/svc/secure":{"get":{"tags":["api_key_service"],"summary":"secure api_key_service","description":"This method requires a valid JWT token.","operationId":"api_key_service#secure","responses":{"204":{"description":"No Content response."}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}}},"securityDefinitions":{"api_key_header_Authorization":{"type":"apiKey","description":"Secures endpoint by requiring an API key.","name":"Authorization","in":"header"},"basic_header_Authorization":{"type":"basic","description":"Basic authentication"},"jwt_header_Authorization":{"type":"apiKey","description":"Secures endpoint by requiring a valid JWT token.","name":"Authorization","in":"header"}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"Security Example API","description":"This API demonstrates the effect of using Security at the API, Service or Method levels","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/default":{"get":{"tags":["default_service"],"summary":"default default_service","description":"The default service default_method is secured with basic authentication","operationId":"default_service#default","responses":{"204":{"description":"No Content response."}},"schemes":["http"],"security":[{"basic_header_Authorization":null}]}},"/svc/bearer":{"get":{"tags":["api_key_service"],"summary":"bearer_secure api_key_service","description":"This method requires a bearer token.","operationId":"api_key_service#bearer_secure","responses":{"204":{"description":"No Content response."}},"schemes":["http"],"security":[{"bearer_header_Authorization":null}]}},"/svc/default":{"get":{"tags":["api_key_service"],"summary":"default api_key_service","operationId":"api_key_service#default","responses":{"204":{"description":"No Content response."}},"schemes":["http"],"security":[{"api_key_header_Authorization":null}]}},"/svc/secure":{"get":{"tags":["api_key_service"],"summary":"secure api_key_service","description":"This method requires a valid JWT token.","operationId":"api_key_service#secure","responses":{"204":{"description":"No Content response."}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]}}},"securityDefinitions":{"api_key_header_Authorization":{"type":"apiKey","description":"Secures endpoint by requiring an API key.","name":"Authorization","in":"header"},"basic_header_Authorization":{"type":"basic","description":"Basic authentication"},"bearer_header_Authorization":{"type":"apiKey","description":"Secures endpoint by requiring a bearer token.","name":"Authorization","in":"header"},"jwt_header_Authorization":{"type":"apiKey","description":"Secures endpoint by requiring a valid JWT token.","name":"Authorization","in":"header"}}} \ No newline at end of file diff --git a/security/hierarchy/gen/http/openapi.yaml b/security/hierarchy/gen/http/openapi.yaml index 3b479890f..d3821e139 100644 --- a/security/hierarchy/gen/http/openapi.yaml +++ b/security/hierarchy/gen/http/openapi.yaml @@ -27,6 +27,20 @@ paths: - http security: - basic_header_Authorization: [] + /svc/bearer: + get: + tags: + - api_key_service + summary: bearer_secure api_key_service + description: This method requires a bearer token. + operationId: api_key_service#bearer_secure + responses: + "204": + description: No Content response. + schemes: + - http + security: + - bearer_header_Authorization: [] /svc/default: get: tags: @@ -63,6 +77,11 @@ securityDefinitions: basic_header_Authorization: type: basic description: Basic authentication + bearer_header_Authorization: + type: apiKey + description: Secures endpoint by requiring a bearer token. + name: Authorization + in: header jwt_header_Authorization: type: apiKey description: Secures endpoint by requiring a valid JWT token. diff --git a/security/hierarchy/gen/http/openapi3.json b/security/hierarchy/gen/http/openapi3.json index bbf8480b0..cf64f5cc3 100644 --- a/security/hierarchy/gen/http/openapi3.json +++ b/security/hierarchy/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"Security Example API","description":"This API demonstrates the effect of using Security at the API, Service or Method levels","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for hierarchy"}],"paths":{"/default":{"get":{"tags":["default_service"],"summary":"default default_service","description":"The default service default_method is secured with basic authentication","operationId":"default_service#default","responses":{"204":{"description":"No Content response."}},"security":[{"basic_header_Authorization":[]}]}},"/svc/default":{"get":{"tags":["api_key_service"],"summary":"default api_key_service","operationId":"api_key_service#default","responses":{"204":{"description":"No Content response."}},"security":[{"api_key_header_Authorization":[]}]}},"/svc/secure":{"get":{"tags":["api_key_service"],"summary":"secure api_key_service","description":"This method requires a valid JWT token.","operationId":"api_key_service#secure","responses":{"204":{"description":"No Content response."}},"security":[{"jwt_header_Authorization":[]}]}}},"components":{"securitySchemes":{"api_key_header_Authorization":{"type":"apiKey","description":"Secures endpoint by requiring an API key.","name":"Authorization","in":"header"},"basic_header_Authorization":{"type":"http","description":"Basic authentication","scheme":"basic"},"jwt_header_Authorization":{"type":"http","description":"Secures endpoint by requiring a valid JWT token.","scheme":"bearer"}}},"tags":[{"name":"api_key_service","description":"The svc service is secured with API key based authentication"},{"name":"default_service"}],"security":[{"basic__":[]}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"Security Example API","description":"This API demonstrates the effect of using Security at the API, Service or Method levels","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for hierarchy"}],"paths":{"/default":{"get":{"tags":["default_service"],"summary":"default default_service","description":"The default service default_method is secured with basic authentication","operationId":"default_service#default","responses":{"204":{"description":"No Content response."}},"security":[{"basic_header_Authorization":[]}]}},"/svc/bearer":{"get":{"tags":["api_key_service"],"summary":"bearer_secure api_key_service","description":"This method requires a bearer token.","operationId":"api_key_service#bearer_secure","responses":{"204":{"description":"No Content response."}},"security":[{"bearer_header_Authorization":[]}]}},"/svc/default":{"get":{"tags":["api_key_service"],"summary":"default api_key_service","operationId":"api_key_service#default","responses":{"204":{"description":"No Content response."}},"security":[{"api_key_header_Authorization":[]}]}},"/svc/secure":{"get":{"tags":["api_key_service"],"summary":"secure api_key_service","description":"This method requires a valid JWT token.","operationId":"api_key_service#secure","responses":{"204":{"description":"No Content response."}},"security":[{"jwt_header_Authorization":[]}]}}},"components":{"securitySchemes":{"api_key_header_Authorization":{"type":"apiKey","description":"Secures endpoint by requiring an API key.","name":"Authorization","in":"header"},"basic_header_Authorization":{"type":"http","description":"Basic authentication","scheme":"basic"},"bearer_header_Authorization":{"type":"http","description":"Secures endpoint by requiring a bearer token.","scheme":"bearer"},"jwt_header_Authorization":{"type":"http","description":"Secures endpoint by requiring a valid JWT token.","scheme":"bearer","bearerFormat":"JWT"}}},"tags":[{"name":"api_key_service","description":"The svc service is secured with API key based authentication"},{"name":"default_service"}],"security":[{"basic__":[]}]} \ No newline at end of file diff --git a/security/hierarchy/gen/http/openapi3.yaml b/security/hierarchy/gen/http/openapi3.yaml index 351e0531a..c59a34167 100644 --- a/security/hierarchy/gen/http/openapi3.yaml +++ b/security/hierarchy/gen/http/openapi3.yaml @@ -19,6 +19,18 @@ paths: description: No Content response. security: - basic_header_Authorization: [] + /svc/bearer: + get: + tags: + - api_key_service + summary: bearer_secure api_key_service + description: This method requires a bearer token. + operationId: api_key_service#bearer_secure + responses: + "204": + description: No Content response. + security: + - bearer_header_Authorization: [] /svc/default: get: tags: @@ -53,10 +65,15 @@ components: type: http description: Basic authentication scheme: basic + bearer_header_Authorization: + type: http + description: Secures endpoint by requiring a bearer token. + scheme: bearer jwt_header_Authorization: type: http description: Secures endpoint by requiring a valid JWT token. scheme: bearer + bearerFormat: JWT tags: - name: api_key_service description: The svc service is secured with API key based authentication diff --git a/security/multiauth/README.md b/security/multiauth/README.md index cc0aecc82..23172920f 100644 --- a/security/multiauth/README.md +++ b/security/multiauth/README.md @@ -1,16 +1,17 @@ # MultiAuth Example The MultiAuth example defines a service that exposes one endpoint for each -security scheme natively supported by Goa (i.e. basic auth, API key, JWT auth -and OAuth2). The example also includes a couple of endpoints that show how to -apply multiple security schemes to a single endpoint. +security scheme natively supported by Goa (i.e. basic auth, API key, bearer +auth, JWT auth and OAuth2). The example also includes a couple of endpoints that +show how to apply multiple security schemes to a single endpoint. ## Design The key design sections for the `multi_auth` service define the various security -requirements. The `JWTAuth`, `BasicAuth`, `APIKeyAuth` and `OAuth2Auth` variables -define the security schemes used by the service endpoints. The `doubly_secure` -endpoint requires both a JWT and an API key to authorize requests: +requirements. The `JWTAuth`, `BearerAuth`, `BasicAuth`, `APIKeyAuth` and +`OAuth2Auth` variables define the security schemes used by the service +endpoints. The `doubly_secure` endpoint requires both a JWT and an API key to +authorize requests: ```go Security(JWTAuth, APIKeyAuth, func() { // Use JWT and an API key to secure this endpoint. @@ -20,7 +21,8 @@ Security(JWTAuth, APIKeyAuth, func() { // Use JWT and an API key to secure this ``` The `Payload` DSL defines two attributes `key` and `token` that hold the API key -and JWT token respectively: +and JWT token respectively. Bearer-secured endpoints use `BearerToken` or +`BearerTokenField` for their token attributes: ```go Payload(func() { @@ -30,6 +32,9 @@ Payload(func() { Token("token", String, func() { Description("JWT used for authentication") }) + BearerToken("bearer_token", String, func() { + Description("Bearer token used for authentication") + }) }) ``` @@ -85,5 +90,7 @@ Payload(func() { }) // AccessTokenField identifies the OAuth2 access token. AccessTokenField(5, "oauth_token", String) + // BearerTokenField identifies the bearer token. + BearerTokenField(6, "bearer_token", String) }) ``` diff --git a/security/multiauth/context.go b/security/multiauth/context.go index 0fd238db6..3359380e1 100644 --- a/security/multiauth/context.go +++ b/security/multiauth/context.go @@ -10,6 +10,7 @@ type authInfo struct { user string claims jwt.MapClaims key string + bearer string } func (auth authInfo) String() string { @@ -19,6 +20,8 @@ func (auth authInfo) String() string { return "AuthInfo: JWT/OAuth" } else if auth.key != "" { return "AuthInfo: API" + } else if auth.bearer != "" { + return "AuthInfo: Bearer" } else { return "AuthInfo: none" } diff --git a/security/multiauth/design/design.go b/security/multiauth/design/design.go index c321ca8d7..d49284071 100644 --- a/security/multiauth/design/design.go +++ b/security/multiauth/design/design.go @@ -20,6 +20,13 @@ var JWTAuth = JWTSecurity("jwt", func() { Scope("api:write", "Read and write access") }) +// BearerAuth defines a security scheme that uses bearer tokens. +var BearerAuth = BearerSecurity("bearer", func() { + Description(`Secures endpoint by requiring a valid bearer token retrieved via the signin endpoint. Supports scopes "api:read" and "api:write".`) + Scope("api:read", "Read-only access") + Scope("api:write", "Read and write access") +}) + // APIKeyAuth defines a security scheme that uses API keys. var APIKeyAuth = APIKeySecurity("api_key", func() { Description("Secures endpoint by requiring an API key.") @@ -117,6 +124,36 @@ var _ = Service("secured_service", func() { }) }) + Method("bearer_secure", func() { + Description("This action is secured with the bearer scheme") + + Security(BearerAuth, func() { // Use bearer token to auth requests to this endpoint. + Scope("api:read") // Enforce presence of "api:read" scope in token claims. + }) + + Payload(func() { + BearerTokenField(1, "bearer_token", String, func() { + Description("Bearer token used for authentication") + }) + Required("bearer_token") + }) + + Result(String) + + Error("invalid-scopes", String, "Token scopes are invalid") + + HTTP(func() { + GET("/bearer") + Response(StatusOK) + Response("invalid-scopes", StatusForbidden) + }) + + GRPC(func() { + Response(CodeOK) + Response("invalid-scopes", CodeUnauthenticated) + }) + }) + Method("doubly_secure", func() { Description("This action is secured with the jwt scheme and also requires an API key query string.") @@ -227,5 +264,8 @@ var Creds = Type("Creds", func() { Field(3, "oauth_token", String, "OAuth2 token", func() { Example("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ") }) - Required("jwt", "api_key", "oauth_token") + Field(4, "bearer_token", String, "Bearer token", func() { + Example("opaque-session-token") + }) + Required("jwt", "api_key", "oauth_token", "bearer_token") }) diff --git a/security/multiauth/gen/grpc/cli/multi_auth/cli.go b/security/multiauth/gen/grpc/cli/multi_auth/cli.go index 729cdcff2..02dc03c28 100644 --- a/security/multiauth/gen/grpc/cli/multi_auth/cli.go +++ b/security/multiauth/gen/grpc/cli/multi_auth/cli.go @@ -22,7 +22,7 @@ import ( // command (subcommand1|subcommand2|...) func UsageCommands() []string { return []string{ - "secured-service (signin|secure|doubly-secure|also-doubly-secure)", + "secured-service (signin|secure|bearer-secure|doubly-secure|also-doubly-secure)", } } @@ -49,6 +49,9 @@ func ParseEndpoint( securedServiceSecureMessageFlag = securedServiceSecureFlags.String("message", "", "") securedServiceSecureTokenFlag = securedServiceSecureFlags.String("token", "REQUIRED", "") + securedServiceBearerSecureFlags = flag.NewFlagSet("bearer-secure", flag.ExitOnError) + securedServiceBearerSecureBearerTokenFlag = securedServiceBearerSecureFlags.String("bearer-token", "REQUIRED", "") + securedServiceDoublySecureFlags = flag.NewFlagSet("doubly-secure", flag.ExitOnError) securedServiceDoublySecureMessageFlag = securedServiceDoublySecureFlags.String("message", "", "") securedServiceDoublySecureTokenFlag = securedServiceDoublySecureFlags.String("token", "REQUIRED", "") @@ -61,6 +64,7 @@ func ParseEndpoint( securedServiceFlags.Usage = securedServiceUsage securedServiceSigninFlags.Usage = securedServiceSigninUsage securedServiceSecureFlags.Usage = securedServiceSecureUsage + securedServiceBearerSecureFlags.Usage = securedServiceBearerSecureUsage securedServiceDoublySecureFlags.Usage = securedServiceDoublySecureUsage securedServiceAlsoDoublySecureFlags.Usage = securedServiceAlsoDoublySecureUsage @@ -104,6 +108,9 @@ func ParseEndpoint( case "secure": epf = securedServiceSecureFlags + case "bearer-secure": + epf = securedServiceBearerSecureFlags + case "doubly-secure": epf = securedServiceDoublySecureFlags @@ -141,6 +148,9 @@ func ParseEndpoint( case "secure": endpoint = c.Secure() data, err = securedservicec.BuildSecurePayload(*securedServiceSecureMessageFlag, *securedServiceSecureTokenFlag) + case "bearer-secure": + endpoint = c.BearerSecure() + data, err = securedservicec.BuildBearerSecurePayload(*securedServiceBearerSecureBearerTokenFlag) case "doubly-secure": endpoint = c.DoublySecure() data, err = securedservicec.BuildDoublySecurePayload(*securedServiceDoublySecureMessageFlag, *securedServiceDoublySecureTokenFlag) @@ -165,6 +175,7 @@ func securedServiceUsage() { fmt.Fprintln(os.Stderr, "COMMAND:") fmt.Fprintln(os.Stderr, ` signin: Creates a valid JWT`) fmt.Fprintln(os.Stderr, ` secure: This action is secured with the jwt scheme`) + fmt.Fprintln(os.Stderr, ` bearer-secure: This action is secured with the bearer scheme`) fmt.Fprintln(os.Stderr, ` doubly-secure: This action is secured with the jwt scheme and also requires an API key query string.`) fmt.Fprintln(os.Stderr, ` also-doubly-secure: This action is secured with the jwt scheme and also requires an API key header.`) fmt.Fprintln(os.Stderr) @@ -208,7 +219,25 @@ func securedServiceSecureUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "secured-service secure --message '{\n \"fail\": true\n }' --token \"Veniam quis nulla officiis id rerum.\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "secured-service secure --message '{\n \"fail\": false\n }' --token \"Maiores porro alias.\"") +} + +func securedServiceBearerSecureUsage() { + // Header with flags + fmt.Fprintf(os.Stderr, "%s [flags] secured-service bearer-secure", os.Args[0]) + fmt.Fprint(os.Stderr, " -bearer-token STRING") + fmt.Fprintln(os.Stderr) + + // Description + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, `This action is secured with the bearer scheme`) + + // Flags list + fmt.Fprintln(os.Stderr, ` -bearer-token STRING: `) + + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "Example:") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "secured-service bearer-secure --bearer-token \"Eaque nesciunt asperiores.\"") } func securedServiceDoublySecureUsage() { @@ -250,5 +279,5 @@ func securedServiceAlsoDoublySecureUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "secured-service also-doubly-secure --message '{\n \"key\": \"abcdef12345\",\n \"password\": \"password\",\n \"username\": \"user\"\n }' --oauth-token \"Magni perferendis unde itaque.\" --token \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "secured-service also-doubly-secure --message '{\n \"key\": \"abcdef12345\",\n \"password\": \"password\",\n \"username\": \"user\"\n }' --oauth-token \"Amet voluptas quia.\" --token \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ\"") } diff --git a/security/multiauth/gen/grpc/secured_service/client/cli.go b/security/multiauth/gen/grpc/secured_service/client/cli.go index 7ce070753..f471485c0 100644 --- a/security/multiauth/gen/grpc/secured_service/client/cli.go +++ b/security/multiauth/gen/grpc/secured_service/client/cli.go @@ -42,7 +42,7 @@ func BuildSecurePayload(securedServiceSecureMessage string, securedServiceSecure if securedServiceSecureMessage != "" { err = json.Unmarshal([]byte(securedServiceSecureMessage), &message) if err != nil { - return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"fail\": true\n }'") + return nil, fmt.Errorf("invalid JSON for message, \nerror: %s, \nexample of valid JSON:\n%s", err, "'{\n \"fail\": false\n }'") } } } @@ -58,6 +58,19 @@ func BuildSecurePayload(securedServiceSecureMessage string, securedServiceSecure return v, nil } +// BuildBearerSecurePayload builds the payload for the secured_service +// bearer_secure endpoint from CLI flags. +func BuildBearerSecurePayload(securedServiceBearerSecureBearerToken string) (*securedservice.BearerSecurePayload, error) { + var bearerToken string + { + bearerToken = securedServiceBearerSecureBearerToken + } + v := &securedservice.BearerSecurePayload{} + v.BearerToken = bearerToken + + return v, nil +} + // BuildDoublySecurePayload builds the payload for the secured_service // doubly_secure endpoint from CLI flags. func BuildDoublySecurePayload(securedServiceDoublySecureMessage string, securedServiceDoublySecureToken string) (*securedservice.DoublySecurePayload, error) { diff --git a/security/multiauth/gen/grpc/secured_service/client/client.go b/security/multiauth/gen/grpc/secured_service/client/client.go index 14e79f2f4..a9ad7f529 100644 --- a/security/multiauth/gen/grpc/secured_service/client/client.go +++ b/security/multiauth/gen/grpc/secured_service/client/client.go @@ -76,6 +76,28 @@ func (c *Client) Secure() goa.Endpoint { } } +// BearerSecure calls the "BearerSecure" function in +// secured_servicepb.SecuredServiceClient interface. +func (c *Client) BearerSecure() goa.Endpoint { + return func(ctx context.Context, v any) (any, error) { + inv := goagrpc.NewInvoker( + BuildBearerSecureFunc(c.grpccli, c.opts...), + EncodeBearerSecureRequest, + DecodeBearerSecureResponse) + res, err := inv.Invoke(ctx, v) + if err != nil { + resp := goagrpc.DecodeError(err) + switch message := resp.(type) { + case *goapb.ErrorResponse: + return nil, goagrpc.NewServiceError(message) + default: + return nil, goa.Fault("%s", err.Error()) + } + } + return res, nil + } +} + // DoublySecure calls the "DoublySecure" function in // secured_servicepb.SecuredServiceClient interface. func (c *Client) DoublySecure() goa.Endpoint { diff --git a/security/multiauth/gen/grpc/secured_service/client/encode_decode.go b/security/multiauth/gen/grpc/secured_service/client/encode_decode.go index e49f8aaa4..f7dc97a27 100644 --- a/security/multiauth/gen/grpc/secured_service/client/encode_decode.go +++ b/security/multiauth/gen/grpc/secured_service/client/encode_decode.go @@ -88,6 +88,42 @@ func DecodeSecureResponse(ctx context.Context, v any, hdr, trlr metadata.MD) (an return res, nil } +// BuildBearerSecureFunc builds the remote method to invoke for +// "secured_service" service "bearer_secure" endpoint. +func BuildBearerSecureFunc(grpccli secured_servicepb.SecuredServiceClient, cliopts ...grpc.CallOption) goagrpc.RemoteFunc { + return func(ctx context.Context, reqpb any, opts ...grpc.CallOption) (any, error) { + for _, opt := range cliopts { + opts = append(opts, opt) + } + if reqpb != nil { + return grpccli.BearerSecure(ctx, reqpb.(*secured_servicepb.BearerSecureRequest), opts...) + } + return grpccli.BearerSecure(ctx, &secured_servicepb.BearerSecureRequest{}, opts...) + } +} + +// EncodeBearerSecureRequest encodes requests sent to secured_service +// bearer_secure endpoint. +func EncodeBearerSecureRequest(ctx context.Context, v any, md *metadata.MD) (any, error) { + payload, ok := v.(*securedservice.BearerSecurePayload) + if !ok { + return nil, goagrpc.ErrInvalidType("secured_service", "bearer_secure", "*securedservice.BearerSecurePayload", v) + } + (*md).Append("authorization", payload.BearerToken) + return NewProtoBearerSecureRequest(), nil +} + +// DecodeBearerSecureResponse decodes responses from the secured_service +// bearer_secure endpoint. +func DecodeBearerSecureResponse(ctx context.Context, v any, hdr, trlr metadata.MD) (any, error) { + message, ok := v.(*secured_servicepb.BearerSecureResponse) + if !ok { + return nil, goagrpc.ErrInvalidType("secured_service", "bearer_secure", "*secured_servicepb.BearerSecureResponse", v) + } + res := NewBearerSecureResult(message) + return res, nil +} + // BuildDoublySecureFunc builds the remote method to invoke for // "secured_service" service "doubly_secure" endpoint. func BuildDoublySecureFunc(grpccli secured_servicepb.SecuredServiceClient, cliopts ...grpc.CallOption) goagrpc.RemoteFunc { diff --git a/security/multiauth/gen/grpc/secured_service/client/types.go b/security/multiauth/gen/grpc/secured_service/client/types.go index 038d5e51b..ebfa75ad7 100644 --- a/security/multiauth/gen/grpc/secured_service/client/types.go +++ b/security/multiauth/gen/grpc/secured_service/client/types.go @@ -23,9 +23,10 @@ func NewProtoSigninRequest() *secured_servicepb.SigninRequest { // "secured_service" service from the gRPC response type. func NewSigninResult(message *secured_servicepb.SigninResponse) *securedservice.Creds { result := &securedservice.Creds{ - JWT: message.Jwt, - APIKey: message.ApiKey, - OauthToken: message.OauthToken, + JWT: message.Jwt, + APIKey: message.ApiKey, + OauthToken: message.OauthToken, + BearerToken: message.BearerToken, } return result } @@ -46,6 +47,20 @@ func NewSecureResult(message *secured_servicepb.SecureResponse) string { return result } +// NewProtoBearerSecureRequest builds the gRPC request type from the payload of +// the "bearer_secure" endpoint of the "secured_service" service. +func NewProtoBearerSecureRequest() *secured_servicepb.BearerSecureRequest { + message := &secured_servicepb.BearerSecureRequest{} + return message +} + +// NewBearerSecureResult builds the result type of the "bearer_secure" endpoint +// of the "secured_service" service from the gRPC response type. +func NewBearerSecureResult(message *secured_servicepb.BearerSecureResponse) string { + result := message.Field + return result +} + // NewProtoDoublySecureRequest builds the gRPC request type from the payload of // the "doubly_secure" endpoint of the "secured_service" service. func NewProtoDoublySecureRequest(payload *securedservice.DoublySecurePayload) *secured_servicepb.DoublySecureRequest { diff --git a/security/multiauth/gen/grpc/secured_service/pb/goagen_multiauth_secured_service.pb.go b/security/multiauth/gen/grpc/secured_service/pb/goagen_multiauth_secured_service.pb.go index 8a4dbae92..c7a98ac87 100644 --- a/security/multiauth/gen/grpc/secured_service/pb/goagen_multiauth_secured_service.pb.go +++ b/security/multiauth/gen/grpc/secured_service/pb/goagen_multiauth_secured_service.pb.go @@ -8,7 +8,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v6.33.2 +// protoc v7.34.1 // source: goagen_multiauth_secured_service.proto package secured_servicepb @@ -71,7 +71,9 @@ type SigninResponse struct { // API Key ApiKey string `protobuf:"bytes,2,opt,name=api_key,json=apiKey,proto3" json:"api_key,omitempty"` // OAuth2 token - OauthToken string `protobuf:"bytes,3,opt,name=oauth_token,json=oauthToken,proto3" json:"oauth_token,omitempty"` + OauthToken string `protobuf:"bytes,3,opt,name=oauth_token,json=oauthToken,proto3" json:"oauth_token,omitempty"` + // Bearer token + BearerToken string `protobuf:"bytes,4,opt,name=bearer_token,json=bearerToken,proto3" json:"bearer_token,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -127,6 +129,13 @@ func (x *SigninResponse) GetOauthToken() string { return "" } +func (x *SigninResponse) GetBearerToken() string { + if x != nil { + return x.BearerToken + } + return "" +} + type SecureRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Whether to force auth failure even with a valid JWT @@ -216,6 +225,86 @@ func (x *SecureResponse) GetField() string { return "" } +type BearerSecureRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BearerSecureRequest) Reset() { + *x = BearerSecureRequest{} + mi := &file_goagen_multiauth_secured_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BearerSecureRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BearerSecureRequest) ProtoMessage() {} + +func (x *BearerSecureRequest) ProtoReflect() protoreflect.Message { + mi := &file_goagen_multiauth_secured_service_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BearerSecureRequest.ProtoReflect.Descriptor instead. +func (*BearerSecureRequest) Descriptor() ([]byte, []int) { + return file_goagen_multiauth_secured_service_proto_rawDescGZIP(), []int{4} +} + +type BearerSecureResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Field string `protobuf:"bytes,1,opt,name=field,proto3" json:"field,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BearerSecureResponse) Reset() { + *x = BearerSecureResponse{} + mi := &file_goagen_multiauth_secured_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BearerSecureResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BearerSecureResponse) ProtoMessage() {} + +func (x *BearerSecureResponse) ProtoReflect() protoreflect.Message { + mi := &file_goagen_multiauth_secured_service_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BearerSecureResponse.ProtoReflect.Descriptor instead. +func (*BearerSecureResponse) Descriptor() ([]byte, []int) { + return file_goagen_multiauth_secured_service_proto_rawDescGZIP(), []int{5} +} + +func (x *BearerSecureResponse) GetField() string { + if x != nil { + return x.Field + } + return "" +} + type DoublySecureRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // API key @@ -226,7 +315,7 @@ type DoublySecureRequest struct { func (x *DoublySecureRequest) Reset() { *x = DoublySecureRequest{} - mi := &file_goagen_multiauth_secured_service_proto_msgTypes[4] + mi := &file_goagen_multiauth_secured_service_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -238,7 +327,7 @@ func (x *DoublySecureRequest) String() string { func (*DoublySecureRequest) ProtoMessage() {} func (x *DoublySecureRequest) ProtoReflect() protoreflect.Message { - mi := &file_goagen_multiauth_secured_service_proto_msgTypes[4] + mi := &file_goagen_multiauth_secured_service_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -251,7 +340,7 @@ func (x *DoublySecureRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DoublySecureRequest.ProtoReflect.Descriptor instead. func (*DoublySecureRequest) Descriptor() ([]byte, []int) { - return file_goagen_multiauth_secured_service_proto_rawDescGZIP(), []int{4} + return file_goagen_multiauth_secured_service_proto_rawDescGZIP(), []int{6} } func (x *DoublySecureRequest) GetKey() string { @@ -270,7 +359,7 @@ type DoublySecureResponse struct { func (x *DoublySecureResponse) Reset() { *x = DoublySecureResponse{} - mi := &file_goagen_multiauth_secured_service_proto_msgTypes[5] + mi := &file_goagen_multiauth_secured_service_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -282,7 +371,7 @@ func (x *DoublySecureResponse) String() string { func (*DoublySecureResponse) ProtoMessage() {} func (x *DoublySecureResponse) ProtoReflect() protoreflect.Message { - mi := &file_goagen_multiauth_secured_service_proto_msgTypes[5] + mi := &file_goagen_multiauth_secured_service_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -295,7 +384,7 @@ func (x *DoublySecureResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DoublySecureResponse.ProtoReflect.Descriptor instead. func (*DoublySecureResponse) Descriptor() ([]byte, []int) { - return file_goagen_multiauth_secured_service_proto_rawDescGZIP(), []int{5} + return file_goagen_multiauth_secured_service_proto_rawDescGZIP(), []int{7} } func (x *DoublySecureResponse) GetField() string { @@ -319,7 +408,7 @@ type AlsoDoublySecureRequest struct { func (x *AlsoDoublySecureRequest) Reset() { *x = AlsoDoublySecureRequest{} - mi := &file_goagen_multiauth_secured_service_proto_msgTypes[6] + mi := &file_goagen_multiauth_secured_service_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -331,7 +420,7 @@ func (x *AlsoDoublySecureRequest) String() string { func (*AlsoDoublySecureRequest) ProtoMessage() {} func (x *AlsoDoublySecureRequest) ProtoReflect() protoreflect.Message { - mi := &file_goagen_multiauth_secured_service_proto_msgTypes[6] + mi := &file_goagen_multiauth_secured_service_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -344,7 +433,7 @@ func (x *AlsoDoublySecureRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AlsoDoublySecureRequest.ProtoReflect.Descriptor instead. func (*AlsoDoublySecureRequest) Descriptor() ([]byte, []int) { - return file_goagen_multiauth_secured_service_proto_rawDescGZIP(), []int{6} + return file_goagen_multiauth_secured_service_proto_rawDescGZIP(), []int{8} } func (x *AlsoDoublySecureRequest) GetUsername() string { @@ -377,7 +466,7 @@ type AlsoDoublySecureResponse struct { func (x *AlsoDoublySecureResponse) Reset() { *x = AlsoDoublySecureResponse{} - mi := &file_goagen_multiauth_secured_service_proto_msgTypes[7] + mi := &file_goagen_multiauth_secured_service_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -389,7 +478,7 @@ func (x *AlsoDoublySecureResponse) String() string { func (*AlsoDoublySecureResponse) ProtoMessage() {} func (x *AlsoDoublySecureResponse) ProtoReflect() protoreflect.Message { - mi := &file_goagen_multiauth_secured_service_proto_msgTypes[7] + mi := &file_goagen_multiauth_secured_service_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -402,7 +491,7 @@ func (x *AlsoDoublySecureResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AlsoDoublySecureResponse.ProtoReflect.Descriptor instead. func (*AlsoDoublySecureResponse) Descriptor() ([]byte, []int) { - return file_goagen_multiauth_secured_service_proto_rawDescGZIP(), []int{7} + return file_goagen_multiauth_secured_service_proto_rawDescGZIP(), []int{9} } func (x *AlsoDoublySecureResponse) GetField() string { @@ -417,16 +506,20 @@ var File_goagen_multiauth_secured_service_proto protoreflect.FileDescriptor const file_goagen_multiauth_secured_service_proto_rawDesc = "" + "\n" + "&goagen_multiauth_secured_service.proto\x12\x0fsecured_service\"\x0f\n" + - "\rSigninRequest\"\\\n" + + "\rSigninRequest\"\x7f\n" + "\x0eSigninResponse\x12\x10\n" + "\x03jwt\x18\x01 \x01(\tR\x03jwt\x12\x17\n" + "\aapi_key\x18\x02 \x01(\tR\x06apiKey\x12\x1f\n" + "\voauth_token\x18\x03 \x01(\tR\n" + - "oauthToken\"1\n" + + "oauthToken\x12!\n" + + "\fbearer_token\x18\x04 \x01(\tR\vbearerToken\"1\n" + "\rSecureRequest\x12\x17\n" + "\x04fail\x18\x01 \x01(\bH\x00R\x04fail\x88\x01\x01B\a\n" + "\x05_fail\"&\n" + "\x0eSecureResponse\x12\x14\n" + + "\x05field\x18\x01 \x01(\tR\x05field\"\x15\n" + + "\x13BearerSecureRequest\",\n" + + "\x14BearerSecureResponse\x12\x14\n" + "\x05field\x18\x01 \x01(\tR\x05field\"'\n" + "\x13DoublySecureRequest\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\",\n" + @@ -440,10 +533,11 @@ const file_goagen_multiauth_secured_service_proto_rawDesc = "" + "\t_passwordB\x06\n" + "\x04_key\"0\n" + "\x18AlsoDoublySecureResponse\x12\x14\n" + - "\x05field\x18\x01 \x01(\tR\x05field2\xec\x02\n" + + "\x05field\x18\x01 \x01(\tR\x05field2\xc9\x03\n" + "\x0eSecuredService\x12I\n" + "\x06Signin\x12\x1e.secured_service.SigninRequest\x1a\x1f.secured_service.SigninResponse\x12I\n" + "\x06Secure\x12\x1e.secured_service.SecureRequest\x1a\x1f.secured_service.SecureResponse\x12[\n" + + "\fBearerSecure\x12$.secured_service.BearerSecureRequest\x1a%.secured_service.BearerSecureResponse\x12[\n" + "\fDoublySecure\x12$.secured_service.DoublySecureRequest\x1a%.secured_service.DoublySecureResponse\x12g\n" + "\x10AlsoDoublySecure\x12(.secured_service.AlsoDoublySecureRequest\x1a).secured_service.AlsoDoublySecureResponseB\x14Z\x12/secured_servicepbb\x06proto3" @@ -459,28 +553,32 @@ func file_goagen_multiauth_secured_service_proto_rawDescGZIP() []byte { return file_goagen_multiauth_secured_service_proto_rawDescData } -var file_goagen_multiauth_secured_service_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_goagen_multiauth_secured_service_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_goagen_multiauth_secured_service_proto_goTypes = []any{ (*SigninRequest)(nil), // 0: secured_service.SigninRequest (*SigninResponse)(nil), // 1: secured_service.SigninResponse (*SecureRequest)(nil), // 2: secured_service.SecureRequest (*SecureResponse)(nil), // 3: secured_service.SecureResponse - (*DoublySecureRequest)(nil), // 4: secured_service.DoublySecureRequest - (*DoublySecureResponse)(nil), // 5: secured_service.DoublySecureResponse - (*AlsoDoublySecureRequest)(nil), // 6: secured_service.AlsoDoublySecureRequest - (*AlsoDoublySecureResponse)(nil), // 7: secured_service.AlsoDoublySecureResponse + (*BearerSecureRequest)(nil), // 4: secured_service.BearerSecureRequest + (*BearerSecureResponse)(nil), // 5: secured_service.BearerSecureResponse + (*DoublySecureRequest)(nil), // 6: secured_service.DoublySecureRequest + (*DoublySecureResponse)(nil), // 7: secured_service.DoublySecureResponse + (*AlsoDoublySecureRequest)(nil), // 8: secured_service.AlsoDoublySecureRequest + (*AlsoDoublySecureResponse)(nil), // 9: secured_service.AlsoDoublySecureResponse } var file_goagen_multiauth_secured_service_proto_depIdxs = []int32{ 0, // 0: secured_service.SecuredService.Signin:input_type -> secured_service.SigninRequest 2, // 1: secured_service.SecuredService.Secure:input_type -> secured_service.SecureRequest - 4, // 2: secured_service.SecuredService.DoublySecure:input_type -> secured_service.DoublySecureRequest - 6, // 3: secured_service.SecuredService.AlsoDoublySecure:input_type -> secured_service.AlsoDoublySecureRequest - 1, // 4: secured_service.SecuredService.Signin:output_type -> secured_service.SigninResponse - 3, // 5: secured_service.SecuredService.Secure:output_type -> secured_service.SecureResponse - 5, // 6: secured_service.SecuredService.DoublySecure:output_type -> secured_service.DoublySecureResponse - 7, // 7: secured_service.SecuredService.AlsoDoublySecure:output_type -> secured_service.AlsoDoublySecureResponse - 4, // [4:8] is the sub-list for method output_type - 0, // [0:4] is the sub-list for method input_type + 4, // 2: secured_service.SecuredService.BearerSecure:input_type -> secured_service.BearerSecureRequest + 6, // 3: secured_service.SecuredService.DoublySecure:input_type -> secured_service.DoublySecureRequest + 8, // 4: secured_service.SecuredService.AlsoDoublySecure:input_type -> secured_service.AlsoDoublySecureRequest + 1, // 5: secured_service.SecuredService.Signin:output_type -> secured_service.SigninResponse + 3, // 6: secured_service.SecuredService.Secure:output_type -> secured_service.SecureResponse + 5, // 7: secured_service.SecuredService.BearerSecure:output_type -> secured_service.BearerSecureResponse + 7, // 8: secured_service.SecuredService.DoublySecure:output_type -> secured_service.DoublySecureResponse + 9, // 9: secured_service.SecuredService.AlsoDoublySecure:output_type -> secured_service.AlsoDoublySecureResponse + 5, // [5:10] is the sub-list for method output_type + 0, // [0:5] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name @@ -492,14 +590,14 @@ func file_goagen_multiauth_secured_service_proto_init() { return } file_goagen_multiauth_secured_service_proto_msgTypes[2].OneofWrappers = []any{} - file_goagen_multiauth_secured_service_proto_msgTypes[6].OneofWrappers = []any{} + file_goagen_multiauth_secured_service_proto_msgTypes[8].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_goagen_multiauth_secured_service_proto_rawDesc), len(file_goagen_multiauth_secured_service_proto_rawDesc)), NumEnums: 0, - NumMessages: 8, + NumMessages: 10, NumExtensions: 0, NumServices: 1, }, diff --git a/security/multiauth/gen/grpc/secured_service/pb/goagen_multiauth_secured_service.proto b/security/multiauth/gen/grpc/secured_service/pb/goagen_multiauth_secured_service.proto index fe34e8542..47ed3d815 100644 --- a/security/multiauth/gen/grpc/secured_service/pb/goagen_multiauth_secured_service.proto +++ b/security/multiauth/gen/grpc/secured_service/pb/goagen_multiauth_secured_service.proto @@ -18,6 +18,8 @@ service SecuredService { rpc Signin (SigninRequest) returns (SigninResponse); // This action is secured with the jwt scheme rpc Secure (SecureRequest) returns (SecureResponse); + // This action is secured with the bearer scheme + rpc BearerSecure (BearerSecureRequest) returns (BearerSecureResponse); // This action is secured with the jwt scheme and also requires an API key // query string. rpc DoublySecure (DoublySecureRequest) returns (DoublySecureResponse); @@ -36,6 +38,8 @@ message SigninResponse { string api_key = 2; // OAuth2 token string oauth_token = 3; + // Bearer token + string bearer_token = 4; } message SecureRequest { @@ -47,6 +51,13 @@ message SecureResponse { string field = 1; } +message BearerSecureRequest { +} + +message BearerSecureResponse { + string field = 1; +} + message DoublySecureRequest { // API key string key = 1; diff --git a/security/multiauth/gen/grpc/secured_service/pb/goagen_multiauth_secured_service_grpc.pb.go b/security/multiauth/gen/grpc/secured_service/pb/goagen_multiauth_secured_service_grpc.pb.go index 0d6c37932..3f69c3325 100644 --- a/security/multiauth/gen/grpc/secured_service/pb/goagen_multiauth_secured_service_grpc.pb.go +++ b/security/multiauth/gen/grpc/secured_service/pb/goagen_multiauth_secured_service_grpc.pb.go @@ -7,8 +7,8 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.6.1 -// - protoc v6.33.2 +// - protoc-gen-go-grpc v1.6.2 +// - protoc v7.34.1 // source: goagen_multiauth_secured_service.proto package secured_servicepb @@ -28,6 +28,7 @@ const _ = grpc.SupportPackageIsVersion9 const ( SecuredService_Signin_FullMethodName = "/secured_service.SecuredService/Signin" SecuredService_Secure_FullMethodName = "/secured_service.SecuredService/Secure" + SecuredService_BearerSecure_FullMethodName = "/secured_service.SecuredService/BearerSecure" SecuredService_DoublySecure_FullMethodName = "/secured_service.SecuredService/DoublySecure" SecuredService_AlsoDoublySecure_FullMethodName = "/secured_service.SecuredService/AlsoDoublySecure" ) @@ -43,6 +44,8 @@ type SecuredServiceClient interface { Signin(ctx context.Context, in *SigninRequest, opts ...grpc.CallOption) (*SigninResponse, error) // This action is secured with the jwt scheme Secure(ctx context.Context, in *SecureRequest, opts ...grpc.CallOption) (*SecureResponse, error) + // This action is secured with the bearer scheme + BearerSecure(ctx context.Context, in *BearerSecureRequest, opts ...grpc.CallOption) (*BearerSecureResponse, error) // This action is secured with the jwt scheme and also requires an API key // query string. DoublySecure(ctx context.Context, in *DoublySecureRequest, opts ...grpc.CallOption) (*DoublySecureResponse, error) @@ -79,6 +82,16 @@ func (c *securedServiceClient) Secure(ctx context.Context, in *SecureRequest, op return out, nil } +func (c *securedServiceClient) BearerSecure(ctx context.Context, in *BearerSecureRequest, opts ...grpc.CallOption) (*BearerSecureResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(BearerSecureResponse) + err := c.cc.Invoke(ctx, SecuredService_BearerSecure_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *securedServiceClient) DoublySecure(ctx context.Context, in *DoublySecureRequest, opts ...grpc.CallOption) (*DoublySecureResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DoublySecureResponse) @@ -110,6 +123,8 @@ type SecuredServiceServer interface { Signin(context.Context, *SigninRequest) (*SigninResponse, error) // This action is secured with the jwt scheme Secure(context.Context, *SecureRequest) (*SecureResponse, error) + // This action is secured with the bearer scheme + BearerSecure(context.Context, *BearerSecureRequest) (*BearerSecureResponse, error) // This action is secured with the jwt scheme and also requires an API key // query string. DoublySecure(context.Context, *DoublySecureRequest) (*DoublySecureResponse, error) @@ -132,6 +147,9 @@ func (UnimplementedSecuredServiceServer) Signin(context.Context, *SigninRequest) func (UnimplementedSecuredServiceServer) Secure(context.Context, *SecureRequest) (*SecureResponse, error) { return nil, status.Error(codes.Unimplemented, "method Secure not implemented") } +func (UnimplementedSecuredServiceServer) BearerSecure(context.Context, *BearerSecureRequest) (*BearerSecureResponse, error) { + return nil, status.Error(codes.Unimplemented, "method BearerSecure not implemented") +} func (UnimplementedSecuredServiceServer) DoublySecure(context.Context, *DoublySecureRequest) (*DoublySecureResponse, error) { return nil, status.Error(codes.Unimplemented, "method DoublySecure not implemented") } @@ -195,6 +213,24 @@ func _SecuredService_Secure_Handler(srv interface{}, ctx context.Context, dec fu return interceptor(ctx, in, info, handler) } +func _SecuredService_BearerSecure_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BearerSecureRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SecuredServiceServer).BearerSecure(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SecuredService_BearerSecure_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SecuredServiceServer).BearerSecure(ctx, req.(*BearerSecureRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _SecuredService_DoublySecure_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DoublySecureRequest) if err := dec(in); err != nil { @@ -246,6 +282,10 @@ var SecuredService_ServiceDesc = grpc.ServiceDesc{ MethodName: "Secure", Handler: _SecuredService_Secure_Handler, }, + { + MethodName: "BearerSecure", + Handler: _SecuredService_BearerSecure_Handler, + }, { MethodName: "DoublySecure", Handler: _SecuredService_DoublySecure_Handler, diff --git a/security/multiauth/gen/grpc/secured_service/server/encode_decode.go b/security/multiauth/gen/grpc/secured_service/server/encode_decode.go index b09a324b1..87fd90e29 100644 --- a/security/multiauth/gen/grpc/secured_service/server/encode_decode.go +++ b/security/multiauth/gen/grpc/secured_service/server/encode_decode.go @@ -108,6 +108,46 @@ func DecodeSecureRequest(ctx context.Context, v any, md metadata.MD) (any, error return payload, nil } +// EncodeBearerSecureResponse encodes responses from the "secured_service" +// service "bearer_secure" endpoint. +func EncodeBearerSecureResponse(ctx context.Context, v any, hdr, trlr *metadata.MD) (any, error) { + result, ok := v.(string) + if !ok { + return nil, goagrpc.ErrInvalidType("secured_service", "bearer_secure", "string", v) + } + resp := NewProtoBearerSecureResponse(result) + return resp, nil +} + +// DecodeBearerSecureRequest decodes requests sent to "secured_service" service +// "bearer_secure" endpoint. +func DecodeBearerSecureRequest(ctx context.Context, v any, md metadata.MD) (any, error) { + var ( + bearerToken string + err error + ) + { + if vals := md.Get("authorization"); len(vals) == 0 { + err = goa.MergeErrors(err, goa.MissingFieldError("authorization", "metadata")) + } else { + bearerToken = vals[0] + } + } + if err != nil { + return nil, err + } + var payload *securedservice.BearerSecurePayload + { + payload = NewBearerSecurePayload(bearerToken) + if strings.Contains(payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(payload.BearerToken, " ", 2)[1] + payload.BearerToken = cred + } + } + return payload, nil +} + // EncodeDoublySecureResponse encodes responses from the "secured_service" // service "doubly_secure" endpoint. func EncodeDoublySecureResponse(ctx context.Context, v any, hdr, trlr *metadata.MD) (any, error) { diff --git a/security/multiauth/gen/grpc/secured_service/server/server.go b/security/multiauth/gen/grpc/secured_service/server/server.go index c15ff69a7..7b4def623 100644 --- a/security/multiauth/gen/grpc/secured_service/server/server.go +++ b/security/multiauth/gen/grpc/secured_service/server/server.go @@ -22,6 +22,7 @@ import ( type Server struct { SigninH goagrpc.UnaryHandler SecureH goagrpc.UnaryHandler + BearerSecureH goagrpc.UnaryHandler DoublySecureH goagrpc.UnaryHandler AlsoDoublySecureH goagrpc.UnaryHandler secured_servicepb.UnimplementedSecuredServiceServer @@ -33,6 +34,7 @@ func New(e *securedservice.Endpoints, uh goagrpc.UnaryHandler) *Server { return &Server{ SigninH: NewSigninHandler(e.Signin, uh), SecureH: NewSecureHandler(e.Secure, uh), + BearerSecureH: NewBearerSecureHandler(e.BearerSecure, uh), DoublySecureH: NewDoublySecureHandler(e.DoublySecure, uh), AlsoDoublySecureH: NewAlsoDoublySecureHandler(e.AlsoDoublySecure, uh), } @@ -96,6 +98,36 @@ func (s *Server) Secure(ctx context.Context, message *secured_servicepb.SecureRe return resp.(*secured_servicepb.SecureResponse), nil } +// NewBearerSecureHandler creates a gRPC handler which serves the +// "secured_service" service "bearer_secure" endpoint. +func NewBearerSecureHandler(endpoint goa.Endpoint, h goagrpc.UnaryHandler) goagrpc.UnaryHandler { + if h == nil { + h = goagrpc.NewUnaryHandler(endpoint, DecodeBearerSecureRequest, EncodeBearerSecureResponse) + } + return h +} + +// BearerSecure implements the "BearerSecure" method in +// secured_servicepb.SecuredServiceServer interface. +func (s *Server) BearerSecure(ctx context.Context, message *secured_servicepb.BearerSecureRequest) (*secured_servicepb.BearerSecureResponse, error) { + ctx = context.WithValue(ctx, goa.MethodKey, "bearer_secure") + ctx = context.WithValue(ctx, goa.ServiceKey, "secured_service") + resp, err := s.BearerSecureH.Handle(ctx, message) + if err != nil { + var en goa.GoaErrorNamer + if errors.As(err, &en) { + switch en.GoaErrorName() { + case "invalid-scopes": + return nil, goagrpc.NewStatusError(codes.Unauthenticated, err, goagrpc.NewErrorResponse(err)) + case "unauthorized": + return nil, goagrpc.NewStatusError(codes.Unauthenticated, err, goagrpc.NewErrorResponse(err)) + } + } + return nil, goagrpc.EncodeError(err) + } + return resp.(*secured_servicepb.BearerSecureResponse), nil +} + // NewDoublySecureHandler creates a gRPC handler which serves the // "secured_service" service "doubly_secure" endpoint. func NewDoublySecureHandler(endpoint goa.Endpoint, h goagrpc.UnaryHandler) goagrpc.UnaryHandler { diff --git a/security/multiauth/gen/grpc/secured_service/server/types.go b/security/multiauth/gen/grpc/secured_service/server/types.go index 7d45d899d..103ba5af0 100644 --- a/security/multiauth/gen/grpc/secured_service/server/types.go +++ b/security/multiauth/gen/grpc/secured_service/server/types.go @@ -25,9 +25,10 @@ func NewSigninPayload(username string, password string) *securedservice.SigninPa // "signin" endpoint of the "secured_service" service. func NewProtoSigninResponse(result *securedservice.Creds) *secured_servicepb.SigninResponse { message := &secured_servicepb.SigninResponse{ - Jwt: result.JWT, - ApiKey: result.APIKey, - OauthToken: result.OauthToken, + Jwt: result.JWT, + ApiKey: result.APIKey, + OauthToken: result.OauthToken, + BearerToken: result.BearerToken, } return message } @@ -50,6 +51,22 @@ func NewProtoSecureResponse(result string) *secured_servicepb.SecureResponse { return message } +// NewBearerSecurePayload builds the payload of the "bearer_secure" endpoint of +// the "secured_service" service from the gRPC request type. +func NewBearerSecurePayload(bearerToken string) *securedservice.BearerSecurePayload { + v := &securedservice.BearerSecurePayload{} + v.BearerToken = bearerToken + return v +} + +// NewProtoBearerSecureResponse builds the gRPC response type from the result +// of the "bearer_secure" endpoint of the "secured_service" service. +func NewProtoBearerSecureResponse(result string) *secured_servicepb.BearerSecureResponse { + message := &secured_servicepb.BearerSecureResponse{} + message.Field = result + return message +} + // NewDoublySecurePayload builds the payload of the "doubly_secure" endpoint of // the "secured_service" service from the gRPC request type. func NewDoublySecurePayload(message *secured_servicepb.DoublySecureRequest, token string) *securedservice.DoublySecurePayload { diff --git a/security/multiauth/gen/http/cli/multi_auth/cli.go b/security/multiauth/gen/http/cli/multi_auth/cli.go index b8e19a9b6..d7720962a 100644 --- a/security/multiauth/gen/http/cli/multi_auth/cli.go +++ b/security/multiauth/gen/http/cli/multi_auth/cli.go @@ -23,7 +23,7 @@ import ( // command (subcommand1|subcommand2|...) func UsageCommands() []string { return []string{ - "secured-service (signin|secure|doubly-secure|also-doubly-secure)", + "secured-service (signin|secure|bearer-secure|doubly-secure|also-doubly-secure)", } } @@ -53,6 +53,9 @@ func ParseEndpoint( securedServiceSecureFailFlag = securedServiceSecureFlags.String("fail", "", "") securedServiceSecureTokenFlag = securedServiceSecureFlags.String("token", "REQUIRED", "") + securedServiceBearerSecureFlags = flag.NewFlagSet("bearer-secure", flag.ExitOnError) + securedServiceBearerSecureBearerTokenFlag = securedServiceBearerSecureFlags.String("bearer-token", "REQUIRED", "") + securedServiceDoublySecureFlags = flag.NewFlagSet("doubly-secure", flag.ExitOnError) securedServiceDoublySecureKeyFlag = securedServiceDoublySecureFlags.String("key", "REQUIRED", "") securedServiceDoublySecureTokenFlag = securedServiceDoublySecureFlags.String("token", "REQUIRED", "") @@ -67,6 +70,7 @@ func ParseEndpoint( securedServiceFlags.Usage = securedServiceUsage securedServiceSigninFlags.Usage = securedServiceSigninUsage securedServiceSecureFlags.Usage = securedServiceSecureUsage + securedServiceBearerSecureFlags.Usage = securedServiceBearerSecureUsage securedServiceDoublySecureFlags.Usage = securedServiceDoublySecureUsage securedServiceAlsoDoublySecureFlags.Usage = securedServiceAlsoDoublySecureUsage @@ -110,6 +114,9 @@ func ParseEndpoint( case "secure": epf = securedServiceSecureFlags + case "bearer-secure": + epf = securedServiceBearerSecureFlags + case "doubly-secure": epf = securedServiceDoublySecureFlags @@ -147,6 +154,9 @@ func ParseEndpoint( case "secure": endpoint = c.Secure() data, err = securedservicec.BuildSecurePayload(*securedServiceSecureFailFlag, *securedServiceSecureTokenFlag) + case "bearer-secure": + endpoint = c.BearerSecure() + data, err = securedservicec.BuildBearerSecurePayload(*securedServiceBearerSecureBearerTokenFlag) case "doubly-secure": endpoint = c.DoublySecure() data, err = securedservicec.BuildDoublySecurePayload(*securedServiceDoublySecureKeyFlag, *securedServiceDoublySecureTokenFlag) @@ -171,6 +181,7 @@ func securedServiceUsage() { fmt.Fprintln(os.Stderr, "COMMAND:") fmt.Fprintln(os.Stderr, ` signin: Creates a valid JWT`) fmt.Fprintln(os.Stderr, ` secure: This action is secured with the jwt scheme`) + fmt.Fprintln(os.Stderr, ` bearer-secure: This action is secured with the bearer scheme`) fmt.Fprintln(os.Stderr, ` doubly-secure: This action is secured with the jwt scheme and also requires an API key query string.`) fmt.Fprintln(os.Stderr, ` also-doubly-secure: This action is secured with the jwt scheme and also requires an API key header.`) fmt.Fprintln(os.Stderr) @@ -214,7 +225,25 @@ func securedServiceSecureUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "secured-service secure --fail true --token \"Non itaque accusamus.\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "secured-service secure --fail true --token \"Sit autem nostrum autem sint officia.\"") +} + +func securedServiceBearerSecureUsage() { + // Header with flags + fmt.Fprintf(os.Stderr, "%s [flags] secured-service bearer-secure", os.Args[0]) + fmt.Fprint(os.Stderr, " -bearer-token STRING") + fmt.Fprintln(os.Stderr) + + // Description + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, `This action is secured with the bearer scheme`) + + // Flags list + fmt.Fprintln(os.Stderr, ` -bearer-token STRING: `) + + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "Example:") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "secured-service bearer-secure --bearer-token \"Quia consequuntur assumenda magnam nostrum sed optio.\"") } func securedServiceDoublySecureUsage() { @@ -260,5 +289,5 @@ func securedServiceAlsoDoublySecureUsage() { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, "Example:") - fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "secured-service also-doubly-secure --key \"abcdef12345\" --oauth-token \"Excepturi quam.\" --token \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ\" --username \"user\" --password \"password\"") + fmt.Fprintf(os.Stderr, " %s %s\n", os.Args[0], "secured-service also-doubly-secure --key \"abcdef12345\" --oauth-token \"Accusantium deserunt consequatur ut modi.\" --token \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ\" --username \"user\" --password \"password\"") } diff --git a/security/multiauth/gen/http/openapi.json b/security/multiauth/gen/http/openapi.json index aad996b71..a180883ee 100644 --- a/security/multiauth/gen/http/openapi.json +++ b/security/multiauth/gen/http/openapi.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"Security Example API","description":"This API demonstrates the use of the goa security DSL","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/secure":{"get":{"tags":["secured_service"],"summary":"secure secured_service","description":"This action is secured with the jwt scheme\n\n**Required security scopes for jwt**:\n * `api:read`","operationId":"secured_service#secure","parameters":[{"name":"fail","in":"query","description":"Whether to force auth failure even with a valid JWT","required":false,"type":"boolean"}],"responses":{"200":{"description":"OK response.","schema":{"type":"string"}},"401":{"description":"Unauthorized response.","schema":{"type":"string"}},"403":{"description":"Forbidden response.","schema":{"type":"string"}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["secured_service"],"summary":"doubly_secure secured_service","description":"This action is secured with the jwt scheme and also requires an API key query string.\n\n**Required security scopes for jwt**:\n * `api:read`\n * `api:write`\n\n**Required security scopes for api_key**:\n * `api:read`\n * `api:write`","operationId":"secured_service#doubly_secure","responses":{"200":{"description":"OK response.","schema":{"type":"string"}},"401":{"description":"Unauthorized response.","schema":{"type":"string"}},"403":{"description":"Forbidden response.","schema":{"type":"string"}}},"schemes":["http"],"security":[{"api_key_query_k":null,"jwt_header_Authorization":null}]},"post":{"tags":["secured_service"],"summary":"also_doubly_secure secured_service","description":"This action is secured with the jwt scheme and also requires an API key header.\n\n**Required security scopes for jwt**:\n * `api:read`\n * `api:write`\n\n**Required security scopes for api_key**:\n * `api:read`\n * `api:write`\n\n**Required security scopes for basic**:\n * `api:read`\n * `api:write`","operationId":"secured_service#also_doubly_secure","responses":{"200":{"description":"OK response.","schema":{"type":"string"}},"401":{"description":"Unauthorized response.","schema":{"type":"string"}},"403":{"description":"Forbidden response.","schema":{"type":"string"}}},"schemes":["http"],"security":[{"api_key_query_k":null,"jwt_header_X-Authorization":null},{"basic_header_Authorization":null,"oauth2_query_oauth":["api:read","api:write"]}]}},"/signin":{"post":{"tags":["secured_service"],"summary":"signin secured_service","description":"Creates a valid JWT","operationId":"secured_service#signin","responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Creds","required":["jwt","api_key","oauth_token"]}},"401":{"description":"Unauthorized response.","schema":{"type":"string"}}},"schemes":["http"],"security":[{"basic_header_Authorization":null}]}}},"definitions":{"Creds":{"title":"Creds","type":"object","properties":{"api_key":{"type":"string","description":"API Key","example":"abcdef12345"},"jwt":{"type":"string","description":"JWT token","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"},"oauth_token":{"type":"string","description":"OAuth2 token","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"}},"example":{"api_key":"abcdef12345","jwt":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ","oauth_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"},"required":["jwt","api_key","oauth_token"]}},"securityDefinitions":{"api_key_query_k":{"type":"apiKey","description":"Secures endpoint by requiring an API key.","name":"k","in":"query"},"basic_header_Authorization":{"type":"basic","description":"Basic authentication used to authenticate security principal during signin\n\n**Security Scopes**:\n * `api:read`: Read-only access"},"jwt_header_Authorization":{"type":"apiKey","description":"Secures endpoint by requiring a valid JWT token retrieved via the signin endpoint. Supports scopes \"api:read\" and \"api:write\".\n\n**Security Scopes**:\n * `api:read`: Read-only access\n * `api:write`: Read and write access","name":"Authorization","in":"header"},"jwt_header_X-Authorization":{"type":"apiKey","description":"Secures endpoint by requiring a valid JWT token retrieved via the signin endpoint. Supports scopes \"api:read\" and \"api:write\".\n\n**Security Scopes**:\n * `api:read`: Read-only access\n * `api:write`: Read and write access","name":"X-Authorization","in":"header"},"oauth2_query_oauth":{"type":"oauth2","description":"Secures endpoint by requiring a valid OAuth2 token retrieved via the signin endpoint. Supports scopes \"api:read\" and \"api:write\".","flow":"accessCode","authorizationUrl":"http://goa.design/authorization","tokenUrl":"http://goa.design/token","scopes":{"api:read":"Read-only access","api:write":"Read and write access"}}},"externalDocs":{"description":"Security example README","url":"https://github.com/goadesign/goa/tree/master/example/security/README.md"}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"Security Example API","description":"This API demonstrates the use of the goa security DSL","version":"0.0.1"},"host":"localhost:80","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/bearer":{"get":{"tags":["secured_service"],"summary":"bearer_secure secured_service","description":"This action is secured with the bearer scheme\n\n**Required security scopes for bearer**:\n * `api:read`","operationId":"secured_service#bearer_secure","responses":{"200":{"description":"OK response.","schema":{"type":"string"}},"401":{"description":"Unauthorized response.","schema":{"type":"string"}},"403":{"description":"Forbidden response.","schema":{"type":"string"}}},"schemes":["http"],"security":[{"bearer_header_Authorization":null}]}},"/secure":{"get":{"tags":["secured_service"],"summary":"secure secured_service","description":"This action is secured with the jwt scheme\n\n**Required security scopes for jwt**:\n * `api:read`","operationId":"secured_service#secure","parameters":[{"name":"fail","in":"query","description":"Whether to force auth failure even with a valid JWT","required":false,"type":"boolean"}],"responses":{"200":{"description":"OK response.","schema":{"type":"string"}},"401":{"description":"Unauthorized response.","schema":{"type":"string"}},"403":{"description":"Forbidden response.","schema":{"type":"string"}}},"schemes":["http"],"security":[{"jwt_header_Authorization":null}]},"put":{"tags":["secured_service"],"summary":"doubly_secure secured_service","description":"This action is secured with the jwt scheme and also requires an API key query string.\n\n**Required security scopes for jwt**:\n * `api:read`\n * `api:write`\n\n**Required security scopes for api_key**:\n * `api:read`\n * `api:write`","operationId":"secured_service#doubly_secure","responses":{"200":{"description":"OK response.","schema":{"type":"string"}},"401":{"description":"Unauthorized response.","schema":{"type":"string"}},"403":{"description":"Forbidden response.","schema":{"type":"string"}}},"schemes":["http"],"security":[{"api_key_query_k":null,"jwt_header_Authorization":null}]},"post":{"tags":["secured_service"],"summary":"also_doubly_secure secured_service","description":"This action is secured with the jwt scheme and also requires an API key header.\n\n**Required security scopes for jwt**:\n * `api:read`\n * `api:write`\n\n**Required security scopes for api_key**:\n * `api:read`\n * `api:write`\n\n**Required security scopes for basic**:\n * `api:read`\n * `api:write`","operationId":"secured_service#also_doubly_secure","responses":{"200":{"description":"OK response.","schema":{"type":"string"}},"401":{"description":"Unauthorized response.","schema":{"type":"string"}},"403":{"description":"Forbidden response.","schema":{"type":"string"}}},"schemes":["http"],"security":[{"api_key_query_k":null,"jwt_header_X-Authorization":null},{"basic_header_Authorization":null,"oauth2_query_oauth":["api:read","api:write"]}]}},"/signin":{"post":{"tags":["secured_service"],"summary":"signin secured_service","description":"Creates a valid JWT","operationId":"secured_service#signin","responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/Creds","required":["jwt","api_key","oauth_token","bearer_token"]}},"401":{"description":"Unauthorized response.","schema":{"type":"string"}}},"schemes":["http"],"security":[{"basic_header_Authorization":null}]}}},"definitions":{"Creds":{"title":"Creds","type":"object","properties":{"api_key":{"type":"string","description":"API Key","example":"abcdef12345"},"bearer_token":{"type":"string","description":"Bearer token","example":"opaque-session-token"},"jwt":{"type":"string","description":"JWT token","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"},"oauth_token":{"type":"string","description":"OAuth2 token","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"}},"example":{"api_key":"abcdef12345","bearer_token":"opaque-session-token","jwt":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ","oauth_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"},"required":["jwt","api_key","oauth_token","bearer_token"]}},"securityDefinitions":{"api_key_query_k":{"type":"apiKey","description":"Secures endpoint by requiring an API key.","name":"k","in":"query"},"basic_header_Authorization":{"type":"basic","description":"Basic authentication used to authenticate security principal during signin\n\n**Security Scopes**:\n * `api:read`: Read-only access"},"bearer_header_Authorization":{"type":"apiKey","description":"Secures endpoint by requiring a valid bearer token retrieved via the signin endpoint. Supports scopes \"api:read\" and \"api:write\".\n\n**Security Scopes**:\n * `api:read`: Read-only access\n * `api:write`: Read and write access","name":"Authorization","in":"header"},"jwt_header_Authorization":{"type":"apiKey","description":"Secures endpoint by requiring a valid JWT token retrieved via the signin endpoint. Supports scopes \"api:read\" and \"api:write\".\n\n**Security Scopes**:\n * `api:read`: Read-only access\n * `api:write`: Read and write access","name":"Authorization","in":"header"},"jwt_header_X-Authorization":{"type":"apiKey","description":"Secures endpoint by requiring a valid JWT token retrieved via the signin endpoint. Supports scopes \"api:read\" and \"api:write\".\n\n**Security Scopes**:\n * `api:read`: Read-only access\n * `api:write`: Read and write access","name":"X-Authorization","in":"header"},"oauth2_query_oauth":{"type":"oauth2","description":"Secures endpoint by requiring a valid OAuth2 token retrieved via the signin endpoint. Supports scopes \"api:read\" and \"api:write\".","flow":"accessCode","authorizationUrl":"http://goa.design/authorization","tokenUrl":"http://goa.design/token","scopes":{"api:read":"Read-only access","api:write":"Read and write access"}}},"externalDocs":{"description":"Security example README","url":"https://github.com/goadesign/goa/tree/master/example/security/README.md"}} \ No newline at end of file diff --git a/security/multiauth/gen/http/openapi.yaml b/security/multiauth/gen/http/openapi.yaml index cf24d65c3..065b34726 100644 --- a/security/multiauth/gen/http/openapi.yaml +++ b/security/multiauth/gen/http/openapi.yaml @@ -13,6 +13,34 @@ produces: - application/xml - application/gob paths: + /bearer: + get: + tags: + - secured_service + summary: bearer_secure secured_service + description: |- + This action is secured with the bearer scheme + + **Required security scopes for bearer**: + * `api:read` + operationId: secured_service#bearer_secure + responses: + "200": + description: OK response. + schema: + type: string + "401": + description: Unauthorized response. + schema: + type: string + "403": + description: Forbidden response. + schema: + type: string + schemes: + - http + security: + - bearer_header_Authorization: [] /secure: get: tags: @@ -137,6 +165,7 @@ paths: - jwt - api_key - oauth_token + - bearer_token "401": description: Unauthorized response. schema: @@ -154,6 +183,10 @@ definitions: type: string description: API Key example: abcdef12345 + bearer_token: + type: string + description: Bearer token + example: opaque-session-token jwt: type: string description: JWT token @@ -164,12 +197,14 @@ definitions: example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ example: api_key: abcdef12345 + bearer_token: opaque-session-token jwt: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ oauth_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ required: - jwt - api_key - oauth_token + - bearer_token securityDefinitions: api_key_query_k: type: apiKey @@ -183,6 +218,16 @@ securityDefinitions: **Security Scopes**: * `api:read`: Read-only access + bearer_header_Authorization: + type: apiKey + description: |- + Secures endpoint by requiring a valid bearer token retrieved via the signin endpoint. Supports scopes "api:read" and "api:write". + + **Security Scopes**: + * `api:read`: Read-only access + * `api:write`: Read and write access + name: Authorization + in: header jwt_header_Authorization: type: apiKey description: |- diff --git a/security/multiauth/gen/http/openapi3.json b/security/multiauth/gen/http/openapi3.json index 5151b4da7..0b0c4e4cb 100644 --- a/security/multiauth/gen/http/openapi3.json +++ b/security/multiauth/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"Security Example API","description":"This API demonstrates the use of the goa security DSL","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for multi_auth"}],"paths":{"/secure":{"get":{"tags":["secured_service"],"summary":"secure secured_service","description":"This action is secured with the jwt scheme","operationId":"secured_service#secure","parameters":[{"name":"fail","in":"query","description":"Whether to force auth failure even with a valid JWT","allowEmptyValue":true,"schema":{"type":"boolean","description":"Whether to force auth failure even with a valid JWT","example":false},"example":false}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Quos ut est ipsum consequatur fugiat at."},"example":"Consequuntur libero aliquid error dolorem deserunt."}}},"401":{"description":"unauthorized: Unauthorized response.","content":{"application/json":{"schema":{"type":"string","example":"Atque eum unde vero dicta minus minima."},"example":"Ratione voluptas."}}},"403":{"description":"invalid-scopes: Forbidden response.","content":{"application/json":{"schema":{"type":"string","example":"Maiores porro alias."},"example":"Ex nulla."}}}},"security":[{"jwt_header_Authorization":["api:read"]}]},"post":{"tags":["secured_service"],"summary":"also_doubly_secure secured_service","description":"This action is secured with the jwt scheme and also requires an API key header.","operationId":"secured_service#also_doubly_secure","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Sed ipsam at hic optio dolores aut."},"example":"Eaque pariatur eos laborum."}}},"401":{"description":"unauthorized: Unauthorized response.","content":{"application/json":{"schema":{"type":"string","example":"Amet voluptas quia."},"example":"Ad explicabo sed."}}},"403":{"description":"invalid-scopes: Forbidden response.","content":{"application/json":{"schema":{"type":"string","example":"Rerum aut incidunt minima."},"example":"Consequatur blanditiis consectetur quis."}}}},"security":[{"api_key_query_k":[],"jwt_header_X-Authorization":["api:read","api:write"]},{"basic_header_Authorization":[],"oauth2_query_oauth":["api:read","api:write"]}]},"put":{"tags":["secured_service"],"summary":"doubly_secure secured_service","description":"This action is secured with the jwt scheme and also requires an API key query string.","operationId":"secured_service#doubly_secure","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Tenetur et eaque nesciunt."},"example":"Quo dicta error et et officiis."}}},"401":{"description":"unauthorized: Unauthorized response.","content":{"application/json":{"schema":{"type":"string","example":"Non nisi error ut inventore sequi."},"example":"Soluta ipsum modi saepe."}}},"403":{"description":"invalid-scopes: Forbidden response.","content":{"application/json":{"schema":{"type":"string","example":"Repudiandae qui sed optio odit dignissimos."},"example":"Corrupti alias architecto."}}}},"security":[{"api_key_query_k":[],"jwt_header_Authorization":["api:read","api:write"]}]}},"/signin":{"post":{"tags":["secured_service"],"summary":"signin secured_service","description":"Creates a valid JWT","operationId":"secured_service#signin","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Creds"},"example":{"api_key":"abcdef12345","jwt":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ","oauth_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"}}}},"401":{"description":"unauthorized: Unauthorized response.","content":{"application/json":{"schema":{"type":"string","example":"Aut expedita optio."},"example":"Sunt id facere est suscipit provident veritatis."}}}},"security":[{"basic_header_Authorization":[]}]}}},"components":{"schemas":{"Creds":{"type":"object","properties":{"api_key":{"type":"string","description":"API Key","example":"abcdef12345"},"jwt":{"type":"string","description":"JWT token","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"},"oauth_token":{"type":"string","description":"OAuth2 token","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"}},"example":{"api_key":"abcdef12345","jwt":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ","oauth_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"},"required":["jwt","api_key","oauth_token"]}},"securitySchemes":{"api_key_query_k":{"type":"apiKey","description":"Secures endpoint by requiring an API key.","name":"k","in":"query"},"basic_header_Authorization":{"type":"http","description":"Basic authentication used to authenticate security principal during signin","scheme":"basic"},"jwt_header_Authorization":{"type":"http","description":"Secures endpoint by requiring a valid JWT token retrieved via the signin endpoint. Supports scopes \"api:read\" and \"api:write\".","scheme":"bearer"},"jwt_header_X-Authorization":{"type":"http","description":"Secures endpoint by requiring a valid JWT token retrieved via the signin endpoint. Supports scopes \"api:read\" and \"api:write\".","scheme":"bearer"},"oauth2_query_oauth":{"type":"oauth2","description":"Secures endpoint by requiring a valid OAuth2 token retrieved via the signin endpoint. Supports scopes \"api:read\" and \"api:write\".","flows":{"authorizationCode":{"authorizationUrl":"http://goa.design/authorization","tokenUrl":"http://goa.design/token","refreshUrl":"http://goa.design/refresh","scopes":{"api:read":"Read-only access","api:write":"Read and write access"}}}}}},"tags":[{"name":"secured_service","description":"The secured service exposes endpoints that require valid authorization credentials."}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"Security Example API","description":"This API demonstrates the use of the goa security DSL","version":"0.0.1"},"servers":[{"url":"http://localhost:80","description":"Default server for multi_auth"}],"paths":{"/bearer":{"get":{"tags":["secured_service"],"summary":"bearer_secure secured_service","description":"This action is secured with the bearer scheme","operationId":"secured_service#bearer_secure","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Ipsum modi saepe."},"example":"Vel et et velit rem est et."}}},"401":{"description":"unauthorized: Unauthorized response.","content":{"application/json":{"schema":{"type":"string","example":"Consequatur blanditiis consectetur quis."},"example":"Dolores hic."}}},"403":{"description":"invalid-scopes: Forbidden response.","content":{"application/json":{"schema":{"type":"string","example":"Eaque pariatur eos laborum."},"example":"Eaque modi sint saepe dolorem."}}}},"security":[{"bearer_header_Authorization":[]}]}},"/secure":{"get":{"tags":["secured_service"],"summary":"secure secured_service","description":"This action is secured with the jwt scheme","operationId":"secured_service#secure","parameters":[{"name":"fail","in":"query","description":"Whether to force auth failure even with a valid JWT","allowEmptyValue":true,"schema":{"type":"boolean","description":"Whether to force auth failure even with a valid JWT","example":true},"example":true}],"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Delectus ratione voluptas sit quo."},"example":"Necessitatibus aut."}}},"401":{"description":"unauthorized: Unauthorized response.","content":{"application/json":{"schema":{"type":"string","example":"Alias architecto quasi."},"example":"Quis eaque."}}},"403":{"description":"invalid-scopes: Forbidden response.","content":{"application/json":{"schema":{"type":"string","example":"Error et et officiis unde."},"example":"Consequatur impedit dolorem qui reiciendis voluptatem."}}}},"security":[{"jwt_header_Authorization":[]}]},"post":{"tags":["secured_service"],"summary":"also_doubly_secure secured_service","description":"This action is secured with the jwt scheme and also requires an API key header.","operationId":"secured_service#also_doubly_secure","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Molestiae quo."},"example":"Ipsum eum et id."}}},"401":{"description":"unauthorized: Unauthorized response.","content":{"application/json":{"schema":{"type":"string","example":"Quos illum praesentium dolores id ut."},"example":"Voluptas dignissimos ut."}}},"403":{"description":"invalid-scopes: Forbidden response.","content":{"application/json":{"schema":{"type":"string","example":"Sed quis tenetur voluptas ea."},"example":"Velit voluptas quidem."}}}},"security":[{"api_key_query_k":[],"jwt_header_X-Authorization":[]},{"basic_header_Authorization":[],"oauth2_query_oauth":["api:read","api:write"]}]},"put":{"tags":["secured_service"],"summary":"doubly_secure secured_service","description":"This action is secured with the jwt scheme and also requires an API key query string.","operationId":"secured_service#doubly_secure","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Ad explicabo sed."},"example":"Sit delectus dolorem amet maiores."}}},"401":{"description":"unauthorized: Unauthorized response.","content":{"application/json":{"schema":{"type":"string","example":"Nam et consequuntur nemo sit cum."},"example":"Expedita voluptatem voluptate ducimus vel doloremque odio."}}},"403":{"description":"invalid-scopes: Forbidden response.","content":{"application/json":{"schema":{"type":"string","example":"Dolorem et totam nemo numquam eum."},"example":"Beatae quod."}}}},"security":[{"api_key_query_k":[],"jwt_header_Authorization":[]}]}},"/signin":{"post":{"tags":["secured_service"],"summary":"signin secured_service","description":"Creates a valid JWT","operationId":"secured_service#signin","responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Creds"},"example":{"api_key":"abcdef12345","bearer_token":"opaque-session-token","jwt":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ","oauth_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"}}}},"401":{"description":"unauthorized: Unauthorized response.","content":{"application/json":{"schema":{"type":"string","example":"Error dolorem deserunt similique ex."},"example":"Fuga distinctio."}}}},"security":[{"basic_header_Authorization":[]}]}}},"components":{"schemas":{"Creds":{"type":"object","properties":{"api_key":{"type":"string","description":"API Key","example":"abcdef12345"},"bearer_token":{"type":"string","description":"Bearer token","example":"opaque-session-token"},"jwt":{"type":"string","description":"JWT token","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"},"oauth_token":{"type":"string","description":"OAuth2 token","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"}},"example":{"api_key":"abcdef12345","bearer_token":"opaque-session-token","jwt":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ","oauth_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"},"required":["jwt","api_key","oauth_token","bearer_token"]}},"securitySchemes":{"api_key_query_k":{"type":"apiKey","description":"Secures endpoint by requiring an API key.","name":"k","in":"query"},"basic_header_Authorization":{"type":"http","description":"Basic authentication used to authenticate security principal during signin","scheme":"basic"},"bearer_header_Authorization":{"type":"http","description":"Secures endpoint by requiring a valid bearer token retrieved via the signin endpoint. Supports scopes \"api:read\" and \"api:write\".","scheme":"bearer"},"jwt_header_Authorization":{"type":"http","description":"Secures endpoint by requiring a valid JWT token retrieved via the signin endpoint. Supports scopes \"api:read\" and \"api:write\".","scheme":"bearer","bearerFormat":"JWT"},"jwt_header_X-Authorization":{"type":"http","description":"Secures endpoint by requiring a valid JWT token retrieved via the signin endpoint. Supports scopes \"api:read\" and \"api:write\".","scheme":"bearer","bearerFormat":"JWT"},"oauth2_query_oauth":{"type":"oauth2","description":"Secures endpoint by requiring a valid OAuth2 token retrieved via the signin endpoint. Supports scopes \"api:read\" and \"api:write\".","flows":{"authorizationCode":{"authorizationUrl":"http://goa.design/authorization","tokenUrl":"http://goa.design/token","refreshUrl":"http://goa.design/refresh","scopes":{"api:read":"Read-only access","api:write":"Read and write access"}}}}}},"tags":[{"name":"secured_service","description":"The secured service exposes endpoints that require valid authorization credentials."}]} \ No newline at end of file diff --git a/security/multiauth/gen/http/openapi3.yaml b/security/multiauth/gen/http/openapi3.yaml index ddf2644c1..c7b023efc 100644 --- a/security/multiauth/gen/http/openapi3.yaml +++ b/security/multiauth/gen/http/openapi3.yaml @@ -7,6 +7,40 @@ servers: - url: http://localhost:80 description: Default server for multi_auth paths: + /bearer: + get: + tags: + - secured_service + summary: bearer_secure secured_service + description: This action is secured with the bearer scheme + operationId: secured_service#bearer_secure + responses: + "200": + description: OK response. + content: + application/json: + schema: + type: string + example: Ipsum modi saepe. + example: Vel et et velit rem est et. + "401": + description: 'unauthorized: Unauthorized response.' + content: + application/json: + schema: + type: string + example: Consequatur blanditiis consectetur quis. + example: Dolores hic. + "403": + description: 'invalid-scopes: Forbidden response.' + content: + application/json: + schema: + type: string + example: Eaque pariatur eos laborum. + example: Eaque modi sint saepe dolorem. + security: + - bearer_header_Authorization: [] /secure: get: tags: @@ -22,8 +56,8 @@ paths: schema: type: boolean description: Whether to force auth failure even with a valid JWT - example: false - example: false + example: true + example: true responses: "200": description: OK response. @@ -31,27 +65,26 @@ paths: application/json: schema: type: string - example: Quos ut est ipsum consequatur fugiat at. - example: Consequuntur libero aliquid error dolorem deserunt. + example: Delectus ratione voluptas sit quo. + example: Necessitatibus aut. "401": description: 'unauthorized: Unauthorized response.' content: application/json: schema: type: string - example: Atque eum unde vero dicta minus minima. - example: Ratione voluptas. + example: Alias architecto quasi. + example: Quis eaque. "403": description: 'invalid-scopes: Forbidden response.' content: application/json: schema: type: string - example: Maiores porro alias. - example: Ex nulla. + example: Error et et officiis unde. + example: Consequatur impedit dolorem qui reiciendis voluptatem. security: - - jwt_header_Authorization: - - api:read + - jwt_header_Authorization: [] post: tags: - secured_service @@ -65,29 +98,27 @@ paths: application/json: schema: type: string - example: Sed ipsam at hic optio dolores aut. - example: Eaque pariatur eos laborum. + example: Molestiae quo. + example: Ipsum eum et id. "401": description: 'unauthorized: Unauthorized response.' content: application/json: schema: type: string - example: Amet voluptas quia. - example: Ad explicabo sed. + example: Quos illum praesentium dolores id ut. + example: Voluptas dignissimos ut. "403": description: 'invalid-scopes: Forbidden response.' content: application/json: schema: type: string - example: Rerum aut incidunt minima. - example: Consequatur blanditiis consectetur quis. + example: Sed quis tenetur voluptas ea. + example: Velit voluptas quidem. security: - api_key_query_k: [] - jwt_header_X-Authorization: - - api:read - - api:write + jwt_header_X-Authorization: [] - basic_header_Authorization: [] oauth2_query_oauth: - api:read @@ -105,29 +136,27 @@ paths: application/json: schema: type: string - example: Tenetur et eaque nesciunt. - example: Quo dicta error et et officiis. + example: Ad explicabo sed. + example: Sit delectus dolorem amet maiores. "401": description: 'unauthorized: Unauthorized response.' content: application/json: schema: type: string - example: Non nisi error ut inventore sequi. - example: Soluta ipsum modi saepe. + example: Nam et consequuntur nemo sit cum. + example: Expedita voluptatem voluptate ducimus vel doloremque odio. "403": description: 'invalid-scopes: Forbidden response.' content: application/json: schema: type: string - example: Repudiandae qui sed optio odit dignissimos. - example: Corrupti alias architecto. + example: Dolorem et totam nemo numquam eum. + example: Beatae quod. security: - api_key_query_k: [] - jwt_header_Authorization: - - api:read - - api:write + jwt_header_Authorization: [] /signin: post: tags: @@ -144,6 +173,7 @@ paths: $ref: '#/components/schemas/Creds' example: api_key: abcdef12345 + bearer_token: opaque-session-token jwt: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ oauth_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ "401": @@ -152,8 +182,8 @@ paths: application/json: schema: type: string - example: Aut expedita optio. - example: Sunt id facere est suscipit provident veritatis. + example: Error dolorem deserunt similique ex. + example: Fuga distinctio. security: - basic_header_Authorization: [] components: @@ -165,6 +195,10 @@ components: type: string description: API Key example: abcdef12345 + bearer_token: + type: string + description: Bearer token + example: opaque-session-token jwt: type: string description: JWT token @@ -175,12 +209,14 @@ components: example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ example: api_key: abcdef12345 + bearer_token: opaque-session-token jwt: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ oauth_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ required: - jwt - api_key - oauth_token + - bearer_token securitySchemes: api_key_query_k: type: apiKey @@ -191,14 +227,20 @@ components: type: http description: Basic authentication used to authenticate security principal during signin scheme: basic + bearer_header_Authorization: + type: http + description: Secures endpoint by requiring a valid bearer token retrieved via the signin endpoint. Supports scopes "api:read" and "api:write". + scheme: bearer jwt_header_Authorization: type: http description: Secures endpoint by requiring a valid JWT token retrieved via the signin endpoint. Supports scopes "api:read" and "api:write". scheme: bearer + bearerFormat: JWT jwt_header_X-Authorization: type: http description: Secures endpoint by requiring a valid JWT token retrieved via the signin endpoint. Supports scopes "api:read" and "api:write". scheme: bearer + bearerFormat: JWT oauth2_query_oauth: type: oauth2 description: Secures endpoint by requiring a valid OAuth2 token retrieved via the signin endpoint. Supports scopes "api:read" and "api:write". diff --git a/security/multiauth/gen/http/secured_service/client/cli.go b/security/multiauth/gen/http/secured_service/client/cli.go index e37fb1bfb..89a7cf01e 100644 --- a/security/multiauth/gen/http/secured_service/client/cli.go +++ b/security/multiauth/gen/http/secured_service/client/cli.go @@ -58,6 +58,19 @@ func BuildSecurePayload(securedServiceSecureFail string, securedServiceSecureTok return v, nil } +// BuildBearerSecurePayload builds the payload for the secured_service +// bearer_secure endpoint from CLI flags. +func BuildBearerSecurePayload(securedServiceBearerSecureBearerToken string) (*securedservice.BearerSecurePayload, error) { + var bearerToken string + { + bearerToken = securedServiceBearerSecureBearerToken + } + v := &securedservice.BearerSecurePayload{} + v.BearerToken = bearerToken + + return v, nil +} + // BuildDoublySecurePayload builds the payload for the secured_service // doubly_secure endpoint from CLI flags. func BuildDoublySecurePayload(securedServiceDoublySecureKey string, securedServiceDoublySecureToken string) (*securedservice.DoublySecurePayload, error) { diff --git a/security/multiauth/gen/http/secured_service/client/client.go b/security/multiauth/gen/http/secured_service/client/client.go index c96883545..2bcafd0d2 100644 --- a/security/multiauth/gen/http/secured_service/client/client.go +++ b/security/multiauth/gen/http/secured_service/client/client.go @@ -23,6 +23,10 @@ type Client struct { // Secure Doer is the HTTP client used to make requests to the secure endpoint. SecureDoer goahttp.Doer + // BearerSecure Doer is the HTTP client used to make requests to the + // bearer_secure endpoint. + BearerSecureDoer goahttp.Doer + // DoublySecure Doer is the HTTP client used to make requests to the // doubly_secure endpoint. DoublySecureDoer goahttp.Doer @@ -54,6 +58,7 @@ func NewClient( return &Client{ SigninDoer: doer, SecureDoer: doer, + BearerSecureDoer: doer, DoublySecureDoer: doer, AlsoDoublySecureDoer: doer, RestoreResponseBody: restoreBody, @@ -112,6 +117,30 @@ func (c *Client) Secure() goa.Endpoint { } } +// BearerSecure returns an endpoint that makes HTTP requests to the +// secured_service service bearer_secure server. +func (c *Client) BearerSecure() goa.Endpoint { + var ( + encodeRequest = EncodeBearerSecureRequest(c.encoder) + decodeResponse = DecodeBearerSecureResponse(c.decoder, c.RestoreResponseBody) + ) + return func(ctx context.Context, v any) (any, error) { + req, err := c.BuildBearerSecureRequest(ctx, v) + if err != nil { + return nil, err + } + err = encodeRequest(req, v) + if err != nil { + return nil, err + } + resp, err := c.BearerSecureDoer.Do(req) + if err != nil { + return nil, goahttp.ErrRequestError("secured_service", "bearer_secure", err) + } + return decodeResponse(resp) + } +} + // DoublySecure returns an endpoint that makes HTTP requests to the // secured_service service doubly_secure server. func (c *Client) DoublySecure() goa.Endpoint { diff --git a/security/multiauth/gen/http/secured_service/client/encode_decode.go b/security/multiauth/gen/http/secured_service/client/encode_decode.go index 0f1e85162..00961b9b7 100644 --- a/security/multiauth/gen/http/secured_service/client/encode_decode.go +++ b/security/multiauth/gen/http/secured_service/client/encode_decode.go @@ -200,6 +200,100 @@ func DecodeSecureResponse(decoder func(*http.Response) goahttp.Decoder, restoreB } } +// BuildBearerSecureRequest instantiates a HTTP request object with method and +// path set to call the "secured_service" service "bearer_secure" endpoint +func (c *Client) BuildBearerSecureRequest(ctx context.Context, v any) (*http.Request, error) { + u := &url.URL{Scheme: c.scheme, Host: c.host, Path: BearerSecureSecuredServicePath()} + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, goahttp.ErrInvalidURL("secured_service", "bearer_secure", u.String(), err) + } + if ctx != nil { + req = req.WithContext(ctx) + } + + return req, nil +} + +// EncodeBearerSecureRequest returns an encoder for requests sent to the +// secured_service bearer_secure server. +func EncodeBearerSecureRequest(encoder func(*http.Request) goahttp.Encoder) func(*http.Request, any) error { + return func(req *http.Request, v any) error { + p, ok := v.(*securedservice.BearerSecurePayload) + if !ok { + return goahttp.ErrInvalidType("secured_service", "bearer_secure", "*securedservice.BearerSecurePayload", v) + } + { + head := p.BearerToken + if !strings.Contains(head, " ") { + req.Header.Set("Authorization", "Bearer "+head) + } else { + req.Header.Set("Authorization", head) + } + } + return nil + } +} + +// DecodeBearerSecureResponse returns a decoder for responses returned by the +// secured_service bearer_secure endpoint. restoreBody controls whether the +// response body should be restored after having been read. +// DecodeBearerSecureResponse may return the following errors: +// - "invalid-scopes" (type securedservice.InvalidScopes): http.StatusForbidden +// - "unauthorized" (type securedservice.Unauthorized): http.StatusUnauthorized +// - error: internal error +func DecodeBearerSecureResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (any, error) { + return func(resp *http.Response) (any, error) { + if restoreBody { + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + defer func() { + resp.Body = io.NopCloser(bytes.NewBuffer(b)) + }() + } else { + defer resp.Body.Close() + } + switch resp.StatusCode { + case http.StatusOK: + var ( + body string + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("secured_service", "bearer_secure", err) + } + return body, nil + case http.StatusForbidden: + var ( + body string + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("secured_service", "bearer_secure", err) + } + return nil, NewBearerSecureInvalidScopes(body) + case http.StatusUnauthorized: + var ( + body string + err error + ) + err = decoder(resp).Decode(&body) + if err != nil { + return nil, goahttp.ErrDecodingError("secured_service", "bearer_secure", err) + } + return nil, NewBearerSecureUnauthorized(body) + default: + body, _ := io.ReadAll(resp.Body) + return nil, goahttp.ErrInvalidResponse("secured_service", "bearer_secure", resp.StatusCode, string(body)) + } + } +} + // BuildDoublySecureRequest instantiates a HTTP request object with method and // path set to call the "secured_service" service "doubly_secure" endpoint func (c *Client) BuildDoublySecureRequest(ctx context.Context, v any) (*http.Request, error) { diff --git a/security/multiauth/gen/http/secured_service/client/paths.go b/security/multiauth/gen/http/secured_service/client/paths.go index fa2f3d37d..682f94690 100644 --- a/security/multiauth/gen/http/secured_service/client/paths.go +++ b/security/multiauth/gen/http/secured_service/client/paths.go @@ -17,6 +17,11 @@ func SecureSecuredServicePath() string { return "/secure" } +// BearerSecureSecuredServicePath returns the URL path to the secured_service service bearer_secure HTTP endpoint. +func BearerSecureSecuredServicePath() string { + return "/bearer" +} + // DoublySecureSecuredServicePath returns the URL path to the secured_service service doubly_secure HTTP endpoint. func DoublySecureSecuredServicePath() string { return "/secure" diff --git a/security/multiauth/gen/http/secured_service/client/types.go b/security/multiauth/gen/http/secured_service/client/types.go index da25bd17e..62c20cabe 100644 --- a/security/multiauth/gen/http/secured_service/client/types.go +++ b/security/multiauth/gen/http/secured_service/client/types.go @@ -21,15 +21,18 @@ type SigninResponseBody struct { APIKey *string `form:"api_key,omitempty" json:"api_key,omitempty" xml:"api_key,omitempty"` // OAuth2 token OauthToken *string `form:"oauth_token,omitempty" json:"oauth_token,omitempty" xml:"oauth_token,omitempty"` + // Bearer token + BearerToken *string `form:"bearer_token,omitempty" json:"bearer_token,omitempty" xml:"bearer_token,omitempty"` } // NewSigninCredsOK builds a "secured_service" service "signin" endpoint result // from a HTTP "OK" response. func NewSigninCredsOK(body *SigninResponseBody) *securedservice.Creds { v := &securedservice.Creds{ - JWT: *body.JWT, - APIKey: *body.APIKey, - OauthToken: *body.OauthToken, + JWT: *body.JWT, + APIKey: *body.APIKey, + OauthToken: *body.OauthToken, + BearerToken: *body.BearerToken, } return v @@ -59,6 +62,22 @@ func NewSecureUnauthorized(body string) securedservice.Unauthorized { return v } +// NewBearerSecureInvalidScopes builds a secured_service service bearer_secure +// endpoint invalid-scopes error. +func NewBearerSecureInvalidScopes(body string) securedservice.InvalidScopes { + v := securedservice.InvalidScopes(body) + + return v +} + +// NewBearerSecureUnauthorized builds a secured_service service bearer_secure +// endpoint unauthorized error. +func NewBearerSecureUnauthorized(body string) securedservice.Unauthorized { + v := securedservice.Unauthorized(body) + + return v +} + // NewDoublySecureInvalidScopes builds a secured_service service doubly_secure // endpoint invalid-scopes error. func NewDoublySecureInvalidScopes(body string) securedservice.InvalidScopes { @@ -102,5 +121,8 @@ func ValidateSigninResponseBody(body *SigninResponseBody) (err error) { if body.OauthToken == nil { err = goa.MergeErrors(err, goa.MissingFieldError("oauth_token", "body")) } + if body.BearerToken == nil { + err = goa.MergeErrors(err, goa.MissingFieldError("bearer_token", "body")) + } return } diff --git a/security/multiauth/gen/http/secured_service/server/encode_decode.go b/security/multiauth/gen/http/secured_service/server/encode_decode.go index dd49659d7..e3c0929f8 100644 --- a/security/multiauth/gen/http/secured_service/server/encode_decode.go +++ b/security/multiauth/gen/http/secured_service/server/encode_decode.go @@ -154,6 +154,77 @@ func EncodeSecureError(encoder func(context.Context, http.ResponseWriter) goahtt } } +// EncodeBearerSecureResponse returns an encoder for responses returned by the +// secured_service bearer_secure endpoint. +func EncodeBearerSecureResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { + return func(ctx context.Context, w http.ResponseWriter, v any) error { + res, _ := v.(string) + enc := encoder(ctx, w) + body := res + w.WriteHeader(http.StatusOK) + return enc.Encode(body) + } +} + +// DecodeBearerSecureRequest returns a decoder for requests sent to the +// secured_service bearer_secure endpoint. +func DecodeBearerSecureRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (*securedservice.BearerSecurePayload, error) { + return func(r *http.Request) (*securedservice.BearerSecurePayload, error) { + var payload *securedservice.BearerSecurePayload + var ( + bearerToken string + err error + ) + bearerToken = r.Header.Get("Authorization") + if bearerToken == "" { + err = goa.MergeErrors(err, goa.MissingFieldError("bearer_token", "header")) + } + if err != nil { + return payload, err + } + payload = NewBearerSecurePayload(bearerToken) + if strings.Contains(payload.BearerToken, " ") { + // Remove authorization scheme prefix (e.g. "Bearer") + cred := strings.SplitN(payload.BearerToken, " ", 2)[1] + payload.BearerToken = cred + } + + return payload, nil + } +} + +// EncodeBearerSecureError returns an encoder for errors returned by the +// bearer_secure secured_service endpoint. +func EncodeBearerSecureError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error { + encodeError := goahttp.ErrorEncoder(encoder, formatter) + return func(ctx context.Context, w http.ResponseWriter, v error) error { + var en goa.GoaErrorNamer + if !errors.As(v, &en) { + return encodeError(ctx, w, v) + } + switch en.GoaErrorName() { + case "invalid-scopes": + var res securedservice.InvalidScopes + errors.As(v, &res) + enc := encoder(ctx, w) + body := res + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusForbidden) + return enc.Encode(body) + case "unauthorized": + var res securedservice.Unauthorized + errors.As(v, &res) + enc := encoder(ctx, w) + body := res + w.Header().Set("goa-error", res.GoaErrorName()) + w.WriteHeader(http.StatusUnauthorized) + return enc.Encode(body) + default: + return encodeError(ctx, w, v) + } + } +} + // EncodeDoublySecureResponse returns an encoder for responses returned by the // secured_service doubly_secure endpoint. func EncodeDoublySecureResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error { diff --git a/security/multiauth/gen/http/secured_service/server/paths.go b/security/multiauth/gen/http/secured_service/server/paths.go index fc841c35e..581e6d3b4 100644 --- a/security/multiauth/gen/http/secured_service/server/paths.go +++ b/security/multiauth/gen/http/secured_service/server/paths.go @@ -17,6 +17,11 @@ func SecureSecuredServicePath() string { return "/secure" } +// BearerSecureSecuredServicePath returns the URL path to the secured_service service bearer_secure HTTP endpoint. +func BearerSecureSecuredServicePath() string { + return "/bearer" +} + // DoublySecureSecuredServicePath returns the URL path to the secured_service service doubly_secure HTTP endpoint. func DoublySecureSecuredServicePath() string { return "/secure" diff --git a/security/multiauth/gen/http/secured_service/server/server.go b/security/multiauth/gen/http/secured_service/server/server.go index 8a9ebf7d1..5fc5b29a0 100644 --- a/security/multiauth/gen/http/secured_service/server/server.go +++ b/security/multiauth/gen/http/secured_service/server/server.go @@ -21,6 +21,7 @@ type Server struct { Mounts []*MountPoint Signin http.Handler Secure http.Handler + BearerSecure http.Handler DoublySecure http.Handler AlsoDoublySecure http.Handler } @@ -54,11 +55,13 @@ func New( Mounts: []*MountPoint{ {"Signin", "POST", "/signin"}, {"Secure", "GET", "/secure"}, + {"BearerSecure", "GET", "/bearer"}, {"DoublySecure", "PUT", "/secure"}, {"AlsoDoublySecure", "POST", "/secure"}, }, Signin: NewSigninHandler(e.Signin, mux, decoder, encoder, errhandler, formatter), Secure: NewSecureHandler(e.Secure, mux, decoder, encoder, errhandler, formatter), + BearerSecure: NewBearerSecureHandler(e.BearerSecure, mux, decoder, encoder, errhandler, formatter), DoublySecure: NewDoublySecureHandler(e.DoublySecure, mux, decoder, encoder, errhandler, formatter), AlsoDoublySecure: NewAlsoDoublySecureHandler(e.AlsoDoublySecure, mux, decoder, encoder, errhandler, formatter), } @@ -71,6 +74,7 @@ func (s *Server) Service() string { return "secured_service" } func (s *Server) Use(m func(http.Handler) http.Handler) { s.Signin = m(s.Signin) s.Secure = m(s.Secure) + s.BearerSecure = m(s.BearerSecure) s.DoublySecure = m(s.DoublySecure) s.AlsoDoublySecure = m(s.AlsoDoublySecure) } @@ -82,6 +86,7 @@ func (s *Server) MethodNames() []string { return securedservice.MethodNames[:] } func Mount(mux goahttp.Muxer, h *Server) { MountSigninHandler(mux, h.Signin) MountSecureHandler(mux, h.Secure) + MountBearerSecureHandler(mux, h.BearerSecure) MountDoublySecureHandler(mux, h.DoublySecure) MountAlsoDoublySecureHandler(mux, h.AlsoDoublySecure) } @@ -197,6 +202,59 @@ func NewSecureHandler( }) } +// MountBearerSecureHandler configures the mux to serve the "secured_service" +// service "bearer_secure" endpoint. +func MountBearerSecureHandler(mux goahttp.Muxer, h http.Handler) { + f, ok := h.(http.HandlerFunc) + if !ok { + f = func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + } + } + mux.Handle("GET", "/bearer", f) +} + +// NewBearerSecureHandler creates a HTTP handler which loads the HTTP request +// and calls the "secured_service" service "bearer_secure" endpoint. +func NewBearerSecureHandler( + endpoint goa.Endpoint, + mux goahttp.Muxer, + decoder func(*http.Request) goahttp.Decoder, + encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, + errhandler func(context.Context, http.ResponseWriter, error), + formatter func(ctx context.Context, err error) goahttp.Statuser, +) http.Handler { + var ( + decodeRequest = DecodeBearerSecureRequest(mux, decoder) + encodeResponse = EncodeBearerSecureResponse(encoder) + encodeError = EncodeBearerSecureError(encoder, formatter) + ) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept")) + ctx = context.WithValue(ctx, goa.MethodKey, "bearer_secure") + ctx = context.WithValue(ctx, goa.ServiceKey, "secured_service") + payload, err := decodeRequest(r) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + res, err := endpoint(ctx, payload) + if err != nil { + if err := encodeError(ctx, w, err); err != nil && errhandler != nil { + errhandler(ctx, w, err) + } + return + } + if err := encodeResponse(ctx, w, res); err != nil { + if errhandler != nil { + errhandler(ctx, w, err) + } + } + }) +} + // MountDoublySecureHandler configures the mux to serve the "secured_service" // service "doubly_secure" endpoint. func MountDoublySecureHandler(mux goahttp.Muxer, h http.Handler) { diff --git a/security/multiauth/gen/http/secured_service/server/types.go b/security/multiauth/gen/http/secured_service/server/types.go index eef60f1a3..c20baab1a 100644 --- a/security/multiauth/gen/http/secured_service/server/types.go +++ b/security/multiauth/gen/http/secured_service/server/types.go @@ -20,15 +20,18 @@ type SigninResponseBody struct { APIKey string `form:"api_key" json:"api_key" xml:"api_key"` // OAuth2 token OauthToken string `form:"oauth_token" json:"oauth_token" xml:"oauth_token"` + // Bearer token + BearerToken string `form:"bearer_token" json:"bearer_token" xml:"bearer_token"` } // NewSigninResponseBody builds the HTTP response body from the result of the // "signin" endpoint of the "secured_service" service. func NewSigninResponseBody(res *securedservice.Creds) *SigninResponseBody { body := &SigninResponseBody{ - JWT: res.JWT, - APIKey: res.APIKey, - OauthToken: res.OauthToken, + JWT: res.JWT, + APIKey: res.APIKey, + OauthToken: res.OauthToken, + BearerToken: res.BearerToken, } return body } @@ -49,6 +52,15 @@ func NewSecurePayload(fail *bool, token string) *securedservice.SecurePayload { return v } +// NewBearerSecurePayload builds a secured_service service bearer_secure +// endpoint payload. +func NewBearerSecurePayload(bearerToken string) *securedservice.BearerSecurePayload { + v := &securedservice.BearerSecurePayload{} + v.BearerToken = bearerToken + + return v +} + // NewDoublySecurePayload builds a secured_service service doubly_secure // endpoint payload. func NewDoublySecurePayload(key string, token string) *securedservice.DoublySecurePayload { diff --git a/security/multiauth/gen/secured_service/client.go b/security/multiauth/gen/secured_service/client.go index 2b0477c53..dfa7bf18b 100644 --- a/security/multiauth/gen/secured_service/client.go +++ b/security/multiauth/gen/secured_service/client.go @@ -17,15 +17,17 @@ import ( type Client struct { SigninEndpoint goa.Endpoint SecureEndpoint goa.Endpoint + BearerSecureEndpoint goa.Endpoint DoublySecureEndpoint goa.Endpoint AlsoDoublySecureEndpoint goa.Endpoint } // NewClient initializes a "secured_service" service client given the endpoints. -func NewClient(signin, secure, doublySecure, alsoDoublySecure goa.Endpoint) *Client { +func NewClient(signin, secure, bearerSecure, doublySecure, alsoDoublySecure goa.Endpoint) *Client { return &Client{ SigninEndpoint: signin, SecureEndpoint: secure, + BearerSecureEndpoint: bearerSecure, DoublySecureEndpoint: doublySecure, AlsoDoublySecureEndpoint: alsoDoublySecure, } @@ -58,6 +60,21 @@ func (c *Client) Secure(ctx context.Context, p *SecurePayload) (res string, err return ires.(string), nil } +// BearerSecure calls the "bearer_secure" endpoint of the "secured_service" +// service. +// BearerSecure may return the following errors: +// - "invalid-scopes" (type InvalidScopes) +// - "unauthorized" (type Unauthorized) +// - error: internal error +func (c *Client) BearerSecure(ctx context.Context, p *BearerSecurePayload) (res string, err error) { + var ires any + ires, err = c.BearerSecureEndpoint(ctx, p) + if err != nil { + return + } + return ires.(string), nil +} + // DoublySecure calls the "doubly_secure" endpoint of the "secured_service" // service. // DoublySecure may return the following errors: diff --git a/security/multiauth/gen/secured_service/endpoints.go b/security/multiauth/gen/secured_service/endpoints.go index 05d60f457..d957616c4 100644 --- a/security/multiauth/gen/secured_service/endpoints.go +++ b/security/multiauth/gen/secured_service/endpoints.go @@ -18,6 +18,7 @@ import ( type Endpoints struct { Signin goa.Endpoint Secure goa.Endpoint + BearerSecure goa.Endpoint DoublySecure goa.Endpoint AlsoDoublySecure goa.Endpoint } @@ -30,6 +31,7 @@ func NewEndpoints(s Service) *Endpoints { return &Endpoints{ Signin: NewSigninEndpoint(s, a.BasicAuth), Secure: NewSecureEndpoint(s, a.JWTAuth), + BearerSecure: NewBearerSecureEndpoint(s, a.BearerAuth), DoublySecure: NewDoublySecureEndpoint(s, a.JWTAuth, a.APIKeyAuth), AlsoDoublySecure: NewAlsoDoublySecureEndpoint(s, a.JWTAuth, a.APIKeyAuth, a.OAuth2Auth, a.BasicAuth), } @@ -40,6 +42,7 @@ func NewEndpoints(s Service) *Endpoints { func (e *Endpoints) Use(m func(goa.Endpoint) goa.Endpoint) { e.Signin = m(e.Signin) e.Secure = m(e.Secure) + e.BearerSecure = m(e.BearerSecure) e.DoublySecure = m(e.DoublySecure) e.AlsoDoublySecure = m(e.AlsoDoublySecure) } @@ -82,6 +85,25 @@ func NewSecureEndpoint(s Service, authJWTFn security.AuthJWTFunc) goa.Endpoint { } } +// NewBearerSecureEndpoint returns an endpoint function that calls the method +// "bearer_secure" of service "secured_service". +func NewBearerSecureEndpoint(s Service, authBearerFn security.AuthBearerFunc) goa.Endpoint { + return func(ctx context.Context, req any) (any, error) { + p := req.(*BearerSecurePayload) + var err error + sc := security.BearerScheme{ + Name: "bearer", + Scopes: []string{"api:read", "api:write"}, + RequiredScopes: []string{"api:read"}, + } + ctx, err = authBearerFn(ctx, p.BearerToken, &sc) + if err != nil { + return nil, err + } + return s.BearerSecure(ctx, p) + } +} + // NewDoublySecureEndpoint returns an endpoint function that calls the method // "doubly_secure" of service "secured_service". func NewDoublySecureEndpoint(s Service, authJWTFn security.AuthJWTFunc, authAPIKeyFn security.AuthAPIKeyFunc) goa.Endpoint { diff --git a/security/multiauth/gen/secured_service/service.go b/security/multiauth/gen/secured_service/service.go index c4958f67f..0a9f8f311 100644 --- a/security/multiauth/gen/secured_service/service.go +++ b/security/multiauth/gen/secured_service/service.go @@ -20,6 +20,8 @@ type Service interface { Signin(context.Context, *SigninPayload) (res *Creds, err error) // This action is secured with the jwt scheme Secure(context.Context, *SecurePayload) (res string, err error) + // This action is secured with the bearer scheme + BearerSecure(context.Context, *BearerSecurePayload) (res string, err error) // This action is secured with the jwt scheme and also requires an API key // query string. DoublySecure(context.Context, *DoublySecurePayload) (res string, err error) @@ -34,6 +36,8 @@ type Auther interface { BasicAuth(ctx context.Context, user, pass string, schema *security.BasicScheme) (context.Context, error) // JWTAuth implements the authorization logic for the JWT security scheme. JWTAuth(ctx context.Context, token string, schema *security.JWTScheme) (context.Context, error) + // BearerAuth implements the authorization logic for the Bearer security scheme. + BearerAuth(ctx context.Context, token string, schema *security.BearerScheme) (context.Context, error) // APIKeyAuth implements the authorization logic for the APIKey security scheme. APIKeyAuth(ctx context.Context, key string, schema *security.APIKeyScheme) (context.Context, error) // OAuth2Auth implements the authorization logic for the OAuth2 security scheme. @@ -54,7 +58,7 @@ const ServiceName = "secured_service" // MethodNames lists the service method names as defined in the design. These // are the same values that are set in the endpoint request contexts under the // MethodKey key. -var MethodNames = [4]string{"signin", "secure", "doubly_secure", "also_doubly_secure"} +var MethodNames = [5]string{"signin", "secure", "bearer_secure", "doubly_secure", "also_doubly_secure"} // AlsoDoublySecurePayload is the payload type of the secured_service service // also_doubly_secure method. @@ -70,6 +74,13 @@ type AlsoDoublySecurePayload struct { OauthToken *string } +// BearerSecurePayload is the payload type of the secured_service service +// bearer_secure method. +type BearerSecurePayload struct { + // Bearer token used for authentication + BearerToken string +} + // Creds is the result type of the secured_service service signin method. type Creds struct { // JWT token @@ -78,6 +89,8 @@ type Creds struct { APIKey string // OAuth2 token OauthToken string + // Bearer token + BearerToken string } // DoublySecurePayload is the payload type of the secured_service service diff --git a/security/multiauth/secured_service.go b/security/multiauth/secured_service.go index 07a5428fa..5d6c104c8 100644 --- a/security/multiauth/secured_service.go +++ b/security/multiauth/secured_service.go @@ -88,6 +88,24 @@ func (s *securedServicesrvc) JWTAuth(ctx context.Context, token string, scheme * return ctx, nil } +// BearerAuth implements the authorization logic for service "secured_service" +// for the "bearer" security scheme. +func (s *securedServicesrvc) BearerAuth(ctx context.Context, token string, scheme *security.BearerScheme) (context.Context, error) { + if token != "opaque-session-token" { + return ctx, ErrInvalidToken + } + + scopesInToken := []string{"api:read", "api:write"} + if err := scheme.Validate(scopesInToken); err != nil { + return ctx, securedservice.InvalidScopes(err.Error()) + } + + ctx = contextWithAuthInfo(ctx, authInfo{ + bearer: token, + }) + return ctx, nil +} + // APIKeyAuth implements the authorization logic for service "secured_service" // for the "api_key" security scheme. func (s *securedServicesrvc) APIKeyAuth(ctx context.Context, key string, scheme *security.APIKeyScheme) (context.Context, error) { @@ -153,9 +171,10 @@ func (s *securedServicesrvc) Signin(ctx context.Context, p *securedservice.Signi return nil, err } return &securedservice.Creds{ - JWT: t, - OauthToken: t, - APIKey: "my_awesome_api_key", + JWT: t, + OauthToken: t, + APIKey: "my_awesome_api_key", + BearerToken: "opaque-session-token", }, nil } @@ -172,6 +191,15 @@ func (s *securedServicesrvc) Secure(ctx context.Context, p *securedservice.Secur return } +// This action is secured with the bearer scheme +func (s *securedServicesrvc) BearerSecure(ctx context.Context, p *securedservice.BearerSecurePayload) (res string, err error) { + res = fmt.Sprintf("User authorized using bearer token %q", p.BearerToken) + authInfo := contextAuthInfo(ctx) + s.logger.Print(res) + s.logger.Printf("%v", authInfo) + return +} + // This action is secured with the jwt scheme and also requires an API key // query string. func (s *securedServicesrvc) DoublySecure(ctx context.Context, p *securedservice.DoublySecurePayload) (res string, err error) {