diff --git a/.golangci.yml b/.golangci.yml index ae5b395..ca3c42d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -46,8 +46,13 @@ linters: - varcheck # abandoned - wrapcheck # don't _always_ need to wrap errors - wsl # generous whitespace violates house style + - nonamedreturns # named returns help document return types issues: exclude: # Don't ban use of fmt.Errorf to create new errors, but the remaining # checks from err113 are useful. - "err113: do not define dynamic errors.*" + exclude-rules: + # We need to init a global in-mem HTTP server for testable examples. + - path: examples_test.go + linters: [gocritic, gochecknoglobals, gosec, exhaustruct] diff --git a/README.md b/README.md index 06d690c..92c4534 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ authn-go =============== +Authn provides authentication middleware for [connect](https://connectrpc.com/). It is designed to work with any authentication scheme, including HTTP Basic Authentication, OAuth2, and custom schemes. It covers both Unary and Streaming RPCs and works with both gRPC and Connect protocols. + ## Status: Alpha This project is currently in alpha. The API should be considered unstable and likely to change. diff --git a/authn-go.go b/authn-go.go deleted file mode 100644 index 3cb330f..0000000 --- a/authn-go.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2023 Buf Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package authn-go diff --git a/authn.go b/authn.go new file mode 100644 index 0000000..7b18f6e --- /dev/null +++ b/authn.go @@ -0,0 +1,179 @@ +// Copyright 2023 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package authn provides authentication middleware for [connect]. +package authn + +import ( + "context" + "crypto/tls" + "fmt" + "net/http" + "strings" + + "connectrpc.com/connect" +) + +type key int + +const infoKey key = iota + +// An AuthFunc authenticates an RPC. The function must return an error if the +// request cannot be authenticated. The error is typically produced with +// [Errorf], but any error will do. +// +// If requests are successfully authenticated, the authentication function may +// return some information about the authenticated caller (or nil). +// Implementations must be safe to call concurrently. +type AuthFunc func(ctx context.Context, req Request) (any, error) + +// SetInfo attaches authentication information to the context. It's often +// useful in tests. +func SetInfo(ctx context.Context, info any) context.Context { + if info == nil { + return ctx + } + return context.WithValue(ctx, infoKey, info) +} + +// GetInfo retrieves authentication information, if any, from the request +// context. +func GetInfo(ctx context.Context) any { + return ctx.Value(infoKey) +} + +// WithoutInfo strips the authentication information, if any, from the provided +// context. +func WithoutInfo(ctx context.Context) context.Context { + return context.WithValue(ctx, infoKey, nil) +} + +// Errorf is a convenience function that returns an error coded with +// [connect.CodeUnauthenticated]. +func Errorf(template string, args ...any) *connect.Error { + return connect.NewError(connect.CodeUnauthenticated, fmt.Errorf(template, args...)) +} + +// Request describes a single RPC invocation. +type Request struct { + request *http.Request +} + +// BasicAuth returns the username and password provided in the request's +// Authorization header, if any. +func (r Request) BasicAuth() (username string, password string, ok bool) { + return r.request.BasicAuth() +} + +// Procedure returns the RPC procedure name, in the form "/service/method". If +// the request path does not contain a procedure name, the entire path is +// returned. +func (r Request) Procedure() string { + path := strings.TrimSuffix(r.request.URL.Path, "/") + ultimate := strings.LastIndex(path, "/") + if ultimate < 0 { + return r.request.URL.Path + } + penultimate := strings.LastIndex(path[:ultimate], "/") + if penultimate < 0 { + return r.request.URL.Path + } + procedure := path[penultimate:] + if len(procedure) < 4 { // two slashes + service + method + return r.request.URL.Path + } + return procedure +} + +// ClientAddr returns the client address, in IP:port format. +func (r Request) ClientAddr() string { + return r.request.RemoteAddr +} + +// Protocol returns the RPC protocol. It is one of connect.ProtocolConnect, +// connect.ProtocolGRPC, or connect.ProtocolGRPCWeb. +func (r Request) Protocol() string { + ct := r.request.Header.Get("Content-Type") + switch { + case strings.HasPrefix(ct, "application/grpc-web"): + return connect.ProtocolGRPCWeb + case strings.HasPrefix(ct, "application/grpc"): + return connect.ProtocolGRPC + default: + return connect.ProtocolConnect + } +} + +// Header returns the HTTP request headers. +func (r Request) Header() http.Header { + return r.request.Header +} + +// TLS returns the TLS connection state, if any. It may be nil if the connection +// is not using TLS. +func (r Request) TLS() *tls.ConnectionState { + return r.request.TLS +} + +// Middleware is server-side HTTP middleware that authenticates RPC requests. +// In addition to rejecting unauthenticated requests, it can optionally attach +// arbitrary information to the context of authenticated requests. Any non-RPC +// requests (as determined by their Content-Type) are forwarded directly to the +// wrapped handler without authentication. +// +// Middleware operates at a lower level than [Interceptor]. For most +// applications, Middleware is preferable because it defers decompressing and +// unmarshaling the request until after the caller has been authenticated. +type Middleware struct { + auth AuthFunc + errW *connect.ErrorWriter +} + +// NewMiddleware constructs HTTP middleware using the supplied authentication +// function. If authentication succeeds, the authentication information (if +// any) will be attached to the context. Subsequent HTTP middleware, all RPC +// interceptors, and application code may access it with [GetInfo]. +// +// In order to properly identify RPC requests and marshal errors, applications +// must pass NewMiddleware the same handler options used when constructing +// Connect handlers. +func NewMiddleware(auth AuthFunc, opts ...connect.HandlerOption) *Middleware { + return &Middleware{ + auth: auth, + errW: connect.NewErrorWriter(opts...), + } +} + +// Wrap returns an HTTP handler that authenticates RPC requests before +// forwarding them to handler. If handler is not an RPC request, it is forwarded +// directly, without authentication. +func (m *Middleware) Wrap(handler http.Handler) http.Handler { + return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + if !m.errW.IsSupported(request) { + handler.ServeHTTP(writer, request) + return // not an RPC request + } + ctx := request.Context() + info, err := m.auth(ctx, Request{request: request}) + if err != nil { + _ = m.errW.Write(writer, request, err) + return + } + if info != nil { + ctx = SetInfo(ctx, info) + request = request.WithContext(ctx) + } + handler.ServeHTTP(writer, request) + }) +} diff --git a/authn_test.go b/authn_test.go new file mode 100644 index 0000000..2d936ed --- /dev/null +++ b/authn_test.go @@ -0,0 +1,107 @@ +// Copyright 2023 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package authn_test + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/bufbuild/authn-go" + "github.com/stretchr/testify/assert" +) + +const ( + hero = "Ali Baba" + passphrase = "opensesame" +) + +func TestMiddleware(t *testing.T) { + t.Parallel() + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("Check-Info") != "" { + assertInfo(r.Context(), t) + } + _, _ = io.WriteString(w, "ok") + }) + handler := authn.NewMiddleware(authenticate).Wrap(mux) + server := httptest.NewServer(handler) + + assertResponse := func(headers http.Header, expectCode int) { + req, err := http.NewRequestWithContext( + context.Background(), + http.MethodPost, + server.URL+"/empty.v1/GetEmpty", + strings.NewReader("{}"), + ) + assert.Nil(t, err) + for k, vals := range headers { + for _, v := range vals { + req.Header.Add(k, v) + } + } + res, err := server.Client().Do(req) + assert.Nil(t, err) + assert.Equal(t, res.StatusCode, expectCode) + assert.Nil(t, res.Body.Close()) + } + // Middleware should ignore non-RPC requests. + assertResponse(http.Header{}, 200) + // RPCs without the right bearer token should be rejected. + assertResponse( + http.Header{"Content-Type": []string{"application/json"}}, + http.StatusUnauthorized, + ) + // RPCs with the right token should be allowed. + assertResponse( + http.Header{ + "Content-Type": []string{"application/json"}, + "Authorization": []string{"Bearer " + passphrase}, + "Check-Info": []string{"1"}, // verify that auth info is attached to context + }, + http.StatusOK, + ) +} + +func assertInfo(ctx context.Context, tb testing.TB) { + tb.Helper() + info := authn.GetInfo(ctx) + if info == nil { + tb.Fatal("no authentication info") + } + name, ok := info.(string) + assert.True(tb, ok, "got info of type %T, expected string", info) + assert.Equal(tb, name, hero) + if id := authn.GetInfo(authn.WithoutInfo(ctx)); id != nil { + tb.Fatalf("got info %v after WithoutInfo", id) + } +} + +func authenticate(_ context.Context, req authn.Request) (any, error) { + parts := strings.SplitN(req.Header().Get("Authorization"), " ", 2) + if len(parts) < 2 || parts[0] != "Bearer" { + err := authn.Errorf("expected Bearer authentication scheme") + err.Meta().Set("WWW-Authenticate", "Bearer") + return nil, err + } + if tok := parts[1]; tok != passphrase { + return nil, authn.Errorf("%q is not the magic passphrase", tok) + } + return hero, nil +} diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..2c1cb92 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,12 @@ +version: v1 +managed: + enabled: true + go_package_prefix: + default: github.com/bufbuild/authn-go/internal/gen +plugins: + - name: go + out: internal/gen + opt: paths=source_relative + - name: connect-go + out: internal/gen + opt: paths=source_relative diff --git a/buf.work.yaml b/buf.work.yaml new file mode 100644 index 0000000..30f1e1f --- /dev/null +++ b/buf.work.yaml @@ -0,0 +1,3 @@ +version: v1 +directories: + - internal/proto diff --git a/examples_test.go b/examples_test.go new file mode 100644 index 0000000..5f98e88 --- /dev/null +++ b/examples_test.go @@ -0,0 +1,316 @@ +// Copyright 2023 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package authn_test + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/subtle" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "io" + "log" + "math/big" + "net" + "net/http" + "net/http/httptest" + "time" + + "connectrpc.com/connect" + "github.com/bufbuild/authn-go" + pingv1 "github.com/bufbuild/authn-go/internal/gen/authn/ping/v1" + "github.com/bufbuild/authn-go/internal/gen/authn/ping/v1/pingv1connect" +) + +func Example_basicAuth() { + // This example demonstrates how to use basic auth with the authn middleware. + // The example uses the ping service from the authn-go/internal/gen/authn/ping/v1 + // package, but the same approach can be used with any service. + mux := http.NewServeMux() + mux.Handle(pingv1connect.NewPingServiceHandler(pingService{})) + + // Wrap the server with authn middleware. + auth := authn.NewMiddleware( + func(_ context.Context, req authn.Request) (any, error) { + username, password, ok := req.BasicAuth() + if !ok { + // If authentication fails, we return an error. authn.Errorf is a + // convenient shortcut to produce an error coded with + // connect.CodeUnauthenticated. + return nil, authn.Errorf("invalid authorization") + } + // Check username and password against a database. In this example, we + // hardcode the credentials. + if subtle.ConstantTimeCompare([]byte(username), []byte("Ali Baba")) != 1 { + return nil, authn.Errorf("invalid username") + } + if subtle.ConstantTimeCompare([]byte(password), []byte("opensesame")) != 1 { + return nil, authn.Errorf("invalid password") + } + // Once we've authenticated the request, we can return some information about + // the client. That information gets attached to the context passed to + // subsequent interceptors and our service implementation. + fmt.Printf("verified user: %s\n", username) + return username, nil + }, + ) + handler := auth.Wrap(mux) + + // Start the server. + server := httptest.NewServer(handler) + defer server.Close() + + // Create a client for the server. + client := pingv1connect.NewPingServiceClient( + server.Client(), + server.URL, + ) + req := connect.NewRequest(&pingv1.PingRequest{ + Text: "hello", + }) + // Attach a basic auth authorization header to the request. + authToken := base64.StdEncoding.EncodeToString([]byte("Ali Baba:opensesame")) + req.Header().Add("Authorization", "Basic "+authToken) + rsp, err := client.Ping(context.Background(), req) + if err != nil { + log.Fatal(err) + } + fmt.Printf("got response: %s\n", rsp.Msg.Text) + // Output: + // verified user: Ali Baba + // got response: hello +} + +func Example_mutualTLS() { + // This example demonstrates how to use mutual TLS with the authn middleware. + // The example uses the ping service from the authn-go/internal/gen/authn/ping/v1 + // package, but the same approach can be used with any service. + + // Create the certificate authority. The server and client will both use this + // certificate authority to verify each other's certificates. + // + // This example uses a self-signed certificate, so + // we need to use a custom root CA pool. In production, you would use a + // certificate signed by a trusted CA. + certPool := x509.NewCertPool() + caCertPEM, caKeyPEM, err := createCertificateAuthority() + if err != nil { + log.Fatal(err) + } + if ok := certPool.AppendCertsFromPEM(caCertPEM); !ok { + log.Fatal("failed to append certs to pool") + } + + // Create the server certificate. The server will use this certificate to + // authenticate itself to the client. We will need to create a custom TLS + // configuration for the server to use this certificate. + certPEM, keyPEM, err := createCertificate(caCertPEM, caKeyPEM, "Server") + if err != nil { + log.Fatal(err) + } + certificate, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + log.Fatal(err) + } + tlsConfig := &tls.Config{ + ClientAuth: tls.RequireAndVerifyClientCert, + Certificates: []tls.Certificate{certificate}, + ClientCAs: certPool, + MinVersion: tls.VersionTLS12, + } + + mux := http.NewServeMux() + mux.Handle(pingv1connect.NewPingServiceHandler(pingService{})) + + // Wrap the server with authn middleware. + auth := authn.NewMiddleware( + func(_ context.Context, req authn.Request) (any, error) { + // Get the TLS connection state from the request. + tls := req.TLS() + if tls == nil { + return nil, authn.Errorf("requires TLS certificate") + } + if len(tls.VerifiedChains) == 0 || len(tls.VerifiedChains[0]) == 0 { + return nil, authn.Errorf("could not verify peer certificate") + } + // Check subject common name against configured username. + // In this example, we hardcode the username. + commonName := tls.VerifiedChains[0][0].Subject.CommonName + if commonName != "Client" { + return nil, authn.Errorf("invalid subject common name") + } + fmt.Printf("verified peer certificate: %s\n", commonName) + return commonName, nil + }, + ) + handler := auth.Wrap(mux) + + // Start the server with TLS. + server := httptest.NewUnstartedServer(handler) + server.TLS = tlsConfig + server.StartTLS() + defer server.Close() + + // Create the client certificate. The client will use this certificate to + // authenticate itself to the server. We will need to create a custom TLS + // configuration for the client to use this certificate. + certPEM, keyPEM, err = createCertificate(caCertPEM, caKeyPEM, "Client") + if err != nil { + log.Fatal(err) + } + certificate, err = tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + log.Fatal(err) + } + tlsClientConfig := &tls.Config{ + Certificates: []tls.Certificate{certificate}, + RootCAs: certPool, + MinVersion: tls.VersionTLS12, + } + + // Create the client with the client certificate. + client := pingv1connect.NewPingServiceClient( + &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsClientConfig, + }, + }, + server.URL, + ) + + // Make a request with the created client. + req := connect.NewRequest(&pingv1.PingRequest{ + Text: "hello", + }) + rsp, err := client.Ping(context.Background(), req) + if err != nil { + log.Fatal(err) + } + fmt.Printf("got response: %s\n", rsp.Msg.Text) + // Output: + // verified peer certificate: Client + // got response: hello +} + +func createCertificateAuthority() ([]byte, []byte, error) { + caCert := &x509.Certificate{ + SerialNumber: big.NewInt(2021), + Subject: pkix.Name{ + Organization: []string{"Acme Co"}, + }, + NotBefore: time.Now().AddDate(-1, 0, 0), + NotAfter: time.Now().AddDate(10, 0, 0), + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, nil, err + } + caBytes, err := x509.CreateCertificate(rand.Reader, caCert, caCert, &caPrivKey.PublicKey, caPrivKey) + if err != nil { + return nil, nil, err + } + caPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: caBytes, + }) + caPrivKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey), + }) + return caPEM, caPrivKeyPEM, nil +} + +func createCertificate(caCertPEM, caKeyPEM []byte, commonName string) ([]byte, []byte, error) { + keyPEMBlock, _ := pem.Decode(caKeyPEM) + privateKey, err := x509.ParsePKCS1PrivateKey(keyPEMBlock.Bytes) + if err != nil { + return nil, nil, err + } + certPEMBlock, _ := pem.Decode(caCertPEM) + parent, err := x509.ParseCertificate(certPEMBlock.Bytes) + if err != nil { + return nil, nil, err + } + cert := &x509.Certificate{ + SerialNumber: big.NewInt(1658), + Subject: pkix.Name{ + Organization: []string{"Acme Co"}, + CommonName: commonName, + }, + IPAddresses: []net.IP{ + net.IPv4(127, 0, 0, 1), + net.IPv6loopback, + net.IPv4(0, 0, 0, 0), + net.IPv6zero, + }, + NotBefore: time.Now().AddDate(-1, 0, 0), + NotAfter: time.Now().AddDate(10, 0, 0), + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature, + } + certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, nil, err + } + certBytes, err := x509.CreateCertificate(rand.Reader, cert, parent, &certPrivKey.PublicKey, privateKey) + if err != nil { + return nil, nil, err + } + certPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }) + certPrivKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), + }) + return certPEM, certPrivKeyPEM, nil +} + +type pingService struct{} + +func (pingService) Ping(_ context.Context, req *connect.Request[pingv1.PingRequest]) (*connect.Response[pingv1.PingResponse], error) { + return connect.NewResponse(&pingv1.PingResponse{ + Text: req.Msg.Text, + }), nil +} + +func (pingService) PingStream(_ context.Context, stream *connect.BidiStream[pingv1.PingStreamRequest, pingv1.PingStreamResponse]) error { + for { + req, err := stream.Receive() + if err != nil { + if errors.Is(err, io.EOF) { + return nil + } + return err + } + if err := stream.Send(&pingv1.PingStreamResponse{ + Text: req.Text, + }); err != nil { + return err + } + } +} diff --git a/go.mod b/go.mod index 48153f9..38c8c14 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,12 @@ module github.com/bufbuild/authn-go go 1.19 + +require ( + connectrpc.com/connect v1.12.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8c7d241 --- /dev/null +++ b/go.sum @@ -0,0 +1,17 @@ +connectrpc.com/connect v1.12.0 h1:HwKdOY0lGhhoHdsza+hW55aqHEC64pYpObRNoAgn70g= +connectrpc.com/connect v1.12.0/go.mod h1:3AGaO6RRGMx5IKFfqbe3hvK1NqLosFNP2BxDYTPmNPo= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/gen/authn/ping/v1/ping.pb.go b/internal/gen/authn/ping/v1/ping.pb.go new file mode 100644 index 0000000..d2a6eff --- /dev/null +++ b/internal/gen/authn/ping/v1/ping.pb.go @@ -0,0 +1,376 @@ +// Copyright 2023 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The canonical location for this file is +// https://github.com/bufbuild/authn-go/blob/main/internal/proto/authn/ping/v1/ping.proto. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc (unknown) +// source: authn/ping/v1/ping.proto + +// The authn.ping.v1 package contains a ping service designed to test the +// authn-go implementation. + +package pingv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PingRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` +} + +func (x *PingRequest) Reset() { + *x = PingRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_authn_ping_v1_ping_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingRequest) ProtoMessage() {} + +func (x *PingRequest) ProtoReflect() protoreflect.Message { + mi := &file_authn_ping_v1_ping_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. +func (*PingRequest) Descriptor() ([]byte, []int) { + return file_authn_ping_v1_ping_proto_rawDescGZIP(), []int{0} +} + +func (x *PingRequest) GetText() string { + if x != nil { + return x.Text + } + return "" +} + +type PingResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` +} + +func (x *PingResponse) Reset() { + *x = PingResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_authn_ping_v1_ping_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PingResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingResponse) ProtoMessage() {} + +func (x *PingResponse) ProtoReflect() protoreflect.Message { + mi := &file_authn_ping_v1_ping_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PingResponse.ProtoReflect.Descriptor instead. +func (*PingResponse) Descriptor() ([]byte, []int) { + return file_authn_ping_v1_ping_proto_rawDescGZIP(), []int{1} +} + +func (x *PingResponse) GetText() string { + if x != nil { + return x.Text + } + return "" +} + +type PingStreamRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` +} + +func (x *PingStreamRequest) Reset() { + *x = PingStreamRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_authn_ping_v1_ping_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PingStreamRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingStreamRequest) ProtoMessage() {} + +func (x *PingStreamRequest) ProtoReflect() protoreflect.Message { + mi := &file_authn_ping_v1_ping_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PingStreamRequest.ProtoReflect.Descriptor instead. +func (*PingStreamRequest) Descriptor() ([]byte, []int) { + return file_authn_ping_v1_ping_proto_rawDescGZIP(), []int{2} +} + +func (x *PingStreamRequest) GetText() string { + if x != nil { + return x.Text + } + return "" +} + +type PingStreamResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` +} + +func (x *PingStreamResponse) Reset() { + *x = PingStreamResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_authn_ping_v1_ping_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PingStreamResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingStreamResponse) ProtoMessage() {} + +func (x *PingStreamResponse) ProtoReflect() protoreflect.Message { + mi := &file_authn_ping_v1_ping_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PingStreamResponse.ProtoReflect.Descriptor instead. +func (*PingStreamResponse) Descriptor() ([]byte, []int) { + return file_authn_ping_v1_ping_proto_rawDescGZIP(), []int{3} +} + +func (x *PingStreamResponse) GetText() string { + if x != nil { + return x.Text + } + return "" +} + +var File_authn_ping_v1_ping_proto protoreflect.FileDescriptor + +var file_authn_ping_v1_ping_proto_rawDesc = []byte{ + 0x0a, 0x18, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2f, 0x70, 0x69, 0x6e, 0x67, 0x2f, 0x76, 0x31, 0x2f, + 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x61, 0x75, 0x74, 0x68, + 0x6e, 0x2e, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x22, 0x21, 0x0a, 0x0b, 0x50, 0x69, 0x6e, + 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x22, 0x22, 0x0a, 0x0c, + 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, + 0x22, 0x27, 0x0a, 0x11, 0x50, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x22, 0x28, 0x0a, 0x12, 0x50, 0x69, 0x6e, + 0x67, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, + 0x65, 0x78, 0x74, 0x32, 0xac, 0x01, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x44, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x1a, 0x2e, 0x61, 0x75, + 0x74, 0x68, 0x6e, 0x2e, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2e, + 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x57, 0x0a, 0x0a, 0x50, 0x69, 0x6e, + 0x67, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x20, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2e, + 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, 0x75, 0x74, 0x68, + 0x6e, 0x2e, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x53, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, + 0x30, 0x01, 0x42, 0xb4, 0x01, 0x0a, 0x11, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6e, + 0x2e, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x42, 0x09, 0x50, 0x69, 0x6e, 0x67, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x62, 0x75, 0x66, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6e, + 0x2d, 0x67, 0x6f, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, + 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2f, 0x70, 0x69, 0x6e, 0x67, 0x2f, 0x76, 0x31, 0x3b, 0x70, + 0x69, 0x6e, 0x67, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x41, 0x50, 0x58, 0xaa, 0x02, 0x0d, 0x41, 0x75, + 0x74, 0x68, 0x6e, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0d, 0x41, 0x75, + 0x74, 0x68, 0x6e, 0x5c, 0x50, 0x69, 0x6e, 0x67, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x19, 0x41, 0x75, + 0x74, 0x68, 0x6e, 0x5c, 0x50, 0x69, 0x6e, 0x67, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0f, 0x41, 0x75, 0x74, 0x68, 0x6e, 0x3a, + 0x3a, 0x50, 0x69, 0x6e, 0x67, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_authn_ping_v1_ping_proto_rawDescOnce sync.Once + file_authn_ping_v1_ping_proto_rawDescData = file_authn_ping_v1_ping_proto_rawDesc +) + +func file_authn_ping_v1_ping_proto_rawDescGZIP() []byte { + file_authn_ping_v1_ping_proto_rawDescOnce.Do(func() { + file_authn_ping_v1_ping_proto_rawDescData = protoimpl.X.CompressGZIP(file_authn_ping_v1_ping_proto_rawDescData) + }) + return file_authn_ping_v1_ping_proto_rawDescData +} + +var file_authn_ping_v1_ping_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_authn_ping_v1_ping_proto_goTypes = []interface{}{ + (*PingRequest)(nil), // 0: authn.ping.v1.PingRequest + (*PingResponse)(nil), // 1: authn.ping.v1.PingResponse + (*PingStreamRequest)(nil), // 2: authn.ping.v1.PingStreamRequest + (*PingStreamResponse)(nil), // 3: authn.ping.v1.PingStreamResponse +} +var file_authn_ping_v1_ping_proto_depIdxs = []int32{ + 0, // 0: authn.ping.v1.PingService.Ping:input_type -> authn.ping.v1.PingRequest + 2, // 1: authn.ping.v1.PingService.PingStream:input_type -> authn.ping.v1.PingStreamRequest + 1, // 2: authn.ping.v1.PingService.Ping:output_type -> authn.ping.v1.PingResponse + 3, // 3: authn.ping.v1.PingService.PingStream:output_type -> authn.ping.v1.PingStreamResponse + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] 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 +} + +func init() { file_authn_ping_v1_ping_proto_init() } +func file_authn_ping_v1_ping_proto_init() { + if File_authn_ping_v1_ping_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_authn_ping_v1_ping_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PingRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_authn_ping_v1_ping_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PingResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_authn_ping_v1_ping_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PingStreamRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_authn_ping_v1_ping_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PingStreamResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_authn_ping_v1_ping_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_authn_ping_v1_ping_proto_goTypes, + DependencyIndexes: file_authn_ping_v1_ping_proto_depIdxs, + MessageInfos: file_authn_ping_v1_ping_proto_msgTypes, + }.Build() + File_authn_ping_v1_ping_proto = out.File + file_authn_ping_v1_ping_proto_rawDesc = nil + file_authn_ping_v1_ping_proto_goTypes = nil + file_authn_ping_v1_ping_proto_depIdxs = nil +} diff --git a/internal/gen/authn/ping/v1/pingv1connect/ping.connect.go b/internal/gen/authn/ping/v1/pingv1connect/ping.connect.go new file mode 100644 index 0000000..3904b03 --- /dev/null +++ b/internal/gen/authn/ping/v1/pingv1connect/ping.connect.go @@ -0,0 +1,155 @@ +// Copyright 2023 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The canonical location for this file is +// https://github.com/bufbuild/authn-go/blob/main/internal/proto/authn/ping/v1/ping.proto. + +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: authn/ping/v1/ping.proto + +// The authn.ping.v1 package contains a ping service designed to test the +// authn-go implementation. +package pingv1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1 "github.com/bufbuild/authn-go/internal/gen/authn/ping/v1" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_7_0 + +const ( + // PingServiceName is the fully-qualified name of the PingService service. + PingServiceName = "authn.ping.v1.PingService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // PingServicePingProcedure is the fully-qualified name of the PingService's Ping RPC. + PingServicePingProcedure = "/authn.ping.v1.PingService/Ping" + // PingServicePingStreamProcedure is the fully-qualified name of the PingService's PingStream RPC. + PingServicePingStreamProcedure = "/authn.ping.v1.PingService/PingStream" +) + +// PingServiceClient is a client for the authn.ping.v1.PingService service. +type PingServiceClient interface { + // Ping is a unary RPC that returns the same text that was sent. + Ping(context.Context, *connect.Request[v1.PingRequest]) (*connect.Response[v1.PingResponse], error) + // PingStream is a bidirectional stream of pings. + PingStream(context.Context) *connect.BidiStreamForClient[v1.PingStreamRequest, v1.PingStreamResponse] +} + +// NewPingServiceClient constructs a client for the authn.ping.v1.PingService service. By default, +// it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and +// sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() +// or connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewPingServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) PingServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &pingServiceClient{ + ping: connect.NewClient[v1.PingRequest, v1.PingResponse]( + httpClient, + baseURL+PingServicePingProcedure, + connect.WithIdempotency(connect.IdempotencyNoSideEffects), + connect.WithClientOptions(opts...), + ), + pingStream: connect.NewClient[v1.PingStreamRequest, v1.PingStreamResponse]( + httpClient, + baseURL+PingServicePingStreamProcedure, + opts..., + ), + } +} + +// pingServiceClient implements PingServiceClient. +type pingServiceClient struct { + ping *connect.Client[v1.PingRequest, v1.PingResponse] + pingStream *connect.Client[v1.PingStreamRequest, v1.PingStreamResponse] +} + +// Ping calls authn.ping.v1.PingService.Ping. +func (c *pingServiceClient) Ping(ctx context.Context, req *connect.Request[v1.PingRequest]) (*connect.Response[v1.PingResponse], error) { + return c.ping.CallUnary(ctx, req) +} + +// PingStream calls authn.ping.v1.PingService.PingStream. +func (c *pingServiceClient) PingStream(ctx context.Context) *connect.BidiStreamForClient[v1.PingStreamRequest, v1.PingStreamResponse] { + return c.pingStream.CallBidiStream(ctx) +} + +// PingServiceHandler is an implementation of the authn.ping.v1.PingService service. +type PingServiceHandler interface { + // Ping is a unary RPC that returns the same text that was sent. + Ping(context.Context, *connect.Request[v1.PingRequest]) (*connect.Response[v1.PingResponse], error) + // PingStream is a bidirectional stream of pings. + PingStream(context.Context, *connect.BidiStream[v1.PingStreamRequest, v1.PingStreamResponse]) error +} + +// NewPingServiceHandler builds an HTTP handler from the service implementation. It returns the path +// on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewPingServiceHandler(svc PingServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + pingServicePingHandler := connect.NewUnaryHandler( + PingServicePingProcedure, + svc.Ping, + connect.WithIdempotency(connect.IdempotencyNoSideEffects), + connect.WithHandlerOptions(opts...), + ) + pingServicePingStreamHandler := connect.NewBidiStreamHandler( + PingServicePingStreamProcedure, + svc.PingStream, + opts..., + ) + return "/authn.ping.v1.PingService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case PingServicePingProcedure: + pingServicePingHandler.ServeHTTP(w, r) + case PingServicePingStreamProcedure: + pingServicePingStreamHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedPingServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedPingServiceHandler struct{} + +func (UnimplementedPingServiceHandler) Ping(context.Context, *connect.Request[v1.PingRequest]) (*connect.Response[v1.PingResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("authn.ping.v1.PingService.Ping is not implemented")) +} + +func (UnimplementedPingServiceHandler) PingStream(context.Context, *connect.BidiStream[v1.PingStreamRequest, v1.PingStreamResponse]) error { + return connect.NewError(connect.CodeUnimplemented, errors.New("authn.ping.v1.PingService.PingStream is not implemented")) +} diff --git a/internal/proto/authn/ping/v1/ping.proto b/internal/proto/authn/ping/v1/ping.proto new file mode 100644 index 0000000..c42c10f --- /dev/null +++ b/internal/proto/authn/ping/v1/ping.proto @@ -0,0 +1,36 @@ +// Copyright 2023 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The canonical location for this file is +// https://github.com/bufbuild/authn-go/blob/main/internal/proto/authn/ping/v1/ping.proto. +syntax = "proto3"; + +// The authn.ping.v1 package contains a ping service designed to test the +// authn-go implementation. +package authn.ping.v1; + +message PingRequest { string text = 1; } +message PingResponse { string text = 1; } +message PingStreamRequest { string text = 1; } +message PingStreamResponse { string text = 1; } + +service PingService { + // Ping is a unary RPC that returns the same text that was sent. + rpc Ping(PingRequest) returns (PingResponse) { + option idempotency_level = NO_SIDE_EFFECTS; + } + // PingStream is a bidirectional stream of pings. + rpc PingStream(stream PingStreamRequest) returns (stream PingStreamResponse) { + } +} diff --git a/internal/proto/buf.yaml b/internal/proto/buf.yaml new file mode 100644 index 0000000..efda402 --- /dev/null +++ b/internal/proto/buf.yaml @@ -0,0 +1,7 @@ +version: v1 +lint: + use: + - DEFAULT +breaking: + use: + - WIRE_JSON