Skip to content

Commit

Permalink
feat: API client (#280)
Browse files Browse the repository at this point in the history
* feat: authorization check for http APIs with example

* feat: authorization check for grpc with example

* remove unused

* improve http example

* cleanup

* comments

* adapt grpc example to http example and lots of comments

* version v3

BREAKING CHANGES:

- remove pkg/api/middleware packages (Introspection middleware)
- remove deprecated `WithKeyPath` client connection option

* update oidc and jose

BREAKING CHANGES:

NewClient functions require context.Context
remove deprecated `WithKeyPath` client connection option

* some improvements

* fix error handling

* fix error handling and add tests

* require at least go 1.20

* use "golang.org/x/exp/slog" for go 1.20 support

* add some basic logging to authorization

* backup

* backup

* working

* backup

* comments

* more comments and fixes

* some fixes

* styling

* fix end session

* improve readability and logging in authenticate

* encrypt session cookie

* cleanup

* feat: client

* fixes

* clean go mod

* comment

* fix tests
  • Loading branch information
livio-a authored Dec 20, 2023
1 parent 5eb9366 commit aad6fea
Show file tree
Hide file tree
Showing 20 changed files with 605 additions and 124 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@

> This branch is WIP. It can AND will continue breaking until version 3.0.0 is released
Go library for [ZITADEL](https://github.com/zitadel/zitadel). It provides features including:
- Authorization checks
Go library for [ZITADEL](https://github.com/zitadel/zitadel).

## Features

- Authentication
- Authorization checks
- Client for ZITADEL API

## Usage
Expand All @@ -23,8 +26,7 @@ Add the package to your go.mod by
go get -u github.com/zitadel/zitadel-go/v3
```

...and check out the [examples](./example) in this repo or head over to our [docs website](https://docs.zitadel.com/docs/quickstarts/introduction).

...and check out the [examples](./example) in this repo or head over to our [docs website](https://zitadel.com/docs/guides/start/quickstart).

## Supported Go Versions

Expand Down
120 changes: 120 additions & 0 deletions example/api/client/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package main

import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"net/http"
"os"

"golang.org/x/exp/slog"

"github.com/zitadel/zitadel-go/v3/pkg/authorization"
"github.com/zitadel/zitadel-go/v3/pkg/authorization/oauth"
"github.com/zitadel/zitadel-go/v3/pkg/client"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/auth"
"github.com/zitadel/zitadel-go/v3/pkg/http/middleware"
"github.com/zitadel/zitadel-go/v3/pkg/zitadel"
)

var (
// flags to be provided for running the example server
domain = flag.String("domain", "", "your ZITADEL instance domain (in the form: <instance>.zitadel.cloud or <yourdomain>)")
key = flag.String("key", "", "path to your api key.json")
port = flag.String("port", "8089", "port to run the server on (default is 8089)")
)

/*
This example demonstrates how to secure an HTTP API with ZITADEL using the provided authorization (AuthZ) middleware
in combination with using the ZITADEL API as well.
It will serve the following 2 different endpoints:
(These are meant to demonstrate the possibilities and do not follow REST best practices):
- /api/healthz (can be called by anyone)
- /api/permissions (requires authorization)
*/

func main() {
flag.Parse()

ctx := context.Background()

// Initiate the zitadel sdk by providing its domain
// and as this example will focus on authorization (using OAuth2 Introspection),
// you will also need to initialize that with the downloaded api key.json

conf := zitadel.New(*domain)
authZ, err := authorization.New(ctx, conf, oauth.DefaultAuthorization(*key))
if err != nil {
slog.Error("zitadel sdk could not initialize authorization", "error", err)
os.Exit(1)
}
// Initialize the HTTP middleware by providing the sdk
mw := middleware.New(authZ)

// as we will also call the ZITADEL API, we need to initialize the client
c, err := client.New(ctx, conf)
if err != nil {
slog.Error("zitadel sdk could not initialize authorization", "error", err)
os.Exit(1)
}

router := http.NewServeMux()

// This endpoint is accessible by anyone and will always return "200 OK" to indicate the API is running
router.Handle("/api/healthz", http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
err = jsonResponse(w, "OK", http.StatusOK)
if err != nil {
slog.Error("error writing response", "error", err)
}
}))

// This endpoint is only accessible with a valid authorization (in this case a valid access_token / PAT).
// It will call ZITADEL to additionally get all permissions granted to the user in ZITADEL and return that.
router.Handle("/api/permissions", mw.RequireAuthorization()(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
// Using the [middleware.Context] function we can gather information about the authorized user.
// This example will just print the users ID using the provided method, and it will also
// print the username by directly access the field of the typed [*oauth.IntrospectionContext].
authCtx := mw.Context(r.Context())
slog.Info("user accessed permission check", "id", authCtx.UserID(), "username", authCtx.Username)

// we use the callers on token to retrieve the permission on the ZITADEL API
// this will only work if ZITADEL is contained in the tokens audience (e.g. a PAT will always do so)
resp, err := c.AuthService().ListMyZitadelPermissions(client.AuthorizedUserCtx(r.Context()), &auth.ListMyZitadelPermissionsRequest{})
if err != nil {
slog.Error("error listing zitadel permissions", "error", err)
return
}

err = jsonResponse(w, resp.Result, http.StatusOK)
if err != nil {
slog.Error("error writing response", "error", err)
}
})))

// start the server on the specified port (default http://localhost:8089)
lis := fmt.Sprintf(":%s", *port)
slog.Info("server listening, press ctrl+c to stop", "addr", "http://localhost"+lis)
err = http.ListenAndServe(lis, router)
if !errors.Is(err, http.ErrServerClosed) {
slog.Error("server terminated", "error", err)
os.Exit(1)
}
}

// jsonResponse is a simple helper function to return a proper JSON response
func jsonResponse(w http.ResponseWriter, resp any, status int) error {
w.Header().Set("content-type", "application/json")
w.WriteHeader(status)
data, err := json.Marshal(resp)
if err != nil {
return err
}
_, err = w.Write(data)
return err
}
25 changes: 6 additions & 19 deletions example/api/grpc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ import (
"google.golang.org/grpc/reflection"

v3alpha "github.com/zitadel/zitadel-go/v3/example/api/grpc/proto"
"github.com/zitadel/zitadel-go/v3/pkg/authorization"
"github.com/zitadel/zitadel-go/v3/pkg/authorization/oauth"
"github.com/zitadel/zitadel-go/v3/pkg/grpc/middleware"
"github.com/zitadel/zitadel-go/v3/pkg/zitadel"
)

var (
// flags to be provided for running the example server
domain = flag.String("domain", "", "your ZITADEL instance domain (in the form: https://<instance>.zitadel.cloud or https://<yourdomain>)")
domain = flag.String("domain", "", "your ZITADEL instance domain (in the form: <instance>.zitadel.cloud or <yourdomain>)")
key = flag.String("key", "", "path to your key.json")
port = flag.String("port", "8089", "port to run the server on (default is 8089)")
)
Expand All @@ -30,30 +31,16 @@ func main() {

ctx := context.Background()

// Initiate the zitadel sdk by providing its domain
// and as this example will focus on authorization (using Oauth2 Introspection),
// you will also need to initialize that with the downloaded api key.json
//
// it's a short form of:
// z, err := zitadel.New("https://your-domain.zitadel.cloud",
// zitadel.WithAuthorization(ctx,
// oauth.WithIntrospection[*oauth.IntrospectionContext](
// oauth.JWTProfileIntrospectionAuthentication("./key.json"),
// ),
// ),
// )
z, err := zitadel.New(*domain,
zitadel.WithAuthorization(ctx,
oauth.DefaultAuthorization(*key),
),
)
// Initiate the authorization by providing a zitadel configuration and a verifier.
// This example will use OAuth2 Introspection for this, therefore you will also need to provide the downloaded api key.json
authZ, err := authorization.New(ctx, zitadel.New(*domain), oauth.DefaultAuthorization(*key))
if err != nil {
slog.Error("zitadel sdk could not initialize", "error", err)
os.Exit(1)
}

// Initialize the GRPC middleware by providing the sdk and the authorization checks
mw := middleware.New(z.Authorization, checks)
mw := middleware.New(authZ, checks)

// Create the GRPC server and provide the necessary interceptors
serverOptions := []grpc.ServerOption{
Expand Down
26 changes: 6 additions & 20 deletions example/api/http/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (

var (
// flags to be provided for running the example server
domain = flag.String("domain", "", "your ZITADEL instance domain (in the form: https://<instance>.zitadel.cloud or https://<yourdomain>)")
domain = flag.String("domain", "", "your ZITADEL instance domain (in the form: <instance>.zitadel.cloud or <yourdomain>)")
key = flag.String("key", "", "path to your key.json")
port = flag.String("port", "8089", "port to run the server on (default is 8089)")

Expand All @@ -44,30 +44,16 @@ func main() {

ctx := context.Background()

// Initiate the zitadel sdk by providing its domain
// and as this example will focus on authorization (using OAuth2 Introspection),
// you will also need to initialize that with the downloaded api key.json
//
// it's a short form of:
// z, err := zitadel.New("https://your-domain.zitadel.cloud",
// zitadel.WithAuthorization(ctx,
// oauth.WithIntrospection[*oauth.IntrospectionContext](
// oauth.JWTProfileIntrospectionAuthentication("./key.json"),
// ),
// ),
// )
z, err := zitadel.New(*domain,
zitadel.WithAuthorization(ctx,
oauth.DefaultAuthorization(*key),
),
)
// Initiate the authorization by providing a zitadel configuration and a verifier.
// This example will use OAuth2 Introspection for this, therefore you will also need to provide the downloaded api key.json
authZ, err := authorization.New(ctx, zitadel.New(*domain), oauth.DefaultAuthorization(*key))
if err != nil {
slog.Error("zitadel sdk could not initialize", "error", err)
os.Exit(1)
}

// Initialize the HTTP middleware by providing the sdk
mw := middleware.New(z.Authorization)
// Initialize the HTTP middleware by providing the authorization
mw := middleware.New(authZ)

router := http.NewServeMux()

Expand Down
15 changes: 6 additions & 9 deletions example/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,18 @@ func main() {
os.Exit(1)
}

// Initiate the zitadel sdk by providing its domain
// and as this example will focus on authentication (using OIDC / OAuth2 PKCE flow),
// you will also need to initialize that with the generated client_id.
z, err := zitadel.New(*domain,
zitadel.WithAuthentication(ctx, *key,
openid.DefaultAuthentication(*clientID, *redirectURI, *key),
),
// Initiate the authentication by providing a zitadel configuration and handler.
// This example will use OIDC/OAuth2 PKCE Flow, therefore you will also need to initialize that with the generated client_id:
authN, err := authentication.New(ctx, zitadel.New(*domain), *key,
openid.DefaultAuthentication(*clientID, *redirectURI, *key),
)
if err != nil {
slog.Error("zitadel sdk could not initialize", "error", err)
os.Exit(1)
}

// Initialize the middleware by providing the sdk
mw := authentication.Middleware(z.Authentication)
mw := authentication.Middleware(authN)

router := http.NewServeMux()

Expand All @@ -75,7 +72,7 @@ func main() {
// - /login (starts the authentication process to the Login UI)
// - /callback (handles the redirect back from the Login UI)
// - /logout (handles the logout process)
router.Handle("/auth/", z.Authentication)
router.Handle("/auth/", authN)
// This endpoint is only accessible with a valid authentication. If there is none, it will directly redirect the user
// to the Login UI for authentication. If successful (or already authenticated), the user will be presented the profile page.
router.Handle("/profile", mw.RequireAuthentication()(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
Expand Down
42 changes: 42 additions & 0 deletions example/client/cli/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package main

import (
"context"
"flag"
"os"

"github.com/zitadel/oidc/v3/pkg/oidc"
"golang.org/x/exp/slog"

"github.com/zitadel/zitadel-go/v3/pkg/client"
"github.com/zitadel/zitadel-go/v3/pkg/client/zitadel/management"
"github.com/zitadel/zitadel-go/v3/pkg/zitadel"
)

var (
// flags to be provided for running the example server
domain = flag.String("domain", "", "your ZITADEL instance domain (in the form: <instance>.zitadel.cloud or <yourdomain>)")
keyPath = flag.String("key", "", "path to your key.json")
)

func main() {
flag.Parse()

ctx := context.Background()

// Initiate the API client by providing at least a zitadel configuration.
// You can also directly set an authorization option, resp. provide its authentication mechanism,
// by passing the downloaded service user key:
api, err := client.New(ctx, zitadel.New(*domain),
client.WithAuth(client.DefaultServiceUserAuthentication(*keyPath, oidc.ScopeOpenID, client.ScopeZitadelAPI())),
)

// In this example we will just use the ManagementService to retrieve the users organisation,
// but you can use the API for all the other services (Admin, Auth, User, Session, ...) too.
resp, err := api.ManagementService().GetMyOrg(ctx, &management.GetMyOrgRequest{})
if err != nil {
slog.Error("cannot retrieve the organisation", "error", err)
os.Exit(1)
}
slog.Info("retrieved the organisation", "orgID", resp.GetOrg().GetId(), "name", resp.GetOrg().GetName())
}
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ go 1.20
require (
github.com/envoyproxy/protoc-gen-validate v1.0.2
github.com/go-jose/go-jose/v3 v3.0.1
github.com/google/uuid v1.4.0
github.com/google/uuid v1.5.0
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1
github.com/stretchr/testify v1.8.4
github.com/zitadel/oidc/v3 v3.5.1
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848
golang.org/x/oauth2 v0.15.0
google.golang.org/genproto/googleapis/api v0.0.0-20231127180814-3a041ad873d4
google.golang.org/grpc v1.59.0
google.golang.org/grpc v1.60.1
google.golang.org/protobuf v1.31.0
)

Expand Down
20 changes: 6 additions & 14 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
Expand Down Expand Up @@ -81,8 +81,6 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zitadel/logging v0.5.0 h1:Kunouvqse/efXy4UDvFw5s3vP+Z4AlHo3y8wF7stXHA=
github.com/zitadel/logging v0.5.0/go.mod h1:IzP5fzwFhzzyxHkSmfF8dsyqFsQRJLLcQmwhIBzlGsE=
github.com/zitadel/oidc/v3 v3.4.0 h1:JkbNnrk/7IG+NOBoZp/P0kx6tPcBvnCekSqDTPCOok4=
github.com/zitadel/oidc/v3 v3.4.0/go.mod h1:jUnLnx5ihKlo88cSEduZkKlzeMrjzcWVZ8fTzKBxZKY=
github.com/zitadel/oidc/v3 v3.5.1 h1:7gyrxRNqX5eZYai2KhzPj8MhBZ7I3YpviQeX1Lp4j4U=
github.com/zitadel/oidc/v3 v3.5.1/go.mod h1:R8sF5DPR98QQnOoyySsaNqI4NcF/VFMkf/XoYiBUuXQ=
github.com/zitadel/schema v1.3.0 h1:kQ9W9tvIwZICCKWcMvCEweXET1OcOyGEuFbHs4o5kg0=
Expand All @@ -102,10 +100,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE=
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
Expand Down Expand Up @@ -176,23 +172,19 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f h1:Vn+VyHU5guc9KjB5KrjI2q0wCOWEOIh0OEsleqakHJg=
google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY=
google.golang.org/genproto v0.0.0-20231127180814-3a041ad873d4 h1:W12Pwm4urIbRdGhMEg2NM9O3TWKjNcxQhs46V0ypf/k=
google.golang.org/genproto v0.0.0-20231127180814-3a041ad873d4/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic=
google.golang.org/genproto/googleapis/api v0.0.0-20231127180814-3a041ad873d4 h1:ZcOkrmX74HbKFYnpPY8Qsw93fC29TbJXspYKaBkSXDQ=
google.golang.org/genproto/googleapis/api v0.0.0-20231127180814-3a041ad873d4/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 h1:DC7wcm+i+P1rN3Ff07vL+OndGg5OhNddHyTA+ocPqYE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
Expand Down
Loading

0 comments on commit aad6fea

Please sign in to comment.